[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:
157
components/vault/useVaultGroupDragHandlers.ts
Normal file
157
components/vault/useVaultGroupDragHandlers.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { cn } from "../../lib/utils";
|
||||
import type { Host, ManagedSource } from "../../types";
|
||||
import { toast } from "../ui/toast";
|
||||
|
||||
type DropTarget =
|
||||
| { kind: "root" }
|
||||
| { kind: "group"; path: string };
|
||||
|
||||
interface UseVaultGroupDragHandlersOptions {
|
||||
hosts: Host[];
|
||||
managedSources: ManagedSource[];
|
||||
onUnmanageSource?: (sourceId: string) => void;
|
||||
onUpdateHosts: (hosts: Host[]) => void;
|
||||
onUpdateManagedSources: (sources: ManagedSource[]) => void;
|
||||
t: (key: string, values?: Record<string, unknown>) => string;
|
||||
}
|
||||
|
||||
export function useVaultGroupDragHandlers({
|
||||
hosts,
|
||||
managedSources,
|
||||
onUnmanageSource,
|
||||
onUpdateHosts,
|
||||
onUpdateManagedSources,
|
||||
t,
|
||||
}: UseVaultGroupDragHandlersOptions) {
|
||||
const [dragOverDropTarget, setDragOverDropTarget] = useState<DropTarget | null>(null);
|
||||
|
||||
const [confirmedDropTarget, setConfirmedDropTarget] = useState<DropTarget | null>(null);
|
||||
|
||||
const dropTargetPulseTimeoutRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (dropTargetPulseTimeoutRef.current !== null) {
|
||||
window.clearTimeout(dropTargetPulseTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const managedGroupPaths = useMemo(() => {
|
||||
return new Set(managedSources.map(s => s.groupName));
|
||||
}, [managedSources]);
|
||||
|
||||
const isSameDropTarget = useCallback((a: DropTarget | null, b: DropTarget | null) => {
|
||||
if (!a || !b) return a === b;
|
||||
if (a.kind !== b.kind) return false;
|
||||
if (a.kind === "root") return true;
|
||||
if (b.kind === "root") return false;
|
||||
return a.path === b.path;
|
||||
}, []);
|
||||
|
||||
const pulseDropTarget = useCallback((target: DropTarget) => {
|
||||
setConfirmedDropTarget(target);
|
||||
if (dropTargetPulseTimeoutRef.current !== null) {
|
||||
window.clearTimeout(dropTargetPulseTimeoutRef.current);
|
||||
}
|
||||
dropTargetPulseTimeoutRef.current = window.setTimeout(() => {
|
||||
setConfirmedDropTarget((current) => (isSameDropTarget(current, target) ? null : current));
|
||||
dropTargetPulseTimeoutRef.current = null;
|
||||
}, 900);
|
||||
}, [isSameDropTarget]);
|
||||
|
||||
const setGroupDragOverDropTarget = useCallback((path: string | null) => {
|
||||
setDragOverDropTarget(path ? { kind: "group", path } : null);
|
||||
}, []);
|
||||
|
||||
const moveHostToGroup = useCallback((hostId: string, groupPath: string | null) => {
|
||||
const targetGroup = groupPath || "";
|
||||
const hostToMove = hosts.find((h) => h.id === hostId);
|
||||
if (!hostToMove || (hostToMove.group || "") === targetGroup) {
|
||||
setDragOverDropTarget(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the most specific (deepest) managed source that matches the target group
|
||||
const targetManagedSource = managedSources
|
||||
.filter(s => targetGroup === s.groupName || targetGroup.startsWith(s.groupName + "/"))
|
||||
.sort((a, b) => b.groupName.length - a.groupName.length)[0];
|
||||
|
||||
const movedHost = {
|
||||
...hostToMove,
|
||||
group: targetGroup,
|
||||
};
|
||||
const updatedHost = (() => {
|
||||
const h = movedHost;
|
||||
|
||||
// Only SSH hosts can be managed (SSH config only supports SSH)
|
||||
const canBeManaged = !h.protocol || h.protocol === "ssh";
|
||||
|
||||
// Sanitize label if moving to a managed group (SSH config requires no spaces in Host alias)
|
||||
let label = h.label;
|
||||
if (targetManagedSource && canBeManaged && label) {
|
||||
label = label.replace(/\s/g, '');
|
||||
}
|
||||
|
||||
return {
|
||||
...h,
|
||||
label,
|
||||
group: targetGroup,
|
||||
managedSourceId: (targetManagedSource && canBeManaged) ? targetManagedSource.id : undefined,
|
||||
};
|
||||
})();
|
||||
onUpdateHosts(hosts.map((host) => (host.id === hostId ? updatedHost : host)));
|
||||
setDragOverDropTarget(null);
|
||||
pulseDropTarget(groupPath ? { kind: "group", path: groupPath } : { kind: "root" });
|
||||
toast.success(
|
||||
t("vault.hosts.moveToGroup.success", {
|
||||
host: hostToMove.label,
|
||||
group: groupPath || t("vault.hosts.allHosts"),
|
||||
}),
|
||||
);
|
||||
}, [hosts, managedSources, onUpdateHosts, pulseDropTarget, t]);
|
||||
|
||||
const getDropTargetClasses = (target: DropTarget) =>
|
||||
cn(
|
||||
isSameDropTarget(dragOverDropTarget, target) &&
|
||||
"!bg-[#e7ebf0] dark:!bg-white/[0.10]",
|
||||
isSameDropTarget(confirmedDropTarget, target) &&
|
||||
"!bg-[#dde3ea] dark:!bg-white/[0.14]",
|
||||
);
|
||||
|
||||
const handleUnmanageGroup = useCallback((groupPath: string) => {
|
||||
const source = managedSources.find(s => s.groupName === groupPath);
|
||||
if (!source) return;
|
||||
|
||||
// Clear managedSourceId from hosts first
|
||||
const updatedHosts = hosts.map(h =>
|
||||
h.managedSourceId === source.id
|
||||
? { ...h, managedSourceId: undefined }
|
||||
: h
|
||||
);
|
||||
onUpdateHosts(updatedHosts);
|
||||
|
||||
// Remove the source association without modifying the SSH config file
|
||||
// This preserves the user's file contents while stopping sync
|
||||
if (onUnmanageSource) {
|
||||
onUnmanageSource(source.id);
|
||||
} else {
|
||||
// Fallback if onUnmanageSource not available
|
||||
const updatedSources = managedSources.filter(s => s.id !== source.id);
|
||||
onUpdateManagedSources(updatedSources);
|
||||
}
|
||||
|
||||
toast.success(t("vault.managedSource.unmanageSuccess"));
|
||||
}, [managedSources, hosts, onUpdateHosts, onUpdateManagedSources, onUnmanageSource, t]);
|
||||
|
||||
return {
|
||||
getDropTargetClasses,
|
||||
handleUnmanageGroup,
|
||||
managedGroupPaths,
|
||||
moveHostToGroup,
|
||||
setDragOverDropTarget,
|
||||
setGroupDragOverDropTarget,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user