Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/tui/components/ItemsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -95,12 +94,12 @@ export const ItemsEditor: React.FC<ItemsEditorProps> = ({ 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) => {
Expand Down Expand Up @@ -200,7 +199,7 @@ export const ItemsEditor: React.FC<ItemsEditorProps> = ({ widgets, onUpdate, onB
setMoveMode,
setShowClearConfirm,
openWidgetPicker,
getVisibleCustomKeybinds,
getCustomKeybindsForWidget,
setCustomEditorWidget
});
});
Expand Down Expand Up @@ -263,7 +262,7 @@ export const ItemsEditor: React.FC<ItemsEditorProps> = ({ 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;
}
Expand Down
10 changes: 5 additions & 5 deletions src/tui/components/items-editor/__tests__/input-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
});

Expand All @@ -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()
});

Expand All @@ -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()
});

Expand All @@ -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()
});

Expand All @@ -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
});

Expand Down

This file was deleted.

6 changes: 3 additions & 3 deletions src/tui/components/items-editor/input-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -354,7 +354,7 @@ export function handleNormalInputMode({
setMoveMode,
setShowClearConfirm,
openWidgetPicker,
getVisibleCustomKeybinds,
getCustomKeybindsForWidget,
setCustomEditorWidget
}: HandleNormalInputModeArgs): void {
if (key.upArrow && widgets.length > 0) {
Expand Down Expand Up @@ -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) {
Expand Down
25 changes: 0 additions & 25 deletions src/tui/components/items-editor/keybind-visibility.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/types/Widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 26 additions & 2 deletions src/utils/__tests__/usage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,40 @@ 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');
});

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(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');
});

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');
});

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');
});
});
3 changes: 1 addition & 2 deletions src/utils/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,11 @@ function renderPowerlineStatusLine(
// - Background: previous widget's background color

// Build separator with raw ANSI codes to avoid reset issues
let separatorOutput: string;

// Check if adjacent widgets have the same background color
const sameBackground = widget.bgColor && nextWidget.bgColor && widget.bgColor === nextWidget.bgColor;

let separatorOutput: string;

if (shouldInvert) {
// Inverted: swap fg/bg logic
if (widget.bgColor && nextWidget.bgColor) {
Expand Down
23 changes: 10 additions & 13 deletions src/utils/usage-windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,17 @@ 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 elapsedHours = Math.floor(clampedMs / (1000 * 60 * 60));
const elapsedMinutes = Math.floor((clampedMs % (1000 * 60 * 60)) / (1000 * 60));

if (compact) {
return elapsedMinutes === 0 ? `${elapsedHours}h` : `${elapsedHours}h${elapsedMinutes}m`;
}

if (elapsedMinutes === 0) {
return `${elapsedHours}hr`;
}

return `${elapsedHours}hr ${elapsedMinutes}m`;
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';
}

export function getUsageErrorMessage(error: UsageError): string {
Expand Down
11 changes: 4 additions & 7 deletions src/widgets/BlockResetTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getUsageDisplayMode,
getUsageDisplayModifierText,
getUsageProgressBarWidth,
getUsageTimerCustomKeybinds,
isUsageCompact,
isUsageInverted,
isUsageProgressMode,
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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; }
Expand Down
11 changes: 4 additions & 7 deletions src/widgets/BlockTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getUsageDisplayMode,
getUsageDisplayModifierText,
getUsageProgressBarWidth,
getUsageTimerCustomKeybinds,
isUsageCompact,
isUsageInverted,
isUsageProgressMode,
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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; }
Expand Down
8 changes: 3 additions & 5 deletions src/widgets/SessionUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
cycleUsageDisplayMode,
getUsageDisplayMode,
getUsageDisplayModifierText,
getUsagePercentCustomKeybinds,
getUsageProgressBarWidth,
isUsageInverted,
isUsageProgressMode,
Expand Down Expand Up @@ -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; }
Expand Down
Loading