Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | import { useEffect, useMemo, useRef } from 'react';
import type { CanvasMap, Layer, SnapshotMap } from '@/shared/types';
import { layerService } from '@/entities/layer/model';
/**
* Manages DOM canvases for layers.
* - bindCanvasRef(id) binds a real <canvas> element to this layer
* - when mounted, restores a snapshot if it exists
* - when unmounted, saves all snapshots to Dexie
*/
export function useLayerCanvases(
layers: Layer[],
projectId: string,
width: number,
height: number,
) {
const canvases = useRef<CanvasMap>(new Map());
// Stores the most recent snapshot for each layer id to detect real changes
const lastSnapshots = useRef<SnapshotMap>(new Map());
// Binds DOM <canvas> to layer id
const bindCanvasRef = useMemo(
() => (id: string) => (el: HTMLCanvasElement | null) => {
if (!el) return;
canvases.current.set(id, el);
// ensure proper canvas dimensions
if (el.width !== width) el.width = width;
if (el.height !== height) el.height = height;
// restore snapshot if present
const layer = layers.find((l) => l.id === id);
if (layer?.snapshot) {
const img = new Image();
img.src = layer.snapshot;
img.onload = () =>
el
.getContext('2d', { willReadFrequently: true })
?.drawImage(img, 0, 0);
// baseline for diff vs canvas state
lastSnapshots.current.set(id, layer.snapshot);
}
},
[layers, width, height],
);
// Update all canvas dimensions when width/height changes
useEffect(() => {
for (const c of canvases.current.values()) {
if (c.width !== width) c.width = width;
if (c.height !== height) c.height = height;
}
}, [width, height]);
// Optional: reset snapshot cache when project changes
useEffect(() => {
lastSnapshots.current = new Map();
}, [projectId]);
// Save all snapshots when component unmounts or project changes
useEffect(() => {
// Capture current references at the time the effect runs
const mapSnapshot = new Map(canvases.current);
const lastSaved = new Map(lastSnapshots.current);
return () => {
for (const [id, canvas] of mapSnapshot.entries()) {
const snapshot = canvas.toDataURL('image/png');
const prevSnapshot = lastSaved.get(id);
// Skip if no change
if (snapshot === prevSnapshot) continue;
// Check that layer still exists before saving
layerService
.getLayers(projectId)
.then((all) => {
if (!all.some((l) => l.id === id)) return;
return layerService.updateLayer({ id, changes: { snapshot } });
})
.catch(() => {
/* ignore missing/deleted layers */
});
}
};
}, [projectId]);
const getCanvas = (id: string) => canvases.current.get(id) ?? null;
return { bindCanvasRef, getCanvas };
}
|