feat(v3): simplify the project to contain only required tools

This commit is contained in:
2026-07-09 04:02:31 +03:30
parent 20a43d5c9a
commit 2112e00982
19 changed files with 482 additions and 508 deletions

View File

@@ -2,16 +2,7 @@ import { useEffect, useMemo, useState } from "react";
import CanvasPane, { CanvasThumbnail } from "./components/CanvasPane.jsx";
import Controls from "./components/Controls.jsx";
import HistogramPanel from "./components/HistogramPanel.jsx";
import { applyStateOperation, combineStates, getOperations, listStates, uploadImage } from "./lib/api.js";
function defaultCropParams(state) {
return {
x: 0,
y: 0,
width: state?.width || 256,
height: state?.height || 256
};
}
import { applyStateOperation, combineStates, deleteState, getOperations, listStates, uploadImage } from "./lib/api.js";
export default function App() {
const [session, setSession] = useState(null);
@@ -29,10 +20,10 @@ export default function App() {
getOperations()
.then((payload) => {
setOperations(payload.operations || []);
const first = payload.operations?.find((operation) => operation.id === "crop") || payload.operations?.[0];
const first = payload.operations?.[0];
if (first) {
setSelectedOperation(first.id);
setParams(Object.fromEntries(Object.entries(first.params || {}).map(([key, schema]) => [key, schema.default])));
setParams({ _repeat: 1, ...Object.fromEntries(Object.entries(first.params || {}).map(([key, schema]) => [key, schema.default])) });
}
})
.catch((error) => setStatus(error.message));
@@ -59,7 +50,6 @@ export default function App() {
setSelectedStateIds(initialStates[0] ? [initialStates[0].state_id] : []);
setTransform({ x: 0, y: 0, scale: 1 });
setStatus(`${payload.width} x ${payload.height} ${payload.color_mode} image loaded as S0.`);
if (selectedOperation === "crop") setParams(defaultCropParams(initialStates[0]));
} catch (error) {
setStatus(error.message);
} finally {
@@ -69,7 +59,7 @@ export default function App() {
function handleSelectOperation(operationId, nextParams) {
setSelectedOperation(operationId);
setParams(operationId === "crop" ? { ...nextParams, ...defaultCropParams(activeState) } : nextParams);
setParams(nextParams);
}
function handleParamChange(key, value) {
@@ -112,14 +102,35 @@ export default function App() {
setSelectedStateIds((current) => current.includes(stateId) ? current.filter((id) => id !== stateId) : [...current, stateId]);
}
async function handleDeleteState(state) {
if (!state || !session) return;
setBusy(true);
setStatus(`Deleting ${state.label}...`);
try {
await deleteState(state.state_id);
const remaining = await listStates(session.session_id);
const nextStates = remaining.states || [];
setStates(nextStates);
const currentActive = nextStates.find((item) => item.state_id === activeState?.state_id);
const fallback = activeState?.state_id === state.state_id ? nextStates.at(-1) || nextStates[0] || null : currentActive || nextStates.at(-1) || nextStates[0] || null;
setActiveState(fallback);
setSelectedStateIds((current) => current.filter((id) => id !== state.state_id));
setStatus(`${state.label} deleted.`);
} catch (error) {
setStatus(error.message);
} finally {
setBusy(false);
}
}
const originalState = states[0] || null;
const viewportTitle = useMemo(() => {
if (!activeState) return "No active state";
return `${activeState.label} · ${activeState.width} x ${activeState.height} ${activeState.color_mode}`;
return `${activeState.label} - ${activeState.width} x ${activeState.height} ${activeState.color_mode}`;
}, [activeState]);
return (
<div className="flex min-h-screen flex-col bg-zinc-950 text-zinc-100 lg:flex-row">
<div className="flex h-screen overflow-hidden bg-zinc-950 text-zinc-100">
<Controls
operations={operations}
selectedOperation={selectedOperation}
@@ -134,13 +145,13 @@ export default function App() {
onApply={handleApply}
onSelectState={(state) => {
setActiveState(state);
if (selectedOperation === "crop") setParams(defaultCropParams(state));
}}
onToggleCombineState={toggleCombineState}
onCombine={handleCombine}
onDeleteState={handleDeleteState}
/>
<main className="flex min-h-0 flex-1 flex-col">
<main className="flex h-screen min-w-0 flex-1 flex-col overflow-hidden">
<header className="flex flex-wrap items-center justify-between gap-3 border-b border-zinc-800 bg-zinc-950 px-5 py-3">
<div>
<p className="text-xs uppercase tracking-[0.18em] text-cyan-300">Professor Slide Workspace</p>
@@ -149,9 +160,15 @@ export default function App() {
<div className="text-sm text-zinc-400">{busy ? "Working..." : status}</div>
</header>
<div className="relative min-h-0 flex-1 bg-zinc-800">
<div className="relative min-h-0 flex-1 overflow-hidden bg-zinc-800">
<CanvasThumbnail title="S0 Original" imageData={originalState?.image_data} />
<CanvasPane title="Active State" imageData={activeState?.image_data} histogram={activeState?.histogram} transform={transform} onTransform={setTransform} />
<CanvasPane
title="Active State"
imageData={activeState?.image_data}
histogram={activeState?.histogram}
transform={transform}
onTransform={setTransform}
/>
</div>
<HistogramPanel original={originalState?.histogram} processed={activeState?.histogram} />