diff --git a/frontend/src/components/BlockContextMenu.vue b/frontend/src/components/BlockContextMenu.vue index c9a1459f..fc449b76 100644 --- a/frontend/src/components/BlockContextMenu.vue +++ b/frontend/src/components/BlockContextMenu.vue @@ -144,7 +144,7 @@ const contextMenuOptions: ContextMenuOption[] = [ const parentBlock = props.block.getParentBlock(); if (!parentBlock) return; - const selectedBlocks = store.selectedBlocks; + const selectedBlocks = store.activeCanvas?.selectedBlocks || []; const blockPosition = Math.min(...selectedBlocks.map(parentBlock.getChildIndex.bind(parentBlock))); const newBlock = parentBlock?.addChild(newBlockObj, blockPosition); @@ -170,11 +170,11 @@ const contextMenuOptions: ContextMenuOption[] = [ }, condition: () => { if (props.block.isRoot()) return false; - if (store.selectedBlocks.length === 1) return true; + if (store.activeCanvas?.selectedBlocks.length === 1) return true; // check if all selected blocks are siblings const parentBlock = props.block.getParentBlock(); if (!parentBlock) return false; - const selectedBlocks = store.selectedBlocks; + const selectedBlocks = store.activeCanvas?.selectedBlocks || []; return selectedBlocks.every((block) => block.getParentBlock() === parentBlock); }, }, diff --git a/frontend/src/components/BlockLayers.vue b/frontend/src/components/BlockLayers.vue index 67e16440..eb21eb8c 100644 --- a/frontend/src/components/BlockLayers.vue +++ b/frontend/src/components/BlockLayers.vue @@ -14,9 +14,6 @@ :title="element.blockId" @contextmenu.prevent.stop="onContextMenu" class="cursor-pointer rounded border border-transparent bg-white pl-2 pr-[2px] text-sm text-gray-700 dark:bg-zinc-900 dark:text-gray-500" - :class="{ - 'block-selected': store.isSelected(element.blockId), - }" @click.stop=" store.activeCanvas?.history.pause(); element.expanded = true; @@ -131,10 +128,10 @@ const toggleExpanded = (block: Block) => { }; watch( - () => store.selectedBlocks, + () => store.activeCanvas?.selectedBlocks, () => { - if (store.selectedBlocks.length) { - store.selectedBlocks.forEach((block: Block) => { + if (store.activeCanvas?.selectedBlocks.length) { + store.activeCanvas?.selectedBlocks.forEach((block: Block) => { if (block) { expandedLayers.value.add(block.blockId); let parentBlock = block.getParentBlock(); diff --git a/frontend/src/components/BuilderBlock.vue b/frontend/src/components/BuilderBlock.vue index 8feb1fa7..873dd44c 100644 --- a/frontend/src/components/BuilderBlock.vue +++ b/frontend/src/components/BuilderBlock.vue @@ -80,10 +80,8 @@ const draggable = computed(() => { return !props.block.isRoot() && !props.preview && false; }); -const hovered = ref(false); -const isSelected = computed(() => { - return store.selectedBlocks.some((block) => block.blockId === props.block.blockId); -}); +const isHovered = ref(false); +const isSelected = ref(false); const getComponentName = (block: Block) => { if (block.isRepeater()) { @@ -146,7 +144,7 @@ const loadEditor = computed(() => { target.value && props.block.getStyle("display") !== "none" && ((isSelected.value && props.breakpoint === store.activeBreakpoint) || - (hovered.value && store.hoveredBreakpoint === props.breakpoint)) && + (isHovered.value && store.hoveredBreakpoint === props.breakpoint)) && !canvasProps?.scaling && !canvasProps?.panning ); @@ -258,10 +256,24 @@ watch( () => store.hoveredBlock, (newValue, oldValue) => { if (newValue === props.block.blockId) { - hovered.value = true; + isHovered.value = true; } else if (oldValue === props.block.blockId) { - hovered.value = false; + isHovered.value = false; } } ); + +watch( + () => store.activeCanvas?.selectedBlockIds, + () => { + if (store.activeCanvas?.isSelected(props.block)) { + isSelected.value = true; + } else { + isSelected.value = false; + } + }, + { + deep: true, + } +); diff --git a/frontend/src/components/BuilderCanvas.vue b/frontend/src/components/BuilderCanvas.vue index d8df0c5f..b37bbc50 100644 --- a/frontend/src/components/BuilderCanvas.vue +++ b/frontend/src/components/BuilderCanvas.vue @@ -165,7 +165,7 @@ const { isOverDropZone } = useDropZone(canvasContainer, { let parentBlock = block.value as Block | null; if (element) { if (element.dataset.blockId) { - parentBlock = store.findBlock(element.dataset.blockId) || parentBlock; + parentBlock = findBlock(element.dataset.blockId) || parentBlock; } } let componentName = ev.dataTransfer?.getData("componentName"); @@ -230,12 +230,12 @@ function setEvents() { let block = getFirstBlock(); if (element) { if (element.dataset.blockId) { - block = store.findBlock(element.dataset.blockId) || block; + block = findBlock(element.dataset.blockId) || block; } } let parentBlock = getFirstBlock(); if (element.dataset.blockId) { - parentBlock = store.findBlock(element.dataset.blockId) || parentBlock; + parentBlock = findBlock(element.dataset.blockId) || parentBlock; while (parentBlock && !parentBlock.canHaveChildren()) { parentBlock = parentBlock.getParentBlock() || getFirstBlock(); } @@ -445,7 +445,7 @@ const handleClick = (ev: MouseEvent) => { // hack to ensure if click is on canvas-container // TODO: Still clears selection if space handlers are dragged over canvas-container if (target?.classList.contains("canvas-container")) { - store.clearSelection(); + clearSelection(); } }; @@ -470,6 +470,77 @@ const setRootBlock = (newBlock: Block, resetCanvas = false) => { } }; +const selectedBlockIds = ref([]) as Ref; +const selectedBlocks = computed(() => { + return selectedBlockIds.value.map((id) => findBlock(id)); +}) as Ref; + +const isSelected = (block: Block) => { + return selectedBlockIds.value.includes(block.blockId); +}; + +const selectBlock = (_block: Block, multiSelect = false) => { + if (multiSelect) { + selectedBlockIds.value.push(_block.blockId); + } else { + selectedBlockIds.value.splice(0, selectedBlockIds.value.length, _block.blockId); + } +}; + +const toggleBlockSelection = (_block: Block) => { + if (isSelected(_block)) { + selectedBlockIds.value.splice(selectedBlockIds.value.indexOf(_block.blockId), 1); + } else { + selectBlock(_block, true); + } +}; + +const clearSelection = () => { + selectedBlockIds.value = []; +}; + +const findParentBlock = (blockId: string, blocks?: Block[]): Block | null => { + if (!blocks) { + const firstBlock = getFirstBlock(); + if (!firstBlock) { + return null; + } + blocks = [firstBlock]; + } + for (const block of blocks) { + if (block.children) { + for (const child of block.children) { + if (child.blockId === blockId) { + return block; + } + } + const found = findParentBlock(blockId, block.children); + if (found) { + return found; + } + } + } + return null; +}; + +const findBlock = (blockId: string, blocks?: Block[]): Block | null => { + if (!blocks) { + blocks = [getFirstBlock()]; + } + for (const block of blocks) { + if (block.blockId === blockId) { + return block; + } + if (block.children) { + const found = findBlock(blockId, block.children); + if (found) { + return found; + } + } + } + return null; +}; + defineExpose({ setScaleAndTranslate, resetZoom, @@ -482,5 +553,13 @@ defineExpose({ block, setRootBlock, canvasProps, + selectBlock, + toggleBlockSelection, + selectedBlocks, + clearSelection, + isSelected, + selectedBlockIds, + findParentBlock, + findBlock, }); diff --git a/frontend/src/components/BuilderLeftPanel.vue b/frontend/src/components/BuilderLeftPanel.vue index 212f60fe..8487c895 100644 --- a/frontend/src/components/BuilderLeftPanel.vue +++ b/frontend/src/components/BuilderLeftPanel.vue @@ -54,7 +54,7 @@ diff --git a/frontend/src/components/TextBlock.vue b/frontend/src/components/TextBlock.vue index 8a648b76..d156a81c 100644 --- a/frontend/src/components/TextBlock.vue +++ b/frontend/src/components/TextBlock.vue @@ -199,10 +199,10 @@ watch( if (!props.preview) { watch( - () => store.isSelected(props.block.blockId), + () => store.activeCanvas?.isSelected(props.block), () => { // only load editor if block is selected for performance reasons - if (store.isSelected(props.block.blockId) && !blockController.multipleBlocksSelected()) { + if (store.activeCanvas?.isSelected(props.block) && !blockController.multipleBlocksSelected()) { editor.value = new Editor({ content: textContent.value, extensions: [ diff --git a/frontend/src/pages/PageBuilder.vue b/frontend/src/pages/PageBuilder.vue index d0ccfa7b..8a4b644d 100644 --- a/frontend/src/pages/PageBuilder.vue +++ b/frontend/src/pages/PageBuilder.vue @@ -105,9 +105,9 @@ useEventListener( useEventListener(document, "copy", (e) => { if (isTargetEditable(e)) return; e.preventDefault(); - if (store.selectedBlocks.length) { + if (store.activeCanvas?.selectedBlocks.length) { const componentDocuments: BuilderComponent[] = []; - store.selectedBlocks.forEach((block: Block) => { + store.activeCanvas?.selectedBlocks.forEach((block: Block) => { const components = block.getUsedComponentNames(); components.forEach((componentName) => { const component = store.getComponent(componentName); @@ -118,7 +118,7 @@ useEventListener(document, "copy", (e) => { }); // just copy non components const dataToCopy = { - blocks: store.selectedBlocks, + blocks: store.activeCanvas?.selectedBlocks, components: componentDocuments, }; copyToClipboard(dataToCopy, e, "builder-copied-blocks"); @@ -162,8 +162,8 @@ useEventListener(document, "paste", async (e) => { await store.createComponent(component, true); } - if (store.selectedBlocks.length && dataObj.blocks[0].blockId !== "root") { - let parentBlock = store.selectedBlocks[0]; + if (store.activeCanvas?.selectedBlocks.length && dataObj.blocks[0].blockId !== "root") { + let parentBlock = store.activeCanvas.selectedBlocks[0]; while (parentBlock && !parentBlock.canHaveChildren()) { parentBlock = parentBlock.getParentBlock() as Block; } @@ -558,20 +558,6 @@ watch( deep: true, } ); - -// moved out of BlockLayers for performance -// TODO: Find a better way to do this -watch( - () => store.hoveredBlock, - () => { - document.querySelectorAll(`[data-block-layer-id].hovered-block`).forEach((el) => { - el.classList.remove("hovered-block"); - }); - if (store.hoveredBlock) { - document.querySelector(`[data-block-layer-id="${store.hoveredBlock}"]`)?.classList.add("hovered-block"); - } - } -);