open and close animations
This commit is contained in:
parent
41f8f18fcb
commit
34e3efb8df
139
src/tui.ts
139
src/tui.ts
|
|
@ -10,6 +10,90 @@ readline.emitKeypressEvents(process.stdin);
|
|||
// Spinner chars for loading animations
|
||||
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||
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;
|
||||
|
||||
/**
|
||||
|
|
@ -105,6 +189,8 @@ export class TuiEngine {
|
|||
private onQuit: () => void = () => {};
|
||||
private activeSearchInput: boolean = false;
|
||||
private searchInputBuffer: string = '';
|
||||
private animationFrame: number = 0;
|
||||
private animationInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(initialState: AppState) {
|
||||
this.state = initialState;
|
||||
|
|
@ -145,6 +231,10 @@ export class TuiEngine {
|
|||
*/
|
||||
public stop() {
|
||||
this.stopSpinner();
|
||||
if (this.animationInterval) {
|
||||
clearInterval(this.animationInterval);
|
||||
this.animationInterval = null;
|
||||
}
|
||||
// Exit alternate screen buffer and show standard cursor
|
||||
process.stdout.write('\x1B[?1049l\x1B[?25h');
|
||||
if (process.stdin.isTTY) {
|
||||
|
|
@ -794,9 +884,29 @@ export class TuiEngine {
|
|||
this.renderAddTimeScreen(cols, rows);
|
||||
} else if (this.state.screen === 'confirm-state-change') {
|
||||
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
|
||||
*/
|
||||
|
|
@ -1944,21 +2054,44 @@ export class TuiEngine {
|
|||
if (!issue) return;
|
||||
|
||||
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();
|
||||
|
||||
if (this.animationInterval) {
|
||||
clearInterval(this.animationInterval);
|
||||
}
|
||||
|
||||
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 {
|
||||
const newState = issue.state === 'open' ? 'closed' : 'open';
|
||||
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.screen = 'details';
|
||||
this.state.loading = false;
|
||||
this.render();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export interface AddTimeForm {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -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