fix: resync realtime canvas after mode switch
This commit is contained in:
parent
8757f76b1f
commit
36f7eea13b
@ -804,6 +804,7 @@ export function RealtimeCanvasDialog({
|
|||||||
const layerObjectUrlsRef = useRef<Set<string>>(new Set());
|
const layerObjectUrlsRef = useRef<Set<string>>(new Set());
|
||||||
const sidebarDraggingLayerIdRef = useRef<string | null>(null);
|
const sidebarDraggingLayerIdRef = useRef<string | null>(null);
|
||||||
const drawingHasContentRef = useRef(false);
|
const drawingHasContentRef = useRef(false);
|
||||||
|
const drawingLayerSnapshotRef = useRef("");
|
||||||
const dragDepthRef = useRef(0);
|
const dragDepthRef = useRef(0);
|
||||||
|
|
||||||
// History Management
|
// History Management
|
||||||
@ -876,9 +877,9 @@ export function RealtimeCanvasDialog({
|
|||||||
);
|
);
|
||||||
const enhanceSourceImageUrl = useMemo(() => {
|
const enhanceSourceImageUrl = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
previewUrl ||
|
|
||||||
selectedImageLayer?.src ||
|
selectedImageLayer?.src ||
|
||||||
imageLayers[0]?.src ||
|
imageLayers[0]?.src ||
|
||||||
|
previewUrl ||
|
||||||
initialImageUrl ||
|
initialImageUrl ||
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
@ -1640,6 +1641,19 @@ export function RealtimeCanvasDialog({
|
|||||||
[adaptiveThrottleMs, markCanvasDirty, pumpFlush]
|
[adaptiveThrottleMs, markCanvasDirty, pumpFlush]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cancelPendingRealtimeFlush = useCallback((clearDirty = false) => {
|
||||||
|
if (pendingFlushTimerRef.current) {
|
||||||
|
window.clearTimeout(pendingFlushTimerRef.current);
|
||||||
|
pendingFlushTimerRef.current = null;
|
||||||
|
}
|
||||||
|
pendingFlushModeRef.current = null;
|
||||||
|
pendingFlushForceRef.current = false;
|
||||||
|
dirtyRegionRef.current = null;
|
||||||
|
if (clearDirty) {
|
||||||
|
dirtyRef.current = false;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const reorderLayersFromSidebar = useCallback(
|
const reorderLayersFromSidebar = useCallback(
|
||||||
(draggedLayerId: string, targetLayerId: string) => {
|
(draggedLayerId: string, targetLayerId: string) => {
|
||||||
if (!draggedLayerId || !targetLayerId || draggedLayerId === targetLayerId)
|
if (!draggedLayerId || !targetLayerId || draggedLayerId === targetLayerId)
|
||||||
@ -1796,16 +1810,83 @@ export function RealtimeCanvasDialog({
|
|||||||
|
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
const ctx = canvas?.getContext("2d");
|
const ctx = canvas?.getContext("2d");
|
||||||
if (!canvas || !ctx) return;
|
if (!canvas || !ctx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
clearDrawingCanvas();
|
clearDrawingCanvas();
|
||||||
const image = await loadRenderableImage(dataUrl);
|
const image = await loadRenderableImage(dataUrl);
|
||||||
|
const cssWidth = Math.max(
|
||||||
|
1,
|
||||||
|
Number.parseFloat(canvas.style.width) || canvas.width
|
||||||
|
);
|
||||||
|
const dpr = Math.max(1, canvas.width / cssWidth);
|
||||||
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
drawingHasContentRef.current = true;
|
drawingHasContentRef.current = true;
|
||||||
},
|
},
|
||||||
[clearDrawingCanvas, loadRenderableImage]
|
[clearDrawingCanvas, loadRenderableImage]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const captureDrawingLayerSnapshot = useCallback(() => {
|
||||||
|
if (!canvasRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
drawingLayerSnapshotRef.current = drawingCanvasHasContent(true)
|
||||||
|
? exportDrawingLayerDataUrl()
|
||||||
|
: "";
|
||||||
|
}, [drawingCanvasHasContent, exportDrawingLayerDataUrl]);
|
||||||
|
|
||||||
|
const syncRealtimeCanvasAfterRemount = useCallback(async () => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const imageLayerCanvas = imageLayerCanvasRef.current;
|
||||||
|
const previewCanvas = previewCanvasRef.current;
|
||||||
|
const wrap = canvasWrapRef.current;
|
||||||
|
if (!canvas || !imageLayerCanvas || !previewCanvas || !wrap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = wrap.getBoundingClientRect();
|
||||||
|
const cssWidth = Math.max(1, Math.floor(rect.width));
|
||||||
|
const cssHeight = Math.max(1, Math.floor(rect.height));
|
||||||
|
if (cssWidth <= 1 || cssHeight <= 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
||||||
|
const pixelWidth = Math.max(1, Math.floor(cssWidth * dpr));
|
||||||
|
const pixelHeight = Math.max(1, Math.floor(cssHeight * dpr));
|
||||||
|
const configureCanvas = (targetCanvas: HTMLCanvasElement) => {
|
||||||
|
targetCanvas.width = pixelWidth;
|
||||||
|
targetCanvas.height = pixelHeight;
|
||||||
|
targetCanvas.style.width = `${cssWidth}px`;
|
||||||
|
targetCanvas.style.height = `${cssHeight}px`;
|
||||||
|
const ctx = targetCanvas.getContext("2d");
|
||||||
|
if (!ctx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawingSnapshot = drawingLayerSnapshotRef.current;
|
||||||
|
configureCanvas(imageLayerCanvas);
|
||||||
|
configureCanvas(canvas);
|
||||||
|
configureCanvas(previewCanvas);
|
||||||
|
|
||||||
|
if (drawingSnapshot) {
|
||||||
|
await loadDrawingLayerFromDataUrl(drawingSnapshot);
|
||||||
|
} else {
|
||||||
|
drawingHasContentRef.current = false;
|
||||||
|
}
|
||||||
|
await renderImageLayers();
|
||||||
|
return true;
|
||||||
|
}, [loadDrawingLayerFromDataUrl, renderImageLayers]);
|
||||||
|
|
||||||
const openSavedProject = useCallback(
|
const openSavedProject = useCallback(
|
||||||
async (projectId: string) => {
|
async (projectId: string) => {
|
||||||
const project = readRealtimeProjectsFromStorage().find(
|
const project = readRealtimeProjectsFromStorage().find(
|
||||||
@ -1956,6 +2037,23 @@ export function RealtimeCanvasDialog({
|
|||||||
previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
|
previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const resetRealtimeInteractionState = useCallback(() => {
|
||||||
|
pointerDownRef.current = false;
|
||||||
|
draggingLayerIdRef.current = null;
|
||||||
|
resizingLayerIdRef.current = null;
|
||||||
|
layerDragOriginRef.current = null;
|
||||||
|
layerResizeOriginRef.current = null;
|
||||||
|
lastPointRef.current = null;
|
||||||
|
shapeStartPointRef.current = null;
|
||||||
|
sidebarDraggingLayerIdRef.current = null;
|
||||||
|
dragDepthRef.current = 0;
|
||||||
|
setIsDragImportActive(false);
|
||||||
|
clearPreviewCanvas();
|
||||||
|
if (cursorRef.current) {
|
||||||
|
cursorRef.current.style.opacity = "0";
|
||||||
|
}
|
||||||
|
}, [clearPreviewCanvas]);
|
||||||
|
|
||||||
const undo = useCallback(() => {
|
const undo = useCallback(() => {
|
||||||
if (historyIndexRef.current <= 0) return;
|
if (historyIndexRef.current <= 0) return;
|
||||||
|
|
||||||
@ -2557,6 +2655,36 @@ export function RealtimeCanvasDialog({
|
|||||||
};
|
};
|
||||||
}, [drawInitialImage, initializeCanvas, open]);
|
}, [drawInitialImage, initializeCanvas, open]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (workspaceMode !== "realtime") {
|
||||||
|
cancelPendingRealtimeFlush(true);
|
||||||
|
resetRealtimeInteractionState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let frameId = 0;
|
||||||
|
let followUpFrameId = 0;
|
||||||
|
frameId = window.requestAnimationFrame(() => {
|
||||||
|
followUpFrameId = window.requestAnimationFrame(() => {
|
||||||
|
void syncRealtimeCanvasAfterRemount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.cancelAnimationFrame(frameId);
|
||||||
|
window.cancelAnimationFrame(followUpFrameId);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
cancelPendingRealtimeFlush,
|
||||||
|
open,
|
||||||
|
resetRealtimeInteractionState,
|
||||||
|
syncRealtimeCanvasAfterRemount,
|
||||||
|
workspaceMode,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
|
|
||||||
@ -2872,6 +3000,28 @@ export function RealtimeCanvasDialog({
|
|||||||
setEraserPopoverOpen(false);
|
setEraserPopoverOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleWorkspaceModeChange = useCallback(
|
||||||
|
(nextMode: RealtimeWorkspaceMode) => {
|
||||||
|
if (nextMode === workspaceMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (workspaceMode === "realtime") {
|
||||||
|
captureDrawingLayerSnapshot();
|
||||||
|
}
|
||||||
|
closeBrushSettingPopovers();
|
||||||
|
cancelPendingRealtimeFlush(true);
|
||||||
|
resetRealtimeInteractionState();
|
||||||
|
setWorkspaceMode(nextMode);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
cancelPendingRealtimeFlush,
|
||||||
|
captureDrawingLayerSnapshot,
|
||||||
|
closeBrushSettingPopovers,
|
||||||
|
resetRealtimeInteractionState,
|
||||||
|
workspaceMode,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
const activateSelectMode = useCallback(() => {
|
const activateSelectMode = useCallback(() => {
|
||||||
setSelectedLayerId(DRAW_LAYER_ID);
|
setSelectedLayerId(DRAW_LAYER_ID);
|
||||||
setBrushMode("select");
|
setBrushMode("select");
|
||||||
@ -3008,7 +3158,7 @@ export function RealtimeCanvasDialog({
|
|||||||
? softActionButtonClass
|
? softActionButtonClass
|
||||||
: "border border-transparent bg-transparent text-[#666666] hover:bg-white hover:text-[#111111]"
|
: "border border-transparent bg-transparent text-[#666666] hover:bg-white hover:text-[#111111]"
|
||||||
)}
|
)}
|
||||||
onClick={() => setWorkspaceMode("realtime")}
|
onClick={() => handleWorkspaceModeChange("realtime")}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Paintbrush className="h-4 w-4" />
|
<Paintbrush className="h-4 w-4" />
|
||||||
@ -3021,7 +3171,7 @@ export function RealtimeCanvasDialog({
|
|||||||
? softActionButtonClass
|
? softActionButtonClass
|
||||||
: "border border-transparent bg-transparent text-[#666666] hover:bg-white hover:text-[#111111]"
|
: "border border-transparent bg-transparent text-[#666666] hover:bg-white hover:text-[#111111]"
|
||||||
)}
|
)}
|
||||||
onClick={() => setWorkspaceMode("enhance-pro")}
|
onClick={() => handleWorkspaceModeChange("enhance-pro")}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Wand2 className="h-4 w-4" />
|
<Wand2 className="h-4 w-4" />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user