From 472b66b105124f51e372593be1a25cccfac59d0d Mon Sep 17 00:00:00 2001 From: Jaahuo <46759334+Jaahuo@users.noreply.github.com> Date: Fri, 13 Mar 2026 13:46:06 +0200 Subject: [PATCH 1/3] fix(timer): show days in formatUsageDuration for durations >= 24h Durations of 24+ hours now display a days component instead of raw hours (e.g. "1d 12hr 30m" instead of "36hr 30m"). Applies to both normal and compact formats. Fixes #210. --- src/utils/__tests__/usage.test.ts | 12 ++++++++++++ src/utils/usage-windows.ts | 25 ++++++++++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/utils/__tests__/usage.test.ts b/src/utils/__tests__/usage.test.ts index f96f42b..7ff8f37 100644 --- a/src/utils/__tests__/usage.test.ts +++ b/src/utils/__tests__/usage.test.ts @@ -140,10 +140,22 @@ describe('usage window helpers', () => { expect(formatUsageDuration(4 * 60 * 60 * 1000 + 5 * 60 * 1000)).toBe('4hr 5m'); }); + it('formats duration with days when >= 24h', () => { + expect(formatUsageDuration(25 * 60 * 60 * 1000)).toBe('1d 1hr'); + expect(formatUsageDuration(36.5 * 60 * 60 * 1000)).toBe('1d 12hr 30m'); + expect(formatUsageDuration(168 * 60 * 60 * 1000)).toBe('7d'); + }); + it('formats duration in compact style', () => { expect(formatUsageDuration(0, true)).toBe('0h'); expect(formatUsageDuration(3 * 60 * 60 * 1000, true)).toBe('3h'); expect(formatUsageDuration(3.5 * 60 * 60 * 1000, true)).toBe('3h30m'); expect(formatUsageDuration(4 * 60 * 60 * 1000 + 5 * 60 * 1000, true)).toBe('4h5m'); }); + + it('formats duration with days in compact style when >= 24h', () => { + expect(formatUsageDuration(25 * 60 * 60 * 1000, true)).toBe('1d1h'); + expect(formatUsageDuration(36.5 * 60 * 60 * 1000, true)).toBe('1d12h30m'); + expect(formatUsageDuration(168 * 60 * 60 * 1000, true)).toBe('7d'); + }); }); \ No newline at end of file diff --git a/src/utils/usage-windows.ts b/src/utils/usage-windows.ts index 2d469c0..4dbce84 100644 --- a/src/utils/usage-windows.ts +++ b/src/utils/usage-windows.ts @@ -91,18 +91,29 @@ export function resolveWeeklyUsageWindow(usageData: UsageData, nowMs = Date.now( export function formatUsageDuration(durationMs: number, compact = false): string { const clampedMs = Math.max(0, durationMs); - const elapsedHours = Math.floor(clampedMs / (1000 * 60 * 60)); + const days = Math.floor(clampedMs / (1000 * 60 * 60 * 24)); + const elapsedHours = Math.floor((clampedMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const elapsedMinutes = Math.floor((clampedMs % (1000 * 60 * 60)) / (1000 * 60)); - if (compact) { - return elapsedMinutes === 0 ? `${elapsedHours}h` : `${elapsedHours}h${elapsedMinutes}m`; - } + const parts: string[] = []; - if (elapsedMinutes === 0) { - return `${elapsedHours}hr`; + if (compact) { + if (days > 0) + parts.push(`${days}d`); + if (elapsedHours > 0) + parts.push(`${elapsedHours}h`); + if (elapsedMinutes > 0) + parts.push(`${elapsedMinutes}m`); + return parts.length > 0 ? parts.join('') : '0h'; } - return `${elapsedHours}hr ${elapsedMinutes}m`; + if (days > 0) + parts.push(`${days}d`); + if (elapsedHours > 0) + parts.push(`${elapsedHours}hr`); + if (elapsedMinutes > 0) + parts.push(`${elapsedMinutes}m`); + return parts.length > 0 ? parts.join(' ') : '0hr'; } export function getUsageErrorMessage(error: UsageError): string { From c94c582fb1b0ad487ec26cdf4a21a14957cba759 Mon Sep 17 00:00:00 2001 From: Jaahuo <46759334+Jaahuo@users.noreply.github.com> Date: Fri, 13 Mar 2026 14:14:15 +0200 Subject: [PATCH 2/3] refactor(timer): simplify formatUsageDuration and show 0m for zero duration Unify compact/normal branches into a single code path differing only in hour label and separator. Zero duration now displays "0m" instead of "0hr"/"0h". --- src/utils/__tests__/usage.test.ts | 4 ++-- src/utils/usage-windows.ts | 31 ++++++++----------------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/utils/__tests__/usage.test.ts b/src/utils/__tests__/usage.test.ts index 7ff8f37..504e866 100644 --- a/src/utils/__tests__/usage.test.ts +++ b/src/utils/__tests__/usage.test.ts @@ -134,7 +134,7 @@ describe('usage window helpers', () => { }); it('formats duration in block timer style', () => { - expect(formatUsageDuration(0)).toBe('0hr'); + expect(formatUsageDuration(0)).toBe('0m'); expect(formatUsageDuration(3 * 60 * 60 * 1000)).toBe('3hr'); expect(formatUsageDuration(3.5 * 60 * 60 * 1000)).toBe('3hr 30m'); expect(formatUsageDuration(4 * 60 * 60 * 1000 + 5 * 60 * 1000)).toBe('4hr 5m'); @@ -147,7 +147,7 @@ describe('usage window helpers', () => { }); it('formats duration in compact style', () => { - expect(formatUsageDuration(0, true)).toBe('0h'); + expect(formatUsageDuration(0, true)).toBe('0m'); expect(formatUsageDuration(3 * 60 * 60 * 1000, true)).toBe('3h'); expect(formatUsageDuration(3.5 * 60 * 60 * 1000, true)).toBe('3h30m'); expect(formatUsageDuration(4 * 60 * 60 * 1000 + 5 * 60 * 1000, true)).toBe('4h5m'); diff --git a/src/utils/usage-windows.ts b/src/utils/usage-windows.ts index 4dbce84..57bb7da 100644 --- a/src/utils/usage-windows.ts +++ b/src/utils/usage-windows.ts @@ -91,29 +91,14 @@ export function resolveWeeklyUsageWindow(usageData: UsageData, nowMs = Date.now( export function formatUsageDuration(durationMs: number, compact = false): string { const clampedMs = Math.max(0, durationMs); - const days = Math.floor(clampedMs / (1000 * 60 * 60 * 24)); - const elapsedHours = Math.floor((clampedMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - const elapsedMinutes = Math.floor((clampedMs % (1000 * 60 * 60)) / (1000 * 60)); - - const parts: string[] = []; - - if (compact) { - if (days > 0) - parts.push(`${days}d`); - if (elapsedHours > 0) - parts.push(`${elapsedHours}h`); - if (elapsedMinutes > 0) - parts.push(`${elapsedMinutes}m`); - return parts.length > 0 ? parts.join('') : '0h'; - } - - if (days > 0) - parts.push(`${days}d`); - if (elapsedHours > 0) - parts.push(`${elapsedHours}hr`); - if (elapsedMinutes > 0) - parts.push(`${elapsedMinutes}m`); - return parts.length > 0 ? parts.join(' ') : '0hr'; + const d = Math.floor(clampedMs / (1000 * 60 * 60 * 24)); + const h = Math.floor((clampedMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const m = Math.floor((clampedMs % (1000 * 60 * 60)) / (1000 * 60)); + + const hLabel = compact ? 'h' : 'hr'; + const sep = compact ? '' : ' '; + const parts = [d > 0 && `${d}d`, h > 0 && `${h}${hLabel}`, m > 0 && `${m}m`].filter(Boolean); + return parts.length > 0 ? parts.join(sep) : '0m'; } export function getUsageErrorMessage(error: UsageError): string { From ef900f719497f1db1cb7f8adb3545e390cebe004 Mon Sep 17 00:00:00 2001 From: Matthew Breedlove Date: Sat, 14 Mar 2026 23:02:08 -0400 Subject: [PATCH 3/3] Add weekly reset hours toggle Default weekly reset timers to day-based formatting while adding an hours-only toggle and keeping preview text aligned with live rendering. Also move custom keybind visibility back into widgets and clear disabled toggle metadata when those options become unavailable in the editor. --- src/tui/components/ItemsEditor.tsx | 9 +- .../__tests__/input-handlers.test.ts | 10 +-- .../__tests__/keybind-visibility.test.ts | 44 ---------- .../components/items-editor/input-handlers.ts | 6 +- .../items-editor/keybind-visibility.ts | 25 ------ src/types/Widget.ts | 2 +- src/utils/__tests__/usage.test.ts | 12 +++ src/utils/renderer.ts | 4 +- src/utils/usage-windows.ts | 7 +- src/widgets/BlockResetTimer.ts | 11 +-- src/widgets/BlockTimer.ts | 11 +-- src/widgets/SessionUsage.ts | 8 +- src/widgets/Skills.tsx | 17 ++-- src/widgets/WeeklyResetTimer.ts | 71 +++++++++++++--- src/widgets/WeeklyUsage.ts | 8 +- src/widgets/__tests__/Skills.test.ts | 25 +++++- .../__tests__/WeeklyResetTimer.test.ts | 83 ++++++++++++++++++- .../__tests__/helpers/usage-widget-suites.ts | 37 +++++++-- src/widgets/shared/metadata.ts | 11 +++ src/widgets/shared/usage-display.ts | 47 +++++++++-- 20 files changed, 302 insertions(+), 146 deletions(-) delete mode 100644 src/tui/components/items-editor/__tests__/keybind-visibility.test.ts delete mode 100644 src/tui/components/items-editor/keybind-visibility.ts diff --git a/src/tui/components/ItemsEditor.tsx b/src/tui/components/ItemsEditor.tsx index d68fbdc..5a564b1 100644 --- a/src/tui/components/ItemsEditor.tsx +++ b/src/tui/components/ItemsEditor.tsx @@ -32,7 +32,6 @@ import { type WidgetPickerAction, type WidgetPickerState } from './items-editor/input-handlers'; -import { shouldShowCustomKeybind } from './items-editor/keybind-visibility'; export interface ItemsEditorProps { widgets: WidgetItem[]; @@ -95,12 +94,12 @@ export const ItemsEditor: React.FC = ({ widgets, onUpdate, onB setCustomEditorWidget(null); }; - const getVisibleCustomKeybinds = (widgetImpl: Widget, widget: WidgetItem): CustomKeybind[] => { + const getCustomKeybindsForWidget = (widgetImpl: Widget, widget: WidgetItem): CustomKeybind[] => { if (!widgetImpl.getCustomKeybinds) { return []; } - return widgetImpl.getCustomKeybinds().filter(keybind => shouldShowCustomKeybind(widget, keybind)); + return widgetImpl.getCustomKeybinds(widget); }; const openWidgetPicker = (action: WidgetPickerAction) => { @@ -200,7 +199,7 @@ export const ItemsEditor: React.FC = ({ widgets, onUpdate, onB setMoveMode, setShowClearConfirm, openWidgetPicker, - getVisibleCustomKeybinds, + getCustomKeybindsForWidget, setCustomEditorWidget }); }); @@ -263,7 +262,7 @@ export const ItemsEditor: React.FC = ({ widgets, onUpdate, onB if (widgetImpl) { canToggleRaw = widgetImpl.supportsRawValue(); // Get custom keybinds from the widget - customKeybinds = getVisibleCustomKeybinds(widgetImpl, currentWidget); + customKeybinds = getCustomKeybindsForWidget(widgetImpl, currentWidget); } else { canToggleRaw = false; } diff --git a/src/tui/components/items-editor/__tests__/input-handlers.test.ts b/src/tui/components/items-editor/__tests__/input-handlers.test.ts index 8064d3a..160d84c 100644 --- a/src/tui/components/items-editor/__tests__/input-handlers.test.ts +++ b/src/tui/components/items-editor/__tests__/input-handlers.test.ts @@ -166,7 +166,7 @@ describe('items-editor input handlers', () => { setMoveMode: vi.fn(), setShowClearConfirm: vi.fn(), openWidgetPicker: vi.fn(), - getVisibleCustomKeybinds: widgetImpl => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds() : [], + getCustomKeybindsForWidget: (widgetImpl, widget) => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds(widget) : [], setCustomEditorWidget: vi.fn() }); @@ -192,7 +192,7 @@ describe('items-editor input handlers', () => { setMoveMode: vi.fn(), setShowClearConfirm: vi.fn(), openWidgetPicker: vi.fn(), - getVisibleCustomKeybinds: widgetImpl => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds() : [], + getCustomKeybindsForWidget: (widgetImpl, widget) => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds(widget) : [], setCustomEditorWidget: vi.fn() }); @@ -218,7 +218,7 @@ describe('items-editor input handlers', () => { setMoveMode: vi.fn(), setShowClearConfirm: vi.fn(), openWidgetPicker: vi.fn(), - getVisibleCustomKeybinds: widgetImpl => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds() : [], + getCustomKeybindsForWidget: (widgetImpl, widget) => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds(widget) : [], setCustomEditorWidget: vi.fn() }); @@ -244,7 +244,7 @@ describe('items-editor input handlers', () => { setMoveMode: vi.fn(), setShowClearConfirm: vi.fn(), openWidgetPicker: vi.fn(), - getVisibleCustomKeybinds: widgetImpl => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds() : [], + getCustomKeybindsForWidget: (widgetImpl, widget) => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds(widget) : [], setCustomEditorWidget: vi.fn() }); @@ -271,7 +271,7 @@ describe('items-editor input handlers', () => { setMoveMode: vi.fn(), setShowClearConfirm: vi.fn(), openWidgetPicker: vi.fn(), - getVisibleCustomKeybinds: widgetImpl => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds() : [], + getCustomKeybindsForWidget: (widgetImpl, widget) => widgetImpl.getCustomKeybinds ? widgetImpl.getCustomKeybinds(widget) : [], setCustomEditorWidget }); diff --git a/src/tui/components/items-editor/__tests__/keybind-visibility.test.ts b/src/tui/components/items-editor/__tests__/keybind-visibility.test.ts deleted file mode 100644 index 98919f1..0000000 --- a/src/tui/components/items-editor/__tests__/keybind-visibility.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - describe, - expect, - it -} from 'vitest'; - -import type { - CustomKeybind, - WidgetItem -} from '../../../../types/Widget'; -import { shouldShowCustomKeybind } from '../keybind-visibility'; - -const TOGGLE_COMPACT_KEYBIND: CustomKeybind = { key: 's', label: '(s)hort time', action: 'toggle-compact' }; -const TOGGLE_INVERT_KEYBIND: CustomKeybind = { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' }; -const EDIT_LIST_LIMIT_KEYBIND: CustomKeybind = { key: 'l', label: '(l)imit list', action: 'edit-list-limit' }; - -function createWidget(type: string, metadata?: Record): WidgetItem { - return { - id: 'widget', - type, - metadata - }; -} - -describe('shouldShowCustomKeybind', () => { - it('shows invert only in progress modes', () => { - expect(shouldShowCustomKeybind(createWidget('block-timer'), TOGGLE_INVERT_KEYBIND)).toBe(false); - expect(shouldShowCustomKeybind(createWidget('block-timer', { display: 'progress' }), TOGGLE_INVERT_KEYBIND)).toBe(true); - expect(shouldShowCustomKeybind(createWidget('block-timer', { display: 'progress-short' }), TOGGLE_INVERT_KEYBIND)).toBe(true); - }); - - it('hides short time in progress modes', () => { - expect(shouldShowCustomKeybind(createWidget('block-timer'), TOGGLE_COMPACT_KEYBIND)).toBe(true); - expect(shouldShowCustomKeybind(createWidget('block-timer', { display: 'time' }), TOGGLE_COMPACT_KEYBIND)).toBe(true); - expect(shouldShowCustomKeybind(createWidget('block-timer', { display: 'progress' }), TOGGLE_COMPACT_KEYBIND)).toBe(false); - expect(shouldShowCustomKeybind(createWidget('block-timer', { display: 'progress-short' }), TOGGLE_COMPACT_KEYBIND)).toBe(false); - }); - - it('shows list limit only for skills list mode', () => { - expect(shouldShowCustomKeybind(createWidget('skills'), EDIT_LIST_LIMIT_KEYBIND)).toBe(false); - expect(shouldShowCustomKeybind(createWidget('skills', { mode: 'count' }), EDIT_LIST_LIMIT_KEYBIND)).toBe(false); - expect(shouldShowCustomKeybind(createWidget('skills', { mode: 'list' }), EDIT_LIST_LIMIT_KEYBIND)).toBe(true); - }); -}); \ No newline at end of file diff --git a/src/tui/components/items-editor/input-handlers.ts b/src/tui/components/items-editor/input-handlers.ts index 0d3cab0..7dafe49 100644 --- a/src/tui/components/items-editor/input-handlers.ts +++ b/src/tui/components/items-editor/input-handlers.ts @@ -338,7 +338,7 @@ export interface HandleNormalInputModeArgs { setMoveMode: (moveMode: boolean) => void; setShowClearConfirm: (show: boolean) => void; openWidgetPicker: (action: WidgetPickerAction) => void; - getVisibleCustomKeybinds: (widgetImpl: Widget, widget: WidgetItem) => CustomKeybind[]; + getCustomKeybindsForWidget: (widgetImpl: Widget, widget: WidgetItem) => CustomKeybind[]; setCustomEditorWidget: (state: CustomEditorWidgetState | null) => void; } @@ -354,7 +354,7 @@ export function handleNormalInputMode({ setMoveMode, setShowClearConfirm, openWidgetPicker, - getVisibleCustomKeybinds, + getCustomKeybindsForWidget, setCustomEditorWidget }: HandleNormalInputModeArgs): void { if (key.upArrow && widgets.length > 0) { @@ -436,7 +436,7 @@ export function handleNormalInputMode({ return; } - const customKeybinds = getVisibleCustomKeybinds(widgetImpl, currentWidget); + const customKeybinds = getCustomKeybindsForWidget(widgetImpl, currentWidget); const matchedKeybind = customKeybinds.find(kb => kb.key === input); if (matchedKeybind && !key.ctrl) { diff --git a/src/tui/components/items-editor/keybind-visibility.ts b/src/tui/components/items-editor/keybind-visibility.ts deleted file mode 100644 index b130e61..0000000 --- a/src/tui/components/items-editor/keybind-visibility.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { - CustomKeybind, - WidgetItem -} from '../../../types/Widget'; - -function isProgressMode(widget: WidgetItem): boolean { - const mode = widget.metadata?.display; - return mode === 'progress' || mode === 'progress-short'; -} - -export function shouldShowCustomKeybind(widget: WidgetItem, keybind: CustomKeybind): boolean { - if (keybind.action === 'edit-list-limit') { - return widget.type === 'skills' && widget.metadata?.mode === 'list'; - } - - if (keybind.action === 'toggle-invert') { - return isProgressMode(widget); - } - - if (keybind.action === 'toggle-compact') { - return !isProgressMode(widget); - } - - return true; -} \ No newline at end of file diff --git a/src/types/Widget.ts b/src/types/Widget.ts index 463a4ca..fd3e5f8 100644 --- a/src/types/Widget.ts +++ b/src/types/Widget.ts @@ -37,7 +37,7 @@ export interface Widget { getCategory(): string; getEditorDisplay(item: WidgetItem): WidgetEditorDisplay; render(item: WidgetItem, context: RenderContext, settings: Settings): string | null; - getCustomKeybinds?(): CustomKeybind[]; + getCustomKeybinds?(item?: WidgetItem): CustomKeybind[]; renderEditor?(props: WidgetEditorProps): React.ReactElement | null; supportsRawValue(): boolean; supportsColors(item: WidgetItem): boolean; diff --git a/src/utils/__tests__/usage.test.ts b/src/utils/__tests__/usage.test.ts index 504e866..1064d41 100644 --- a/src/utils/__tests__/usage.test.ts +++ b/src/utils/__tests__/usage.test.ts @@ -158,4 +158,16 @@ describe('usage window helpers', () => { expect(formatUsageDuration(36.5 * 60 * 60 * 1000, true)).toBe('1d12h30m'); expect(formatUsageDuration(168 * 60 * 60 * 1000, true)).toBe('7d'); }); + + it('formats duration without days when requested', () => { + expect(formatUsageDuration(25 * 60 * 60 * 1000, false, false)).toBe('25hr'); + expect(formatUsageDuration(36.5 * 60 * 60 * 1000, false, false)).toBe('36hr 30m'); + expect(formatUsageDuration(168 * 60 * 60 * 1000, false, false)).toBe('168hr'); + }); + + it('formats duration without days in compact style when requested', () => { + expect(formatUsageDuration(25 * 60 * 60 * 1000, true, false)).toBe('25h'); + expect(formatUsageDuration(36.5 * 60 * 60 * 1000, true, false)).toBe('36h30m'); + expect(formatUsageDuration(168 * 60 * 60 * 1000, true, false)).toBe('168h'); + }); }); \ No newline at end of file diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 546f30d..94fb7fd 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -352,7 +352,7 @@ function renderPowerlineStatusLine( // - Background: previous widget's background color // Build separator with raw ANSI codes to avoid reset issues - let separatorOutput = ''; + let separatorOutput: string; // Check if adjacent widgets have the same background color const sameBackground = widget.bgColor && nextWidget.bgColor && widget.bgColor === nextWidget.bgColor; @@ -832,7 +832,7 @@ export function renderStatusLine( }); // Build the final status line - let statusLine = ''; + let statusLine: string; if (hasFlexSeparator && terminalWidth) { // Split elements by flex separators diff --git a/src/utils/usage-windows.ts b/src/utils/usage-windows.ts index 57bb7da..a1f70c1 100644 --- a/src/utils/usage-windows.ts +++ b/src/utils/usage-windows.ts @@ -89,14 +89,15 @@ export function resolveWeeklyUsageWindow(usageData: UsageData, nowMs = Date.now( return getWeeklyUsageWindowFromResetAt(usageData.weeklyResetAt, nowMs); } -export function formatUsageDuration(durationMs: number, compact = false): string { +export function formatUsageDuration(durationMs: number, compact = false, useDays = true): string { const clampedMs = Math.max(0, durationMs); - const d = Math.floor(clampedMs / (1000 * 60 * 60 * 24)); - const h = Math.floor((clampedMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const totalHours = Math.floor(clampedMs / (1000 * 60 * 60)); const m = Math.floor((clampedMs % (1000 * 60 * 60)) / (1000 * 60)); const hLabel = compact ? 'h' : 'hr'; const sep = compact ? '' : ' '; + const d = useDays ? Math.floor(totalHours / 24) : 0; + const h = useDays ? totalHours % 24 : totalHours; const parts = [d > 0 && `${d}d`, h > 0 && `${h}${hLabel}`, m > 0 && `${m}m`].filter(Boolean); return parts.length > 0 ? parts.join(sep) : '0m'; } diff --git a/src/widgets/BlockResetTimer.ts b/src/widgets/BlockResetTimer.ts index 4c23b92..45d80ca 100644 --- a/src/widgets/BlockResetTimer.ts +++ b/src/widgets/BlockResetTimer.ts @@ -18,6 +18,7 @@ import { getUsageDisplayMode, getUsageDisplayModifierText, getUsageProgressBarWidth, + getUsageTimerCustomKeybinds, isUsageCompact, isUsageInverted, isUsageProgressMode, @@ -47,7 +48,7 @@ export class BlockResetTimerWidget implements Widget { handleEditorAction(action: string, item: WidgetItem): WidgetItem | null { if (action === 'toggle-progress') { - return cycleUsageDisplayMode(item); + return cycleUsageDisplayMode(item, ['compact']); } if (action === 'toggle-invert') { @@ -101,12 +102,8 @@ export class BlockResetTimerWidget implements Widget { return formatRawOrLabeledValue(item, 'Reset: ', remainingTime); } - getCustomKeybinds(): CustomKeybind[] { - return [ - { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, - { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' }, - { key: 's', label: '(s)hort time', action: 'toggle-compact' } - ]; + getCustomKeybinds(item?: WidgetItem): CustomKeybind[] { + return getUsageTimerCustomKeybinds(item); } supportsRawValue(): boolean { return true; } diff --git a/src/widgets/BlockTimer.ts b/src/widgets/BlockTimer.ts index bfce5d5..bbd0420 100644 --- a/src/widgets/BlockTimer.ts +++ b/src/widgets/BlockTimer.ts @@ -17,6 +17,7 @@ import { getUsageDisplayMode, getUsageDisplayModifierText, getUsageProgressBarWidth, + getUsageTimerCustomKeybinds, isUsageCompact, isUsageInverted, isUsageProgressMode, @@ -46,7 +47,7 @@ export class BlockTimerWidget implements Widget { handleEditorAction(action: string, item: WidgetItem): WidgetItem | null { if (action === 'toggle-progress') { - return cycleUsageDisplayMode(item); + return cycleUsageDisplayMode(item, ['compact']); } if (action === 'toggle-invert') { @@ -102,12 +103,8 @@ export class BlockTimerWidget implements Widget { return formatRawOrLabeledValue(item, 'Block: ', elapsedTime); } - getCustomKeybinds(): CustomKeybind[] { - return [ - { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, - { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' }, - { key: 's', label: '(s)hort time', action: 'toggle-compact' } - ]; + getCustomKeybinds(item?: WidgetItem): CustomKeybind[] { + return getUsageTimerCustomKeybinds(item); } supportsRawValue(): boolean { return true; } diff --git a/src/widgets/SessionUsage.ts b/src/widgets/SessionUsage.ts index 32ed6dd..1805e91 100644 --- a/src/widgets/SessionUsage.ts +++ b/src/widgets/SessionUsage.ts @@ -16,6 +16,7 @@ import { cycleUsageDisplayMode, getUsageDisplayMode, getUsageDisplayModifierText, + getUsagePercentCustomKeybinds, getUsageProgressBarWidth, isUsageInverted, isUsageProgressMode, @@ -81,11 +82,8 @@ export class SessionUsageWidget implements Widget { return formatRawOrLabeledValue(item, 'Session: ', `${percent.toFixed(1)}%`); } - getCustomKeybinds(): CustomKeybind[] { - return [ - { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, - { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' } - ]; + getCustomKeybinds(item?: WidgetItem): CustomKeybind[] { + return getUsagePercentCustomKeybinds(item); } supportsRawValue(): boolean { return true; } diff --git a/src/widgets/Skills.tsx b/src/widgets/Skills.tsx index 1dd7446..cbed430 100644 --- a/src/widgets/Skills.tsx +++ b/src/widgets/Skills.tsx @@ -20,6 +20,7 @@ import { shouldInsertInput } from '../utils/input-guards'; import { makeModifierText } from './shared/editor-display'; import { isMetadataFlagEnabled, + removeMetadataKeys, toggleMetadataFlag } from './shared/metadata'; @@ -73,12 +74,17 @@ export class SkillsWidget implements Widget { ]; } - getCustomKeybinds(): CustomKeybind[] { - return [ + getCustomKeybinds(item?: WidgetItem): CustomKeybind[] { + const keybinds: CustomKeybind[] = [ { key: 'v', label: '(v)iew: last/count/list', action: 'cycle-mode' }, - { key: 'h', label: '(h)ide when empty', action: TOGGLE_HIDE_EMPTY_ACTION }, - { key: 'l', label: '(l)imit', action: EDIT_LIST_LIMIT_ACTION } + { key: 'h', label: '(h)ide when empty', action: TOGGLE_HIDE_EMPTY_ACTION } ]; + + if (item && this.getMode(item) === 'list') { + keybinds.push({ key: 'l', label: '(l)imit', action: EDIT_LIST_LIMIT_ACTION }); + } + + return keybinds; } getEditorDisplay(item: WidgetItem): WidgetEditorDisplay { @@ -98,7 +104,8 @@ export class SkillsWidget implements Widget { handleEditorAction(action: string, item: WidgetItem): WidgetItem | null { if (action === 'cycle-mode') { const next = MODES[(MODES.indexOf(this.getMode(item)) + 1) % MODES.length] ?? 'current'; - return { ...item, metadata: { ...item.metadata, mode: next } }; + const nextItem = next === 'list' ? item : removeMetadataKeys(item, [LIST_LIMIT_KEY]); + return { ...nextItem, metadata: { ...nextItem.metadata, mode: next } }; } if (action === TOGGLE_HIDE_EMPTY_ACTION) { return toggleMetadataFlag(item, HIDE_WHEN_EMPTY_KEY); diff --git a/src/widgets/WeeklyResetTimer.ts b/src/widgets/WeeklyResetTimer.ts index ead9af7..a293af9 100644 --- a/src/widgets/WeeklyResetTimer.ts +++ b/src/widgets/WeeklyResetTimer.ts @@ -12,12 +12,17 @@ import { resolveWeeklyUsageWindow } from '../utils/usage'; +import { makeModifierText } from './shared/editor-display'; +import { + isMetadataFlagEnabled, + toggleMetadataFlag +} from './shared/metadata'; import { formatRawOrLabeledValue } from './shared/raw-or-labeled'; import { cycleUsageDisplayMode, getUsageDisplayMode, - getUsageDisplayModifierText, getUsageProgressBarWidth, + getUsageTimerCustomKeybinds, isUsageCompact, isUsageInverted, isUsageProgressMode, @@ -32,6 +37,43 @@ function makeTimerProgressBar(percent: number, width: number): string { return '█'.repeat(filledWidth) + '░'.repeat(emptyWidth); } +const WEEKLY_PREVIEW_DURATION_MS = 36.5 * 60 * 60 * 1000; + +function isWeeklyResetHoursOnly(item: WidgetItem): boolean { + return isMetadataFlagEnabled(item, 'hours'); +} + +function toggleWeeklyResetHoursOnly(item: WidgetItem): WidgetItem { + return toggleMetadataFlag(item, 'hours'); +} + +function getWeeklyResetModifierText(item: WidgetItem): string | undefined { + const displayMode = getUsageDisplayMode(item); + const modifiers: string[] = []; + + if (displayMode === 'progress') { + modifiers.push('progress bar'); + } else if (displayMode === 'progress-short') { + modifiers.push('short bar'); + } + + if (isUsageInverted(item)) { + modifiers.push('inverted'); + } + + if (!isUsageProgressMode(displayMode)) { + if (isUsageCompact(item)) { + modifiers.push('compact'); + } + + if (isWeeklyResetHoursOnly(item)) { + modifiers.push('hours only'); + } + } + + return makeModifierText(modifiers); +} + export class WeeklyResetTimerWidget implements Widget { getDefaultColor(): string { return 'brightBlue'; } getDescription(): string { return 'Shows time remaining until weekly usage reset'; } @@ -41,13 +83,13 @@ export class WeeklyResetTimerWidget implements Widget { getEditorDisplay(item: WidgetItem): WidgetEditorDisplay { return { displayText: this.getDisplayName(), - modifierText: getUsageDisplayModifierText(item, { includeCompact: true }) + modifierText: getWeeklyResetModifierText(item) }; } handleEditorAction(action: string, item: WidgetItem): WidgetItem | null { if (action === 'toggle-progress') { - return cycleUsageDisplayMode(item); + return cycleUsageDisplayMode(item, ['compact', 'hours']); } if (action === 'toggle-invert') { @@ -58,6 +100,10 @@ export class WeeklyResetTimerWidget implements Widget { return toggleUsageCompact(item); } + if (action === 'toggle-hours') { + return toggleWeeklyResetHoursOnly(item); + } + return null; } @@ -65,6 +111,7 @@ export class WeeklyResetTimerWidget implements Widget { const displayMode = getUsageDisplayMode(item); const inverted = isUsageInverted(item); const compact = isUsageCompact(item); + const useDays = !isWeeklyResetHoursOnly(item); if (context.isPreview) { const previewPercent = inverted ? 90.0 : 10.0; @@ -75,7 +122,7 @@ export class WeeklyResetTimerWidget implements Widget { return formatRawOrLabeledValue(item, 'Weekly Reset ', `[${progressBar}] ${previewPercent.toFixed(1)}%`); } - return formatRawOrLabeledValue(item, 'Weekly Reset: ', compact ? '36h30m' : '36hr 30m'); + return formatRawOrLabeledValue(item, 'Weekly Reset: ', formatUsageDuration(WEEKLY_PREVIEW_DURATION_MS, compact, useDays)); } const usageData = context.usageData ?? {}; @@ -97,16 +144,18 @@ export class WeeklyResetTimerWidget implements Widget { return formatRawOrLabeledValue(item, 'Weekly Reset ', `[${progressBar}] ${percentage}%`); } - const remainingTime = formatUsageDuration(window.remainingMs, compact); + const remainingTime = formatUsageDuration(window.remainingMs, compact, useDays); return formatRawOrLabeledValue(item, 'Weekly Reset: ', remainingTime); } - getCustomKeybinds(): CustomKeybind[] { - return [ - { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, - { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' }, - { key: 's', label: '(s)hort time', action: 'toggle-compact' } - ]; + getCustomKeybinds(item?: WidgetItem): CustomKeybind[] { + const keybinds = getUsageTimerCustomKeybinds(item); + + if (!item || !isUsageProgressMode(getUsageDisplayMode(item))) { + keybinds.push({ key: 'h', label: '(h)ours only', action: 'toggle-hours' }); + } + + return keybinds; } supportsRawValue(): boolean { return true; } diff --git a/src/widgets/WeeklyUsage.ts b/src/widgets/WeeklyUsage.ts index 161f755..c485804 100644 --- a/src/widgets/WeeklyUsage.ts +++ b/src/widgets/WeeklyUsage.ts @@ -16,6 +16,7 @@ import { cycleUsageDisplayMode, getUsageDisplayMode, getUsageDisplayModifierText, + getUsagePercentCustomKeybinds, getUsageProgressBarWidth, isUsageInverted, isUsageProgressMode, @@ -81,11 +82,8 @@ export class WeeklyUsageWidget implements Widget { return formatRawOrLabeledValue(item, 'Weekly: ', `${percent.toFixed(1)}%`); } - getCustomKeybinds(): CustomKeybind[] { - return [ - { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, - { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' } - ]; + getCustomKeybinds(item?: WidgetItem): CustomKeybind[] { + return getUsagePercentCustomKeybinds(item); } supportsRawValue(): boolean { return true; } diff --git a/src/widgets/__tests__/Skills.test.ts b/src/widgets/__tests__/Skills.test.ts index a8eae7c..fc26fc2 100644 --- a/src/widgets/__tests__/Skills.test.ts +++ b/src/widgets/__tests__/Skills.test.ts @@ -18,7 +18,15 @@ function render(item: WidgetItem, context: RenderContext): string | null { describe('SkillsWidget', () => { it('uses v as the mode toggle keybind', () => { const widget = new SkillsWidget(); - expect(widget.getCustomKeybinds()).toEqual([ + expect(widget.getCustomKeybinds({ id: 'skills', type: 'skills' })).toEqual([ + { key: 'v', label: '(v)iew: last/count/list', action: 'cycle-mode' }, + { key: 'h', label: '(h)ide when empty', action: 'toggle-hide-empty' } + ]); + expect(widget.getCustomKeybinds({ + id: 'skills', + type: 'skills', + metadata: { mode: 'list' } + })).toEqual([ { key: 'v', label: '(v)iew: last/count/list', action: 'cycle-mode' }, { key: 'h', label: '(h)ide when empty', action: 'toggle-hide-empty' }, { key: 'l', label: '(l)imit', action: 'edit-list-limit' } @@ -37,6 +45,21 @@ describe('SkillsWidget', () => { expect(current?.metadata?.mode).toBe('current'); }); + it('clears list limit metadata when leaving list mode', () => { + const widget = new SkillsWidget(); + const updated = widget.handleEditorAction('cycle-mode', { + id: 'skills', + type: 'skills', + metadata: { + mode: 'list', + listLimit: '2' + } + }); + + expect(updated?.metadata?.mode).toBe('current'); + expect(updated?.metadata?.listLimit).toBeUndefined(); + }); + it('toggles hide-when-empty metadata', () => { const widget = new SkillsWidget(); const base: WidgetItem = { id: 'skills', type: 'skills' }; diff --git a/src/widgets/__tests__/WeeklyResetTimer.test.ts b/src/widgets/__tests__/WeeklyResetTimer.test.ts index 341095b..6ba218e 100644 --- a/src/widgets/__tests__/WeeklyResetTimer.test.ts +++ b/src/widgets/__tests__/WeeklyResetTimer.test.ts @@ -39,7 +39,17 @@ describe('WeeklyResetTimerWidget', () => { it('renders preview using weekly reset format', () => { const widget = new WeeklyResetTimerWidget(); - expect(render(widget, { id: 'weekly-reset', type: 'weekly-reset-timer' }, { isPreview: true })).toBe('Weekly Reset: 36hr 30m'); + expect(render(widget, { id: 'weekly-reset', type: 'weekly-reset-timer' }, { isPreview: true })).toBe('Weekly Reset: 1d 12hr 30m'); + }); + + it('renders preview in hours-only mode when toggled', () => { + const widget = new WeeklyResetTimerWidget(); + + expect(render(widget, { + id: 'weekly-reset', + type: 'weekly-reset-timer', + metadata: { hours: 'true' } + }, { isPreview: true })).toBe('Weekly Reset: 36hr 30m'); }); it('renders remaining time in time mode', () => { @@ -55,6 +65,27 @@ describe('WeeklyResetTimerWidget', () => { mockFormatUsageDuration.mockReturnValue('134hr 40m'); expect(render(widget, { id: 'weekly-reset', type: 'weekly-reset-timer' }, { usageData: {} })).toBe('Weekly Reset: 134hr 40m'); + expect(mockFormatUsageDuration).toHaveBeenCalledWith(484800000, false, true); + }); + + it('renders remaining time in hours-only mode', () => { + const widget = new WeeklyResetTimerWidget(); + + mockResolveWeeklyUsageWindow.mockReturnValue({ + sessionDurationMs: 604800000, + elapsedMs: 120000000, + remainingMs: 484800000, + elapsedPercent: 19.8412698413, + remainingPercent: 80.1587301587 + }); + mockFormatUsageDuration.mockReturnValue('134hr 40m'); + + expect(render(widget, { + id: 'weekly-reset', + type: 'weekly-reset-timer', + metadata: { hours: 'true' } + }, { usageData: {} })).toBe('Weekly Reset: 134hr 40m'); + expect(mockFormatUsageDuration).toHaveBeenCalledWith(484800000, false, false); }); it('renders short progress bar with inverted fill', () => { @@ -111,10 +142,60 @@ describe('WeeklyResetTimerWidget', () => { expect(render(widget, { id: 'weekly-reset', type: 'weekly-reset-timer', rawValue: true }, { usageData: {} })).toBe('120hr 15m'); }); + it('toggles hours-only metadata and shows hours-only modifier text', () => { + const widget = new WeeklyResetTimerWidget(); + const baseItem: WidgetItem = { id: 'weekly-reset', type: 'weekly-reset-timer' }; + + const hoursOnly = widget.handleEditorAction('toggle-hours', baseItem); + const cleared = widget.handleEditorAction('toggle-hours', hoursOnly ?? baseItem); + + expect(hoursOnly?.metadata?.hours).toBe('true'); + expect(cleared?.metadata?.hours).toBe('false'); + expect(widget.getEditorDisplay(baseItem).modifierText).toBeUndefined(); + expect(widget.getEditorDisplay({ + ...baseItem, + metadata: { hours: 'true' } + }).modifierText).toBe('(hours only)'); + }); + + it('clears compact and hours-only metadata when cycling into progress mode', () => { + const widget = new WeeklyResetTimerWidget(); + const updated = widget.handleEditorAction('toggle-progress', { + id: 'weekly-reset', + type: 'weekly-reset-timer', + metadata: { + compact: 'true', + hours: 'true' + } + }); + + expect(updated?.metadata?.display).toBe('progress'); + expect(updated?.metadata?.compact).toBeUndefined(); + expect(updated?.metadata?.hours).toBeUndefined(); + }); + + it('ignores stale hours-only metadata in progress mode editor modifiers', () => { + const widget = new WeeklyResetTimerWidget(); + + expect(widget.getEditorDisplay({ + id: 'weekly-reset', + type: 'weekly-reset-timer', + metadata: { + display: 'progress', + hours: 'true' + } + }).modifierText).toBe('(progress bar)'); + }); + runUsageTimerEditorSuite({ baseItem: { id: 'weekly-reset', type: 'weekly-reset-timer' }, createWidget: () => new WeeklyResetTimerWidget(), expectedDisplayName: 'Weekly Reset Timer', + expectedTimeKeybinds: [ + { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, + { key: 's', label: '(s)hort time', action: 'toggle-compact' }, + { key: 'h', label: '(h)ours only', action: 'toggle-hours' } + ], expectedModifierText: '(short bar, inverted)', modifierItem: { id: 'weekly-reset', diff --git a/src/widgets/__tests__/helpers/usage-widget-suites.ts b/src/widgets/__tests__/helpers/usage-widget-suites.ts index 0e9c9af..a73cca3 100644 --- a/src/widgets/__tests__/helpers/usage-widget-suites.ts +++ b/src/widgets/__tests__/helpers/usage-widget-suites.ts @@ -13,7 +13,7 @@ import type { } from '../../../types/Widget'; interface UsageWidgetLike { - getCustomKeybinds(): CustomKeybind[]; + getCustomKeybinds(item?: WidgetItem): CustomKeybind[]; getEditorDisplay(item: WidgetItem): WidgetEditorDisplay; handleEditorAction(action: string, item: WidgetItem): WidgetItem | null; supportsRawValue(): boolean; @@ -41,21 +41,31 @@ interface UsageTimerEditorSuiteConfig TWidget; expectedDisplayName: string; + expectedProgressKeybinds?: CustomKeybind[]; expectedModifierText: string; modifierItem: WidgetItem; + expectedTimeKeybinds?: CustomKeybind[]; } const EXPECTED_USAGE_KEYBINDS: CustomKeybind[] = [ + { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' } +]; + +const EXPECTED_USAGE_PROGRESS_KEYBINDS: CustomKeybind[] = [ { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' } ]; -const EXPECTED_TIMER_KEYBINDS: CustomKeybind[] = [ +const EXPECTED_TIMER_TIME_KEYBINDS: CustomKeybind[] = [ { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, - { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' }, { key: 's', label: '(s)hort time', action: 'toggle-compact' } ]; +const EXPECTED_TIMER_PROGRESS_KEYBINDS: CustomKeybind[] = [ + { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }, + { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' } +]; + function getUsageContext(field: 'sessionUsage' | 'weeklyUsage', value: number): RenderContext { return field === 'sessionUsage' ? { usageData: { sessionUsage: value } } @@ -67,11 +77,12 @@ export function runUsagePercentWidgetSuite(conf vi.clearAllMocks(); }); - it('exposes progress and invert keybinds', () => { + it('exposes widget-managed keybinds for time and progress modes', () => { const widget = config.createWidget(); expect(widget.supportsRawValue()).toBe(true); - expect(widget.getCustomKeybinds()).toEqual(EXPECTED_USAGE_KEYBINDS); + expect(widget.getCustomKeybinds(config.baseItem)).toEqual(EXPECTED_USAGE_KEYBINDS); + expect(widget.getCustomKeybinds(config.progressItem)).toEqual(EXPECTED_USAGE_PROGRESS_KEYBINDS); }); it.each([ @@ -170,12 +181,13 @@ export function runUsageTimerEditorSuite { + it('supports raw value and exposes widget-managed keybinds for time and progress modes', () => { const widget = config.createWidget(); expect(widget.getDisplayName()).toBe(config.expectedDisplayName); expect(widget.supportsRawValue()).toBe(true); - expect(widget.getCustomKeybinds()).toEqual(EXPECTED_TIMER_KEYBINDS); + expect(widget.getCustomKeybinds(config.baseItem)).toEqual(config.expectedTimeKeybinds ?? EXPECTED_TIMER_TIME_KEYBINDS); + expect(widget.getCustomKeybinds(config.modifierItem)).toEqual(config.expectedProgressKeybinds ?? EXPECTED_TIMER_PROGRESS_KEYBINDS); }); it('clears invert metadata when cycling back to time mode', () => { @@ -204,6 +216,17 @@ export function runUsageTimerEditorSuite { + const widget = config.createWidget(); + const updated = widget.handleEditorAction('toggle-progress', { + ...config.baseItem, + metadata: { compact: 'true' } + }); + + expect(updated?.metadata?.display).toBe('progress'); + expect(updated?.metadata?.compact).toBeUndefined(); + }); + it('toggles invert metadata and shows editor modifiers', () => { const widget = config.createWidget(); diff --git a/src/widgets/shared/metadata.ts b/src/widgets/shared/metadata.ts index 4a469ca..5256287 100644 --- a/src/widgets/shared/metadata.ts +++ b/src/widgets/shared/metadata.ts @@ -12,4 +12,15 @@ export function toggleMetadataFlag(item: WidgetItem, key: string): WidgetItem { [key]: (!isMetadataFlagEnabled(item, key)).toString() } }; +} + +export function removeMetadataKeys(item: WidgetItem, keys: string[]): WidgetItem { + const nextMetadata = Object.fromEntries( + Object.entries(item.metadata ?? {}).filter(([key]) => !keys.includes(key)) + ); + + return { + ...item, + metadata: Object.keys(nextMetadata).length > 0 ? nextMetadata : undefined + }; } \ No newline at end of file diff --git a/src/widgets/shared/usage-display.ts b/src/widgets/shared/usage-display.ts index 1c97e06..e42e99e 100644 --- a/src/widgets/shared/usage-display.ts +++ b/src/widgets/shared/usage-display.ts @@ -1,13 +1,21 @@ -import type { WidgetItem } from '../../types/Widget'; +import type { + CustomKeybind, + WidgetItem +} from '../../types/Widget'; import { makeModifierText } from './editor-display'; import { isMetadataFlagEnabled, + removeMetadataKeys, toggleMetadataFlag } from './metadata'; export type UsageDisplayMode = 'time' | 'progress' | 'progress-short'; +const PROGRESS_TOGGLE_KEYBIND: CustomKeybind = { key: 'p', label: '(p)rogress toggle', action: 'toggle-progress' }; +const INVERT_TOGGLE_KEYBIND: CustomKeybind = { key: 'v', label: 'in(v)ert fill', action: 'toggle-invert' }; +const COMPACT_TOGGLE_KEYBIND: CustomKeybind = { key: 's', label: '(s)hort time', action: 'toggle-compact' }; + export function getUsageDisplayMode(item: WidgetItem): UsageDisplayMode { const mode = item.metadata?.display; if (mode === 'progress' || mode === 'progress-short') { @@ -55,14 +63,14 @@ export function getUsageDisplayModifierText( modifiers.push('inverted'); } - if (options.includeCompact && isUsageCompact(item)) { + if (options.includeCompact && !isUsageProgressMode(mode) && isUsageCompact(item)) { modifiers.push('compact'); } return makeModifierText(modifiers); } -export function cycleUsageDisplayMode(item: WidgetItem): WidgetItem { +export function cycleUsageDisplayMode(item: WidgetItem, disabledInProgressKeys: string[] = []): WidgetItem { const currentMode = getUsageDisplayMode(item); const nextMode: UsageDisplayMode = currentMode === 'time' ? 'progress' @@ -70,21 +78,42 @@ export function cycleUsageDisplayMode(item: WidgetItem): WidgetItem { ? 'progress-short' : 'time'; + const nextItem = removeMetadataKeys(item, nextMode === 'time' + ? ['invert'] + : disabledInProgressKeys); const nextMetadata: Record = { - ...(item.metadata ?? {}), + ...(nextItem.metadata ?? {}), display: nextMode }; - if (nextMode === 'time') { - delete nextMetadata.invert; - } - return { - ...item, + ...nextItem, metadata: nextMetadata }; } export function toggleUsageInverted(item: WidgetItem): WidgetItem { return toggleMetadataFlag(item, 'invert'); +} + +export function getUsagePercentCustomKeybinds(item?: WidgetItem): CustomKeybind[] { + const keybinds = [PROGRESS_TOGGLE_KEYBIND]; + + if (item && isUsageProgressMode(getUsageDisplayMode(item))) { + keybinds.push(INVERT_TOGGLE_KEYBIND); + } + + return keybinds; +} + +export function getUsageTimerCustomKeybinds(item?: WidgetItem): CustomKeybind[] { + const keybinds = [PROGRESS_TOGGLE_KEYBIND]; + + if (item && isUsageProgressMode(getUsageDisplayMode(item))) { + keybinds.push(INVERT_TOGGLE_KEYBIND); + } else { + keybinds.push(COMPACT_TOGGLE_KEYBIND); + } + + return keybinds; } \ No newline at end of file