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
159 lines
4.7 KiB
TypeScript
159 lines
4.7 KiB
TypeScript
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],
|
|
);
|
|
}
|