Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
150 lines
4.7 KiB
TypeScript
150 lines
4.7 KiB
TypeScript
import { useCallback, useMemo } from 'react';
|
|
|
|
import {
|
|
normalizeToolbarItemLayout,
|
|
reorderToolbarItems,
|
|
type ToolbarItemLayout,
|
|
type ToolbarItemLayoutDefaults,
|
|
type ToolbarItemPlacement,
|
|
} from '../../domain/toolbarItemLayout';
|
|
import {
|
|
STORAGE_KEY_TERMINAL_SIDE_PANEL_TAB_LAYOUT,
|
|
STORAGE_KEY_TERMINAL_SIDE_PANEL_TAB_ORDER,
|
|
} from '../../infrastructure/config/storageKeys';
|
|
import { localStorageAdapter } from '../../infrastructure/persistence/localStorageAdapter';
|
|
import { useToolbarItemLayout } from './useToolbarItemLayout';
|
|
import type { SidePanelTool } from '../../domain/sidePanelLayout';
|
|
|
|
export type TerminalSidePanelTabId = SidePanelTool;
|
|
|
|
export const TERMINAL_SIDE_PANEL_TAB_DEFAULT_ORDER: TerminalSidePanelTabId[] = [
|
|
'sftp',
|
|
'scripts',
|
|
'history',
|
|
'theme',
|
|
'system',
|
|
'notes',
|
|
'ai',
|
|
'system-monitor',
|
|
];
|
|
|
|
export const TERMINAL_SIDE_PANEL_TAB_IDS = new Set<TerminalSidePanelTabId>(
|
|
TERMINAL_SIDE_PANEL_TAB_DEFAULT_ORDER,
|
|
);
|
|
|
|
export const TERMINAL_SIDE_PANEL_TAB_LAYOUT_DEFAULTS: ToolbarItemLayoutDefaults = {
|
|
order: [...TERMINAL_SIDE_PANEL_TAB_DEFAULT_ORDER],
|
|
placement: Object.fromEntries(
|
|
TERMINAL_SIDE_PANEL_TAB_DEFAULT_ORDER.map((id) => [id, 'show' as const]),
|
|
),
|
|
};
|
|
|
|
/** Order-only normalizer (legacy + tests). */
|
|
export function normalizeTerminalSidePanelTabOrder(value: unknown): TerminalSidePanelTabId[] {
|
|
return normalizeToolbarItemLayout(value, TERMINAL_SIDE_PANEL_TAB_LAYOUT_DEFAULTS)
|
|
.order as TerminalSidePanelTabId[];
|
|
}
|
|
|
|
export function reorderTerminalSidePanelTab(
|
|
order: TerminalSidePanelTabId[],
|
|
draggedTab: TerminalSidePanelTabId,
|
|
targetTab: TerminalSidePanelTabId,
|
|
placement: 'before' | 'after' = 'before',
|
|
): TerminalSidePanelTabId[] {
|
|
const layout: ToolbarItemLayout = {
|
|
order,
|
|
placement: Object.fromEntries(order.map((id) => [id, 'show' as ToolbarItemPlacement])),
|
|
};
|
|
return reorderToolbarItems(layout, draggedTab, targetTab, placement)
|
|
.order as TerminalSidePanelTabId[];
|
|
}
|
|
|
|
export function fitTerminalSidePanelTabs(params: {
|
|
shown: TerminalSidePanelTabId[];
|
|
collapsed: TerminalSidePanelTabId[];
|
|
active: TerminalSidePanelTabId | null;
|
|
maxShown: number;
|
|
}): { shown: TerminalSidePanelTabId[]; collapsed: TerminalSidePanelTabId[] } {
|
|
if (params.shown.length <= params.maxShown) {
|
|
return { shown: params.shown, collapsed: params.collapsed };
|
|
}
|
|
|
|
const kept = new Set(params.shown.slice(0, Math.max(1, params.maxShown)));
|
|
if (params.active && params.shown.includes(params.active) && !kept.has(params.active)) {
|
|
const lastKept = [...kept].at(-1);
|
|
if (lastKept) kept.delete(lastKept);
|
|
kept.add(params.active);
|
|
}
|
|
const shown = params.shown.filter((id) => kept.has(id));
|
|
const autoCollapsed = params.shown.filter((id) => !kept.has(id));
|
|
return {
|
|
shown,
|
|
collapsed: [...autoCollapsed, ...params.collapsed.filter((id) => !autoCollapsed.includes(id))],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Seed the modern layout storage from the legacy order-only key when needed.
|
|
*/
|
|
function migrateSidePanelTabLayoutIfNeeded(): void {
|
|
try {
|
|
const modern = localStorageAdapter.read(STORAGE_KEY_TERMINAL_SIDE_PANEL_TAB_LAYOUT);
|
|
if (modern != null) return;
|
|
const legacy = localStorageAdapter.read(STORAGE_KEY_TERMINAL_SIDE_PANEL_TAB_ORDER);
|
|
if (legacy == null) return;
|
|
const layout = normalizeToolbarItemLayout(legacy, TERMINAL_SIDE_PANEL_TAB_LAYOUT_DEFAULTS);
|
|
localStorageAdapter.write(STORAGE_KEY_TERMINAL_SIDE_PANEL_TAB_LAYOUT, layout);
|
|
} catch {
|
|
// Best effort.
|
|
}
|
|
}
|
|
|
|
migrateSidePanelTabLayoutIfNeeded();
|
|
|
|
export function useTerminalSidePanelTabOrder(): {
|
|
sidePanelTabOrder: TerminalSidePanelTabId[];
|
|
setSidePanelTabOrder: (order: TerminalSidePanelTabId[]) => void;
|
|
layout: ToolbarItemLayout;
|
|
setPlacement: ReturnType<typeof useToolbarItemLayout>['setPlacement'];
|
|
move: ReturnType<typeof useToolbarItemLayout>['move'];
|
|
reorder: (draggedId: string, targetId: string, placement?: 'before' | 'after') => void;
|
|
resetLayout: () => void;
|
|
partition: ReturnType<typeof useToolbarItemLayout>['partition'];
|
|
} {
|
|
const {
|
|
layout,
|
|
setPlacement,
|
|
setOrder,
|
|
move,
|
|
reorder,
|
|
reset,
|
|
partition,
|
|
} = useToolbarItemLayout(
|
|
STORAGE_KEY_TERMINAL_SIDE_PANEL_TAB_LAYOUT,
|
|
TERMINAL_SIDE_PANEL_TAB_LAYOUT_DEFAULTS,
|
|
);
|
|
|
|
const sidePanelTabOrder = layout.order as TerminalSidePanelTabId[];
|
|
|
|
const setSidePanelTabOrder = useCallback(
|
|
(order: TerminalSidePanelTabId[]) => {
|
|
setOrder(order);
|
|
},
|
|
[setOrder],
|
|
);
|
|
|
|
return useMemo(
|
|
() => ({
|
|
sidePanelTabOrder,
|
|
setSidePanelTabOrder,
|
|
layout,
|
|
setPlacement,
|
|
move,
|
|
reorder,
|
|
resetLayout: reset,
|
|
partition,
|
|
}),
|
|
[layout, move, partition, reorder, reset, setPlacement, setSidePanelTabOrder, sidePanelTabOrder],
|
|
);
|
|
}
|