-
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user