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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user