feat(v3): simplify the project to contain only required tools
This commit is contained in:
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user