[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

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
moveToolbarItem,
normalizeToolbarItemLayout,
partitionToolbarItems,
reorderToolbarItems,
resetToolbarItemLayout,
setToolbarItemPlacement,
type ToolbarItemLayout,
type ToolbarItemLayoutDefaults,
type ToolbarItemPartition,
type ToolbarItemPlacement,
} from '../../domain/toolbarItemLayout';
import {
LOCAL_STORAGE_ADAPTER_CHANGED_EVENT,
localStorageAdapter,
} from '../../infrastructure/persistence/localStorageAdapter';
export type UseToolbarItemLayoutResult = {
layout: ToolbarItemLayout;
partition: (availableIds?: readonly string[] | ReadonlySet<string>) => ToolbarItemPartition;
setPlacement: (
id: string,
placement: ToolbarItemPlacement,
availableIds?: readonly string[] | ReadonlySet<string> | null,
) => ToolbarItemLayout;
/** Replace full order while preserving placements (normalized against defaults). */
setOrder: (order: string[]) => void;
reorder: (draggedId: string, targetId: string, placement?: 'before' | 'after') => void;
move: (
id: string,
direction: 'earlier' | 'later',
availableIds?: readonly string[] | ReadonlySet<string> | null,
) => void;
reset: () => void;
};
function readLayout(storageKey: string, defaults: ToolbarItemLayoutDefaults): ToolbarItemLayout {
try {
return normalizeToolbarItemLayout(localStorageAdapter.read(storageKey), defaults);
} catch {
return resetToolbarItemLayout(defaults);
}
}
function writeLayout(storageKey: string, layout: ToolbarItemLayout): void {
try {
localStorageAdapter.write(storageKey, layout);
} catch {
// Best effort; in-memory state still applies for the session.
}
}
/**
* Persist show / collapse / hide + order for a dense toolbar region.
* `defaults` should be a stable reference (module-level const).
*/
export function useToolbarItemLayout(
storageKey: string,
defaults: ToolbarItemLayoutDefaults,
): UseToolbarItemLayoutResult {
const [layout, setLayout] = useState<ToolbarItemLayout>(() => readLayout(storageKey, defaults));
useEffect(() => {
const sync = (event: Event) => {
if (event instanceof StorageEvent && event.key !== storageKey) return;
if (event instanceof CustomEvent && event.detail?.key !== storageKey) return;
setLayout(readLayout(storageKey, defaults));
};
window.addEventListener('storage', sync);
window.addEventListener(LOCAL_STORAGE_ADAPTER_CHANGED_EVENT, sync);
return () => {
window.removeEventListener('storage', sync);
window.removeEventListener(LOCAL_STORAGE_ADAPTER_CHANGED_EVENT, sync);
};
}, [defaults, storageKey]);
const commit = useCallback(
(next: ToolbarItemLayout) => {
setLayout(next);
writeLayout(storageKey, next);
},
[storageKey],
);
const setPlacement = useCallback(
(
id: string,
placement: ToolbarItemPlacement,
availableIds?: readonly string[] | ReadonlySet<string> | null,
): ToolbarItemLayout => {
let nextLayout!: ToolbarItemLayout;
setLayout((current) => {
nextLayout = setToolbarItemPlacement(current, id, placement, defaults, availableIds);
writeLayout(storageKey, nextLayout);
return nextLayout;
});
return nextLayout;
},
[defaults, storageKey],
);
const setOrder = useCallback(
(order: string[]) => {
setLayout((current) => {
const next = normalizeToolbarItemLayout(
{ order, placement: current.placement },
defaults,
);
writeLayout(storageKey, next);
return next;
});
},
[defaults, storageKey],
);
const reorder = useCallback(
(draggedId: string, targetId: string, placement: 'before' | 'after' = 'before') => {
setLayout((current) => {
const next = reorderToolbarItems(current, draggedId, targetId, placement);
writeLayout(storageKey, next);
return next;
});
},
[storageKey],
);
const move = useCallback(
(
id: string,
direction: 'earlier' | 'later',
availableIds?: readonly string[] | ReadonlySet<string> | null,
) => {
setLayout((current) => {
const next = moveToolbarItem(current, id, direction, availableIds);
writeLayout(storageKey, next);
return next;
});
},
[storageKey],
);
const reset = useCallback(() => {
commit(resetToolbarItemLayout(defaults));
}, [commit, defaults]);
const partition = useCallback(
(availableIds?: readonly string[] | ReadonlySet<string>) =>
partitionToolbarItems(layout, availableIds),
[layout],
);
return useMemo(
() => ({ layout, partition, setPlacement, setOrder, reorder, move, reset }),
[layout, partition, setPlacement, setOrder, reorder, move, reset],
);
}