tab to a refresh section on issue list

This commit is contained in:
Isaac Johnson 2026-06-03 13:38:41 -05:00
parent 56dde66aca
commit dcc2bbaa82
4 changed files with 76 additions and 6 deletions

View File

@ -9,6 +9,7 @@ export interface SavedConfig {
apiKey?: string; apiKey?: string;
repo?: string; repo?: string;
repository?: string; repository?: string;
autoRefreshInterval?: number;
} }
export const LOCAL_CONFIG_PATH = path.join(process.cwd(), 'fjtui.json'); export const LOCAL_CONFIG_PATH = path.join(process.cwd(), 'fjtui.json');
@ -18,7 +19,7 @@ export const GLOBAL_CONFIG_PATH = path.join(os.homedir(), '.config', 'fjtui', 'f
* Loads configuration from either local or global fjtui.json. * Loads configuration from either local or global fjtui.json.
* Local configuration overrides global configuration. * Local configuration overrides global configuration.
*/ */
export function loadSavedConfig(): { url: string; userid: string; token: string; repo: string } { export function loadSavedConfig(): { url: string; userid: string; token: string; repo: string; autoRefreshInterval: number } {
let localConfig: SavedConfig = {}; let localConfig: SavedConfig = {};
let globalConfig: SavedConfig = {}; let globalConfig: SavedConfig = {};
@ -48,13 +49,14 @@ export function loadSavedConfig(): { url: string; userid: string; token: string;
userid: merged.userid || '', userid: merged.userid || '',
token: merged.token || merged.apiKey || '', token: merged.token || merged.apiKey || '',
repo: merged.repo || merged.repository || '', repo: merged.repo || merged.repository || '',
autoRefreshInterval: merged.autoRefreshInterval || 0,
}; };
} }
/** /**
* Saves settings to the global configuration file. * Saves settings to the global configuration file.
*/ */
export function saveGlobalConfig(config: { url: string; userid: string; token: string | null; repo: string }) { export function saveGlobalConfig(config: { url: string; userid: string; token: string | null; repo: string; autoRefreshInterval?: number }) {
try { try {
const dir = path.dirname(GLOBAL_CONFIG_PATH); const dir = path.dirname(GLOBAL_CONFIG_PATH);
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
@ -64,7 +66,8 @@ export function saveGlobalConfig(config: { url: string; userid: string; token: s
url: config.url, url: config.url,
userid: config.userid, userid: config.userid,
token: config.token || '', token: config.token || '',
repo: config.repo repo: config.repo,
autoRefreshInterval: config.autoRefreshInterval || 0
}, null, 2); }, null, 2);
fs.writeFileSync(GLOBAL_CONFIG_PATH, data, 'utf-8'); fs.writeFileSync(GLOBAL_CONFIG_PATH, data, 'utf-8');
} catch (e) { } catch (e) {

View File

@ -37,12 +37,14 @@ async function bootstrap() {
previousScreen: 'list', previousScreen: 'list',
focusedPane: 'list', focusedPane: 'list',
selectedSettingIndex: 0, selectedSettingIndex: 0,
autoRefreshInterval: savedConfig.autoRefreshInterval || 0,
config: { config: {
url: '', url: '',
token: null, token: null,
userid: '', userid: '',
owner: '', owner: '',
repo: '', repo: '',
autoRefreshInterval: savedConfig.autoRefreshInterval || 0,
}, },
issues: [], issues: [],
currentPage: 1, currentPage: 1,
@ -128,6 +130,7 @@ async function bootstrap() {
userid: config.userid, userid: config.userid,
token: config.token, token: config.token,
repo: repo, repo: repo,
autoRefreshInterval: state.autoRefreshInterval,
}); });
console.log(chalk.green('Settings saved successfully to global config file!')); console.log(chalk.green('Settings saved successfully to global config file!'));
} }

View File

@ -199,6 +199,42 @@ export class TuiEngine {
private launchFrameIndex = 0; private launchFrameIndex = 0;
private launchInterval: NodeJS.Timeout | null = null; private launchInterval: NodeJS.Timeout | null = null;
private maxLaunchFrames = 60; private maxLaunchFrames = 60;
private autoRefreshTimer: NodeJS.Timeout | null = null;
private restartAutoRefresh() {
if (this.autoRefreshTimer) {
clearInterval(this.autoRefreshTimer);
this.autoRefreshTimer = null;
}
if (this.state.autoRefreshInterval && this.state.autoRefreshInterval > 0) {
this.autoRefreshTimer = setInterval(() => {
// We need a way to check if input is active, but isTextInputActive isn't accessible here if we didn't add it.
// Wait, isTextInputActive is a method of TuiEngine, or I reverted it?
// Ah! I reverted isTextInputActive earlier!
const isTextInputActive =
this.state.screen === 'setup' ||
(this.state.screen === 'repo-picker' && this.state.repoPickerActiveSearch) ||
(this.state.screen === 'list' && this.activeSearchInput) ||
this.state.screen === 'create-issue' ||
this.state.screen === 'add-comment' ||
this.state.screen === 'edit-issue' ||
this.state.screen === 'add-time' ||
this.state.screen === 'set-assignees' ||
this.state.screen === 'create-label' ||
this.state.screen === 'edit-label';
if (isTextInputActive) return;
// Background refresh
if (this.state.screen === 'list') {
this.loadIssues();
} else if (this.state.screen === 'details' && this.state.selectedIssue) {
this.reloadSingleIssue();
}
}, this.state.autoRefreshInterval * 1000);
}
}
constructor(initialState: AppState) { constructor(initialState: AppState) {
this.state = initialState; this.state = initialState;
@ -232,6 +268,7 @@ export class TuiEngine {
this.launchDestScreen = 'setup'; this.launchDestScreen = 'setup';
} }
this.restartAutoRefresh();
this.state.screen = 'launch'; this.state.screen = 'launch';
this.startLaunchAnimation(); this.startLaunchAnimation();
} }
@ -249,6 +286,10 @@ export class TuiEngine {
clearInterval(this.launchInterval); clearInterval(this.launchInterval);
this.launchInterval = null; this.launchInterval = null;
} }
if (this.autoRefreshTimer) {
clearInterval(this.autoRefreshTimer);
this.autoRefreshTimer = 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) {
@ -676,12 +717,12 @@ export class TuiEngine {
if (this.state.focusedPane === 'settings') { if (this.state.focusedPane === 'settings') {
if (key && key.name === 'left') { if (key && key.name === 'left') {
this.state.selectedSettingIndex = (this.state.selectedSettingIndex - 1 + 4) % 4; this.state.selectedSettingIndex = (this.state.selectedSettingIndex - 1 + 5) % 5;
this.render(); this.render();
return; return;
} }
if (key && key.name === 'right') { if (key && key.name === 'right') {
this.state.selectedSettingIndex = (this.state.selectedSettingIndex + 1) % 4; this.state.selectedSettingIndex = (this.state.selectedSettingIndex + 1) % 5;
this.render(); this.render();
return; return;
} }
@ -727,6 +768,17 @@ export class TuiEngine {
this.state.currentPage = 1; this.state.currentPage = 1;
this.state.selectedIssueIndex = 0; this.state.selectedIssueIndex = 0;
this.loadIssues(); this.loadIssues();
} else if (selIdx === 4) {
// Auto-Refresh
const intervals = [0, 15, 30, 60, 300];
const currentIdx = intervals.indexOf(this.state.autoRefreshInterval || 0);
this.state.autoRefreshInterval = intervals[(currentIdx + 1) % intervals.length];
this.state.config.autoRefreshInterval = this.state.autoRefreshInterval;
// Restart timer
this.restartAutoRefresh();
// Save to config globally
saveGlobalConfig(this.state.config);
this.render();
} }
return; return;
} }
@ -1385,8 +1437,18 @@ export class TuiEngine {
const seg2 = buildSegment(2, 'Type', typePlain, typeLabel); const seg2 = buildSegment(2, 'Type', typePlain, typeLabel);
const seg3 = buildSegment(3, 'Sort', sortPlain, sortLabel); const seg3 = buildSegment(3, 'Sort', sortPlain, sortLabel);
let refreshStr = 'Off';
if (this.state.autoRefreshInterval) {
if (this.state.autoRefreshInterval >= 60) {
refreshStr = `${this.state.autoRefreshInterval / 60}m`;
} else {
refreshStr = `${this.state.autoRefreshInterval}s`;
}
}
const seg4 = buildSegment(4, 'Refresh', refreshStr, chalk.bold(refreshStr));
const separator = chalk.bold.hex('#4A90E2')(' ─ '); const separator = chalk.bold.hex('#4A90E2')(' ─ ');
const filtersText = ` ${seg0}${separator}${seg1}${separator}${seg2}${separator}${seg3}`; const filtersText = ` ${seg0}${separator}${seg1}${separator}${seg2}${separator}${seg3}${separator}${seg4}`;
const filtersPlainLen = stripAnsi(filtersText).length; const filtersPlainLen = stripAnsi(filtersText).length;
const padding = Math.max(0, cols - 2 - filtersPlainLen); const padding = Math.max(0, cols - 2 - filtersPlainLen);

View File

@ -4,6 +4,7 @@ export interface Config {
userid: string; userid: string;
owner: string; owner: string;
repo: string; repo: string;
autoRefreshInterval?: number;
} }
export interface User { export interface User {
@ -95,6 +96,7 @@ export interface AppState {
screen: ScreenType; screen: ScreenType;
previousScreen?: ScreenType; previousScreen?: ScreenType;
config: Config; config: Config;
autoRefreshInterval: number;
focusedPane: 'list' | 'settings'; focusedPane: 'list' | 'settings';
selectedSettingIndex: number; selectedSettingIndex: number;