feat: auto-select imported items after upload
Pass SelectionManager to setupDragDrop and setupPaste. After each upload completes, the newly created scene items are selected: - Single file/URL: selectOnly - Multi-file drop/paste: select all new items
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { Graphics } from 'pixi.js';
|
import { Graphics } from 'pixi.js';
|
||||||
import type { Viewport } from 'pixi-viewport';
|
import type { Viewport } from 'pixi-viewport';
|
||||||
import type { SceneManager } from './SceneManager';
|
import type { SceneManager } from './SceneManager';
|
||||||
|
import type { SelectionManager } from './SelectionManager';
|
||||||
import { uploadImage, uploadImageFromUrl } from '../api';
|
import { uploadImage, uploadImageFromUrl } from '../api';
|
||||||
|
|
||||||
type OnChange = () => void;
|
type OnChange = () => void;
|
||||||
@@ -29,7 +30,7 @@ function removePlaceholder(viewport: Viewport, g: Graphics) {
|
|||||||
/* Handle upload response (image or video) */
|
/* Handle upload response (image or video) */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
/** Returns the placed dimensions so callers can offset subsequent items. */
|
/** Returns the placed dimensions and item ID so callers can offset subsequent items and select them. */
|
||||||
function handleUploadResult(
|
function handleUploadResult(
|
||||||
res: any,
|
res: any,
|
||||||
viewport: Viewport,
|
viewport: Viewport,
|
||||||
@@ -37,7 +38,7 @@ function handleUploadResult(
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
onChange: OnChange,
|
onChange: OnChange,
|
||||||
): { w: number; h: number } {
|
): { w: number; h: number; id: string } {
|
||||||
const imgData = res.data.image || res.data;
|
const imgData = res.data.image || res.data;
|
||||||
const mediaType: string = imgData.media_type || 'image';
|
const mediaType: string = imgData.media_type || 'image';
|
||||||
const assetKey: string | undefined = imgData.asset_key;
|
const assetKey: string | undefined = imgData.asset_key;
|
||||||
@@ -54,20 +55,21 @@ function handleUploadResult(
|
|||||||
finalH = Math.round(h * scale);
|
finalH = Math.round(h * scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let item;
|
||||||
if (mediaType === 'video' && assetKey) {
|
if (mediaType === 'video' && assetKey) {
|
||||||
const posterKey: string | undefined = imgData.poster_asset_key;
|
const posterKey: string | undefined = imgData.poster_asset_key;
|
||||||
const duration: number | undefined = imgData.duration;
|
const duration: number | undefined = imgData.duration;
|
||||||
sceneManager.addVideoFromUpload(assetKey, finalW, finalH, x, y, posterKey, duration);
|
item = sceneManager.addVideoFromUpload(assetKey, finalW, finalH, x, y, posterKey, duration);
|
||||||
} else if (assetKey) {
|
} else if (assetKey) {
|
||||||
sceneManager.addImageFromUpload(assetKey, finalW, finalH, x, y);
|
item = sceneManager.addImageFromUpload(assetKey, finalW, finalH, x, y);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: legacy response without asset_key — use addImageFromUpload with public_url as key
|
// Fallback: legacy response without asset_key — use addImageFromUpload with public_url as key
|
||||||
const fallbackKey = imgData.public_url || imgData.id || 'unknown';
|
const fallbackKey = imgData.public_url || imgData.id || 'unknown';
|
||||||
sceneManager.addImageFromUpload(fallbackKey, finalW, finalH, x, y);
|
item = sceneManager.addImageFromUpload(fallbackKey, finalW, finalH, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange();
|
onChange();
|
||||||
return { w: finalW, h: finalH };
|
return { w: finalW, h: finalH, id: item.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -80,6 +82,7 @@ export function setupDragDrop(
|
|||||||
sceneManager: SceneManager,
|
sceneManager: SceneManager,
|
||||||
boardId: string,
|
boardId: string,
|
||||||
onChange: OnChange,
|
onChange: OnChange,
|
||||||
|
selection?: SelectionManager | null,
|
||||||
): () => void {
|
): () => void {
|
||||||
function onDragOver(e: DragEvent) {
|
function onDragOver(e: DragEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -113,7 +116,8 @@ export function setupDragDrop(
|
|||||||
try {
|
try {
|
||||||
const res = await uploadImageFromUrl(boardId, url);
|
const res = await uploadImageFromUrl(boardId, url);
|
||||||
removePlaceholder(viewport, placeholder);
|
removePlaceholder(viewport, placeholder);
|
||||||
handleUploadResult(res, viewport, sceneManager, world.x, world.y, onChange);
|
const { id } = handleUploadResult(res, viewport, sceneManager, world.x, world.y, onChange);
|
||||||
|
selection?.selectOnly(id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('URL image upload failed:', err);
|
console.error('URL image upload failed:', err);
|
||||||
removePlaceholder(viewport, placeholder);
|
removePlaceholder(viewport, placeholder);
|
||||||
@@ -137,6 +141,7 @@ export function setupDragDrop(
|
|||||||
let rowMaxH = 0;
|
let rowMaxH = 0;
|
||||||
let cursorY = world.y;
|
let cursorY = world.y;
|
||||||
const colWidths: number[] = new Array(cols).fill(0); // track widest per column
|
const colWidths: number[] = new Array(cols).fill(0); // track widest per column
|
||||||
|
const newItemIds: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
@@ -151,7 +156,8 @@ export function setupDragDrop(
|
|||||||
try {
|
try {
|
||||||
const res = await uploadImage(boardId, file);
|
const res = await uploadImage(boardId, file);
|
||||||
removePlaceholder(viewport, placeholder);
|
removePlaceholder(viewport, placeholder);
|
||||||
const { w: placedW, h: placedH } = handleUploadResult(res, viewport, sceneManager, cursorX, cursorY, onChange);
|
const { w: placedW, h: placedH, id } = handleUploadResult(res, viewport, sceneManager, cursorX, cursorY, onChange);
|
||||||
|
newItemIds.push(id);
|
||||||
if (placedW > (colWidths[col] || 0)) colWidths[col] = placedW;
|
if (placedW > (colWidths[col] || 0)) colWidths[col] = placedW;
|
||||||
if (placedH > rowMaxH) rowMaxH = placedH;
|
if (placedH > rowMaxH) rowMaxH = placedH;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -169,6 +175,14 @@ export function setupDragDrop(
|
|||||||
rowMaxH = 0;
|
rowMaxH = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Select all newly imported items
|
||||||
|
if (selection && newItemIds.length > 0) {
|
||||||
|
selection.selectOnly(newItemIds[0]);
|
||||||
|
for (let i = 1; i < newItemIds.length; i++) {
|
||||||
|
selection.toggle(newItemIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent browser default (opening file in new tab) on the whole document
|
// Prevent browser default (opening file in new tab) on the whole document
|
||||||
@@ -201,11 +215,14 @@ export function setupPaste(
|
|||||||
sceneManager: SceneManager,
|
sceneManager: SceneManager,
|
||||||
boardId: string,
|
boardId: string,
|
||||||
onChange: OnChange,
|
onChange: OnChange,
|
||||||
|
selection?: SelectionManager | null,
|
||||||
): () => void {
|
): () => void {
|
||||||
async function onPaste(e: ClipboardEvent) {
|
async function onPaste(e: ClipboardEvent) {
|
||||||
const items = e.clipboardData?.items;
|
const items = e.clipboardData?.items;
|
||||||
if (!items) return;
|
if (!items) return;
|
||||||
|
|
||||||
|
const newItemIds: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
if (!item.type.startsWith('image/') && !item.type.startsWith('video/')) continue;
|
if (!item.type.startsWith('image/') && !item.type.startsWith('video/')) continue;
|
||||||
@@ -224,12 +241,21 @@ export function setupPaste(
|
|||||||
try {
|
try {
|
||||||
const res = await uploadImage(boardId, file);
|
const res = await uploadImage(boardId, file);
|
||||||
removePlaceholder(viewport, placeholder);
|
removePlaceholder(viewport, placeholder);
|
||||||
handleUploadResult(res, viewport, sceneManager, cx - 100, cy - 75, onChange);
|
const { id } = handleUploadResult(res, viewport, sceneManager, cx - 100, cy - 75, onChange);
|
||||||
|
newItemIds.push(id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Image paste upload failed:', err);
|
console.error('Image paste upload failed:', err);
|
||||||
removePlaceholder(viewport, placeholder);
|
removePlaceholder(viewport, placeholder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Select all pasted items
|
||||||
|
if (selection && newItemIds.length > 0) {
|
||||||
|
selection.selectOnly(newItemIds[0]);
|
||||||
|
for (let i = 1; i < newItemIds.length; i++) {
|
||||||
|
selection.toggle(newItemIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('paste', onPaste);
|
document.addEventListener('paste', onPaste);
|
||||||
|
|||||||
@@ -296,9 +296,9 @@ export function useCanvasSetup(deps: CanvasSetupDeps) {
|
|||||||
dropTarget = canvasEl?.parentElement ?? null;
|
dropTarget = canvasEl?.parentElement ?? null;
|
||||||
}
|
}
|
||||||
if (dropTarget) {
|
if (dropTarget) {
|
||||||
dropCleanupRef.current = setupDragDrop(dropTarget, viewport, scene, resolvedBoardId, onCanvasChange);
|
dropCleanupRef.current = setupDragDrop(dropTarget, viewport, scene, resolvedBoardId, onCanvasChange, selection);
|
||||||
}
|
}
|
||||||
pasteCleanupRef.current = setupPaste(viewport, scene, resolvedBoardId, onCanvasChange);
|
pasteCleanupRef.current = setupPaste(viewport, scene, resolvedBoardId, onCanvasChange, selection);
|
||||||
}
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user