[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
149
application/state/terminalSidePanelTabs.ts
Normal file
149
application/state/terminalSidePanelTabs.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
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],
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user