[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:
160
application/state/usePluginVaultImporter.ts
Normal file
160
application/state/usePluginVaultImporter.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { VaultImportDestination } from "../../domain/vaultImport";
|
||||
import { pluginExtensionBridge } from "./pluginExtensionBridge";
|
||||
|
||||
type Translation = (key: string, params?: Record<string, unknown>) => string;
|
||||
|
||||
type PluginVaultImporterOptions = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onPluginPreviewCommit: (
|
||||
preview: NetcattyPluginImporterPreview,
|
||||
destination?: VaultImportDestination,
|
||||
) => Promise<void> | void;
|
||||
destination?: VaultImportDestination | null;
|
||||
t: Translation;
|
||||
};
|
||||
|
||||
const localizeProviderLabel = (
|
||||
provider: NetcattyExtensionProviderContribution,
|
||||
locale = typeof navigator === "undefined" ? "en" : navigator.language,
|
||||
): string => {
|
||||
const label = provider.provider.label;
|
||||
if (typeof label === "string") return label;
|
||||
return label[locale] ?? label[locale.split("-")[0]] ?? label.en ?? provider.provider.id;
|
||||
};
|
||||
|
||||
const summarizePluginImporterPreview = (preview: NetcattyPluginImporterPreview | null): string | null => {
|
||||
if (!preview) return null;
|
||||
const drafts = preview.records.filter((record) => record.type === "draft");
|
||||
const byKind = drafts.reduce<Record<string, number>>((counts, record) => {
|
||||
if (record.type === "draft") counts[record.draft.kind] = (counts[record.draft.kind] ?? 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
return Object.entries(byKind).map(([kind, count]) => `${kind}: ${count}`).join(" · ");
|
||||
};
|
||||
|
||||
export function usePluginVaultImporter({
|
||||
open,
|
||||
onOpenChange,
|
||||
onPluginPreviewCommit,
|
||||
destination,
|
||||
t,
|
||||
}: PluginVaultImporterOptions) {
|
||||
const activePluginImportRequestRef = useRef<string | null>(null);
|
||||
const pluginImportGenerationRef = useRef(0);
|
||||
const [pluginProviders, setPluginProviders] = useState<ReadonlyArray<NetcattyExtensionProviderContribution>>([]);
|
||||
const [pluginPreview, setPluginPreview] = useState<NetcattyPluginImporterPreview | null>(null);
|
||||
const [pluginBusy, setPluginBusy] = useState(false);
|
||||
const [pluginError, setPluginError] = useState<string | null>(null);
|
||||
const [pluginProgress, setPluginProgress] = useState<NetcattyPluginImporterProgressEvent["progress"] | null>(null);
|
||||
|
||||
useEffect(() => pluginExtensionBridge.onImporterProgress((event) => {
|
||||
if (event.requestId === activePluginImportRequestRef.current) setPluginProgress(event.progress);
|
||||
}), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
let cancelled = false;
|
||||
void pluginExtensionBridge.listProviders("importer").then((providers) => {
|
||||
if (!cancelled) setPluginProviders(providers);
|
||||
}).catch(() => {
|
||||
if (!cancelled) setPluginProviders([]);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [open]);
|
||||
|
||||
const resetPluginImporterState = useCallback(() => {
|
||||
pluginImportGenerationRef.current += 1;
|
||||
const requestId = activePluginImportRequestRef.current;
|
||||
activePluginImportRequestRef.current = null;
|
||||
if (requestId) void pluginExtensionBridge.cancelRequest(requestId).catch(() => false);
|
||||
setPluginPreview(null);
|
||||
setPluginError(null);
|
||||
setPluginProgress(null);
|
||||
setPluginBusy(false);
|
||||
}, []);
|
||||
|
||||
const handleOpenChange = useCallback((newOpen: boolean) => {
|
||||
if (!newOpen) resetPluginImporterState();
|
||||
onOpenChange(newOpen);
|
||||
}, [onOpenChange, resetPluginImporterState]);
|
||||
|
||||
const pickPluginFile = useCallback((provider: NetcattyExtensionProviderContribution) => {
|
||||
if (pluginBusy) return;
|
||||
const generation = ++pluginImportGenerationRef.current;
|
||||
const isCurrent = () => pluginImportGenerationRef.current === generation;
|
||||
setPluginBusy(true);
|
||||
setPluginError(null);
|
||||
setPluginProgress(null);
|
||||
void (async () => {
|
||||
let selection: Awaited<ReturnType<typeof pluginExtensionBridge.selectImporterFile>> = null;
|
||||
let consumed = false;
|
||||
let requestId: string | null = null;
|
||||
try {
|
||||
selection = await pluginExtensionBridge.selectImporterFile();
|
||||
if (!selection || !isCurrent()) return;
|
||||
requestId = crypto.randomUUID();
|
||||
activePluginImportRequestRef.current = requestId;
|
||||
const detection = await pluginExtensionBridge.detectImporter({
|
||||
requestId,
|
||||
providerId: provider.provider.id,
|
||||
sample: selection.sample,
|
||||
fileName: selection.fileName,
|
||||
});
|
||||
if (!isCurrent()) return;
|
||||
if (detection && detection.confidence <= 0) {
|
||||
throw new Error(detection.reason || t("vault.import.plugins.notRecognized"));
|
||||
}
|
||||
const preview = await pluginExtensionBridge.parseImporterFile({
|
||||
requestId,
|
||||
providerId: provider.provider.id,
|
||||
selectionToken: selection.selectionToken,
|
||||
});
|
||||
consumed = true;
|
||||
if (isCurrent()) setPluginPreview(preview);
|
||||
} catch (error) {
|
||||
if (isCurrent()) setPluginError(error instanceof Error ? error.message : t("common.unknownError"));
|
||||
} finally {
|
||||
if (activePluginImportRequestRef.current === requestId) activePluginImportRequestRef.current = null;
|
||||
if (selection && !consumed) {
|
||||
await pluginExtensionBridge.releaseImporterFile(selection.selectionToken).catch(() => false);
|
||||
}
|
||||
if (isCurrent()) {
|
||||
setPluginBusy(false);
|
||||
setPluginProgress(null);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [pluginBusy, t]);
|
||||
|
||||
const commitPluginPreview = useCallback(async () => {
|
||||
if (!pluginPreview || pluginBusy) return;
|
||||
setPluginBusy(true);
|
||||
setPluginError(null);
|
||||
try {
|
||||
await onPluginPreviewCommit(pluginPreview, destination ?? undefined);
|
||||
handleOpenChange(false);
|
||||
} catch (error) {
|
||||
setPluginError(error instanceof Error ? error.message : t("common.unknownError"));
|
||||
} finally {
|
||||
setPluginBusy(false);
|
||||
}
|
||||
}, [destination, handleOpenChange, onPluginPreviewCommit, pluginBusy, pluginPreview, t]);
|
||||
|
||||
const previewSummary = useMemo(() => summarizePluginImporterPreview(pluginPreview), [pluginPreview]);
|
||||
|
||||
return {
|
||||
pluginProviders,
|
||||
pluginPreview,
|
||||
pluginBusy,
|
||||
pluginError,
|
||||
pluginProgress,
|
||||
previewSummary,
|
||||
pickPluginFile,
|
||||
commitPluginPreview,
|
||||
clearPluginPreview: () => setPluginPreview(null),
|
||||
handleOpenChange,
|
||||
localizeProviderLabel,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user