feat(refboard): canvas polish — transform box sync, dot grid, snap tuning, perf fixes
- Fix transform box not updating after alignment/arrangement/normalize/flip operations (shortcuts, context menu, and selection toolbar all fixed with DRY _opUpdate helper) - Replace 100ms polling loop with event-driven viewport updates (moved + wheel-scroll) - Add adaptive dot grid background that responds to zoom/pan - Reduce snap guide threshold from 8px to 4px for subtler snapping - Remove PresenceOverlay (remote selection highlighting) — too heavy for minimal benefit - Offset multiple dropped images so they don't overlap - Add new canvas modules: SnapGuides, clipboard, grouping, FrameSprite, DrawingSprite, LaserPointer, context-menu-items, SelectionToolbar, Minimap - Extract Editor hooks into dedicated files (useBoardLoader, useCanvasSetup, useShortcutHandler, useLayerPanel, useSaveManager, useFollowMode) - Sync improvements: real-time transform broadcast, board rooms, viewport sync
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { Graphics } from 'pixi.js';
|
||||
import type { Viewport } from 'pixi-viewport';
|
||||
import type { SceneManager } from './SceneManager';
|
||||
import { VideoSprite } from './sprites/VideoSprite';
|
||||
import { uploadImage, uploadImageFromUrl } from '../api';
|
||||
|
||||
type OnChange = () => void;
|
||||
@@ -55,10 +54,7 @@ function handleUploadResult(
|
||||
}
|
||||
|
||||
if (mediaType === 'video' && assetKey) {
|
||||
const url = imgData.public_url;
|
||||
const video = new VideoSprite(assetKey, finalW, finalH, url);
|
||||
video.position.set(x, y);
|
||||
viewport.addChild(video);
|
||||
sceneManager.addVideoFromUpload(assetKey, finalW, finalH, x, y);
|
||||
} else if (assetKey) {
|
||||
sceneManager.addImageFromUpload(assetKey, finalW, finalH, x, y);
|
||||
} else {
|
||||
@@ -122,18 +118,23 @@ export function setupDragDrop(
|
||||
return;
|
||||
}
|
||||
|
||||
let dropIndex = 0;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
if (!file.type.startsWith('image/') && !file.type.startsWith('video/')) continue;
|
||||
|
||||
const rect = container.getBoundingClientRect();
|
||||
const world = viewport.toWorld(e.clientX - rect.left, e.clientY - rect.top);
|
||||
const placeholder = createPlaceholder(viewport, world.x, world.y);
|
||||
// Offset each subsequent file so they don't overlap
|
||||
const offsetX = dropIndex * 30;
|
||||
const offsetY = dropIndex * 30;
|
||||
const placeholder = createPlaceholder(viewport, world.x + offsetX, world.y + offsetY);
|
||||
dropIndex++;
|
||||
|
||||
try {
|
||||
const res = await uploadImage(boardId, file);
|
||||
removePlaceholder(viewport, placeholder);
|
||||
handleUploadResult(res, viewport, sceneManager, world.x, world.y, onChange);
|
||||
handleUploadResult(res, viewport, sceneManager, world.x + offsetX, world.y + offsetY, onChange);
|
||||
} catch (err) {
|
||||
console.error('Image upload failed:', err);
|
||||
removePlaceholder(viewport, placeholder);
|
||||
@@ -141,10 +142,22 @@ export function setupDragDrop(
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent browser default (opening file in new tab) on the whole document
|
||||
function onDocDragOver(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
function onDocDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
document.addEventListener('dragover', onDocDragOver);
|
||||
document.addEventListener('drop', onDocDrop);
|
||||
container.addEventListener('dragover', onDragOver);
|
||||
container.addEventListener('drop', onDrop);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('dragover', onDocDragOver);
|
||||
document.removeEventListener('drop', onDocDrop);
|
||||
container.removeEventListener('dragover', onDragOver);
|
||||
container.removeEventListener('drop', onDrop);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user