mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: resolve 41 bugs and wire 21 unimplemented features in image editor
Canvas rendering: - Fix Konva filter application order (filters before cache) - Implement 6 missing filters (motionBlur, radialBlur, surfaceBlur, vignette, grain, sharpen) - Implement exposure, vibrance, warmth adjustments as custom Konva filters - Apply layer blend modes via globalCompositeOperation - Apply object effects (drop shadow, outer glow, stroke) to all shapes - Mount SmartGuidesOverlay during move tool drag - Clip pixel grid to visible viewport (200-line cap for performance) Store logic: - resizeImage now scales all objects proportionally (points, radii, fontSize) - rotate/flip/trim handle line/arrow points arrays and center-based objects - applyCrop creates cropped source image via offscreen canvas - invertSelection creates mask from bounds when no mask exists - cutObjects uses single atomic set() to prevent race conditions - sendToBack respects layer ordering in multi-layer documents - Add batchNudge() and commitHistory() for undoable nudge operations - Add updateLayerThumbnail() method Tool hooks: - Fix clone stamp/dodge/burn perf (toDataURL only on mouseUp, not every move) - Fix magic wand zoom/pixelRatio with explicit stage.toCanvas() viewport - Fix eyedropper sampling with unzoomed canvas export - Fix selection tool stale closure via isDrawingRef - Implement polygonal lasso (click-to-place vertices, double-click to close) - Implement selection subtract mode (geometric and mask-based) - Implement gradient live preview during drag - Fix transform/move tool to persist changes and handle ellipse/polygon/star UI wiring: - Mount rulers and guidelines in editor page - Wire histogram with live canvas imageData - Wire autosave recovery with blob-to-dataURL conversion - Wire fill dialog to Shift+Backspace shortcut - Wire eyedropper and transform options to options bar - Fix history panel undo/redo button reactive state via useSyncExternalStore - Fix layer row name click to select layer (timer-based click/dblclick) - Fix zoom animation coordinate drift with progressive store sync - Fix copy merged to use Konva stage composite export Tests: - 49 new unit tests (store fixes + konva filters) - 8 new E2E test files with 39 test cases
This commit is contained in:
@@ -2,8 +2,15 @@
|
||||
import { Monitor } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { CanvasResizeDialog } from "@/components/editor/common/canvas-resize-dialog";
|
||||
import { ExportDialog, saveEditorState } from "@/components/editor/common/export-dialog";
|
||||
import {
|
||||
AutosaveRecoveryBanner,
|
||||
ExportDialog,
|
||||
saveEditorState,
|
||||
useAutosave,
|
||||
} from "@/components/editor/common/export-dialog";
|
||||
import { FillDialog } from "@/components/editor/common/fill-dialog";
|
||||
import { ImageResizeDialog } from "@/components/editor/common/image-resize-dialog";
|
||||
import { HorizontalRuler, VerticalRuler } from "@/components/editor/common/rulers";
|
||||
import { WelcomeScreen } from "@/components/editor/common/welcome-screen";
|
||||
import { EditorCanvas } from "@/components/editor/editor-canvas";
|
||||
import { EditorOptionsBar } from "@/components/editor/editor-options-bar";
|
||||
@@ -19,16 +26,29 @@ export function EditorPage() {
|
||||
const sourceImageUrl = useEditorStore((s) => s.sourceImageUrl);
|
||||
const isDirty = useEditorStore((s) => s.isDirty);
|
||||
const loadImage = useEditorStore((s) => s.loadImage);
|
||||
const rulersVisible = useEditorStore((s) => s.rulersVisible);
|
||||
const [showExport, setShowExport] = useState(false);
|
||||
const [showCanvasResize, setShowCanvasResize] = useState(false);
|
||||
const [showImageResize, setShowImageResize] = useState(false);
|
||||
const [fillDialogOpen, setFillDialogOpen] = useState(false);
|
||||
|
||||
// Autosave recovery
|
||||
const { recoveryData, dismissRecovery, restoreRecovery } = useAutosave();
|
||||
|
||||
// Issue #10: Shortcuts belong at page level, not canvas level
|
||||
useEditorShortcuts({
|
||||
onSave: () => saveEditorState(),
|
||||
onExport: () => setShowExport(true),
|
||||
onFillDialog: () => setFillDialogOpen(true),
|
||||
});
|
||||
|
||||
// Listen for fill-dialog custom event (dispatched from Shift+Backspace shortcut)
|
||||
useEffect(() => {
|
||||
const handler = () => setFillDialogOpen(true);
|
||||
window.addEventListener("snapotter:open-fill-dialog", handler);
|
||||
return () => window.removeEventListener("snapotter:open-fill-dialog", handler);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: BeforeUnloadEvent) => {
|
||||
if (isDirty) {
|
||||
@@ -90,9 +110,21 @@ export function EditorPage() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* Autosave recovery banner */}
|
||||
{recoveryData && (
|
||||
<AutosaveRecoveryBanner
|
||||
data={recoveryData}
|
||||
onRestore={restoreRecovery}
|
||||
onDiscard={dismissRecovery}
|
||||
/>
|
||||
)}
|
||||
<EditorOptionsBar />
|
||||
{/* Horizontal ruler along the top edge */}
|
||||
{rulersVisible && <HorizontalRuler />}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<EditorToolbar />
|
||||
{/* Vertical ruler along the left edge */}
|
||||
{rulersVisible && <VerticalRuler />}
|
||||
<div className="relative flex-1 overflow-hidden bg-muted/30">
|
||||
<EditorCanvas
|
||||
onCanvasResize={() => setShowCanvasResize(true)}
|
||||
@@ -106,6 +138,7 @@ export function EditorPage() {
|
||||
{showExport && <ExportDialog onClose={() => setShowExport(false)} />}
|
||||
<CanvasResizeDialog open={showCanvasResize} onClose={() => setShowCanvasResize(false)} />
|
||||
<ImageResizeDialog open={showImageResize} onClose={() => setShowImageResize(false)} />
|
||||
<FillDialog open={fillDialogOpen} onClose={() => setFillDialogOpen(false)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user