open and close animations
This commit is contained in:
parent
41f8f18fcb
commit
34e3efb8df
157
src/tui.ts
157
src/tui.ts
|
|
@ -10,6 +10,90 @@ readline.emitKeypressEvents(process.stdin);
|
||||||
// Spinner chars for loading animations
|
// Spinner chars for loading animations
|
||||||
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||||
let spinnerIndex = 0;
|
let spinnerIndex = 0;
|
||||||
|
|
||||||
|
const GRAVESTONE_FRAMES = [
|
||||||
|
[
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' ___________ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' ___/___\\___ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' / \\ '),
|
||||||
|
chalk.gray(' __|_____|__ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' '),
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' / \\ '),
|
||||||
|
chalk.gray(' | RIP | '),
|
||||||
|
chalk.gray(' __|_____|__ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' / \\ '),
|
||||||
|
chalk.gray(' | RIP | '),
|
||||||
|
chalk.gray(' | | '),
|
||||||
|
chalk.gray(' __|_____|__ '),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
const ZOMBIE_FRAMES = [
|
||||||
|
[
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' / \\ '),
|
||||||
|
chalk.gray(' | RIP | '),
|
||||||
|
chalk.gray(' | | '),
|
||||||
|
chalk.gray(' __|_____|__ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' / \\ '),
|
||||||
|
chalk.gray(' | RIP | '),
|
||||||
|
chalk.gray(' | | '),
|
||||||
|
chalk.gray(' __|_ ') + chalk.green('.') + chalk.gray(' _|__ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' / \\ '),
|
||||||
|
chalk.gray(' | RIP | '),
|
||||||
|
chalk.gray(' | ') + chalk.green('^') + chalk.gray(' | '),
|
||||||
|
chalk.gray(' __|_') + chalk.green('/ \\') + chalk.gray('_|__ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' / \\ '),
|
||||||
|
chalk.gray(' | ') + chalk.green('o_o') + chalk.gray(' | '),
|
||||||
|
chalk.gray(' | ') + chalk.green('/|\\') + chalk.gray(' | '),
|
||||||
|
chalk.gray(' __|_ ') + chalk.green('|') + chalk.gray(' _|__ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' ___ '),
|
||||||
|
chalk.gray(' /') + chalk.green('o_o') + chalk.gray('\\ '),
|
||||||
|
chalk.gray(' | ') + chalk.green('/|\\') + chalk.gray(' | '),
|
||||||
|
chalk.gray(' | ') + chalk.green('|') + chalk.gray(' | '),
|
||||||
|
chalk.gray(' __|_') + chalk.green('/ \\') + chalk.gray('_|__ '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
chalk.gray(' \\') + chalk.green('o_o') + chalk.gray('/ '),
|
||||||
|
chalk.gray(' / ') + chalk.green('|') + chalk.gray(' \\ '),
|
||||||
|
chalk.gray(' | ') + chalk.green('/ \\') + chalk.gray(' | '),
|
||||||
|
chalk.gray(' | | '),
|
||||||
|
chalk.gray(' __|_____|__ '),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
let spinnerInterval: NodeJS.Timeout | null = null;
|
let spinnerInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -105,6 +189,8 @@ export class TuiEngine {
|
||||||
private onQuit: () => void = () => {};
|
private onQuit: () => void = () => {};
|
||||||
private activeSearchInput: boolean = false;
|
private activeSearchInput: boolean = false;
|
||||||
private searchInputBuffer: string = '';
|
private searchInputBuffer: string = '';
|
||||||
|
private animationFrame: number = 0;
|
||||||
|
private animationInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
constructor(initialState: AppState) {
|
constructor(initialState: AppState) {
|
||||||
this.state = initialState;
|
this.state = initialState;
|
||||||
|
|
@ -145,6 +231,10 @@ export class TuiEngine {
|
||||||
*/
|
*/
|
||||||
public stop() {
|
public stop() {
|
||||||
this.stopSpinner();
|
this.stopSpinner();
|
||||||
|
if (this.animationInterval) {
|
||||||
|
clearInterval(this.animationInterval);
|
||||||
|
this.animationInterval = null;
|
||||||
|
}
|
||||||
// Exit alternate screen buffer and show standard cursor
|
// Exit alternate screen buffer and show standard cursor
|
||||||
process.stdout.write('\x1B[?1049l\x1B[?25h');
|
process.stdout.write('\x1B[?1049l\x1B[?25h');
|
||||||
if (process.stdin.isTTY) {
|
if (process.stdin.isTTY) {
|
||||||
|
|
@ -794,9 +884,29 @@ export class TuiEngine {
|
||||||
this.renderAddTimeScreen(cols, rows);
|
this.renderAddTimeScreen(cols, rows);
|
||||||
} else if (this.state.screen === 'confirm-state-change') {
|
} else if (this.state.screen === 'confirm-state-change') {
|
||||||
this.renderConfirmStateChangeScreen(cols, rows);
|
this.renderConfirmStateChangeScreen(cols, rows);
|
||||||
|
} else if (this.state.screen === 'animating-close') {
|
||||||
|
this.renderAnimationScreen(cols, rows, GRAVESTONE_FRAMES);
|
||||||
|
} else if (this.state.screen === 'animating-reopen') {
|
||||||
|
this.renderAnimationScreen(cols, rows, ZOMBIE_FRAMES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderAnimationScreen(cols: number, rows: number, frames: string[][]) {
|
||||||
|
const frame = frames[Math.min(this.animationFrame, frames.length - 1)];
|
||||||
|
const paddingRows = Math.max(0, Math.floor((rows - frame.length) / 2));
|
||||||
|
|
||||||
|
for (let i = 0; i < paddingRows; i++) console.log(' '.repeat(cols));
|
||||||
|
|
||||||
|
for (const line of frame) {
|
||||||
|
const plainLen = stripAnsi(line).length;
|
||||||
|
const padLeft = Math.max(0, Math.floor((cols - plainLen) / 2));
|
||||||
|
console.log(' '.repeat(padLeft) + line);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingRows = Math.max(0, rows - paddingRows - frame.length - 1);
|
||||||
|
for (let i = 0; i < remainingRows; i++) console.log(' '.repeat(cols));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw Setup Form
|
* Draw Setup Form
|
||||||
*/
|
*/
|
||||||
|
|
@ -1944,21 +2054,44 @@ export class TuiEngine {
|
||||||
if (!issue) return;
|
if (!issue) return;
|
||||||
|
|
||||||
this.state.error = null;
|
this.state.error = null;
|
||||||
this.state.loading = true;
|
const newState = issue.state === 'open' ? 'closed' : 'open';
|
||||||
|
this.state.screen = newState === 'closed' ? 'animating-close' : 'animating-reopen';
|
||||||
|
this.animationFrame = 0;
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
try {
|
if (this.animationInterval) {
|
||||||
const newState = issue.state === 'open' ? 'closed' : 'open';
|
clearInterval(this.animationInterval);
|
||||||
await changeIssueState(this.state.config, issue.number, newState);
|
|
||||||
this.state.screen = 'details';
|
|
||||||
this.state.loading = false;
|
|
||||||
// Reload to show the updated state
|
|
||||||
this.reloadSingleIssue();
|
|
||||||
} catch (err: any) {
|
|
||||||
this.state.error = err.message;
|
|
||||||
this.state.loading = false;
|
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.animationInterval = setInterval(() => {
|
||||||
|
this.animationFrame++;
|
||||||
|
const frameCount = newState === 'closed' ? GRAVESTONE_FRAMES.length : ZOMBIE_FRAMES.length;
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
if (this.animationFrame >= frameCount - 1) {
|
||||||
|
if (this.animationInterval) {
|
||||||
|
clearInterval(this.animationInterval);
|
||||||
|
this.animationInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
this.state.loading = true;
|
||||||
|
this.render();
|
||||||
|
try {
|
||||||
|
await changeIssueState(this.state.config, issue.number, newState);
|
||||||
|
this.state.screen = 'details';
|
||||||
|
this.state.loading = false;
|
||||||
|
this.reloadSingleIssue();
|
||||||
|
} catch (err: any) {
|
||||||
|
this.state.error = err.message;
|
||||||
|
this.state.screen = 'details';
|
||||||
|
this.state.loading = false;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export interface AddTimeForm {
|
||||||
timeInput: string;
|
timeInput: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScreenType = 'setup' | 'repo-picker' | 'list' | 'details' | 'create-issue' | 'add-comment' | 'edit-issue' | 'add-time' | 'confirm-state-change';
|
export type ScreenType = 'setup' | 'repo-picker' | 'list' | 'details' | 'create-issue' | 'add-comment' | 'edit-issue' | 'add-time' | 'confirm-state-change' | 'animating-close' | 'animating-reopen';
|
||||||
|
|
||||||
|
|
||||||
export interface RepoItem {
|
export interface RepoItem {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
const GRAVESTONE_FRAMES = [
|
||||||
|
[
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ___________ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ___ ',
|
||||||
|
' ___/___\\___ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ___ ',
|
||||||
|
' / \\ ',
|
||||||
|
' __|_____|__ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' ',
|
||||||
|
' ___ ',
|
||||||
|
' / \\ ',
|
||||||
|
' | RIP | ',
|
||||||
|
' __|_____|__ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' ___ ',
|
||||||
|
' / \\ ',
|
||||||
|
' | RIP | ',
|
||||||
|
' | | ',
|
||||||
|
' __|_____|__ ',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
const ZOMBIE_FRAMES = [
|
||||||
|
[
|
||||||
|
' ___ ',
|
||||||
|
' / \\ ',
|
||||||
|
' | RIP | ',
|
||||||
|
' | | ',
|
||||||
|
' __|_____|__ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' ___ ',
|
||||||
|
' / \\ ',
|
||||||
|
' | RIP | ',
|
||||||
|
' | | ',
|
||||||
|
' __|_ . _|__ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' ___ ',
|
||||||
|
' / \\ ',
|
||||||
|
' | RIP | ',
|
||||||
|
' | ^ | ',
|
||||||
|
' __|_/ \\_|__ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' ___ ',
|
||||||
|
' / \\ ',
|
||||||
|
' | o_o | ',
|
||||||
|
' | /|\\ | ',
|
||||||
|
' __|_ | _|__ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' ___ ',
|
||||||
|
' /o_o\\ ',
|
||||||
|
' | /|\\ | ',
|
||||||
|
' | | | ',
|
||||||
|
' __|_/ \\_|__ ',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
' \\o_o/ ',
|
||||||
|
' / | \\ ',
|
||||||
|
' | / \\ | ',
|
||||||
|
' | | ',
|
||||||
|
' __|_____|__ ',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
const checkLens = frames => frames.forEach((f, i) => f.forEach((l, j) => {
|
||||||
|
if (l.length !== 17) console.log(`Frame ${i} line ${j} len ${l.length}: ${l}`);
|
||||||
|
}));
|
||||||
|
checkLens(GRAVESTONE_FRAMES);
|
||||||
|
checkLens(ZOMBIE_FRAMES);
|
||||||
|
console.log("Checked lengths!");
|
||||||
Loading…
Reference in New Issue