@@ -14,8 +14,12 @@ import from "..constants.tools" { TOOL_SHORTCUTS }
1414import from .canvas .CanvasRenderer { CanvasRenderer }
1515import from .canvas .TextInput { TextInput }
1616import from .canvas .ContextMenu { ContextMenu }
17+ import from .layout .ShortcutHelp { ShortcutHelp }
18+ import from .layout .ConfirmDialog { ConfirmDialog }
1719
1820def :pub Canvas (props : dict ) -> JsxElement {
21+ showShortcutsProp = props.showShortcuts or False ;
22+ onToggleShortcuts = props.onToggleShortcuts;
1923 currentTool = props.currentTool or " select" ;
2024 currentColor = props.currentColor or " #000000" ;
2125 currentStrokeWidth = props.currentStrokeWidth or 2 ;
@@ -45,6 +49,8 @@ def:pub Canvas(props: dict) -> JsxElement {
4549 selectedElementsRef = useRef([]);
4650 altDragCopiedRef = useRef(False );
4751 drawingStateRef = useRef(drawingState);
52+ viewportHookRef = useRef(viewportHook);
53+ selectionHookRef = useRef(selectionHook);
4854 has isDraggingBend : bool = False ;
4955 has isRotating : bool = False ;
5056 has rotatingElementId : any = None ;
@@ -54,10 +60,13 @@ def:pub Canvas(props: dict) -> JsxElement {
5460 has draggingVertexHandle : str = " " ;
5561 has isDraggingElbowSegment : bool = False ;
5662 has draggingElbowSegIndex : int = 0 ;
63+ has showResetConfirm : bool = False ;
5764
5865 selectedElementRef.current = selectionHook.selectedElement;
5966 selectedElementsRef.current = selectionHook.selectedElements;
6067 drawingStateRef.current = drawingState;
68+ viewportHookRef.current = viewportHook;
69+ selectionHookRef.current = selectionHook;
6170
6271 # Pass editing shape id to suppress its shapeText during editing
6372 editingShapeId = None ;
@@ -225,21 +234,21 @@ def:pub Canvas(props: dict) -> JsxElement {
225234 canvas = canvasRef.current;
226235 if canvas {
227236 rect = canvas.getBoundingClientRect();
228- viewportHook .handleZoom(- 1 , rect.left + rect.width / 2 , rect.top + rect.height / 2 , canvas);
237+ viewportHookRef.current .handleZoom(- 1 , rect.left + rect.width / 2 , rect.top + rect.height / 2 , canvas);
229238 }
230239 }
231240 if not textInputHook.textInput and e.key == " -" and (e.ctrlKey or e.metaKey) {
232241 e.preventDefault();
233242 canvas = canvasRef.current;
234243 if canvas {
235244 rect = canvas.getBoundingClientRect();
236- viewportHook .handleZoom(1 , rect.left + rect.width / 2 , rect.top + rect.height / 2 , canvas);
245+ viewportHookRef.current .handleZoom(1 , rect.left + rect.width / 2 , rect.top + rect.height / 2 , canvas);
237246 }
238247 }
239248 if not textInputHook.textInput and e.key == " 0" and (e.ctrlKey or e.metaKey) {
240249 e.preventDefault();
241- viewportHook .setZoom(1 );
242- viewportHook .setPanOffset({" x" : 0 , " y" : 0 });
250+ viewportHookRef.current .setZoom(1 );
251+ viewportHookRef.current .setPanOffset({" x" : 0 , " y" : 0 });
243252 }
244253 if not textInputHook.textInput and (e.key == " s" or e.key == " S" ) and (e.ctrlKey or e.metaKey) and not e.shiftKey {
245254 e.preventDefault();
@@ -249,6 +258,58 @@ def:pub Canvas(props: dict) -> JsxElement {
249258 e.preventDefault();
250259 triggerJaSketchLoad();
251260 }
261+ if not textInputHook.textInput and (e.key == " x" or e.key == " X" ) and (e.ctrlKey or e.metaKey) {
262+ e.preventDefault();
263+ curMultiCut = selectedElementsRef.current;
264+ curSingleCut = selectedElementRef.current;
265+ if curMultiCut and curMultiCut.length > 0 {
266+ copied = [];
267+ ci = 0 ;
268+ while ci < curMultiCut.length {
269+ copied.push(Object.assign({}, curMultiCut[ci].element));
270+ ci = ci + 1 ;
271+ }
272+ clipboardRef.current = copied;
273+ } elif curSingleCut and curSingleCut.element {
274+ clipboardRef.current = [Object.assign({}, curSingleCut.element)];
275+ }
276+ deleteSelected();
277+ }
278+ if not textInputHook.textInput and e.key == " Delete" and (e.ctrlKey or e.metaKey) {
279+ e.preventDefault();
280+ showResetConfirm = True ;
281+ }
282+ if not textInputHook.textInput and (e.key == " <" or e.key == " ," ) and (e.ctrlKey or e.metaKey) and e.shiftKey {
283+ e.preventDefault();
284+ curSel = selectedElementRef.current;
285+ if curSel and curSel.element.type == " text" {
286+ element = curSel.element;
287+ newSize = element.fontSize or 16 ;
288+ newSize = newSize - 2 ;
289+ if newSize < 8 {
290+ newSize = 8 ;
291+ }
292+ elementsHook.updateElement(element.id, {** element, " fontSize" : newSize});
293+ selectionHookRef.current.setSelectedElement({" element" : {** element, " fontSize" : newSize}, " id" : element.id});
294+ }
295+ }
296+ if not textInputHook.textInput and (e.key == " >" or e.key == " ." ) and (e.ctrlKey or e.metaKey) and e.shiftKey {
297+ e.preventDefault();
298+ curSel = selectedElementRef.current;
299+ if curSel and curSel.element.type == " text" {
300+ element = curSel.element;
301+ newSize = element.fontSize or 16 ;
302+ newSize = newSize + 2 ;
303+ elementsHook.updateElement(element.id, {** element, " fontSize" : newSize});
304+ selectionHookRef.current.setSelectedElement({" element" : {** element, " fontSize" : newSize}, " id" : element.id});
305+ }
306+ }
307+ if not textInputHook.textInput and e.key == " ?" {
308+ e.preventDefault();
309+ if onToggleShortcuts {
310+ onToggleShortcuts();
311+ }
312+ }
252313 if not textInputHook.textInput and not e.ctrlKey and not e.metaKey and not e.shiftKey and not e.altKey {
253314 if e.key == " 9" {
254315 triggerImageUpload();
@@ -538,6 +599,7 @@ def:pub Canvas(props: dict) -> JsxElement {
538599
539600 def offsetElement (original : dict , dx : float , dy : float ) -> dict {
540601 dup = Object.assign({}, original);
602+ dup.id = None ;
541603 if original.type == " text" or original.type == " rectangle" or original.type == " circle" or original.type == " image" or original.type == " diamond" {
542604 dup.x = original.x + dx;
543605 dup.y = original.y + dy;
@@ -1322,7 +1384,9 @@ def:pub Canvas(props: dict) -> JsxElement {
13221384 clones = [];
13231385 aci = 0 ;
13241386 while aci < selectionHook.selectedElements.length {
1325- clones.push(Object.assign({}, selectionHook.selectedElements[aci].element));
1387+ clone = Object.assign({}, selectionHook.selectedElements[aci].element);
1388+ clone.id = None ;
1389+ clones.push(clone);
13261390 aci = aci + 1 ;
13271391 }
13281392 elementsHook.addMultipleElements(clones);
@@ -1339,6 +1403,7 @@ def:pub Canvas(props: dict) -> JsxElement {
13391403 return ;
13401404 } elif selectionHook.selectedElement {
13411405 singleClone = Object.assign({}, selectionHook.selectedElement.element);
1406+ singleClone.id = None ;
13421407 elementsHook.addElement(singleClone);
13431408 selectionHook.setSelectedElement({" element" : singleClone, " id" : singleClone.id});
13441409 selectionHook.setSelectedElements([]);
@@ -1864,6 +1929,22 @@ def:pub Canvas(props: dict) -> JsxElement {
18641929 { toastMessage and <div className = " fixed bottom-6 left-1/2 -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg text-sm z-50 pointer-events-none animate-fade-in" >
18651930 { toastMessage}
18661931 </div > or None }
1932+
1933+ { (showShortcutsProp or False ) and <ShortcutHelp onClose = { lambda -> None { if onToggleShortcuts { onToggleShortcuts(); } }} /> or None }
1934+
1935+ { showResetConfirm and <ConfirmDialog
1936+ title = " Reset Canvas"
1937+ message = " Reset the canvas? This will clear all elements."
1938+ onConfirm = { lambda -> None {
1939+ elementsHook.clearElements();
1940+ selectionHook.clearSelection();
1941+ showResetConfirm = False ;
1942+ }}
1943+ onCancel = { lambda -> None { showResetConfirm = False ; }}
1944+ confirmText = " Reset"
1945+ cancelText = " Cancel"
1946+ isDangerous = { True }
1947+ /> or None }
18671948 </div >
18681949 );
18691950}
0 commit comments