Skip to content

Commit

Permalink
fix: implement cursor blinking
Browse files Browse the repository at this point in the history
  • Loading branch information
iamogbz authored Dec 6, 2024
1 parent 1b97c12 commit 8ba060c
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 40 deletions.
Binary file modified docs/usage.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 59 additions & 40 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ const config = {
return `${config.dimensionsPx.text}px`;
},
},
cursor: "█",
cursor: {
/** Set to 0 to disable blinking */
blinkMs: 1000,
/** Set to empty string "" to disable cursor */
token: "█",
},
fps: 15,
lineNumber: true,
quality: 10,
Expand Down Expand Up @@ -96,9 +101,18 @@ function getTerminalLines(state) {
return state.terminalContent.split(TOKEN_NL);
}

/**
* Get the number of frames displayed for the milliseconds given
* @param {number} ms
*/
function msToFrameCount(ms) {
const framesPerMs = config.animation.fps / config.animation.timing.secondMs;
return Math.floor(ms * framesPerMs);
}

/**
* Get visible lines from the terminal content as list
* @param {{ terminalContent: string; }} state
* @param {{ frameCount: number; terminalContent: string; }} state
*/
function getVisibleTerminalLines(state) {
const paddingLength = 5;
Expand All @@ -109,38 +123,26 @@ function getVisibleTerminalLines(state) {
const lines = getTerminalLines(state).map(
(line, i) => `${lineNumber(i + 1)}:\$ ${line}`
);
if (lines) {
lines[lines.length - 1] += config.animation.cursor;
const cursorVisibleFrames = msToFrameCount(config.animation.cursor.blinkMs);
const cursorVisible =
state.frameCount % Math.max(1, cursorVisibleFrames) <=
cursorVisibleFrames / 2;
if (lines && cursorVisible) {
lines[lines.length - 1] += config.animation.cursor.token;
}
return lines.slice(-config.lineCount);
}
/**
* Get visible lines from the terminal content as single string blob
* @param {{ terminalContent: string; }} state
*/
function getVisibleTerminalContent(state) {
return getVisibleTerminalLines(state).join(TOKEN_NL);
}

/**
* Update the terminal's display content
* @param {{ terminalContent: any; clipboard?: string; }} state
*/
function updateTerminal(state) {
terminalBox.setContent(getVisibleTerminalContent(state));
screen.render();
recordFrame(state);
}

/**
* Record a frame of the terminal state
* @param {{ terminalContent: string; }} state
* @param {{ frameCount: number; terminalContent: string; }} state
*/
function recordFrame(state) {
ctx.fillStyle = config.animation.css.backgroundColor;
ctx.fillRect(0, 0, width, height);
ctx.font = `${config.animation.css.fontSize} ${config.animation.css.fontStyle}`;
ctx.fillStyle = config.animation.css.color;
state.frameCount += 1;
getVisibleTerminalLines(state).forEach((line, index) => {
ctx.fillText(
line,
Expand All @@ -153,9 +155,40 @@ function recordFrame(state) {
encoder.addFrame(ctx);
}

/**
* Delay function
* @param {{ frameCount: number; terminalContent: string; }} state
* @param {number} ms
*/
function delay(state, ms) {
const frameCount = msToFrameCount(ms);
for (let i = 0; i < frameCount; i++) {
recordFrame(state);
}
return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
* Get visible lines from the terminal content as single string blob
* @param {{ frameCount: number; terminalContent: string; }} state
*/
function getVisibleTerminalContent(state) {
return getVisibleTerminalLines(state).join(TOKEN_NL);
}

/**
* Update the terminal's display content
* @param {{ clipboard?: string; frameCount: number; terminalContent: any; }} state
*/
function updateTerminal(state) {
terminalBox.setContent(getVisibleTerminalContent(state));
screen.render();
recordFrame(state);
}

/**
* Utility to terminate and save recording
* @param {{ outputPath: string; terminalContent: string; }} state
* @param {{ frameCount: number; outputPath: string; terminalContent: string; }} state
*/
async function finishRecording(state, exitCode = 0) {
await delay(state, config.animation.timing.secondMs * 5);
Expand All @@ -169,7 +202,7 @@ async function finishRecording(state, exitCode = 0) {

/**
* Utility to throw an error and abort execution
* @param {{ clipboard?: string | undefined; outputPath: string; pendingExecution: string; terminalContent: string; }} state
* @param {{ clipboard?: string | undefined; frameCount: number; outputPath: string; pendingExecution: string; terminalContent: string; }} state
* @param {string} errorMessage
*/
function abortExecution(state, errorMessage) {
Expand All @@ -187,7 +220,7 @@ function abortExecution(state, errorMessage) {
@type {
Record<string, (
step: { action: "clear" | "copy" | "enter" | "paste" | "type" | "waitForOutput"; payload: string | { startLine: number, endLine: number, startPos: number, endPos:number }; timeoutMs: number},
state: { env: Record<string, string | undefined>; pendingExecution:string; terminalContent: string; clipboard: string; }
state: { env: Record<string, string | undefined>; frameCount: number; pendingExecution:string; terminalContent: string; clipboard: string; }
) => Promise<void>>
}
*/
Expand Down Expand Up @@ -278,21 +311,6 @@ const actionsRegistry = Object.freeze({
},
});

/**
* Delay function
* @param {{ terminalContent: string; }} state
* @param {number} ms
*/
function delay(state, ms) {
const frameCount = Math.floor(
config.animation.fps * (ms / config.animation.timing.secondMs)
);
for (let i = 0; i < frameCount; i++) {
recordFrame(state);
}
return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
* Simulate steps with dynamic handling of actions
* @param {object[]} steps
Expand All @@ -303,6 +321,7 @@ async function simulateSteps(steps, outputPath) {
const state = {
clipboard: "",
env: { ...process.env },
frameCount: 0,
outputPath,
pendingExecution: "",
terminalContent: "",
Expand Down

1 comment on commit 8ba060c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated Image

Please sign in to comment.