feat(ui): separate dev info into a Diagnose view, declutter the scan workspace
Reworks the UI so the Scan tab shows only core actions and all developer / diagnostic surfaces live in one dedicated view. - New "Diagnose" nav view. ScanView stays mounted for scan|diagnose (the scan controller state persists across the switch) and renders either the clean workspace or the new DiagnosticsView by mode. - DiagnosticsView consolidates runtime/rights, grid detection, learning + data staleness, fingerprint, auto-scan counters, the automation log, the dev-output toggle, the Demo-Daten action, and the raw crops/OCR/confidence dump. Reuses the existing diagnostics/details model hooks. - Scan workspace decluttered: removed the Scanner Diagnose button and the Details button, dropped the rules/grid/mode dev fields from the result brief and capture meta, shortened the topbar headline, and moved Demo-Daten out of the topbar. - Removed the now-dead ScanDiagnosticsModal / ScanDetailsModal components (their content moved into the view); metrics grid hidden on the Diagnose view. - Added dev:web script + .claude/launch.json for browser preview. Verified in the browser preview (both views render, no console errors); 120 tests + build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -90,7 +90,7 @@ export function AppTopbar({
|
||||
<header className="topbar">
|
||||
<div>
|
||||
<p className="eyebrow">Lokaler Windows-Assistent</p>
|
||||
<h1>Scanne dein Inventar, triff einfache Artifact-Entscheidungen.</h1>
|
||||
<h1>Inventar scannen, Artifacts entscheiden.</h1>
|
||||
</div>
|
||||
<div className="topbar-actions">
|
||||
{topbarStatus && <span className="topbar-status">{topbarStatus}</span>}
|
||||
@@ -107,15 +107,6 @@ export function AppTopbar({
|
||||
{overlayIcon}
|
||||
<span>{overlayButtonLabel}</span>
|
||||
</button>
|
||||
<button
|
||||
className="ghost-button"
|
||||
onClick={handleDemoScan}
|
||||
disabled={isDemoDisabled}
|
||||
title={demoButtonTitle}
|
||||
>
|
||||
{demoIcon}
|
||||
{demoButtonLabel}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Layers3, Radar, Wand2, Eye } from "lucide-react";
|
||||
import { Layers3, Radar, Wand2, Eye, Wrench } from "lucide-react";
|
||||
import type { AppNavigationItem } from "./types";
|
||||
|
||||
export const appNavigationItems: AppNavigationItem[] = [
|
||||
@@ -6,5 +6,6 @@ export const appNavigationItems: AppNavigationItem[] = [
|
||||
{ id: "triage", label: "Triage", icon: Layers3 },
|
||||
{ id: "builds", label: "Builds", icon: Wand2 },
|
||||
{ id: "overlay", label: "Overlay", icon: Eye },
|
||||
{ id: "diagnose", label: "Diagnose", icon: Wrench },
|
||||
];
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type ComponentType, type ReactNode } from "react";
|
||||
import type { LucideProps } from "lucide-react";
|
||||
|
||||
export type NavigationId = "scan" | "triage" | "builds" | "overlay";
|
||||
export type NavigationId = "scan" | "triage" | "builds" | "overlay" | "diagnose";
|
||||
|
||||
export interface AppNavigationItem {
|
||||
id: NavigationId;
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
import { ScanViewLayout } from "./components/ScanViewLayout";
|
||||
import { ScanViewLayout } from "./components/ScanViewLayout";
|
||||
import { DiagnosticsView } from "./components/DiagnosticsView";
|
||||
import { useScanViewController } from "./hooks/useScanViewController";
|
||||
import type { ScanViewProps } from "./types";
|
||||
|
||||
export function ScanView(props: ScanViewProps) {
|
||||
interface ScanViewExtraProps {
|
||||
/** "workspace" shows the clean scan surface; "diagnose" shows all dev info. */
|
||||
mode?: "workspace" | "diagnose";
|
||||
onDemoScan?: () => void;
|
||||
canDemoScan?: boolean;
|
||||
}
|
||||
|
||||
export function ScanView({ mode = "workspace", onDemoScan, canDemoScan, ...props }: ScanViewProps & ScanViewExtraProps) {
|
||||
const controller = useScanViewController(props);
|
||||
|
||||
if (mode === "diagnose") {
|
||||
return (
|
||||
<DiagnosticsView
|
||||
controller={controller}
|
||||
latestCapture={props.latestCapture}
|
||||
captureStatus={props.captureStatus}
|
||||
onDemoScan={onDemoScan}
|
||||
canDemoScan={canDemoScan}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <ScanViewLayout {...props} controller={controller} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { AlertTriangle, Play, Wrench } from "lucide-react";
|
||||
import type { CaptureResult } from "../../../types/global";
|
||||
import type { ScanViewControllerResult } from "../types";
|
||||
import { FieldConfidenceList } from "./ScanResultCards";
|
||||
import { useScanDiagnosticsModalModel } from "./modals/hooks/useScanDiagnosticsModalModel";
|
||||
import { useScanDetailsModalModel } from "./modals/hooks/useScanDetailsModalModel";
|
||||
|
||||
interface DiagnosticsViewProps {
|
||||
controller: ScanViewControllerResult;
|
||||
latestCapture: CaptureResult | null;
|
||||
captureStatus: string;
|
||||
onDemoScan?: () => void;
|
||||
canDemoScan?: boolean;
|
||||
}
|
||||
|
||||
// All developer / diagnostic surfaces live here, separated from the Scan
|
||||
// workspace: runtime + rights, grid detection, learning + data-package status,
|
||||
// fingerprint, auto-scan counters, the automation log, and the raw crop/OCR/
|
||||
// confidence dump. Read-only; it drives no scan action except the demo snapshot.
|
||||
export function DiagnosticsView({ controller, latestCapture, captureStatus, onDemoScan, canDemoScan }: DiagnosticsViewProps) {
|
||||
const {
|
||||
statusTitle,
|
||||
rightsClassName,
|
||||
rightsValue,
|
||||
genshinClassName,
|
||||
genshinValue,
|
||||
shouldShowAdminBanner,
|
||||
gridSourceClass,
|
||||
gridMainValue,
|
||||
gridMetaValue,
|
||||
learningRulesText,
|
||||
learningRulesSubtext,
|
||||
autoScanModeLabel,
|
||||
fingerprintText,
|
||||
runtimeRows,
|
||||
scanLimitText,
|
||||
autoScanStatsLines,
|
||||
playerProgress,
|
||||
reviewStatus,
|
||||
automationLogLines,
|
||||
canSaveReviewSample,
|
||||
handleSaveReviewSample,
|
||||
} = useScanDiagnosticsModalModel({
|
||||
setDetailsOpen: controller.setDetailsOpen,
|
||||
setDiagnosticsOpen: controller.setDiagnosticsOpen,
|
||||
saveReviewSample: controller.saveReviewSample,
|
||||
canSaveReviewSample: controller.canSaveReviewSample,
|
||||
latestCapture,
|
||||
controller,
|
||||
captureStatus,
|
||||
});
|
||||
|
||||
const { parsedNotes, showParsedNotes, cropRows, ocrRows, debugText, showCrops, showOcr } = useScanDetailsModalModel({
|
||||
setDetailsOpen: controller.setDetailsOpen,
|
||||
parsedArtifact: controller.parsedArtifact,
|
||||
latestCapture,
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="diagnose-view">
|
||||
<div className="diagnose-header">
|
||||
<div>
|
||||
<p className="eyebrow">Diagnose & Dev</p>
|
||||
<h2>Laufzeit, Erkennung & Rohdaten</h2>
|
||||
</div>
|
||||
<div className="diagnose-header-actions">
|
||||
<button className="ghost-button" onClick={controller.toggleDevMode}>
|
||||
<Wrench size={15} />
|
||||
{statusTitle}
|
||||
</button>
|
||||
{onDemoScan && (
|
||||
<button className="ghost-button" onClick={onDemoScan} disabled={!canDemoScan}>
|
||||
<Play size={15} />
|
||||
Demo-Daten laden
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="diagnose-grid">
|
||||
<div className="diagnose-card">
|
||||
<p className="eyebrow">Status</p>
|
||||
<div className="scanner-preflight diagnostics-preflight">
|
||||
<div className={rightsClassName}>
|
||||
<span>App-Rechte</span>
|
||||
<strong>{rightsValue}</strong>
|
||||
</div>
|
||||
<div className={genshinClassName}>
|
||||
<span>Genshin</span>
|
||||
<strong>{genshinValue}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{shouldShowAdminBanner && (
|
||||
<p className="scanner-subcopy">
|
||||
App laeuft im Standard-Modus. Auto-Scan braucht Administrator-Rechte: App schliessen und als Administrator neu starten.
|
||||
</p>
|
||||
)}
|
||||
<div className={`grid-detection-strip ${gridSourceClass}`}>
|
||||
<span>Tile grid</span>
|
||||
<strong>{gridMainValue}</strong>
|
||||
<small>{gridMetaValue}</small>
|
||||
</div>
|
||||
<div className="learning-strip">
|
||||
<span>Learning & Daten</span>
|
||||
<strong>{learningRulesText}</strong>
|
||||
<small>{learningRulesSubtext}</small>
|
||||
</div>
|
||||
<div className="learning-strip">
|
||||
<span>Fingerprint</span>
|
||||
<strong>{fingerprintText}</strong>
|
||||
<small>Aktiver Capture-Fingerprint fuer die Duplikat-Erkennung.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="diagnose-card">
|
||||
<p className="eyebrow">{autoScanModeLabel}</p>
|
||||
{playerProgress.show ? (
|
||||
<div className="auto-scan-strip">
|
||||
{autoScanStatsLines.map((entry) => (
|
||||
<span className="auto-scan-stat" key={entry.label}>
|
||||
<strong>{entry.value}</strong>
|
||||
<small>{entry.label}</small>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="result-empty">Noch keine Scan-Aktivitaet.</p>
|
||||
)}
|
||||
<p className="scanner-result-caption">{scanLimitText}</p>
|
||||
<div className="scanner-status-row diagnostics-status">
|
||||
{runtimeRows.map((row) => (
|
||||
<span key={row}>{row}</span>
|
||||
))}
|
||||
</div>
|
||||
{reviewStatus && <p className="review-status">{reviewStatus}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="diagnose-card">
|
||||
<p className="eyebrow">Automation log</p>
|
||||
<div className="automation-log-lines">
|
||||
{automationLogLines.length > 0 ? (
|
||||
automationLogLines.map((line, index) => <span key={`${index}-${line}`}>{line}</span>)
|
||||
) : (
|
||||
<strong>Keine Scan-Aktivitaet.</strong>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="diagnose-card">
|
||||
<div className="diagnose-card-heading">
|
||||
<p className="eyebrow">Crops, OCR & Confidence</p>
|
||||
<button className="ghost-button" onClick={handleSaveReviewSample} disabled={!canSaveReviewSample}>
|
||||
<AlertTriangle size={15} />
|
||||
Review-Sample speichern
|
||||
</button>
|
||||
</div>
|
||||
{controller.parsedArtifact ? (
|
||||
<>
|
||||
<FieldConfidenceList parsedArtifact={controller.parsedArtifact} />
|
||||
{showParsedNotes && (
|
||||
<div className="parsed-notes compact">
|
||||
{parsedNotes.map((note) => (
|
||||
<span key={note}>{note}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="result-empty">Noch kein Artifact gelesen. Lies ein Artifact im Scan-Tab, um Crops und OCR zu sehen.</p>
|
||||
)}
|
||||
{showCrops && (
|
||||
<div className="crop-grid details-grid">
|
||||
{cropRows.map((crop) => (
|
||||
<div className="crop-card" key={crop.id}>
|
||||
<img src={crop.dataUrl} alt={crop.label} />
|
||||
<div>
|
||||
<strong>{crop.label}</strong>
|
||||
<span>{crop.x},{crop.y} - {crop.width}x{crop.height}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{showOcr && (
|
||||
<div className="ocr-panel">
|
||||
<strong>OCR candidates</strong>
|
||||
{ocrRows.map((entry) => (
|
||||
<div className="ocr-row" key={entry.id}>
|
||||
<div>
|
||||
<span>{entry.label}</span>
|
||||
<small>{entry.confidence}% confidence</small>
|
||||
</div>
|
||||
<pre>{entry.text}</pre>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{latestCapture && <p className="capture-debug">{debugText}</p>}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AlertTriangle, Camera, Eye } from "lucide-react";
|
||||
import { AlertTriangle, Camera } from "lucide-react";
|
||||
import { ArtifactResultCard } from "./ScanResultCards";
|
||||
import { useScanMainSectionModel } from "./hooks/useScanMainSectionModel";
|
||||
import type { ScanMainSectionProps } from "./types";
|
||||
@@ -63,9 +63,7 @@ export function ScanMainSection({
|
||||
</div>
|
||||
<div className="capture-stage-meta">
|
||||
<div><span>Quelle</span><strong>{sourceLabel}</strong></div>
|
||||
<div><span>Grid</span><strong>{gridLabel}</strong></div>
|
||||
<div><span>Inventar</span><strong>{inventoryLabel}</strong></div>
|
||||
<div><span>Modus</span><strong>{captureModeText}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,17 +79,8 @@ export function ScanMainSection({
|
||||
<span>{targetLabel}</span>
|
||||
<span>{dbLabel}</span>
|
||||
<span>{reviewLabel}</span>
|
||||
<span>{rulesLabel}</span>
|
||||
</div>
|
||||
<div className="scanner-result-actions">
|
||||
<button
|
||||
className="ghost-button"
|
||||
onClick={handleOpenDetails}
|
||||
disabled={!canOpenDetails}
|
||||
>
|
||||
<Eye size={15} />
|
||||
Details
|
||||
</button>
|
||||
<button className="ghost-button" onClick={handleOpenReviewQueue} disabled={autoScanRunning || !canOpenReviewQueue}>
|
||||
<AlertTriangle size={15} />
|
||||
Review Queue
|
||||
|
||||
@@ -1,46 +1,27 @@
|
||||
import { ScanDiagnosticsModal } from "./modals/ScanDiagnosticsModal";
|
||||
import { ScanSettingsModal } from "./modals/ScanSettingsModal";
|
||||
import { ScanDetailsModal } from "./modals/ScanDetailsModal";
|
||||
import { ScanReviewQueueModal } from "./modals/ScanReviewQueueModal";
|
||||
import { ScanSummaryModal } from "./modals/ScanSummaryModal";
|
||||
import type { ScanModalsSectionProps } from "./types";
|
||||
|
||||
// Diagnostics + crop/OCR details moved to the dedicated Diagnose view; only the
|
||||
// core workspace modals live here.
|
||||
export function ScanModalsSection({
|
||||
captureStatus,
|
||||
latestCapture,
|
||||
diagnosticsOpen,
|
||||
settingsOpen,
|
||||
detailsOpen,
|
||||
reviewQueueOpen,
|
||||
controller,
|
||||
setDiagnosticsOpen,
|
||||
setSettingsOpen,
|
||||
setDetailsOpen,
|
||||
setReviewQueueOpen,
|
||||
setScanSummary,
|
||||
}: ScanModalsSectionProps) {
|
||||
return (
|
||||
<>
|
||||
<ScanDiagnosticsModal
|
||||
open={diagnosticsOpen}
|
||||
captureStatus={captureStatus}
|
||||
latestCapture={latestCapture}
|
||||
controller={controller}
|
||||
setDetailsOpen={setDetailsOpen}
|
||||
setDiagnosticsOpen={setDiagnosticsOpen}
|
||||
/>
|
||||
<ScanSettingsModal
|
||||
open={settingsOpen}
|
||||
latestCapture={latestCapture}
|
||||
controller={controller}
|
||||
setSettingsOpen={setSettingsOpen}
|
||||
/>
|
||||
<ScanDetailsModal
|
||||
open={detailsOpen}
|
||||
latestCapture={latestCapture}
|
||||
controller={controller}
|
||||
setDetailsOpen={setDetailsOpen}
|
||||
/>
|
||||
<ScanReviewQueueModal
|
||||
open={reviewQueueOpen}
|
||||
controller={controller}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Radar,
|
||||
RefreshCw,
|
||||
SlidersHorizontal,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
import type { ScanTopControlsSectionProps } from "./types";
|
||||
import { useScanTopControlsModel } from "./hooks/useScanTopControlsModel";
|
||||
@@ -66,7 +65,7 @@ export function ScanTopControlsSection({
|
||||
<div>
|
||||
<p className="eyebrow">Scanner</p>
|
||||
<h2>Artifact capture workspace</h2>
|
||||
<p className="scanner-subcopy">Grosse Vorschau vorn, klare Aktionen oben, Diagnose und Review nur bei Bedarf.</p>
|
||||
<p className="scanner-subcopy">Quelle waehlen, Auto-Scan starten, Ergebnis rechts pruefen. Dev-Details im Diagnose-Tab.</p>
|
||||
</div>
|
||||
<div className="scanner-header-pills">
|
||||
<span className={`runtime-pill ${bridgePillClass}`}>{bridgeStatusText}</span>
|
||||
@@ -118,15 +117,6 @@ export function ScanTopControlsSection({
|
||||
<SlidersHorizontal size={15} />
|
||||
Scan-Setup
|
||||
</button>
|
||||
<button
|
||||
className="ghost-button dev-toggle"
|
||||
onClick={openDiagnostics}
|
||||
disabled={!bridgeReady}
|
||||
title={diagnosticsButtonTitle}
|
||||
>
|
||||
<Wrench size={15} />
|
||||
Scanner Diagnose
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="player-scan-actions">
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import { FieldConfidenceList } from "../ScanResultCards";
|
||||
import type { ScanDetailsModalProps } from "./types";
|
||||
import { useScanDetailsModalModel } from "./hooks/useScanDetailsModalModel";
|
||||
|
||||
export function ScanDetailsModal({
|
||||
open,
|
||||
latestCapture,
|
||||
controller,
|
||||
setDetailsOpen,
|
||||
}: ScanDetailsModalProps) {
|
||||
const { parsedArtifact } = controller;
|
||||
const {
|
||||
closeDetails,
|
||||
stopPropagation,
|
||||
parsedNotes,
|
||||
showParsedNotes,
|
||||
cropRows,
|
||||
ocrRows,
|
||||
debugText,
|
||||
showCrops,
|
||||
showOcr,
|
||||
} = useScanDetailsModalModel({
|
||||
setDetailsOpen,
|
||||
parsedArtifact,
|
||||
latestCapture,
|
||||
});
|
||||
|
||||
if (!open || !latestCapture) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" role="presentation" onClick={closeDetails}>
|
||||
<div className="modal-panel" role="dialog" aria-modal="true" onClick={stopPropagation}>
|
||||
<div className="modal-header">
|
||||
<div>
|
||||
<p className="eyebrow">Dev</p>
|
||||
<h2>Crops, OCR & Confidence</h2>
|
||||
</div>
|
||||
<button className="ghost-button" onClick={closeDetails}>Schliessen</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{parsedArtifact && (
|
||||
<>
|
||||
<FieldConfidenceList parsedArtifact={parsedArtifact} />
|
||||
{showParsedNotes && (
|
||||
<div className="parsed-notes compact">
|
||||
{parsedNotes.map((note) => <span key={note}>{note}</span>)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{showCrops && (
|
||||
<div className="crop-grid details-grid">
|
||||
{cropRows.map((crop) => (
|
||||
<div className="crop-card" key={crop.id}>
|
||||
<img src={crop.dataUrl} alt={crop.label} />
|
||||
<div>
|
||||
<strong>{crop.label}</strong>
|
||||
<span>{crop.x},{crop.y} - {crop.width}x{crop.height}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{showOcr && (
|
||||
<div className="ocr-panel">
|
||||
<strong>OCR candidates</strong>
|
||||
{ocrRows.map((entry) => (
|
||||
<div className="ocr-row" key={entry.id}>
|
||||
<div>
|
||||
<span>{entry.label}</span>
|
||||
<small>{entry.confidence}% confidence</small>
|
||||
</div>
|
||||
<pre>{entry.text}</pre>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<p className="capture-debug">{debugText}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
import { AlertTriangle, Eye, Wrench } from "lucide-react";
|
||||
import type { ScanDiagnosticsModalProps } from "./types";
|
||||
import { useScanDiagnosticsModalModel } from "./hooks/useScanDiagnosticsModalModel";
|
||||
|
||||
export function ScanDiagnosticsModal({
|
||||
open,
|
||||
captureStatus,
|
||||
latestCapture,
|
||||
controller,
|
||||
setDetailsOpen,
|
||||
setDiagnosticsOpen,
|
||||
}: ScanDiagnosticsModalProps) {
|
||||
const {
|
||||
toggleDevMode,
|
||||
} = controller;
|
||||
|
||||
const {
|
||||
closeDiagnostics,
|
||||
openDetails,
|
||||
handleSaveReviewSample,
|
||||
stopPropagation,
|
||||
canOpenDetails,
|
||||
statusTitle,
|
||||
rightsClassName,
|
||||
rightsValue,
|
||||
genshinClassName,
|
||||
genshinValue,
|
||||
shouldShowAdminBanner,
|
||||
gridSourceClass,
|
||||
gridMainValue,
|
||||
gridMetaValue,
|
||||
learningRulesText,
|
||||
learningRulesSubtext,
|
||||
autoScanModeLabel,
|
||||
autoScanRunning,
|
||||
fingerprintText,
|
||||
runtimeRows,
|
||||
scanLimitText,
|
||||
scanTipText,
|
||||
autoScanStatsLines,
|
||||
playerProgress,
|
||||
showDevRows,
|
||||
reviewStatus,
|
||||
automationLogLines,
|
||||
canSaveReviewSample,
|
||||
} = useScanDiagnosticsModalModel({
|
||||
setDetailsOpen,
|
||||
setDiagnosticsOpen,
|
||||
saveReviewSample: controller.saveReviewSample,
|
||||
canSaveReviewSample: controller.canSaveReviewSample,
|
||||
latestCapture,
|
||||
controller,
|
||||
captureStatus,
|
||||
});
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" role="presentation" onClick={closeDiagnostics}>
|
||||
<div className="modal-panel scanner-diagnostics-modal" role="dialog" aria-modal="true" onClick={stopPropagation}>
|
||||
<div className="modal-header">
|
||||
<div>
|
||||
<p className="eyebrow">Scanner Diagnose</p>
|
||||
<h2>Input, Grid & Lernstatus</h2>
|
||||
</div>
|
||||
<button className="ghost-button" onClick={closeDiagnostics}>Schliessen</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="diagnostics-actions">
|
||||
<button className="ghost-button" onClick={toggleDevMode}>
|
||||
<Wrench size={15} />
|
||||
{statusTitle}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="scanner-preflight diagnostics-preflight">
|
||||
<div className={rightsClassName}>
|
||||
<span>App-Rechte</span>
|
||||
<strong>{rightsValue}</strong>
|
||||
</div>
|
||||
<div className={genshinClassName}>
|
||||
<span>Genshin</span>
|
||||
<strong>{genshinValue}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{shouldShowAdminBanner && (
|
||||
<p className="scanner-subcopy">
|
||||
App laeuft im Standard-Modus. Auto-Scan braucht Administrator-Rechte: App schliessen und als Administrator neu starten (z.B. Terminal per Rechtsklick "Als Administrator ausfuehren" und darin "npm run dev").
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className={`grid-detection-strip ${gridSourceClass}`}>
|
||||
<span>Tile grid</span>
|
||||
<strong>{gridMainValue}</strong>
|
||||
<small>{gridMetaValue}</small>
|
||||
</div>
|
||||
|
||||
<div className="learning-strip">
|
||||
<span>Learning</span>
|
||||
<strong>{learningRulesText}</strong>
|
||||
<small>{learningRulesSubtext}</small>
|
||||
</div>
|
||||
|
||||
{playerProgress.show && (
|
||||
<div className="auto-scan-strip">
|
||||
<span>{autoScanModeLabel}</span>
|
||||
{autoScanStatsLines.map((entry) => (
|
||||
<span className="auto-scan-stat" key={entry.label}>
|
||||
<strong>{entry.value}</strong>
|
||||
<small>{entry.label}</small>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="learning-strip">
|
||||
<span>Fingerprint</span>
|
||||
<strong>{fingerprintText}</strong>
|
||||
<small>Active capture fingerprint used for deterministic duplicate guard checks.</small>
|
||||
</div>
|
||||
|
||||
{showDevRows && (
|
||||
<div className="scanner-status-row diagnostics-status">
|
||||
{runtimeRows.map((row) => (
|
||||
<span key={row}>{row}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{autoScanRunning && playerProgress.show && (
|
||||
<div className="player-progress">
|
||||
<div className="player-progress-bar">
|
||||
<div style={{ width: `${playerProgress.width}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{reviewStatus && (
|
||||
<p className="review-status">{reviewStatus}</p>
|
||||
)}
|
||||
|
||||
<div className="automation-log">
|
||||
<span>Automation</span>
|
||||
<div className="automation-log-lines">
|
||||
{automationLogLines.length > 0 ? (
|
||||
automationLogLines.map((line, index) => (
|
||||
<span key={`${index}-${line}`}>{line}</span>
|
||||
))
|
||||
) : (
|
||||
<strong>No scan activity yet.</strong>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dev-section-actions">
|
||||
<button className="ghost-button" onClick={openDetails} disabled={!canOpenDetails}>
|
||||
<Eye size={15} />
|
||||
Crops, OCR & Confidence
|
||||
</button>
|
||||
<button className="ghost-button" onClick={handleSaveReviewSample} disabled={!canSaveReviewSample}>
|
||||
<AlertTriangle size={15} />
|
||||
Review-Sample speichern
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="scanner-result-caption">{scanLimitText}</p>
|
||||
<p className="scan-summary-copy">{scanTipText}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user