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
158 lines
4.6 KiB
TypeScript
158 lines
4.6 KiB
TypeScript
import { useCallback } from 'react';
|
|
|
|
import { hostTreeInlineGroupDeleteStore } from '../../application/state/hostTreeInlineGroupDeleteStore';
|
|
import { hostTreeInlineGroupEditStore } from '../../application/state/hostTreeInlineGroupEditStore';
|
|
import { hostTreeInlineHostEditStore } from '../../application/state/hostTreeInlineHostEditStore';
|
|
import {
|
|
allocateUnnamedGroupPath,
|
|
applyGroupPathRename,
|
|
ensureAncestorPathsExpanded,
|
|
groupDisplayName,
|
|
} from '../../domain/hostGroupPathMutations';
|
|
import type { Host, ManagedSource } from '../../types';
|
|
import { toast } from '../ui/toast';
|
|
|
|
type UseHostTreeInlineGroupActionsParams = {
|
|
customGroups: string[];
|
|
hosts: Host[];
|
|
managedSources: ManagedSource[];
|
|
onUpdateCustomGroups: (groups: string[]) => void;
|
|
onCommitGroupPathChange: (
|
|
sourcePath: string,
|
|
nextPath: string,
|
|
) => Promise<
|
|
| { ok: true }
|
|
| { ok: false; error?: string; superseded?: true }
|
|
>;
|
|
selectedGroupPath: string | null;
|
|
setSelectedGroupPath: (path: string | null) => void;
|
|
ensurePathExpanded: (path: string) => void;
|
|
unnamedGroupLabel: string;
|
|
t: (key: string) => string;
|
|
};
|
|
|
|
export function useHostTreeInlineGroupActions({
|
|
customGroups,
|
|
hosts,
|
|
managedSources,
|
|
onUpdateCustomGroups,
|
|
onCommitGroupPathChange,
|
|
selectedGroupPath,
|
|
setSelectedGroupPath,
|
|
ensurePathExpanded,
|
|
unnamedGroupLabel,
|
|
t,
|
|
}: UseHostTreeInlineGroupActionsParams) {
|
|
const startInlineNewGroup = useCallback((parentPath?: string) => {
|
|
hostTreeInlineHostEditStore.clear();
|
|
const parent = parentPath ?? null;
|
|
const { name, path } = allocateUnnamedGroupPath(customGroups, parent, unnamedGroupLabel);
|
|
onUpdateCustomGroups(Array.from(new Set([...customGroups, path])));
|
|
if (parent) {
|
|
ensureAncestorPathsExpanded(parent, ensurePathExpanded);
|
|
ensurePathExpanded(parent);
|
|
}
|
|
hostTreeInlineGroupEditStore.startEdit({
|
|
groupPath: path,
|
|
initialName: name,
|
|
isNew: true,
|
|
});
|
|
}, [customGroups, ensurePathExpanded, onUpdateCustomGroups, unnamedGroupLabel]);
|
|
|
|
const startInlineRenameGroup = useCallback((groupPath: string) => {
|
|
hostTreeInlineHostEditStore.clear();
|
|
hostTreeInlineGroupEditStore.startEdit({
|
|
groupPath,
|
|
initialName: groupDisplayName(groupPath),
|
|
isNew: false,
|
|
});
|
|
}, []);
|
|
|
|
const cancelInlineGroupEdit = useCallback(() => {
|
|
const edit = hostTreeInlineGroupEditStore.getEdit();
|
|
if (!edit) return;
|
|
if (edit.isNew) {
|
|
onUpdateCustomGroups(customGroups.filter((groupPath) => groupPath !== edit.groupPath));
|
|
}
|
|
hostTreeInlineGroupEditStore.clear();
|
|
}, [customGroups, onUpdateCustomGroups]);
|
|
|
|
const commitInlineGroupRename = useCallback(async (rawName: string): Promise<boolean> => {
|
|
const edit = hostTreeInlineGroupEditStore.getEdit();
|
|
if (!edit) return false;
|
|
|
|
const result = applyGroupPathRename({
|
|
renameTargetPath: edit.groupPath,
|
|
nextName: rawName,
|
|
customGroups,
|
|
hosts,
|
|
managedSources,
|
|
});
|
|
|
|
if (result.ok === false) {
|
|
if (result.error === 'unchanged') {
|
|
hostTreeInlineGroupEditStore.clear();
|
|
return true;
|
|
}
|
|
if (result.error === 'required') {
|
|
if (edit.isNew) {
|
|
cancelInlineGroupEdit();
|
|
return true;
|
|
}
|
|
toast.error(t('vault.groups.errors.required'));
|
|
return false;
|
|
}
|
|
if (result.error === 'invalidChars') {
|
|
toast.error(t('vault.groups.errors.invalidChars'));
|
|
return false;
|
|
}
|
|
if (result.error === 'duplicatePath') {
|
|
toast.error(t('vault.groups.errors.duplicatePath'));
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const committed = await onCommitGroupPathChange(edit.groupPath, result.nextPath);
|
|
if (!committed.ok) {
|
|
toast.error(committed.error || t('common.error'));
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
selectedGroupPath
|
|
&& (selectedGroupPath === edit.groupPath
|
|
|| selectedGroupPath.startsWith(`${edit.groupPath}/`))
|
|
) {
|
|
const suffix = selectedGroupPath === edit.groupPath
|
|
? ''
|
|
: selectedGroupPath.slice(edit.groupPath.length);
|
|
setSelectedGroupPath(result.nextPath + suffix);
|
|
}
|
|
|
|
hostTreeInlineGroupEditStore.clear();
|
|
return true;
|
|
}, [
|
|
cancelInlineGroupEdit,
|
|
customGroups,
|
|
hosts,
|
|
managedSources,
|
|
onCommitGroupPathChange,
|
|
selectedGroupPath,
|
|
setSelectedGroupPath,
|
|
t,
|
|
]);
|
|
|
|
const startInlineDeleteGroup = useCallback((groupPath: string) => {
|
|
hostTreeInlineGroupDeleteStore.open(groupPath);
|
|
}, []);
|
|
|
|
return {
|
|
startInlineNewGroup,
|
|
startInlineRenameGroup,
|
|
commitInlineGroupRename,
|
|
cancelInlineGroupEdit,
|
|
startInlineDeleteGroup,
|
|
};
|
|
}
|