-
This commit is contained in:
43
CLAUDE.md
43
CLAUDE.md
@@ -52,6 +52,34 @@ XX. **Nome Problema (FIX/IMPLEMENTATO DATA):** - **Problema:** Descrizione breve
|
|||||||
|
|
||||||
**Lavoro completato nell'ultima sessione:**
|
**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
|
- **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
|
- **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
|
- **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
|
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
|
## Project Overview
|
||||||
@@ -1202,6 +1235,16 @@ frontend/src/
|
|||||||
- Import inutilizzati: `TextField`, `ToggleButton`, `ToggleButtonGroup`, icone formattazione
|
- Import inutilizzati: `TextField`, `ToggleButton`, `ToggleButtonGroup`, icone formattazione
|
||||||
- **File:** `EditorToolbar.tsx`, `ReportEditorPage.tsx`
|
- **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
|
### Schema Database Report System
|
||||||
|
|
||||||
Le tabelle sono già nel DbContext (`AppollinareDbContext.cs`):
|
Le tabelle sono già nel DbContext (`AppollinareDbContext.cs`):
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ interface EditorCanvasProps {
|
|||||||
export interface EditorCanvasRef {
|
export interface EditorCanvasRef {
|
||||||
getCanvas: () => fabric.Canvas | null;
|
getCanvas: () => fabric.Canvas | null;
|
||||||
addElement: (element: AprtElement) => void;
|
addElement: (element: AprtElement) => void;
|
||||||
|
/** Check if a text element is currently being edited */
|
||||||
|
isTextEditing: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snap configuration
|
// Snap configuration
|
||||||
@@ -155,6 +157,12 @@ const EditorCanvas = forwardRef<EditorCanvasRef, EditorCanvasProps>(
|
|||||||
fabricRef.current.renderAll();
|
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
|
// Clear guide lines
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import EditorCanvas, {
|
import EditorCanvas, {
|
||||||
type ContextMenuEvent,
|
type ContextMenuEvent,
|
||||||
|
type EditorCanvasRef,
|
||||||
} from "../components/reportEditor/EditorCanvas";
|
} from "../components/reportEditor/EditorCanvas";
|
||||||
import EditorToolbar, {
|
import EditorToolbar, {
|
||||||
type SnapOptions,
|
type SnapOptions,
|
||||||
@@ -179,6 +180,9 @@ export default function ReportEditorPage() {
|
|||||||
// Template version for collaboration sync (increments on each save)
|
// Template version for collaboration sync (increments on each save)
|
||||||
const templateVersionRef = useRef(0);
|
const templateVersionRef = useRef(0);
|
||||||
|
|
||||||
|
// Ref to EditorCanvas for checking text editing state
|
||||||
|
const canvasRef = useRef<EditorCanvasRef>(null);
|
||||||
|
|
||||||
// ============ COLLABORATION (using global context) ============
|
// ============ COLLABORATION (using global context) ============
|
||||||
// Room key format: "report-template:{id}"
|
// Room key format: "report-template:{id}"
|
||||||
const roomKey = id ? `report-template:${id}` : null;
|
const roomKey = id ? `report-template:${id}` : null;
|
||||||
@@ -803,6 +807,28 @@ export default function ReportEditorPage() {
|
|||||||
setSelectedElementIds([]); // Clear selection when switching pages
|
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 ============
|
// ============ END PAGE MANAGEMENT HANDLERS ============
|
||||||
|
|
||||||
// Add new element
|
// Add new element
|
||||||
@@ -1487,24 +1513,161 @@ export default function ReportEditorPage() {
|
|||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.ctrlKey || e.metaKey) {
|
// Don't handle shortcuts when typing in input fields
|
||||||
switch (e.key) {
|
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":
|
case "z":
|
||||||
e.preventDefault();
|
if (!hasShift) {
|
||||||
handleUndo();
|
e.preventDefault();
|
||||||
|
handleUndo();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Redo: Ctrl+Y or Ctrl+Shift+Z
|
||||||
case "y":
|
case "y":
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleRedo();
|
handleRedo();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Save: Ctrl+S
|
||||||
case "s":
|
case "s":
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSave();
|
handleSave();
|
||||||
break;
|
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,
|
handleUndo,
|
||||||
handleRedo,
|
handleRedo,
|
||||||
handleSave,
|
handleSave,
|
||||||
|
handleCut,
|
||||||
|
handleCopy,
|
||||||
|
handlePaste,
|
||||||
|
handleDuplicate,
|
||||||
|
handleSelectAll,
|
||||||
|
handleDeselectAll,
|
||||||
|
handleToggleLock,
|
||||||
|
handleGroup,
|
||||||
|
handleUngroup,
|
||||||
|
handleBringToFront,
|
||||||
|
handleBringForward,
|
||||||
|
handleSendToBack,
|
||||||
|
handleSendBackward,
|
||||||
handleDeleteElement,
|
handleDeleteElement,
|
||||||
|
handlePrevPage,
|
||||||
|
handleNextPage,
|
||||||
selectedElementId,
|
selectedElementId,
|
||||||
|
selectedElement,
|
||||||
|
clipboard,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Auto-save: simple debounced save on every template change
|
// Auto-save: simple debounced save on every template change
|
||||||
@@ -1701,18 +1881,8 @@ export default function ReportEditorPage() {
|
|||||||
currentPageIndex={currentPageIndex}
|
currentPageIndex={currentPageIndex}
|
||||||
totalPages={template.pages.length}
|
totalPages={template.pages.length}
|
||||||
currentPageName={currentPage?.name || "Pagina 1"}
|
currentPageName={currentPage?.name || "Pagina 1"}
|
||||||
onPrevPage={() => {
|
onPrevPage={handlePrevPage}
|
||||||
if (currentPageIndex > 0) {
|
onNextPage={handleNextPage}
|
||||||
setCurrentPageId(template.pages[currentPageIndex - 1].id);
|
|
||||||
setSelectedElementIds([]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onNextPage={() => {
|
|
||||||
if (currentPageIndex < template.pages.length - 1) {
|
|
||||||
setCurrentPageId(template.pages[currentPageIndex + 1].id);
|
|
||||||
setSelectedElementIds([]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
hasUnsavedChanges={hasUnsavedChanges}
|
hasUnsavedChanges={hasUnsavedChanges}
|
||||||
// Auto-save props
|
// Auto-save props
|
||||||
autoSaveEnabled={autoSaveEnabled}
|
autoSaveEnabled={autoSaveEnabled}
|
||||||
@@ -1747,6 +1917,7 @@ export default function ReportEditorPage() {
|
|||||||
|
|
||||||
{/* Canvas - show only elements for current page */}
|
{/* Canvas - show only elements for current page */}
|
||||||
<EditorCanvas
|
<EditorCanvas
|
||||||
|
ref={canvasRef}
|
||||||
template={{
|
template={{
|
||||||
...template,
|
...template,
|
||||||
elements: currentPageElements,
|
elements: currentPageElements,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user