diff --git a/src/components/CanvasContextMenu.vue b/src/components/CanvasContextMenu.vue
new file mode 100644
index 000000000..252e30581
--- /dev/null
+++ b/src/components/CanvasContextMenu.vue
@@ -0,0 +1,323 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/EditorCanvas.vue b/src/components/EditorCanvas.vue
index 77aa58eed..ccd974a51 100644
--- a/src/components/EditorCanvas.vue
+++ b/src/components/EditorCanvas.vue
@@ -4,6 +4,7 @@ import { ref, computed } from 'vue'
import { useCanvas } from '../composables/use-canvas'
import { useCanvasInput } from '../composables/use-canvas-input'
import { useEditorStore } from '../stores/editor'
+import CanvasContextMenu from './CanvasContextMenu.vue'
const store = useEditorStore()
const canvasRef = ref(null)
@@ -66,6 +67,7 @@ function onTextKeyDown(e: KeyboardEvent) {
+
+
diff --git a/src/composables/use-canvas-input.ts b/src/composables/use-canvas-input.ts
index fcc09b592..ca88ff562 100644
--- a/src/composables/use-canvas-input.ts
+++ b/src/composables/use-canvas-input.ts
@@ -927,6 +927,19 @@ export function useCanvasInput(
})
}
+ function onContextMenu(e: MouseEvent) {
+ const { cx, cy } = getCoords(e)
+ const hit = store.graph.hitTest(cx, cy, store.state.currentPageId)
+ if (hit) {
+ if (!store.state.selectedIds.has(hit.id)) {
+ store.select([hit.id])
+ }
+ } else {
+ store.clearSelection()
+ }
+ }
+
+ useEventListener(canvasRef, 'contextmenu', onContextMenu)
useEventListener(canvasRef, 'dblclick', onDblClick)
useEventListener(canvasRef, 'mousedown', onMouseDown)
useEventListener(canvasRef, 'mousemove', onMouseMove)
diff --git a/src/composables/use-keyboard.ts b/src/composables/use-keyboard.ts
index 3927a7f9d..89b6e1c59 100644
--- a/src/composables/use-keyboard.ts
+++ b/src/composables/use-keyboard.ts
@@ -58,6 +58,16 @@ export function useKeyboard(store: EditorStore) {
store.createComponentSetFromComponents()
return
}
+ if (e.key === 'H' || e.key === 'h') {
+ e.preventDefault()
+ store.toggleVisibility()
+ return
+ }
+ if (e.key === 'L' || e.key === 'l') {
+ e.preventDefault()
+ store.toggleLock()
+ return
+ }
}
if (e.metaKey || e.ctrlKey) {
@@ -105,6 +115,17 @@ export function useKeyboard(store: EditorStore) {
return
}
+ if (e.key === ']') {
+ e.preventDefault()
+ store.bringToFront()
+ return
+ }
+ if (e.key === '[') {
+ e.preventDefault()
+ store.sendToBack()
+ return
+ }
+
if (e.key === 'Backspace' || e.key === 'Delete') {
store.deleteSelected()
}
diff --git a/src/stores/editor.ts b/src/stores/editor.ts
index 72fd0e93b..f156e4309 100644
--- a/src/stores/editor.ts
+++ b/src/stores/editor.ts
@@ -1097,6 +1097,68 @@ export function createEditorStore() {
requestRender()
}
+ function bringToFront() {
+ for (const id of state.selectedIds) {
+ const node = graph.getNode(id)
+ if (!node?.parentId) continue
+ const parent = graph.getNode(node.parentId)
+ if (!parent) continue
+ const idx = parent.childIds.indexOf(id)
+ if (idx === parent.childIds.length - 1) continue
+ parent.childIds = parent.childIds.filter((cid) => cid !== id)
+ parent.childIds.push(id)
+ }
+ requestRender()
+ }
+
+ function sendToBack() {
+ for (const id of state.selectedIds) {
+ const node = graph.getNode(id)
+ if (!node?.parentId) continue
+ const parent = graph.getNode(node.parentId)
+ if (!parent) continue
+ const idx = parent.childIds.indexOf(id)
+ if (idx === 0) continue
+ parent.childIds = parent.childIds.filter((cid) => cid !== id)
+ parent.childIds.unshift(id)
+ }
+ requestRender()
+ }
+
+ function toggleVisibility() {
+ for (const id of state.selectedIds) {
+ const node = graph.getNode(id)
+ if (!node) continue
+ graph.updateNode(id, { visible: !node.visible })
+ }
+ requestRender()
+ }
+
+ function toggleLock() {
+ for (const id of state.selectedIds) {
+ const node = graph.getNode(id)
+ if (!node) continue
+ graph.updateNode(id, { locked: !node.locked })
+ }
+ requestRender()
+ }
+
+ function moveToPage(pageId: string) {
+ const targetPage = graph.getNode(pageId)
+ if (!targetPage || targetPage.type !== 'CANVAS') return
+ const ids = [...state.selectedIds]
+ for (const id of ids) {
+ graph.reparentNode(id, pageId)
+ }
+ clearSelection()
+ requestRender()
+ }
+
+ function renameNode(id: string, name: string) {
+ graph.updateNode(id, { name })
+ requestRender()
+ }
+
function createShape(
type: NodeType,
x: number,
@@ -1548,6 +1610,12 @@ export function createEditorStore() {
createInstanceFromComponent,
detachInstance,
goToMainComponent,
+ bringToFront,
+ sendToBack,
+ toggleVisibility,
+ toggleLock,
+ moveToPage,
+ renameNode,
createShape,
adoptNodesIntoSection,
duplicateSelected,