This commit is contained in:
2025-11-29 01:59:19 +01:00
parent 796873e7d3
commit 53c366c20e
5 changed files with 240 additions and 18 deletions

View File

@@ -52,6 +52,34 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve
**Lavoro completato nell'ultima sessione:**
- **NUOVA FEATURE: Scorciatoie da Tastiera Complete per Report Designer** - COMPLETATO
- **Problema:** Le scorciatoie da tastiera (Ctrl+X, Ctrl+C, etc.) venivano intercettate dal browser invece che dalla pagina
- **Soluzione:** Riscritto completamente l'handler delle scorciatoie con:
- Controllo se il focus è su campi input/textarea/contenteditable
- Nuovo metodo `isTextEditing()` esposto da EditorCanvasRef per verificare editing testo nel canvas
- `e.preventDefault()` per bloccare comportamento browser
- **Scorciatoie implementate:**
- `Ctrl+Z` - Annulla
- `Ctrl+Y` - Ripeti
- `Ctrl+S` - Salva
- `Ctrl+X` - Taglia
- `Ctrl+C` - Copia
- `Ctrl+V` - Incolla
- `Ctrl+D` - Duplica
- `Ctrl+A` - Seleziona tutto
- `Ctrl+L` - Blocca/Sblocca elemento
- `Ctrl+G` - Raggruppa
- `Ctrl+Shift+G` - Separa
- `Ctrl+]` - Porta avanti
- `Ctrl+Shift+]` - Porta in primo piano
- `Ctrl+[` - Porta indietro
- `Ctrl+Shift+[` - Porta in fondo
- `Delete/Backspace` - Elimina elemento
- `Escape` - Deseleziona
- **File modificati:**
- `EditorCanvas.tsx` - Aggiunto `isTextEditing()` a `EditorCanvasRef`
- `ReportEditorPage.tsx` - Importato `EditorCanvasRef`, aggiunto `canvasRef`, riscritto `useEffect` delle scorciatoie
- **FIX: Rimossa Toolbar Contestuale che causava Layout Shift** - COMPLETATO
- **Problema:** Quando si selezionava un oggetto nel canvas, appariva una toolbar aggiuntiva sotto quella principale che causava uno scroll della pagina
- **Soluzione:** Rimossa completamente la toolbar contestuale - le proprietà degli oggetti vengono gestite solo dal `PropertiesPanel` sulla destra
@@ -233,6 +261,11 @@ cd src/Apollinare.API && dotnet run
cd frontend && npm run dev
```
**IMPORTANTE:** Dopo modifiche al codice, i servizi backend e frontend vanno **sempre riavviati** per vedere le modifiche:
- Backend: fermare con `Ctrl+C` e rilanciare `dotnet run`
- Frontend: in dev mode (`npm run dev`) il hot-reload è automatico per la maggior parte delle modifiche, ma per modifiche strutturali (nuovi file, cambi a tipi, etc.) potrebbe essere necessario riavviare
---
## Project Overview
@@ -1202,6 +1235,16 @@ frontend/src/
- Import inutilizzati: `TextField`, `ToggleButton`, `ToggleButtonGroup`, icone formattazione
- **File:** `EditorToolbar.tsx`, `ReportEditorPage.tsx`
28. **Scorciatoie da Tastiera Intercettate dal Browser (FIX 29/11/2025):**
- **Problema:** Le scorciatoie da tastiera (Ctrl+S, Ctrl+C, Ctrl+V, etc.) nel report designer venivano catturate dal browser invece che dalla pagina - ad esempio Ctrl+S apriva il dialog di salvataggio del browser invece di salvare il template
- **Causa:** L'handler delle scorciatoie non chiamava `e.preventDefault()` in modo consistente e non verificava se l'utente stava modificando testo in un input field o nel canvas
- **Soluzione:** Riscritto completamente l'handler delle scorciatoie:
- Verifica se il target è un campo input (`INPUT`, `TEXTAREA`, `contentEditable`)
- Nuovo metodo `isTextEditing()` esposto da `EditorCanvasRef` per verificare se un elemento testo è in editing nel canvas Fabric.js
- `e.preventDefault()` chiamato subito dopo il riconoscimento della scorciatoia
- Implementate tutte le scorciatoie dichiarate nel context menu (Ctrl+X/C/V/D/A/L/G, Ctrl+[/], Delete, Escape)
- **File:** `EditorCanvas.tsx` (aggiunto `isTextEditing()` a `EditorCanvasRef`), `ReportEditorPage.tsx` (riscritto `useEffect` delle scorciatoie, aggiunto `canvasRef`)
### Schema Database Report System
Le tabelle sono già nel DbContext (`AppollinareDbContext.cs`):

View File

@@ -68,6 +68,8 @@ interface EditorCanvasProps {
export interface EditorCanvasRef {
getCanvas: () => fabric.Canvas | null;
addElement: (element: AprtElement) => void;
/** Check if a text element is currently being edited */
isTextEditing: () => boolean;
}
// Snap configuration
@@ -155,6 +157,12 @@ const EditorCanvas = forwardRef<EditorCanvasRef, EditorCanvasProps>(
fabricRef.current.renderAll();
}
},
isTextEditing: () => {
if (!fabricRef.current) return false;
const activeObj = fabricRef.current.getActiveObject();
if (!activeObj) return false;
return !!(activeObj as fabric.Textbox).isEditing;
},
}));
// Clear guide lines

View File

@@ -46,6 +46,7 @@ import {
} from "@mui/icons-material";
import EditorCanvas, {
type ContextMenuEvent,
type EditorCanvasRef,
} from "../components/reportEditor/EditorCanvas";
import EditorToolbar, {
type SnapOptions,
@@ -179,6 +180,9 @@ export default function ReportEditorPage() {
// Template version for collaboration sync (increments on each save)
const templateVersionRef = useRef(0);
// Ref to EditorCanvas for checking text editing state
const canvasRef = useRef<EditorCanvasRef>(null);
// ============ COLLABORATION (using global context) ============
// Room key format: "report-template:{id}"
const roomKey = id ? `report-template:${id}` : null;
@@ -803,6 +807,28 @@ export default function ReportEditorPage() {
setSelectedElementIds([]); // Clear selection when switching pages
}, []);
// Navigate to previous page
const handlePrevPage = useCallback(() => {
const currentIndex = template.pages.findIndex(
(p) => p.id === currentPageId,
);
if (currentIndex > 0) {
setCurrentPageId(template.pages[currentIndex - 1].id);
setSelectedElementIds([]);
}
}, [template.pages, currentPageId]);
// Navigate to next page
const handleNextPage = useCallback(() => {
const currentIndex = template.pages.findIndex(
(p) => p.id === currentPageId,
);
if (currentIndex < template.pages.length - 1) {
setCurrentPageId(template.pages[currentIndex + 1].id);
setSelectedElementIds([]);
}
}, [template.pages, currentPageId]);
// ============ END PAGE MANAGEMENT HANDLERS ============
// Add new element
@@ -1487,24 +1513,161 @@ export default function ReportEditorPage() {
// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey || e.metaKey) {
switch (e.key) {
// Don't handle shortcuts when typing in input fields
const target = e.target as HTMLElement;
const isInputField =
target.tagName === "INPUT" ||
target.tagName === "TEXTAREA" ||
target.isContentEditable;
// Don't handle shortcuts when editing text in canvas
const isCanvasTextEditing = canvasRef.current?.isTextEditing() ?? false;
// Allow browser defaults for input fields and text editing
if (isInputField || isCanvasTextEditing) {
return;
}
const hasCtrl = e.ctrlKey || e.metaKey;
const hasShift = e.shiftKey;
// Handle Ctrl/Cmd shortcuts
if (hasCtrl) {
switch (e.key.toLowerCase()) {
// Undo: Ctrl+Z
case "z":
e.preventDefault();
handleUndo();
if (!hasShift) {
e.preventDefault();
handleUndo();
}
break;
// Redo: Ctrl+Y or Ctrl+Shift+Z
case "y":
e.preventDefault();
handleRedo();
break;
// Save: Ctrl+S
case "s":
e.preventDefault();
handleSave();
break;
// Cut: Ctrl+X
case "x":
if (selectedElementId) {
e.preventDefault();
handleCut();
}
break;
// Copy: Ctrl+C
case "c":
if (selectedElementId) {
e.preventDefault();
handleCopy();
}
break;
// Paste: Ctrl+V
case "v":
if (clipboard) {
e.preventDefault();
handlePaste();
}
break;
// Duplicate: Ctrl+D
case "d":
if (selectedElementId) {
e.preventDefault();
handleDuplicate();
}
break;
// Select All: Ctrl+A
case "a":
e.preventDefault();
handleSelectAll();
break;
// Lock/Unlock: Ctrl+L
case "l":
if (selectedElementId) {
e.preventDefault();
handleToggleLock();
}
break;
// Group: Ctrl+G / Ungroup: Ctrl+Shift+G
case "g":
if (selectedElementId) {
e.preventDefault();
if (hasShift) {
handleUngroup();
} else {
handleGroup();
}
}
break;
// Layer ordering with brackets
// Bring Forward: Ctrl+] / Bring to Front: Ctrl+Shift+]
case "]":
if (selectedElementId) {
e.preventDefault();
if (hasShift) {
handleBringToFront();
} else {
handleBringForward();
}
}
break;
// Send Backward: Ctrl+[ / Send to Back: Ctrl+Shift+[
case "[":
if (selectedElementId) {
e.preventDefault();
if (hasShift) {
handleSendToBack();
} else {
handleSendBackward();
}
}
break;
}
}
if (e.key === "Delete" && selectedElementId) {
handleDeleteElement();
// Non-Ctrl shortcuts
if (!hasCtrl) {
switch (e.key) {
// Delete element
case "Delete":
case "Backspace":
if (selectedElementId && !selectedElement?.locked) {
e.preventDefault();
handleDeleteElement();
}
break;
// Escape to deselect
case "Escape":
e.preventDefault();
handleDeselectAll();
break;
// Page navigation
case "PageUp":
e.preventDefault();
handlePrevPage();
break;
case "PageDown":
e.preventDefault();
handleNextPage();
break;
}
}
};
@@ -1514,8 +1677,25 @@ export default function ReportEditorPage() {
handleUndo,
handleRedo,
handleSave,
handleCut,
handleCopy,
handlePaste,
handleDuplicate,
handleSelectAll,
handleDeselectAll,
handleToggleLock,
handleGroup,
handleUngroup,
handleBringToFront,
handleBringForward,
handleSendToBack,
handleSendBackward,
handleDeleteElement,
handlePrevPage,
handleNextPage,
selectedElementId,
selectedElement,
clipboard,
]);
// Auto-save: simple debounced save on every template change
@@ -1701,18 +1881,8 @@ export default function ReportEditorPage() {
currentPageIndex={currentPageIndex}
totalPages={template.pages.length}
currentPageName={currentPage?.name || "Pagina 1"}
onPrevPage={() => {
if (currentPageIndex > 0) {
setCurrentPageId(template.pages[currentPageIndex - 1].id);
setSelectedElementIds([]);
}
}}
onNextPage={() => {
if (currentPageIndex < template.pages.length - 1) {
setCurrentPageId(template.pages[currentPageIndex + 1].id);
setSelectedElementIds([]);
}
}}
onPrevPage={handlePrevPage}
onNextPage={handleNextPage}
hasUnsavedChanges={hasUnsavedChanges}
// Auto-save props
autoSaveEnabled={autoSaveEnabled}
@@ -1747,6 +1917,7 @@ export default function ReportEditorPage() {
{/* Canvas - show only elements for current page */}
<EditorCanvas
ref={canvasRef}
template={{
...template,
elements: currentPageElements,

Binary file not shown.

Binary file not shown.