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;
repo?: string;
repository?: string;
autoRefreshInterval?: number;
}
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.
* 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 globalConfig: SavedConfig = {};
@ -48,13 +49,14 @@ export function loadSavedConfig(): { url: string; userid: string; token: string;
userid: merged.userid || '',
token: merged.token || merged.apiKey || '',
repo: merged.repo || merged.repository || '',
autoRefreshInterval: merged.autoRefreshInterval || 0,
};
}
/**
* 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 {
const dir = path.dirname(GLOBAL_CONFIG_PATH);
if (!fs.existsSync(dir)) {
@ -64,7 +66,8 @@ export function saveGlobalConfig(config: { url: string; userid: string; token: s
url: config.url,
userid: config.userid,
token: config.token || '',
repo: config.repo
repo: config.repo,
autoRefreshInterval: config.autoRefreshInterval || 0
}, null, 2);
fs.writeFileSync(GLOBAL_CONFIG_PATH, data, 'utf-8');
} catch (e) {

View File

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

View File

@ -199,6 +199,42 @@ export class TuiEngine {
private launchFrameIndex = 0;
private launchInterval: NodeJS.Timeout | null = null;
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) {
this.state = initialState;
@ -232,6 +268,7 @@ export class TuiEngine {
this.launchDestScreen = 'setup';
}
this.restartAutoRefresh();
this.state.screen = 'launch';
this.startLaunchAnimation();
}
@ -249,6 +286,10 @@ export class TuiEngine {
clearInterval(this.launchInterval);
this.launchInterval = null;
}
if (this.autoRefreshTimer) {
clearInterval(this.autoRefreshTimer);
this.autoRefreshTimer = null;
}
// Exit alternate screen buffer and show standard cursor
process.stdout.write('\x1B[?1049l\x1B[?25h');
if (process.stdin.isTTY) {
@ -676,12 +717,12 @@ export class TuiEngine {
if (this.state.focusedPane === 'settings') {
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();
return;
}
if (key && key.name === 'right') {
this.state.selectedSettingIndex = (this.state.selectedSettingIndex + 1) % 4;
this.state.selectedSettingIndex = (this.state.selectedSettingIndex + 1) % 5;
this.render();
return;
}
@ -727,6 +768,17 @@ export class TuiEngine {
this.state.currentPage = 1;
this.state.selectedIssueIndex = 0;
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;
}
@ -1385,8 +1437,18 @@ export class TuiEngine {
const seg2 = buildSegment(2, 'Type', typePlain, typeLabel);
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 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 padding = Math.max(0, cols - 2 - filtersPlainLen);

View File

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