diff --git a/src/features/scan/components/DiagnosticsView.tsx b/src/features/scan/components/DiagnosticsView.tsx index ebbb992..bd59eea 100644 --- a/src/features/scan/components/DiagnosticsView.tsx +++ b/src/features/scan/components/DiagnosticsView.tsx @@ -1,6 +1,8 @@ -import { AlertTriangle, Play, Wrench } from "lucide-react"; +import { useRef, useState, type ChangeEvent } from "react"; +import { AlertTriangle, Download, Play, Upload, Wrench } from "lucide-react"; import type { CaptureResult } from "../../../types/global"; import type { ScanViewControllerResult } from "../types"; +import { goodDatabaseToStoredArtifacts, type GoodImportDatabase } from "../../../lib/goodInterop"; import { FieldConfidenceList } from "./ScanResultCards"; import { useScanDiagnosticsModalModel } from "./modals/hooks/useScanDiagnosticsModalModel"; import { useScanDetailsModalModel } from "./modals/hooks/useScanDetailsModalModel"; @@ -56,6 +58,42 @@ export function DiagnosticsView({ controller, latestCapture, captureStatus, onDe latestCapture, }); + const [interopStatus, setInteropStatus] = useState(""); + const fileInputRef = useRef(null); + + const handleExportGood = async () => { + setInteropStatus("Exportiere GOOD..."); + const result = await controller.exportGoodFromStore(); + setInteropStatus( + result.ok + ? `GOOD exportiert: ${result.count} Artifacts${result.path ? ` -> ${result.path}` : ""}` + : "GOOD-Export fehlgeschlagen (App im Electron-Fenster oeffnen).", + ); + }; + + const handleImportGood = async (event: ChangeEvent) => { + const file = event.target.files?.[0]; + event.target.value = ""; + if (!file) return; + try { + const database = JSON.parse(await file.text()) as GoodImportDatabase; + const records = goodDatabaseToStoredArtifacts(database); + if (records.length === 0) { + setInteropStatus("Keine gueltigen Artifacts in der Datei gefunden."); + return; + } + setInteropStatus(`Importiere ${records.length} Artifacts...`); + const result = await controller.importGoodArtifacts(records); + setInteropStatus( + result.ok + ? `Importiert: ${result.added} neu, ${result.updated} aktualisiert.` + : "Import fehlgeschlagen (App im Electron-Fenster oeffnen).", + ); + } catch { + setInteropStatus("Datei ist kein gueltiges GOOD/JSON."); + } + }; + return (
@@ -136,6 +174,27 @@ export function DiagnosticsView({ controller, latestCapture, captureStatus, onDe
+
+
+

GOOD Interop

+
+ + + +
+
+

+ Exportiert den Scan-Store als GOOD (Genshin Optimizer / Inventory Kamera / Akasha) oder importiert eine GOOD-Datei in den Store. +

+ {interopStatus &&

{interopStatus}

} +
+

Automation log

diff --git a/src/features/scan/hooks/useScanViewController.ts b/src/features/scan/hooks/useScanViewController.ts index 78e3998..2a51761 100644 --- a/src/features/scan/hooks/useScanViewController.ts +++ b/src/features/scan/hooks/useScanViewController.ts @@ -16,6 +16,8 @@ import { useScanViewStateSync } from "./useScanViewStateSync"; import { emptyAutoScanStats, resolveScanTargetCount, type AutoScanStats, type ScanSummary } from "../../../lib/scannerSession"; import type { ScanViewProps, ScanViewControllerResult } from "../types"; import type { CaptureResult, ClickResult, ReviewSampleRecord } from "../../../types/global"; +import type { StoredArtifactRecord } from "../../../types/storage"; +import { storedArtifactsToGood } from "../../../lib/goodInterop"; import { createRendererRepositories } from "../../../infrastructure/repositories/rendererBridgeRepositories"; export function useScanViewController({ @@ -37,6 +39,7 @@ export function useScanViewController({ const snapshotRepo = repositories?.snapshot; const automationRepo = repositories?.automation; const captureRepo = repositories?.capture; + const exportRepo = repositories?.export; const [detailsOpen, setDetailsOpen] = useState(false); const [diagnosticsOpen, setDiagnosticsOpen] = useState(false); @@ -152,6 +155,25 @@ export function useScanViewController({ setReviewQueueOpen, }); + const canGoodInterop = bridgeReady && Boolean(artifactRepo?.loadAll) && Boolean(artifactRepo?.saveMany); + + const exportGoodFromStore = useCallback(async () => { + if (!artifactRepo?.loadAll || !exportRepo?.exportGood) return { ok: false, count: 0 }; + const loaded = await artifactRepo.loadAll(); + const records = loaded.artifacts ?? []; + const good = storedArtifactsToGood(records); + const result = await exportRepo.exportGood(good); + return { ok: Boolean(result.ok), path: result.path, count: good.artifacts.length }; + }, [artifactRepo, exportRepo]); + + const importGoodArtifacts = useCallback(async (records: StoredArtifactRecord[]) => { + if (!artifactRepo?.saveMany || records.length === 0) return { ok: false, added: 0, updated: 0 }; + const result = await artifactRepo.saveMany(records); + if (typeof result.total === "number") setStoredTotal(result.total); + await onStoredArtifactsChanged?.(); + return { ok: Boolean(result.ok), added: result.added ?? 0, updated: result.updated ?? 0 }; + }, [artifactRepo, onStoredArtifactsChanged]); + useScanViewStateSync({ artifactRepo, latestCapture, @@ -229,5 +251,8 @@ export function useScanViewController({ openReviewQueue: openReviewQueueModal, runAutoReviewScan, runVisibleGridScan, + canGoodInterop, + exportGoodFromStore, + importGoodArtifacts, }; } diff --git a/src/features/scan/types.ts b/src/features/scan/types.ts index 3ca9860..3ce1056 100644 --- a/src/features/scan/types.ts +++ b/src/features/scan/types.ts @@ -1,5 +1,6 @@ import type { AppSnapshot } from "../../types/domain"; import type { BooleanResult, CaptureOptions, CaptureResult, CaptureSourceInfo, RuntimeInfo, ReviewSampleRecord } from "../../types/global"; +import type { StoredArtifactRecord } from "../../types/storage"; import type { AutoScanStats, ScanSummary } from "../../lib/scannerSession"; import type { ParsedArtifactCandidate } from "../../lib/artifactOcrParser"; import type { ScannerLearningRules } from "../../lib/scannerLearning"; @@ -79,4 +80,7 @@ export interface ScanViewControllerResult { openReviewQueue: () => Promise; runAutoReviewScan: () => Promise; runVisibleGridScan: () => Promise; + canGoodInterop: boolean; + exportGoodFromStore: () => Promise<{ ok: boolean; path?: string; count: number }>; + importGoodArtifacts: (records: StoredArtifactRecord[]) => Promise<{ ok: boolean; added: number; updated: number }>; }