From 4fa9c97189760abd027d055fbf82aa63654b87b3 Mon Sep 17 00:00:00 2001 From: dnviti Date: Sat, 29 Nov 2025 01:36:07 +0100 Subject: [PATCH] - --- CLAUDE.md | 35 +- .../components/reportEditor/EditorCanvas.tsx | 375 +++++++++++++++++- frontend/src/pages/ReportEditorPage.tsx | 76 ++-- src/Apollinare.API/apollinare.db-shm | Bin 32768 -> 32768 bytes src/Apollinare.API/apollinare.db-wal | Bin 280192 -> 3959352 bytes 5 files changed, 427 insertions(+), 59 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b80dd09..f5c4ace 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,12 +46,25 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve ## Quick Start - Session Recovery -**Ultima sessione:** 28 Novembre 2025 (notte) +**Ultima sessione:** 29 Novembre 2025 **Stato progetto:** Migrazione Oracle APEX → .NET + React TypeScript in corso **Lavoro completato nell'ultima sessione:** +- **NUOVA FEATURE: Selezione Multipla nel Report Editor** - COMPLETATO + - Implementato sistema di selezione multipla personalizzato (senza usare ActiveSelection di Fabric.js che causava riposizionamento oggetti) + - **Selezione con rettangolo di trascinamento**: trascinando sul canvas vuoto appare rettangolo blu tratteggiato, al rilascio seleziona tutti gli oggetti che intersecano + - **Shift+click**: aggiunge/rimuove oggetti dalla selezione + - **Spostamento multiplo**: quando più oggetti sono selezionati, trascinandone uno si spostano tutti insieme + - **Feedback visivo**: oggetti selezionati mostrano bordo blu (#1976d2) e ombra + - **Gestione corretta degli eventi**: i ref (`selectedElementIdsRef`, `onSelectElementRef`, etc.) evitano stale closures negli event handler + - **File modificati:** + - `EditorCanvas.tsx` - Nuovi handler `handleMouseDown`, `handleMouseUp`, logica selezione multipla, refs per valori correnti + - `ReportEditorPage.tsx` - Cambiato `selectedElementId: string | null` → `selectedElementIds: string[]`, aggiornati tutti i riferimenti + +**Lavoro completato nelle sessioni precedenti (28 Novembre 2025 notte):** + - **NUOVA FEATURE: Sincronizzazione Real-Time Efficiente** - COMPLETATO - **Prima:** Al salvataggio veniva inviata solo una notifica `DataSaved`, l'altra sessione ricaricava il template dal server (lento) - **Dopo:** Al salvataggio viene inviato l'intero template via SignalR (`BroadcastTemplateSync`), l'altra sessione lo applica direttamente (istantaneo) @@ -1141,6 +1154,26 @@ frontend/src/ - Check `isPending` spostato dentro il callback del setTimeout - **File:** `ReportEditorPage.tsx` +26. **Selezione Multipla Fabric.js - Riposizionamento Oggetti (FIX 29/11/2025):** + - **Problema:** Usando `ActiveSelection` di Fabric.js per la selezione multipla, gli oggetti venivano riposizionati/spostati quando selezionati + - **Causa:** `ActiveSelection` raggruppa gli oggetti e le loro coordinate diventano relative al centro del gruppo. Inoltre, ricreando l'`ActiveSelection` nell'effect quando cambiava `selectedElementIds`, gli oggetti venivano spostati + - **Soluzione:** Sistema di selezione multipla completamente personalizzato: + - Disabilitata selezione nativa di Fabric.js (`selection: false` nel canvas) + - Implementato `handleMouseDown` per: + - Click su oggetto → selezione singola + - Shift+click → aggiunge/rimuove dalla selezione + - Click su canvas vuoto → inizio rettangolo di selezione + - Click su oggetto già selezionato (multi) → inizio drag multiplo + - Implementato `handleMouseMove` per: + - Disegno rettangolo di selezione (Rect blu tratteggiato) + - Spostamento multiplo oggetti (aggiorna `left`/`top` di ogni oggetto) + - Implementato `handleMouseUp` per: + - Fine rettangolo → calcola intersezione e seleziona oggetti + - Fine drag multiplo → aggiorna template con nuove posizioni + - Feedback visivo: bordo blu e ombra sugli oggetti selezionati (invece di ActiveSelection) + - Usati refs (`selectedElementIdsRef`, `onSelectElementRef`, etc.) per evitare stale closures negli event handler registrati una sola volta + - **File:** `EditorCanvas.tsx`, `ReportEditorPage.tsx` + ### Schema Database Report System Le tabelle sono già nel DbContext (`AppollinareDbContext.cs`): diff --git a/frontend/src/components/reportEditor/EditorCanvas.tsx b/frontend/src/components/reportEditor/EditorCanvas.tsx index 62d5a54..e681d47 100644 --- a/frontend/src/components/reportEditor/EditorCanvas.tsx +++ b/frontend/src/components/reportEditor/EditorCanvas.tsx @@ -43,8 +43,8 @@ export interface RemoteCursor { interface EditorCanvasProps { template: AprtTemplate; - selectedElementId: string | null; - onSelectElement: (id: string | null) => void; + selectedElementIds: string[]; + onSelectElement: (ids: string[]) => void; onUpdateElement: (id: string, updates: Partial) => void; /** Called when a drag/resize operation completes - use to commit to history */ onUpdateElementComplete?: () => void; @@ -78,7 +78,7 @@ const EditorCanvas = forwardRef( ( { template, - selectedElementId, + selectedElementIds, onSelectElement, onUpdateElement, onUpdateElementComplete, @@ -106,6 +106,28 @@ const EditorCanvas = forwardRef( const loadingImagesRef = useRef>(new Set()); // elementIds currently being loaded const templateElementsRef = useRef(template.elements); // Ref for accessing current elements in callbacks + // Custom multi-selection state + const isDrawingSelectionRef = useRef(false); + const selectionStartRef = useRef<{ x: number; y: number } | null>(null); + const selectionRectRef = useRef(null); + + // Track selected objects for multi-move + const multiSelectedObjectsRef = useRef([]); + const isDraggingMultiRef = useRef(false); + const dragStartPosRef = useRef<{ x: number; y: number } | null>(null); + + // Refs to access current values in event handlers (avoids stale closures) + const selectedElementIdsRef = useRef(selectedElementIds); + selectedElementIdsRef.current = selectedElementIds; + const onSelectElementRef = useRef(onSelectElement); + onSelectElementRef.current = onSelectElement; + const onUpdateElementRef = useRef(onUpdateElement); + onUpdateElementRef.current = onUpdateElement; + const onUpdateElementCompleteRef = useRef(onUpdateElementComplete); + onUpdateElementCompleteRef.current = onUpdateElementComplete; + const zoomRef = useRef(zoom); + zoomRef.current = zoom; + // Keep templateElementsRef in sync templateElementsRef.current = template.elements; @@ -167,18 +189,49 @@ const EditorCanvas = forwardRef( // Event handlers const handleSelectionCreated = useCallback( (e: { selected: fabric.FabricObject[] }) => { - const selected = e.selected?.[0] as FabricObjectWithData | undefined; - if (selected?.data?.id) { - onSelectElement(selected.data.id as string); + // Ignore if we're handling multi-selection manually + if (isDraggingMultiRef.current || isDrawingSelectionRef.current) { + return; + } + // Ignore if we already have multi-selection (don't let Fabric override it) + if (selectedElementIdsRef.current.length > 1) { + return; + } + + const selectedIds: string[] = []; + if (e.selected) { + for (const obj of e.selected) { + const objData = (obj as FabricObjectWithData).data; + if ( + objData?.id && + !objData.isGrid && + !objData.isMargin && + !objData.isGuide + ) { + selectedIds.push(objData.id as string); + } + } + } + if (selectedIds.length > 0) { + onSelectElementRef.current(selectedIds); } }, - [onSelectElement], + [], ); const handleSelectionCleared = useCallback(() => { - onSelectElement(null); + // Ignore if we're handling multi-selection manually + if (isDraggingMultiRef.current || isDrawingSelectionRef.current) { + return; + } + // Ignore if we have multi-selection (don't let Fabric clear it) + if (selectedElementIdsRef.current.length > 1) { + return; + } + + onSelectElementRef.current([]); clearGuideLines(); - }, [onSelectElement, clearGuideLines]); + }, [clearGuideLines]); const handleObjectMoving = useCallback( (e: fabric.TEvent & { target?: fabric.FabricObject }) => { @@ -661,10 +714,101 @@ const EditorCanvas = forwardRef( [], ); + // Custom multi-selection: mouse down handler + const handleMouseDown = useCallback( + (e: { e: MouseEvent; target?: fabric.FabricObject }) => { + if (!fabricRef.current) return; + const canvas = fabricRef.current; + const pointer = canvas.getScenePoint(e.e); + + // If clicking on an object, handle normal selection or multi-drag + if (e.target) { + const objData = (e.target as FabricObjectWithData).data; + if ( + objData?.id && + !objData.isGrid && + !objData.isMargin && + !objData.isGuide + ) { + const currentSelectedIds = selectedElementIdsRef.current; + + // Check if this object is already in multi-selection + if ( + currentSelectedIds.length > 1 && + currentSelectedIds.includes(objData.id as string) + ) { + // Start dragging all selected objects + isDraggingMultiRef.current = true; + dragStartPosRef.current = { x: pointer.x, y: pointer.y }; + + // Store references to all selected objects + multiSelectedObjectsRef.current = []; + for (const id of currentSelectedIds) { + const obj = elementsMapRef.current.get(id); + if (obj) { + multiSelectedObjectsRef.current.push(obj); + } + } + return; + } + + // Shift+click to add/remove from selection + if (e.e.shiftKey) { + if (currentSelectedIds.includes(objData.id as string)) { + // Remove from selection + onSelectElementRef.current( + currentSelectedIds.filter((id) => id !== objData.id), + ); + } else { + // Add to selection + onSelectElementRef.current([ + ...currentSelectedIds, + objData.id as string, + ]); + } + return; + } + + // Normal click on object - select just this one + onSelectElementRef.current([objData.id as string]); + } + return; + } + + // Clicking on empty canvas - clear selection and start drawing selection rectangle + onSelectElementRef.current([]); + isDrawingSelectionRef.current = true; + selectionStartRef.current = { x: pointer.x, y: pointer.y }; + + // Create selection rectangle + const selectionRect = new fabric.Rect({ + left: pointer.x, + top: pointer.y, + width: 0, + height: 0, + fill: "rgba(25, 118, 210, 0.1)", + stroke: "#1976d2", + strokeWidth: 1, + strokeDashArray: [4, 4], + selectable: false, + evented: false, + excludeFromExport: true, + }); + (selectionRect as FabricObjectWithData).data = { + isSelectionRect: true, + }; + selectionRectRef.current = selectionRect; + canvas.add(selectionRect); + canvas.renderAll(); + }, + [], // No dependencies - uses refs + ); + const handleMouseMove = useCallback( (e: { e: MouseEvent }) => { if (!fabricRef.current) return; - const pointer = fabricRef.current.getScenePoint(e.e); + const canvas = fabricRef.current; + const pointer = canvas.getScenePoint(e.e); const xMm = Math.round((pxToMm(pointer.x) / zoom) * 10) / 10; const yMm = Math.round((pxToMm(pointer.y) / zoom) * 10) / 10; setCursorPosition({ x: xMm, y: yMm }); @@ -673,10 +817,139 @@ const EditorCanvas = forwardRef( if (onCursorMove) { onCursorMove(xMm, yMm); } + + // Handle multi-drag + if (isDraggingMultiRef.current && dragStartPosRef.current) { + const dx = pointer.x - dragStartPosRef.current.x; + const dy = pointer.y - dragStartPosRef.current.y; + + for (const obj of multiSelectedObjectsRef.current) { + obj.set({ + left: (obj.left || 0) + dx, + top: (obj.top || 0) + dy, + }); + obj.setCoords(); + } + + dragStartPosRef.current = { x: pointer.x, y: pointer.y }; + canvas.renderAll(); + return; + } + + // Handle selection rectangle drawing + if ( + isDrawingSelectionRef.current && + selectionStartRef.current && + selectionRectRef.current + ) { + const startX = selectionStartRef.current.x; + const startY = selectionStartRef.current.y; + + const left = Math.min(startX, pointer.x); + const top = Math.min(startY, pointer.y); + const width = Math.abs(pointer.x - startX); + const height = Math.abs(pointer.y - startY); + + selectionRectRef.current.set({ + left, + top, + width, + height, + }); + canvas.renderAll(); + } }, [zoom, onCursorMove], ); + // Custom multi-selection: mouse up handler + const handleMouseUp = useCallback(() => { + if (!fabricRef.current) return; + const canvas = fabricRef.current; + + // Handle end of multi-drag + if (isDraggingMultiRef.current) { + isDraggingMultiRef.current = false; + dragStartPosRef.current = null; + + // Update all moved objects in the template + for (const obj of multiSelectedObjectsRef.current) { + const objData = obj.data; + if (objData?.id) { + const scaleX = obj.scaleX || 1; + const scaleY = obj.scaleY || 1; + const finalWidth = (obj.width || 0) * scaleX; + const finalHeight = (obj.height || 0) * scaleY; + + onUpdateElementRef.current(objData.id as string, { + position: { + x: pxToMm(obj.left || 0) / zoomRef.current, + y: pxToMm(obj.top || 0) / zoomRef.current, + width: pxToMm(finalWidth) / zoomRef.current, + height: pxToMm(finalHeight) / zoomRef.current, + rotation: obj.angle || 0, + }, + }); + } + } + + // Commit to history + onUpdateElementCompleteRef.current?.(); + multiSelectedObjectsRef.current = []; + return; + } + + // Handle end of selection rectangle + if (isDrawingSelectionRef.current && selectionRectRef.current) { + const selRect = selectionRectRef.current; + const rectLeft = selRect.left || 0; + const rectTop = selRect.top || 0; + const rectRight = rectLeft + (selRect.width || 0); + const rectBottom = rectTop + (selRect.height || 0); + + // Find all objects within the selection rectangle + const selectedIds: string[] = []; + const objects = canvas.getObjects(); + + for (const obj of objects) { + const objData = (obj as FabricObjectWithData).data; + if ( + objData?.id && + !objData.isGrid && + !objData.isMargin && + !objData.isGuide && + !objData.isSelectionRect + ) { + const objLeft = obj.left || 0; + const objTop = obj.top || 0; + const objRight = objLeft + (obj.width || 0) * (obj.scaleX || 1); + const objBottom = objTop + (obj.height || 0) * (obj.scaleY || 1); + + // Check if object intersects with selection rectangle + if ( + objLeft < rectRight && + objRight > rectLeft && + objTop < rectBottom && + objBottom > rectTop + ) { + selectedIds.push(objData.id as string); + } + } + } + + // Remove selection rectangle + canvas.remove(selRect); + selectionRectRef.current = null; + isDrawingSelectionRef.current = false; + selectionStartRef.current = null; + + // Update selection + onSelectElementRef.current(selectedIds); + + canvas.renderAll(); + } + }, []); // No dependencies - uses refs + // Keyboard navigation const handleKeyDown = useCallback( (e: KeyboardEvent) => { @@ -741,7 +1014,7 @@ const EditorCanvas = forwardRef( width: canvasWidth * zoom, height: canvasHeight * zoom, backgroundColor: "#ffffff", - selection: true, + selection: false, // Disable native multi-selection (causes object repositioning issues) preserveObjectStacking: true, uniformScaling: false, // Allow free resize from corners (not locked to aspect ratio) }); @@ -770,6 +1043,8 @@ const EditorCanvas = forwardRef( canvas.on("mouse:over", handleMouseOver as any); canvas.on("mouse:out", handleMouseOut as any); canvas.on("mouse:move", handleMouseMove as any); + canvas.on("mouse:down", handleMouseDown as any); + canvas.on("mouse:up", handleMouseUp as any); canvas.on("text:changed", handleTextChanged as any); window.addEventListener("keydown", handleKeyDown); @@ -784,6 +1059,8 @@ const EditorCanvas = forwardRef( canvas.off("mouse:over"); canvas.off("mouse:out"); canvas.off("mouse:move"); + canvas.off("mouse:down"); + canvas.off("mouse:up"); canvas.off("text:changed"); window.removeEventListener("keydown", handleKeyDown); canvas.dispose(); @@ -1158,21 +1435,79 @@ const EditorCanvas = forwardRef( }); }, [template.elements, zoom]); - // Update selection when selectedElementId changes externally + // Update visual selection feedback when selectedElementIds changes useEffect(() => { if (!fabricRef.current) return; - if (selectedElementId) { - const obj = elementsMapRef.current.get(selectedElementId); - if (obj && fabricRef.current.getActiveObject() !== obj) { - fabricRef.current.setActiveObject(obj); - fabricRef.current.renderAll(); + const canvas = fabricRef.current; + + // Clear all selection styling first + const allObjects = canvas.getObjects(); + for (const obj of allObjects) { + const objData = (obj as FabricObjectWithData).data; + if ( + objData?.id && + !objData.isGrid && + !objData.isMargin && + !objData.isGuide && + !objData.isSelectionRect + ) { + // Reset to default styling + obj.set({ + strokeWidth: + obj.type === "line" + ? obj.strokeWidth || 1 + : (objData.originalStrokeWidth ?? 0), + stroke: + objData.originalStroke ?? (obj.type === "line" ? obj.stroke : ""), + shadow: undefined, + }); + } + } + + if (selectedElementIds.length === 0) { + canvas.discardActiveObject(); + canvas.renderAll(); + return; + } + + if (selectedElementIds.length === 1) { + // Single selection - use Fabric's native selection (with controls) + const obj = elementsMapRef.current.get(selectedElementIds[0]); + if (obj && canvas.getActiveObject() !== obj) { + canvas.setActiveObject(obj); } } else { - fabricRef.current.discardActiveObject(); - fabricRef.current.renderAll(); + // Multi-selection - show visual feedback on each selected object + canvas.discardActiveObject(); // Clear native selection + + for (const id of selectedElementIds) { + const obj = elementsMapRef.current.get(id); + if (obj) { + const objData = obj.data; + // Store original values if not already stored + if (objData && objData.originalStrokeWidth === undefined) { + objData.originalStrokeWidth = obj.strokeWidth || 0; + objData.originalStroke = obj.stroke || ""; + } + + // Apply selection styling - blue border and shadow + obj.set({ + stroke: "#1976d2", + strokeWidth: 2, + shadow: new fabric.Shadow({ + color: "rgba(25, 118, 210, 0.4)", + blur: 10, + offsetX: 0, + offsetY: 0, + }), + }); + } + } } - }, [selectedElementId]); + + canvas.renderAll(); + }, [selectedElementIds]); return ( (defaultPage.id); - // Editor state - const [selectedElementId, setSelectedElementId] = useState( - null, - ); + // Editor state - support multiple selection + const [selectedElementIds, setSelectedElementIds] = useState([]); const [zoom, setZoom] = useState(isMobile ? 0.5 : 1); const [showGrid, setShowGrid] = useState(true); const [snapOptions, setSnapOptions] = useState({ @@ -256,8 +254,10 @@ export default function ReportEditorPage() { elements: prev.elements.filter((el) => el.id !== message.itemId), })); // Clear selection if deleted element was selected - if (selectedElementId === message.itemId) { - setSelectedElementId(null); + if (selectedElementIds.includes(message.itemId)) { + setSelectedElementIds((prev) => + prev.filter((id) => id !== message.itemId), + ); } setTimeout(() => { isApplyingRemoteChange.current = false; @@ -396,18 +396,18 @@ export default function ReportEditorPage() { }, [ collaboration, historyActions, - selectedElementId, + selectedElementIds, template, queryClient, id, ]); - // Send selection changes to collaborators + // Send selection changes to collaborators (send first selected element for compatibility) useEffect(() => { if (collaboration.isConnected && !isApplyingRemoteChange.current) { - collaboration.sendSelectionChanged(selectedElementId); + collaboration.sendSelectionChanged(selectedElementIds[0] || null); } - }, [collaboration, selectedElementId]); + }, [collaboration, selectedElementIds]); // Send view/page navigation to collaborators useEffect(() => { @@ -629,10 +629,14 @@ export default function ReportEditorPage() { historyActions.redo(); }, [historyActions]); - // Get selected element + // Get selected element(s) - for single selection compatibility, use first selected + const selectedElementId = selectedElementIds[0] || null; const selectedElement = selectedElementId ? template.elements.find((e) => e.id === selectedElementId) : null; + const selectedElements = selectedElementIds + .map((id) => template.elements.find((e) => e.id === id)) + .filter((e): e is AprtElement => e !== undefined); // Dataset management const handleAddDataset = useCallback((dataset: DatasetTypeDto) => { @@ -682,7 +686,7 @@ export default function ReportEditorPage() { // Switch to the new page setCurrentPageId(newPageId); - setSelectedElementId(null); + setSelectedElementIds([]); }, [template.pages.length, historyActions, collaboration]); // Duplicate page with all its elements @@ -717,7 +721,7 @@ export default function ReportEditorPage() { // Switch to the new page setCurrentPageId(newPageId); - setSelectedElementId(null); + setSelectedElementIds([]); }, [template.pages, template.elements, historyActions], ); @@ -749,7 +753,7 @@ export default function ReportEditorPage() { if (newCurrentPage) { setCurrentPageId(newCurrentPage.id); } - setSelectedElementId(null); + setSelectedElementIds([]); }, [template.pages, historyActions, collaboration], ); @@ -796,7 +800,7 @@ export default function ReportEditorPage() { // Select page const handleSelectPage = useCallback((pageId: string) => { setCurrentPageId(pageId); - setSelectedElementId(null); // Clear selection when switching pages + setSelectedElementIds([]); // Clear selection when switching pages }, []); // ============ END PAGE MANAGEMENT HANDLERS ============ @@ -851,7 +855,7 @@ export default function ReportEditorPage() { ...prev, elements: [...prev.elements, newElement], })); - setSelectedElementId(newElement.id); + setSelectedElementIds([newElement.id]); // Send to collaborators if (collaboration.isConnected && !isApplyingRemoteChange.current) { @@ -958,7 +962,7 @@ export default function ReportEditorPage() { ...prev, elements: [...prev.elements, newElement], })); - setSelectedElementId(newElement.id); + setSelectedElementIds([newElement.id]); }, [ selectedElementId, @@ -991,7 +995,7 @@ export default function ReportEditorPage() { ...prev, elements: prev.elements.filter((el) => el.id !== selectedElementId), })); - setSelectedElementId(null); + setSelectedElementIds([]); }, [selectedElementId, historyActions, collaboration]); // Copy element @@ -1011,7 +1015,7 @@ export default function ReportEditorPage() { ...prev, elements: [...prev.elements, copy], })); - setSelectedElementId(copy.id); + setSelectedElementIds([copy.id]); }, [selectedElement, historyActions]); // Toggle lock @@ -1066,7 +1070,7 @@ export default function ReportEditorPage() { // Handle context menu event from canvas const handleContextMenu = useCallback((event: ContextMenuEvent) => { if (event.elementId) { - setSelectedElementId(event.elementId); + setSelectedElementIds(event.elementId ? [event.elementId] : []); } setContextMenu({ open: true, @@ -1113,7 +1117,7 @@ export default function ReportEditorPage() { ...prev, elements: [...prev.elements, pastedElement], })); - setSelectedElementId(pastedElement.id); + setSelectedElementIds([pastedElement.id]); }, [clipboard, historyActions]); // Duplicate element @@ -1366,19 +1370,15 @@ export default function ReportEditorPage() { // Selection operations const handleSelectAll = useCallback(() => { - // For now, just show a message - multi-selection requires more work - if (template.elements.length > 0) { - setSelectedElementId(template.elements[template.elements.length - 1].id); - setSnackbar({ - open: true, - message: "Selezione multipla non ancora implementata", - severity: "error", - }); + // Select all elements on the current page + const pageElementIds = currentPageElements.map((e) => e.id); + if (pageElementIds.length > 0) { + setSelectedElementIds(pageElementIds); } - }, [template.elements]); + }, [currentPageElements]); const handleDeselectAll = useCallback(() => { - setSelectedElementId(null); + setSelectedElementIds([]); }, []); // Toggle visibility @@ -1704,13 +1704,13 @@ export default function ReportEditorPage() { onPrevPage={() => { if (currentPageIndex > 0) { setCurrentPageId(template.pages[currentPageIndex - 1].id); - setSelectedElementId(null); + setSelectedElementIds([]); } }} onNextPage={() => { if (currentPageIndex < template.pages.length - 1) { setCurrentPageId(template.pages[currentPageIndex + 1].id); - setSelectedElementId(null); + setSelectedElementIds([]); } }} // New props for enhanced toolbar @@ -1764,11 +1764,11 @@ export default function ReportEditorPage() { margins: currentPage?.margins || template.meta.margins, }, }} - selectedElementId={selectedElementId} - onSelectElement={(id) => { - setSelectedElementId(id); + selectedElementIds={selectedElementIds} + onSelectElement={(ids) => { + setSelectedElementIds(ids); // On mobile, auto-open properties when selecting element - if (isMobile && id) { + if (isMobile && ids.length > 0) { setMobilePanel("properties"); } }} @@ -1973,7 +1973,7 @@ export default function ReportEditorPage() { open={contextMenu.open} position={contextMenu.position} selectedElement={selectedElement || null} - selectedElements={selectedElement ? [selectedElement] : []} + selectedElements={selectedElements} hasClipboard={clipboard !== null} onClose={closeContextMenu} // Clipboard actions diff --git a/src/Apollinare.API/apollinare.db-shm b/src/Apollinare.API/apollinare.db-shm index 792bf508f9b37f48efa8447d8df99ed9958a5e89..3584c8cca55b40a9efb1f214cbb2fb3abd3624ab 100644 GIT binary patch literal 32768 zcmeI5WsDqW5{B!2%goHo%#6EcW@ct)d(F(u%*@P~IAA**+llSOlsIwX#16Bs=5#Bi ztw?vyX?NPn*V41oHJX{OeyYD2?T@ZXaW}67wyXw35}Z?!akif!jR%1*w)zR_OZvO|JnZi5hqq0%OsA^O*sv9+oz&Qlo*VE`_^fm(L-q+}7^fv|=1C2q(U}K0e z)EH(AH%1s?#z2^=rjuEKK(v0VNaF9FXH?IloyV0#I_o}SlF;F^nQ zKY{Br*nXbZOGK`xz&Z*zdG;|p$H4P1ny94)Gb@bO>{IHI?|LOZK#P);z8x{T? z^CGULp60KY5mEn71>1?8n%kTKXTTY72AlzBz!`7`oB?OR8E^)i0cXG&a0Z+KXTTY7 z2AlzBz!`7`oB?OR8E^)i0cXG&a0Z+KXTTY72AlzBz!`7`oB?OR8E^)i0cXG&_&*F3 zvwP8Bl2~YhmAH>Q9KfUegOzo<-P!N9KO2Z=-3Zpm11XH*IER?5%SF7wjM`0i`OH;K znI|7edi2CT{ESde;T0y<78>Z*5E)w@>w|FIZV0V~`S=i-*p0jS9ZT!DNIE_wr`_4z zSLVwR`A~kBY^aW2n1h43hTo8$71)_mxr1-8xOHK8Sz@CpR^b!mRddgQadXD5ZIgN3+>EbPI({DEb4g6}+)wvKDBNqn@#I(&|T9LiHv zt)X*#m#If^5Q4RjJt>Jc1-Ac=LF+$4{rB*x$s#Ajo!;2mby zK6=2p^3GBfl1e+-EMG|}jK!-+z$RSDyUd||^`LX*sYgNU@_I(1qCS@3V`OJ<9^lUm(aFB^ zR8A(!Wl4lK*oZGtgd=#C(X_VC_g$tQ!9fU;S$E+r^0k!4IJ}00Y|2%<$DG?z! zdK9vr&co!aL_-E-LKfse11!aD{DBnJ0JeD#TkJpii&A6KPnM?cY;eWgvZ`lf4XYYk_PNJhB`d}Fj;0|)K68rKH ze`PYQpi_NfEJP;D8u#WQE0C z!rVGQk2qJJ!l)FnuJ-5Uro=!-WJXpr!g3tOUHpn%?8oUm%->j1TkAAkA5?dtGDY5$ zq-c*V_!`AIiWeAD>*=DPX62FTlOUzdQrIrvNI6WzWh7w>uH~o9qXYG*bLFW~6q{tR zNN!0^^u-Z8MP<(5+f1%)bc4@KRgkIjmLx+5Y{g@g;ApLPZ z`Butf60RUATXG#AFs}~MW6qVQB2jII%VPORa-koNLRf_}`3_TPTixh0Qx#>Jye-Mm z5!>(tB{_yKGqyI+B|gj3qcv8AGQ;}#ky=aMi zEV+DC?c&Fxw$ww6QMtS)LxfK?%~?w7@;`qg2E+ zyp5D>%T4@(1$CI7a;`iTiD|PMm&;wrhe0@rD6GM`yvj7%QMdZcR8^TJ?@MZQ#V&l0 zvYf!z7+;&{3ZLca(HoQ?z0JkkD?dpkOvgJ&#dh4xFIh;3>uKl8Q;}FUrF4bdll&Nr zQ;5o%oX2ZStDSV4&rDU5*>Y9Vpc{7M2bAMPzRm>NR9E^ePmkW91Q{fYWS62gTXCQK zEHP0TGw?3%AvN1`3y<&-3+o6y*SyXX#|*{LCO<+`Lp5A4NH2;pR2W+H8_t9_QI z=RyohkjbW`XOkQ@f9`-BmJ9N$RK+Z;!23wcj_l8E{F+6zl8)5C6!m}d7T0F7ua-}x z5QgC_Vz3Su@Z9`o5xH!bgEN{ zr=$C`%PTke-+8{ul*{|)pZd{3J*+*I)pya8-?S^-tF{8?y3o3HJ|fhO9G{*onu4nX z=bQ*h&L%~W=f=CDC>8}A6c~;bwIO9jVptMkW7dcsEQ@GI+K67PhzMcMh(4@}=s?DZ zeykZ`hcS;sRyDFP&6%bFtV^pC3-DMqXb?rCs0&#ZO&Y=m3w~uBn*zhw5=dZMU<5k? cqu3P~!=Au6N&*wu7nsBWLybsbM)QAs0V?!;IRF3v diff --git a/src/Apollinare.API/apollinare.db-wal b/src/Apollinare.API/apollinare.db-wal index d39da59cdf8a5039617caa1cd960d5d2a264679b..8ca6b33ab77e1d7e4c97be9c26ad8bd9825c2a30 100644 GIT binary patch delta 177207 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

$GFb!VmV&{1rwA~g+MHk$wY?wR3gAJ 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 delta 3634 zcmchZc~nzZ9>9va4~M4W$cUya^S>yy zt|dZ(k+Q*6G2D(2wyUSxbb+O}Dj+kV>M)Fb;Vh4xFD!m*-N)HBMWjL&f#Py8cIP6% zhF{^ak;3*zHn)YnrD+3@%||V(Fm_cpkBt%*4?Sc;J4#+PKo*HgcVlc*CSWb!@z`i# z`?>B7>C609{T{L?)L)3P+huOf%E=JK2#e3oEn!;L<_8@4*4&v zO{XT@KwaUA3gN9 zdM!t@G)IzpmLf@x8NP%fC@p888HS;BdOgR{`oo&Nv@?D?76_M<@TjMEu+pg&vS_5K z!8_>Tyxlks-)7B65%d z?8S2ayf+Gq+nv*bce*GCA+sRm_ZS=B77}bNyD&*{_`rJi;WyMGyn_1ECvtO}M>1p? zNO}XK-!Sv&3}G?kRJ&_X>dj`zGEum%3eaW!fF{;ZEUNP5+CA`YWC|~7%dVY%>ZLv9 zkY%B^lbF51#Iv)6MeiG3M`FSa%OT4~78bLQde49#)d{8jN0lvXn|>>EqFnc^ge(Wu z?Z?)vrDkhaxbrc~op*v{@Na&A2z^bj(X_2ML; zUY_MWZg|J?guPe7RN+l3C;WrlguckbRL>=_*av+I`(if<+ffZonyp^VDzoE%3NqeN)DG`hzOX$sL;X&tr!)kz0yMM-v*%<3`^;sYT_9|`zh9O8Ze1Oomz$9{ zhd1I#v%>zi;fEgimAH)8dL53i(GV3P?Qx9Fcpb2n^*pvv*gkvVLT32qu2#s3(4C1G zTX=xS772?fhl+gyedgimR*aIq#Ms&fz$R7k*kWP($=zp{bX%(%AuB=021vlhZsD;d zqGE2)>RlDScxaVc+b$AOsH=ypmm+nFhyB?~h3)z`B=c-tA=nLNsQfy{s@Cz?GGQ?z z&MRf%IP(<9%F!7=HLUih?_jk<@%(D>ds^<$&I+0K!oA;_HIT)i#V0WK{#=-a(;yIe zXl{}vxi??a^q+fZF>3kR?_YQ*eK6pp^6cFvxJw#3_7o40r*-_hE%H!ee(u@c*wbxL zG@^iI{-6BpA=l9!@=-Uwp84whKSJAt?x-2ShK%W<{`hwJ{kEJqC`O?8T#R*i$c`0x z=xE7bvu`H4;P+}i@}7;ctH<<^^y7}AOuZQout-$48)F?FvSURa`YN?uk>A~X9@wVd8tmeN%F8Lp7NIRk-N~@zOEC;xu$U|G+{`BqFUb%&3@L%ze zwd)0UKW{y))#+J+qBus!F$4@+u#R-Jb^>o-&0{;|+u-ieD61H6nERO8spz0;Ym)zP z3ySIJA&t+$p&k1w4>?Fs8DS4w?WZ@Jio`- z5$zz*j&!tl$^s-SB0CF~!6g}}?KEcBn0dCyJ6ratx}Lweo`7N|n&Ybiw#YlzMtkSm zuI4z)v*ZN0BnwGzVRo8{XN$aJJ!yOFf5P$w6thtVi`fqE*tbLEogWS@H=5s@jR!~$ zBA;RGEDd0>Npj#jGH#>a_i#@9%c2Jm{gZZ_hBrc_ot@+3I6(Qb#`jBey>VPxyRP{cFrO zHt3SEfDVinP_$XzHxQ#-2<-y&(;m!r2*}PB2`C~x^i3w!_7SuNfyTfi}B7oq+Vj4c{dK#v9n9(RYTQlMCj)_#ex z4guM*A^~}Hs1pM}i^5M(f;4Yo?1C``)ar4)>3VI)4k(sdTkfeBHuf#5_r%jjWS*qc zt+Iy9t4kp-LyON~o_b947-O$kEw^W;L9rY)`Ke)<4)fTTDKgKJgY$@#YN8U_F{u1D z#(s^>Giv&gmbPn4*PX-n|Nl%sR;#0E1Ig&&hIsmo$nww4Phx92wKyL&{eoyl%WwqE mu`Hn{NS3hw2`3qYmer9o#lYWc5e2te{|*1Q