From 3c4227a4025d60a54f3e2dbdb9697cdd50dd8eb8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 15 Dec 2024 17:59:39 +0530 Subject: [PATCH 01/27] fix: Only show radiusHandler if block is selected --- frontend/src/components/BlockEditor.vue | 2 +- frontend/src/components/PaddingHandler.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/BlockEditor.vue b/frontend/src/components/BlockEditor.vue index 2c2aa2146..a123b1828 100644 --- a/frontend/src/components/BlockEditor.vue +++ b/frontend/src/components/BlockEditor.vue @@ -117,7 +117,7 @@ const showMarginHandler = computed(() => { const showBorderRadiusHandler = computed(() => { return ( - isBlockSelected && + isBlockSelected.value && !props.block.isRoot() && !props.block.isText() && !props.block.isHTML() && diff --git a/frontend/src/components/PaddingHandler.vue b/frontend/src/components/PaddingHandler.vue index 2237117dd..2c50991d7 100644 --- a/frontend/src/components/PaddingHandler.vue +++ b/frontend/src/components/PaddingHandler.vue @@ -308,7 +308,7 @@ const handlePadding = (ev: MouseEvent, position: Position) => { updating.value = false; mouseUpEvent.preventDefault(); }, - { once: true } + { once: true }, ); }; From 4c1e4392ace2bc641093165850c41e031fefa15f Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 15 Dec 2024 22:42:00 +0530 Subject: [PATCH 02/27] refactor: Make BuilderCanvas lighter and improve readability - Move all canvas events to useCanvasEvents - DropZone funtionality to useCanvasrDropZone - All utilities to useCanvasrUtils --- frontend/src/components/BuilderBlock.vue | 2 +- frontend/src/components/BuilderCanvas.vue | 697 ++----------------- frontend/src/components/BuilderLeftPanel.vue | 4 +- frontend/src/store.ts | 13 +- frontend/src/types/Builder/BuilderCanvas.ts | 24 + frontend/src/utils/block.ts | 2 +- frontend/src/utils/useBlockSelection.ts | 86 +++ frontend/src/utils/useCanvasDropZone.ts | 119 ++++ frontend/src/utils/useCanvasEvents.ts | 210 ++++++ frontend/src/utils/useCanvasUtils.ts | 286 ++++++++ 10 files changed, 806 insertions(+), 637 deletions(-) create mode 100644 frontend/src/types/Builder/BuilderCanvas.ts create mode 100644 frontend/src/utils/useBlockSelection.ts create mode 100644 frontend/src/utils/useCanvasDropZone.ts create mode 100644 frontend/src/utils/useCanvasEvents.ts create mode 100644 frontend/src/utils/useCanvasUtils.ts diff --git a/frontend/src/components/BuilderBlock.vue b/frontend/src/components/BuilderBlock.vue index 3b8ccbafb..683bf01fd 100644 --- a/frontend/src/components/BuilderBlock.vue +++ b/frontend/src/components/BuilderBlock.vue @@ -4,7 +4,7 @@ :selected="isSelected" @click="handleClick" @dblclick="handleDoubleClick" - @contextmenu="triggerContextMenu($event)" + @contextmenu="triggerContextMenu" @mouseover="handleMouseOver" @mouseleave="handleMouseLeave" :data-block-id="block.blockId" diff --git a/frontend/src/components/BuilderCanvas.vue b/frontend/src/components/BuilderCanvas.vue index 6ffa34eeb..9f215f4c8 100644 --- a/frontend/src/components/BuilderCanvas.vue +++ b/frontend/src/components/BuilderCanvas.vue @@ -76,22 +76,15 @@ diff --git a/frontend/src/components/BuilderCanvas.vue b/frontend/src/components/BuilderCanvas.vue index 9f215f4c8..8f570aea6 100644 --- a/frontend/src/components/BuilderCanvas.vue +++ b/frontend/src/components/BuilderCanvas.vue @@ -78,11 +78,12 @@ import LoadingIcon from "@/components/Icons/Loading.vue"; import { BreakpointConfig, CanvasHistory } from "@/types/Builder/BuilderCanvas"; import Block from "@/utils/block"; -import { useCanvasUtils } from "@/utils/useCanvasUtils"; import { getBlockCopy } from "@/utils/helpers"; -import { useCanvasEvents } from "@/utils/useCanvasEvents"; +import { useBlockEventHandlers } from "@/utils/useBlockEventHandlers"; import { useBlockSelection } from "@/utils/useBlockSelection"; import { useCanvasDropZone } from "@/utils/useCanvasDropZone"; +import { useCanvasEvents } from "@/utils/useCanvasEvents"; +import { useCanvasUtils } from "@/utils/useCanvasUtils"; import { FeatherIcon } from "frappe-ui"; import { Ref, computed, onMounted, provide, reactive, ref, watch } from "vue"; import useStore from "../store"; @@ -202,6 +203,7 @@ onMounted(() => { findBlock, ); setPanAndZoom(canvasEl, canvasContainerEl, canvasProps); + useBlockEventHandlers(); }); const handleClick = (ev: MouseEvent) => { diff --git a/frontend/src/components/TextBlock.vue b/frontend/src/components/TextBlock.vue index 5363732ab..d0b269362 100644 --- a/frontend/src/components/TextBlock.vue +++ b/frontend/src/components/TextBlock.vue @@ -1,11 +1,5 @@ diff --git a/frontend/src/components/Modals/NewComponent.vue b/frontend/src/components/Modals/NewComponent.vue new file mode 100644 index 000000000..a4a3488de --- /dev/null +++ b/frontend/src/components/Modals/NewComponent.vue @@ -0,0 +1,67 @@ + + From a9e6449e2c470a83dc2e5c0b8f9c09cc9c618736 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 21 Dec 2024 15:41:06 +0530 Subject: [PATCH 07/27] perf: Remove forceful target bound updates on panAndZoom --- frontend/src/utils/panAndZoom.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/utils/panAndZoom.ts b/frontend/src/utils/panAndZoom.ts index 8e3b6d431..b12897a26 100644 --- a/frontend/src/utils/panAndZoom.ts +++ b/frontend/src/utils/panAndZoom.ts @@ -21,7 +21,6 @@ function setPanAndZoom( props.scaling = true; if (!pinchPointSet) { // set pinch point before setting new scale value - targetBound.update(); const middleX = targetBound.left + targetBound.width / 2; const middleY = targetBound.top + targetBound.height / 2; pointFromCenterX = (e.clientX - middleX) / props.scale; @@ -53,7 +52,6 @@ function setPanAndZoom( scale = Math.min(Math.max(scale, zoomLimits.min), zoomLimits.max); props.scale = scale; nextTick(() => { - targetBound.update(); const middleX = targetBound.left + targetBound.width / 2; const middleY = targetBound.top + targetBound.height / 2; From 6ea2f679b846934408e63d5038baa057a0223e88 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 21 Dec 2024 16:32:32 +0530 Subject: [PATCH 08/27] fix: Missing component doc --- frontend/src/store.ts | 14 +++++++++++++- frontend/src/utils/blockOperations.ts | 0 frontend/src/utils/blockTemplate.ts | 18 +++++++++++++++--- 3 files changed, 28 insertions(+), 4 deletions(-) delete mode 100644 frontend/src/utils/blockOperations.ts diff --git a/frontend/src/store.ts b/frontend/src/store.ts index 2ea1c79b9..b2f0ae4fd 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -315,7 +315,7 @@ const useStore = defineStore("store", { getComponentBlock(componentName: string) { return ( (this.componentMap.get(componentName) as Block) || - getBlockInstance(getBlockTemplate("fallback-component")) + getBlockInstance(getBlockTemplate("loading-component")) ); }, async loadComponent(componentName: string) { @@ -325,6 +325,18 @@ const useStore = defineStore("store", { .then((componentDoc) => { this.setComponentMap(componentDoc); }) + .catch(() => { + const missingComponentDoc = { + name: componentName, + block: JSON.stringify(getBlockTemplate("missing-component")), + creation: "", + modified: "", + owner: "Administrator", + modified_by: "Administrator", + docstatus: 1 as 0 | 1 | 2, + }; + this.setComponentMap(missingComponentDoc); + }) .finally(() => { this.fetchingComponent.delete(componentName); }); diff --git a/frontend/src/utils/blockOperations.ts b/frontend/src/utils/blockOperations.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/utils/blockTemplate.ts b/frontend/src/utils/blockTemplate.ts index df61ff7e1..5d49b455f 100644 --- a/frontend/src/utils/blockTemplate.ts +++ b/frontend/src/utils/blockTemplate.ts @@ -6,7 +6,8 @@ function getBlockTemplate( | "container" | "body" | "fit-container" - | "fallback-component" + | "missing-component" + | "loading-component" | "repeater" | "video", ): BlockOptions { @@ -85,12 +86,23 @@ function getBlockTemplate( overflow: "hidden", } as BlockStyleMap, }; - case "fallback-component": + case "missing-component": return { name: "HTML", element: "p", originalElement: "__raw_html__", - innerHTML: `

Component missing

`, + innerHTML: `

Component Missing

`, + baseStyles: { + height: "fit-content", + width: "fit-content", + } as BlockStyleMap, + }; + case "loading-component": + return { + name: "HTML", + element: "p", + originalElement: "__raw_html__", + innerHTML: `

Loading...

`, baseStyles: { height: "fit-content", width: "fit-content", From d484be746f07283f084c446c7dc87ebe4bdd7776 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 21 Dec 2024 18:48:50 +0530 Subject: [PATCH 09/27] refactor: Move component management related code to separate store --- frontend/src/components/BlockContextMenu.vue | 4 +- frontend/src/components/BuilderAssets.vue | 24 +-- .../src/components/Modals/NewComponent.vue | 4 +- frontend/src/store.ts | 142 +-------------- frontend/src/utils/block.ts | 14 +- frontend/src/utils/useBuilderEvents.ts | 7 +- frontend/src/utils/useCanvasDropZone.ts | 6 +- frontend/src/utils/useComponentStore.ts | 162 ++++++++++++++++++ 8 files changed, 196 insertions(+), 167 deletions(-) create mode 100644 frontend/src/utils/useComponentStore.ts diff --git a/frontend/src/components/BlockContextMenu.vue b/frontend/src/components/BlockContextMenu.vue index fbac54ca2..76eb01d1f 100644 --- a/frontend/src/components/BlockContextMenu.vue +++ b/frontend/src/components/BlockContextMenu.vue @@ -21,12 +21,14 @@ import Block from "@/utils/block"; import blockController from "@/utils/blockController"; import getBlockTemplate from "@/utils/blockTemplate"; import { confirm, detachBlockFromComponent, getBlockCopy } from "@/utils/helpers"; +import useComponentStore from "@/utils/useComponentStore"; import { vOnClickOutside } from "@vueuse/components"; import { useStorage } from "@vueuse/core"; import { Ref, nextTick, ref } from "vue"; import { toast } from "vue-sonner"; const store = useStore(); +const componentStore = useComponentStore(); const props = defineProps<{ block: Block; @@ -206,7 +208,7 @@ const contextMenuOptions: ContextMenuOption[] = [ { label: "Edit Component", action: () => { - store.editComponent(props.block); + componentStore.editComponent(props.block); }, condition: () => Boolean(props.block.extendedFromComponent), }, diff --git a/frontend/src/components/BuilderAssets.vue b/frontend/src/components/BuilderAssets.vue index 1411f21cd..987de3807 100644 --- a/frontend/src/components/BuilderAssets.vue +++ b/frontend/src/components/BuilderAssets.vue @@ -24,7 +24,7 @@ selectedComponent === component.component_id, }" @click="selectComponent(component)" - @dblclick="store.editComponent(null, component.name)" + @dblclick="componentStore.editComponent(null, component.name)" @dragstart="(ev) => setComponentData(ev, component)">
@@ -35,7 +35,7 @@ + @click.stop.prevent="componentStore.deleteComponent(component)">
@@ -46,10 +46,11 @@ import webComponent from "@/data/webComponent"; import useStore from "@/store"; import { BuilderComponent } from "@/types/Builder/BuilderComponent"; -import { confirm } from "@/utils/helpers"; +import useComponentStore from "@/utils/useComponentStore"; import { computed, onMounted, ref } from "vue"; const store = useStore(); +const componentStore = useComponentStore(); const componentFilter = ref(""); onMounted(() => { @@ -69,21 +70,6 @@ const components = computed(() => }), ); -const deleteComponent = async (component: BlockComponent) => { - if (store.isComponentUsed(component.name)) { - alert("Component is used in current page. You cannot delete it."); - } else { - const confirmed = await confirm( - `Are you sure you want to delete component: ${component.component_name}?`, - ); - if (confirmed) { - webComponent.delete.submit(component.name).then(() => { - store.componentMap.delete(component.name); - }); - } - } -}; - const setComponentData = (ev: DragEvent, component: BlockComponent) => { ev?.dataTransfer?.setData("componentName", component.name); }; @@ -93,7 +79,7 @@ const selectComponent = (component: BlockComponent) => { selectedComponent.value = component.component_id; // if in edit mode, open the component in editor if (store.fragmentData.fragmentId) { - store.editComponent(null, component.name); + componentStore.editComponent(null, component.name); } }; diff --git a/frontend/src/components/Modals/NewComponent.vue b/frontend/src/components/Modals/NewComponent.vue index 4943c17af..3ba9d0579 100644 --- a/frontend/src/components/Modals/NewComponent.vue +++ b/frontend/src/components/Modals/NewComponent.vue @@ -31,9 +31,11 @@ import { posthog } from "@/telemetry"; import { BuilderComponent } from "@/types/Builder/BuilderComponent"; import Block from "@/utils/block"; import { getBlockCopy, getBlockString } from "@/utils/helpers"; +import useComponentStore from "@/utils/useComponentStore"; import { ref } from "vue"; const store = useStore(); +const componentStore = useComponentStore(); const props = defineProps<{ block: Block; @@ -57,7 +59,7 @@ const createComponentHandler = (close: () => void) => { }) .then(async (data: BuilderComponent) => { posthog.capture("builder_component_created", { component_name: data.name }); - store.setComponentMap(data); + componentStore.setComponentMap(data); const block = store.activeCanvas?.findBlock(props.block.blockId); if (!block) return; block.extendFromComponent(data.name); diff --git a/frontend/src/store.ts b/frontend/src/store.ts index b2f0ae4fd..271755c0f 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -10,10 +10,8 @@ import BlockLayers from "./components/BlockLayers.vue"; import BuilderCanvas from "./components/BuilderCanvas.vue"; import builderBlockTemplate from "./data/builderBlockTemplate"; import { builderSettings } from "./data/builderSettings"; -import webComponent from "./data/webComponent"; import { webPages } from "./data/webPage"; import { BlockTemplate } from "./types/Builder/BlockTemplate"; -import { BuilderComponent } from "./types/Builder/BuilderComponent"; import { BuilderPage } from "./types/Builder/BuilderPage"; import Block from "./utils/block"; import getBlockTemplate from "./utils/blockTemplate"; @@ -21,7 +19,6 @@ import { confirm, getBlockCopy, getBlockInstance, - getBlockObject, getBlockString, getCopyWithoutParent, getRouteVariables, @@ -71,16 +68,12 @@ const useStore = defineStore("store", { showDashboardSidebar: useStorage("showDashboardSidebar", true), showRightPanel: true, showLeftPanel: true, - components: [], showHTMLDialog: false, activePage: null, savingPage: false, realtime: new RealTimeHandler(), viewers: [], - componentMap: >new Map(), - componentDocMap: >new Map(), blockTemplateMap: >new Map(), - fetchingComponent: new Set(), activeFolder: useStorage("activeFolder", ""), fragmentData: { block: null, @@ -161,11 +154,11 @@ const useStore = defineStore("store", { this.activeCanvas?.setRootBlock(this.pageBlocks[0], resetCanvas); nextTick(() => { const interval = setInterval(() => { - if (this.fetchingComponent.size === 0) { - this.settingPage = false; - window.name = `editor-${pageName}`; - clearInterval(interval); - } + // if (this.fetchingComponent.size === 0) { + this.settingPage = false; + window.name = `editor-${pageName}`; + clearInterval(interval); + // } }, 100); }); }, @@ -239,32 +232,7 @@ const useStore = defineStore("store", { this.activeCanvas?.scrollBlockIntoView(block); } }, - async editComponent(block?: Block | null, componentName?: string) { - if (!block?.isExtendedFromComponent() && !componentName) { - return; - } - componentName = componentName || (block?.extendedFromComponent as string); - await this.loadComponent(componentName); - const component = this.getComponent(componentName); - const componentBlock = this.getComponentBlock(componentName); - this.editOnCanvas( - componentBlock, - (block: Block) => { - webComponent.setValue - .submit({ - name: componentName, - block: getBlockObject(block), - }) - .then((data: BuilderComponent) => { - this.componentDocMap.set(data.name, data); - this.componentMap.set(data.name, getBlockInstance(data.block)); - toast.success("Component saved!"); - }); - }, - "Save Component", - component.component_name, - ); - }, + async editBlockTemplate(blockTemplateName: string) { await this.fetchBlockTemplate(blockTemplateName); const blockTemplate = this.getBlockTemplate(blockTemplateName); @@ -284,77 +252,12 @@ const useStore = defineStore("store", { getBlockTemplate(blockTemplateName: string) { return this.blockTemplateMap.get(blockTemplateName) as BlockTemplate; }, - isComponentUsed(componentName: string) { - // TODO: Refactor or reduce complexity - const checkComponent = (block: Block) => { - if (block.extendedFromComponent === componentName) { - return true; - } - if (block.children) { - for (const child of block.children) { - if (checkComponent(child)) { - return true; - } - } - } - return false; - }; - for (const block of this.activeCanvas?.getRootBlock()?.children || []) { - if (checkComponent(block)) { - return true; - } - } - return false; - }, editPage(retainSelection = false) { if (!retainSelection) { this.activeCanvas?.clearSelection(); } this.editingMode = "page"; }, - getComponentBlock(componentName: string) { - return ( - (this.componentMap.get(componentName) as Block) || - getBlockInstance(getBlockTemplate("loading-component")) - ); - }, - async loadComponent(componentName: string) { - if (!this.componentMap.has(componentName) && !this.fetchingComponent.has(componentName)) { - this.fetchingComponent.add(componentName); - return this.fetchComponent(componentName) - .then((componentDoc) => { - this.setComponentMap(componentDoc); - }) - .catch(() => { - const missingComponentDoc = { - name: componentName, - block: JSON.stringify(getBlockTemplate("missing-component")), - creation: "", - modified: "", - owner: "Administrator", - modified_by: "Administrator", - docstatus: 1 as 0 | 1 | 2, - }; - this.setComponentMap(missingComponentDoc); - }) - .finally(() => { - this.fetchingComponent.delete(componentName); - }); - } - }, - setComponentMap(componentDoc: BuilderComponent) { - this.componentDocMap.set(componentDoc.name, componentDoc); - this.componentMap.set(componentDoc.name, getBlockInstance(componentDoc.block)); - }, - async fetchComponent(componentName: string) { - const webComponentDoc = await createDocumentResource({ - doctype: "Builder Component", - name: componentName, - auto: true, - }); - await webComponentDoc.get.promise; - return webComponentDoc.doc as BuilderComponent; - }, async fetchBlockTemplate(blockTemplateName: string) { const blockTemplate = this.getBlockTemplate(blockTemplateName); if (!blockTemplate) { @@ -368,39 +271,6 @@ const useStore = defineStore("store", { this.blockTemplateMap.set(blockTemplateName, blockTemplate); } }, - getComponent(componentName: string) { - return this.componentDocMap.get(componentName) as BuilderComponent; - }, - createComponent(obj: BuilderComponent, updateExisting = false) { - const component = this.getComponent(obj.name); - if (component) { - const existingComponent = component.block; - const newComponent = obj.block; - if (updateExisting && existingComponent !== newComponent) { - return webComponent.setValue.submit({ - name: obj.name, - block: obj.block, - }); - } else { - return; - } - } - return webComponent.insert - .submit(obj) - .then(() => { - this.componentMap.set(obj.name, getBlockInstance(obj.block)); - }) - .catch(() => { - console.log(`There was an error while creating ${obj.component_name}`); - }); - }, - getComponentName(componentId: string) { - let componentObj = webComponent.getRow(componentId); - if (!componentObj) { - return componentId; - } - return componentObj.component_name; - }, async duplicatePage(page: BuilderPage) { const webPageResource = await createDocumentResource({ doctype: "Builder Page", diff --git a/frontend/src/utils/block.ts b/frontend/src/utils/block.ts index 15ea9b758..d891ecaaf 100644 --- a/frontend/src/utils/block.ts +++ b/frontend/src/utils/block.ts @@ -1,4 +1,5 @@ import useStore from "@/store"; +import useComponentStore from "@/utils/useComponentStore"; import { Editor } from "@tiptap/vue-3"; import { clamp } from "@vueuse/core"; import { CSSProperties, markRaw, nextTick, reactive } from "vue"; @@ -93,8 +94,8 @@ class Block implements BlockOptions { } if (this.extendedFromComponent) { - const store = useStore(); - store.loadComponent(this.extendedFromComponent); + const componentStore = useComponentStore(); + componentStore.loadComponent(this.extendedFromComponent); } if (this.isImage()) { @@ -152,11 +153,12 @@ class Block implements BlockOptions { } getComponent() { const store = useStore(); + const componentStore = useComponentStore(); if (this.extendedFromComponent) { - return store.getComponentBlock(this.extendedFromComponent as string); + return componentStore.getComponentBlock(this.extendedFromComponent as string); } if (this.isChildOfComponent) { - const componentBlock = store.getComponentBlock(this.isChildOfComponent as string); + const componentBlock = componentStore.getComponentBlock(this.isChildOfComponent as string); return ( store.activeCanvas?.findBlock(this.referenceBlockId as string, [componentBlock]) || store.activeCanvas?.findBlock(this.blockId as string, [componentBlock]) || @@ -242,8 +244,8 @@ class Block implements BlockOptions { return description; } getComponentBlockDescription() { - const store = useStore(); - return store.getComponentName(this.extendedFromComponent as string); + const componentStore = useComponentStore(); + return componentStore.getComponentName(this.extendedFromComponent as string); } getTextContent() { return getTextContent(this.getInnerHTML() || ""); diff --git a/frontend/src/utils/useBuilderEvents.ts b/frontend/src/utils/useBuilderEvents.ts index 9073fc74d..d49e94a44 100644 --- a/frontend/src/utils/useBuilderEvents.ts +++ b/frontend/src/utils/useBuilderEvents.ts @@ -19,11 +19,14 @@ import { isTargetEditable, uploadImage, } from "@/utils/helpers"; +import useComponentStore from "@/utils/useComponentStore"; import { useEventListener, useStorage } from "@vueuse/core"; import { Ref } from "vue"; import { useRoute, useRouter } from "vue-router"; import { toast } from "vue-sonner"; + const store = useStore(); +const componentStore = useComponentStore(); export function useBuilderEvents( pageCanvas: Ref | null>, @@ -54,7 +57,7 @@ export function useBuilderEvents( for (const block of store.activeCanvas?.selectedBlocks) { const components = block.getUsedComponentNames(); for (const componentName of components) { - const component = store.getComponent(componentName); + const component = componentStore.getComponent(componentName); if (component) { componentDocuments.push(component); } @@ -110,7 +113,7 @@ export function useBuilderEvents( for (const component of dataObj.components) { delete component.for_web_page; - await store.createComponent(component, true); + await componentStore.createComponent(component, true); } if (store.activeCanvas?.selectedBlocks.length && dataObj.blocks[0].blockId !== "root") { diff --git a/frontend/src/utils/useCanvasDropZone.ts b/frontend/src/utils/useCanvasDropZone.ts index c14257fae..b8ce8fd5d 100644 --- a/frontend/src/utils/useCanvasDropZone.ts +++ b/frontend/src/utils/useCanvasDropZone.ts @@ -2,10 +2,12 @@ import useStore from "@/store"; import { posthog } from "@/telemetry"; import Block from "@/utils/block"; import { getBlockCopy, getBlockInstance, uploadImage } from "@/utils/helpers"; +import useComponentStore from "@/utils/useComponentStore"; import { useDropZone } from "@vueuse/core"; import { Ref } from "vue"; const store = useStore(); +const componentStore = useComponentStore(); export function useCanvasDropZone( canvasContainer: Ref, @@ -24,8 +26,8 @@ export function useCanvasDropZone( const componentName = ev.dataTransfer?.getData("componentName"); const blockTemplate = ev.dataTransfer?.getData("blockTemplate"); if (componentName) { - await store.loadComponent(componentName); - const component = store.componentMap.get(componentName) as Block; + await componentStore.loadComponent(componentName); + const component = componentStore.componentMap.get(componentName) as Block; const newBlock = getBlockCopy(component); newBlock.extendFromComponent(componentName); // if shift key is pressed, replace parent block with new block diff --git a/frontend/src/utils/useComponentStore.ts b/frontend/src/utils/useComponentStore.ts new file mode 100644 index 000000000..f3e2d0471 --- /dev/null +++ b/frontend/src/utils/useComponentStore.ts @@ -0,0 +1,162 @@ +import webComponent from "@/data/webComponent"; +import useStore from "@/store"; +import { BuilderComponent } from "@/types/Builder/BuilderComponent"; +import Block from "@/utils/block"; +import getBlockTemplate from "@/utils/blockTemplate"; +import { alert, getBlockInstance, getBlockObject } from "@/utils/helpers"; +import { createDocumentResource } from "frappe-ui"; +import { defineStore } from "pinia"; +import { toast } from "vue-sonner"; + +const useComponentStore = defineStore("componentStore", { + state: () => ({ + components: [], + componentMap: >new Map(), + componentDocMap: >new Map(), + fetchingComponent: new Set(), + }), + actions: { + async editComponent(block?: Block | null, componentName?: string) { + if (!block?.isExtendedFromComponent() && !componentName) { + return; + } + componentName = componentName || (block?.extendedFromComponent as string); + await this.loadComponent(componentName); + const component = this.getComponent(componentName); + const componentBlock = this.getComponentBlock(componentName); + const store = useStore(); + store.editOnCanvas( + componentBlock, + (block: Block) => { + webComponent.setValue + .submit({ + name: componentName, + block: getBlockObject(block), + }) + .then((data: BuilderComponent) => { + this.componentDocMap.set(data.name, data); + this.componentMap.set(data.name, getBlockInstance(data.block)); + toast.success("Component saved!"); + }); + }, + "Save Component", + component.component_name, + ); + }, + isComponentUsed(componentName: string) { + // TODO: Refactor or reduce complexity + const checkComponent = (block: Block) => { + if (block.extendedFromComponent === componentName) { + return true; + } + if (block.children) { + for (const child of block.children) { + if (checkComponent(child)) { + return true; + } + } + } + return false; + }; + const store = useStore(); + for (const block of store.activeCanvas?.getRootBlock()?.children || []) { + if (checkComponent(block)) { + return true; + } + } + return false; + }, + getComponentBlock(componentName: string) { + return ( + (this.componentMap.get(componentName) as Block) || + getBlockInstance(getBlockTemplate("loading-component")) + ); + }, + async loadComponent(componentName: string) { + if (!this.componentMap.has(componentName) && !this.fetchingComponent.has(componentName)) { + this.fetchingComponent.add(componentName); + return this.fetchComponent(componentName) + .then((componentDoc) => { + this.setComponentMap(componentDoc); + }) + .catch(() => { + const missingComponentDoc = { + name: componentName, + block: JSON.stringify(getBlockTemplate("missing-component")), + creation: "", + modified: "", + owner: "Administrator", + modified_by: "Administrator", + docstatus: 1 as 0 | 1 | 2, + }; + this.setComponentMap(missingComponentDoc); + }) + .finally(() => { + this.fetchingComponent.delete(componentName); + }); + } + }, + setComponentMap(componentDoc: BuilderComponent) { + this.componentDocMap.set(componentDoc.name, componentDoc); + this.componentMap.set(componentDoc.name, getBlockInstance(componentDoc.block)); + }, + async fetchComponent(componentName: string) { + const webComponentDoc = await createDocumentResource({ + doctype: "Builder Component", + name: componentName, + auto: true, + }); + await webComponentDoc.get.promise; + return webComponentDoc.doc as BuilderComponent; + }, + getComponent(componentName: string) { + return this.componentDocMap.get(componentName) as BuilderComponent; + }, + createComponent(obj: BuilderComponent, updateExisting = false) { + const component = this.getComponent(obj.name); + if (component) { + const existingComponent = component.block; + const newComponent = obj.block; + if (updateExisting && existingComponent !== newComponent) { + return webComponent.setValue.submit({ + name: obj.name, + block: obj.block, + }); + } else { + return; + } + } + return webComponent.insert + .submit(obj) + .then(() => { + this.componentMap.set(obj.name, getBlockInstance(obj.block)); + }) + .catch(() => { + console.log(`There was an error while creating ${obj.component_name}`); + }); + }, + getComponentName(componentId: string) { + let componentObj = webComponent.getRow(componentId); + if (!componentObj) { + return componentId; + } + return componentObj.component_name; + }, + async deleteComponent(component: BlockComponent) { + if (this.isComponentUsed(component.name)) { + alert("Component is used in current page. You cannot delete it."); + } else { + const confirmed = await confirm( + `Are you sure you want to delete component: ${component.component_name}?`, + ); + if (confirmed) { + webComponent.delete.submit(component.name).then(() => { + this.componentMap.delete(component.name); + }); + } + } + }, + }, +}); + +export default useComponentStore; From 9eddd84ea13d5636285c3f1433f2df2f559ecd3f Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 22 Dec 2024 18:13:03 +0530 Subject: [PATCH 10/27] wip --- frontend/src/utils/block.ts | 84 ++++++++++++++++------------------- frontend/src/utils/helpers.ts | 3 +- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/frontend/src/utils/block.ts b/frontend/src/utils/block.ts index d891ecaaf..80cc4518e 100644 --- a/frontend/src/utils/block.ts +++ b/frontend/src/utils/block.ts @@ -2,7 +2,7 @@ import useStore from "@/store"; import useComponentStore from "@/utils/useComponentStore"; import { Editor } from "@tiptap/vue-3"; import { clamp } from "@vueuse/core"; -import { CSSProperties, markRaw, nextTick, reactive } from "vue"; +import { computed, CSSProperties, markRaw, nextTick, reactive } from "vue"; import { addPxToNumber, dataURLtoFile, @@ -47,8 +47,10 @@ class Block implements BlockOptions { visibilityCondition?: string; elementBeforeConversion?: string; parentBlock: Block | null; + referenceComponent: Block | null; customAttributes: BlockAttributeMap; constructor(options: BlockOptions) { + const componentStore = useComponentStore(); this.element = options.element; this.innerHTML = options.innerHTML; this.extendedFromComponent = options.extendedFromComponent; @@ -57,6 +59,20 @@ class Block implements BlockOptions { this.referenceBlockId = options.referenceBlockId; this.visibilityCondition = options.visibilityCondition; this.parentBlock = options.parentBlock || null; + if (this.extendedFromComponent) { + componentStore.loadComponent(this.extendedFromComponent); + } + const store = useStore(); + this.referenceComponent = computed(() => { + if (this.extendedFromComponent) { + componentStore.componentMap; + return componentStore.getComponentBlock(this.extendedFromComponent as string) || null; + } else if (this.isChildOfComponent) { + const componentBlock = componentStore.getComponentBlock(this.isChildOfComponent as string); + return store.activeCanvas?.findBlock(this.referenceBlockId as string, [componentBlock]) || null; + } + return null; + }) as unknown as Block | null; this.dataKey = options.dataKey || null; @@ -93,11 +109,6 @@ class Block implements BlockOptions { this.removeStyle("minHeight"); } - if (this.extendedFromComponent) { - const componentStore = useComponentStore(); - componentStore.loadComponent(this.extendedFromComponent); - } - if (this.isImage()) { // if src is base64, convert it to a file const src = this.getAttribute("src") as string; @@ -151,24 +162,8 @@ class Block implements BlockOptions { this.tabletStyles = {}; } } - getComponent() { - const store = useStore(); - const componentStore = useComponentStore(); - if (this.extendedFromComponent) { - return componentStore.getComponentBlock(this.extendedFromComponent as string); - } - if (this.isChildOfComponent) { - const componentBlock = componentStore.getComponentBlock(this.isChildOfComponent as string); - return ( - store.activeCanvas?.findBlock(this.referenceBlockId as string, [componentBlock]) || - store.activeCanvas?.findBlock(this.blockId as string, [componentBlock]) || - new Block({}) - ); - } - return this; - } getComponentStyles(breakpoint: string): BlockStyleMap { - return this.getComponent()?.getStyles(breakpoint); + return this.referenceComponent?.getStyles(breakpoint) || {}; } getAttributes(): BlockAttributeMap { let attributes = {}; @@ -179,7 +174,7 @@ class Block implements BlockOptions { return attributes; } getComponentAttributes() { - return this.getComponent()?.attributes || {}; + return this.referenceComponent?.attributes || {}; } getClasses() { let classes = [] as Array; @@ -190,7 +185,7 @@ class Block implements BlockOptions { return classes; } getComponentClasses() { - return this.getComponent()?.classes || []; + return this.referenceComponent?.classes || []; } getChildren() { return this.children; @@ -198,13 +193,10 @@ class Block implements BlockOptions { hasChildren() { return this.getChildren().length > 0; } - getComponentChildren() { - return this.getComponent()?.children || []; - } getCustomAttributes() { let customAttributes = {}; if (this.isExtendedFromComponent()) { - customAttributes = this.getComponent()?.customAttributes || {}; + customAttributes = this.referenceComponent?.customAttributes || {}; } customAttributes = { ...customAttributes, ...this.customAttributes }; return customAttributes; @@ -212,15 +204,15 @@ class Block implements BlockOptions { getRawStyles() { let rawStyles = {}; if (this.isExtendedFromComponent()) { - rawStyles = this.getComponent()?.rawStyles || {}; + rawStyles = this.referenceComponent?.rawStyles || {}; } rawStyles = { ...rawStyles, ...this.rawStyles }; return rawStyles; } getVisibilityCondition() { let visibilityCondition = this.visibilityCondition; - if (this.isExtendedFromComponent() && this.getComponent()?.visibilityCondition) { - visibilityCondition = this.getComponent()?.visibilityCondition; + if (this.isExtendedFromComponent() && this.referenceComponent?.visibilityCondition) { + visibilityCondition = this.referenceComponent?.visibilityCondition; } return visibilityCondition; } @@ -328,7 +320,7 @@ class Block implements BlockOptions { styleValue = this.baseStyles[style]; } if (styleValue === undefined && this.isExtendedFromComponent()) { - styleValue = this.getComponent()?.getStyle(style) as StyleValue; + styleValue = this.referenceComponent?.getStyle(style) as StyleValue; } return styleValue; } @@ -387,7 +379,7 @@ class Block implements BlockOptions { return this.getElement() || "div"; } getComponentTag() { - return this.getComponent()?.getTag() || "div"; + return this.referenceComponent?.getTag() || "div"; } isDiv() { return this.getElement() === "div"; @@ -628,7 +620,7 @@ class Block implements BlockOptions { getDataKey(key: keyof BlockDataKey): string { let dataKey = (this.dataKey && this.dataKey[key]) || ""; if (!dataKey && this.isExtendedFromComponent()) { - dataKey = this.getComponent()?.getDataKey(key); + dataKey = this.referenceComponent?.getDataKey(key) || ""; } return dataKey; } @@ -645,7 +637,7 @@ class Block implements BlockOptions { getInnerHTML(): string { let innerHTML = this.innerHTML || ""; if (!innerHTML && this.isExtendedFromComponent()) { - innerHTML = this.getComponent().getInnerHTML(); + innerHTML = this.referenceComponent?.getInnerHTML() || ""; } return innerHTML; } @@ -666,20 +658,22 @@ class Block implements BlockOptions { } extendFromComponent(componentName: string) { this.extendedFromComponent = componentName; - const component = this.getComponent(); - extendWithComponent(this, componentName, component.children); + const component = this.referenceComponent as Block; + if (component) { + extendWithComponent(this, componentName, component.children); + } } isChildOfComponentBlock() { return Boolean(this.isChildOfComponent); } resetWithComponent() { - const component = this.getComponent(); + const component = this.referenceComponent; if (component) { resetWithComponent(this, this.extendedFromComponent as string, component.children); } } syncWithComponent() { - const component = this.getComponent(); + const component = this.referenceComponent; if (component) { syncBlockWithComponent(this, this, this.extendedFromComponent as string, component.children); } @@ -700,12 +694,12 @@ class Block implements BlockOptions { } getElement() { if (this.isExtendedFromComponent()) { - return this.getComponent()?.element || this.element; + return this.referenceComponent?.element || this.element; } return this.element; } getUsedComponentNames() { - const store = useStore(); + const componentStore = useComponentStore(); const componentNames = [] as string[]; if (this.extendedFromComponent) { componentNames.push(this.extendedFromComponent); @@ -718,7 +712,7 @@ class Block implements BlockOptions { }); componentNames.forEach((name) => { - componentNames.push(...store.getComponentBlock(name).getUsedComponentNames()); + componentNames.push(...componentStore.getComponentBlock(name).getUsedComponentNames()); }); return new Set(componentNames); @@ -904,7 +898,7 @@ function extendWithComponent( child.isChildOfComponent = extendedFromComponent; let componentChild = componentChildren[index]; if (child.extendedFromComponent) { - const component = child.getComponent(); + const component = child.referenceComponent; child.referenceBlockId = componentChild.blockId; extendWithComponent(child, child.extendedFromComponent, component.children, false); } else if (componentChild) { @@ -928,7 +922,7 @@ function resetWithComponent( blockComponent.referenceBlockId = componentChild.blockId; const childBlock = block.addChild(blockComponent, null, false); if (componentChild.extendedFromComponent) { - const component = childBlock.getComponent(); + const component = childBlock.referenceComponent; resetWithComponent(childBlock, componentChild.extendedFromComponent, component.children, false); } else { resetWithComponent(childBlock, extendedWithComponent, componentChild.children, resetOverrides); diff --git a/frontend/src/utils/helpers.ts b/frontend/src/utils/helpers.ts index 810b3e558..938184864 100644 --- a/frontend/src/utils/helpers.ts +++ b/frontend/src/utils/helpers.ts @@ -284,7 +284,7 @@ function isCtrlOrCmd(e: KeyboardEvent) { const detachBlockFromComponent = (block: Block) => { const blockCopy = getBlockCopy(block, true); - const component = block.getComponent(); + const component = block.referenceComponent; blockCopy.element = block?.getElement(); blockCopy.attributes = block.getAttributes(); blockCopy.classes = block.getClasses(); @@ -325,6 +325,7 @@ function getCopyWithoutParent(block: BlockOptions | Block): BlockOptions { const blockCopy = { ...toRaw(block) }; blockCopy.children = blockCopy.children?.map((child) => getCopyWithoutParent(child)); delete blockCopy.parentBlock; + delete blockCopy.referenceComponent; return blockCopy; } From 4b3244a98bffacc9b22a3822555ff5af2550df0a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 22 Dec 2024 21:32:23 +0530 Subject: [PATCH 11/27] perf: Mark components raw to disable unnecessary reactivity --- frontend/src/utils/block.ts | 22 ++++++++++++++++++---- frontend/src/utils/useComponentStore.ts | 5 +++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/frontend/src/utils/block.ts b/frontend/src/utils/block.ts index 80cc4518e..82db9d8ba 100644 --- a/frontend/src/utils/block.ts +++ b/frontend/src/utils/block.ts @@ -62,14 +62,13 @@ class Block implements BlockOptions { if (this.extendedFromComponent) { componentStore.loadComponent(this.extendedFromComponent); } - const store = useStore(); this.referenceComponent = computed(() => { if (this.extendedFromComponent) { - componentStore.componentMap; - return componentStore.getComponentBlock(this.extendedFromComponent as string) || null; + return markRaw(componentStore.getComponentBlock(this.extendedFromComponent as string)) || null; } else if (this.isChildOfComponent) { const componentBlock = componentStore.getComponentBlock(this.isChildOfComponent as string); - return store.activeCanvas?.findBlock(this.referenceBlockId as string, [componentBlock]) || null; + const componentBlockInstance = findBlock(this.referenceBlockId as string, [componentBlock]); + return componentBlockInstance ? markRaw(componentBlockInstance) : null; } return null; }) as unknown as Block | null; @@ -997,4 +996,19 @@ function resetBlock( } } +function findBlock(blockId: string, blocks: Block[]): Block | null { + 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; +} + export default Block; diff --git a/frontend/src/utils/useComponentStore.ts b/frontend/src/utils/useComponentStore.ts index f3e2d0471..3921ac424 100644 --- a/frontend/src/utils/useComponentStore.ts +++ b/frontend/src/utils/useComponentStore.ts @@ -6,6 +6,7 @@ import getBlockTemplate from "@/utils/blockTemplate"; import { alert, getBlockInstance, getBlockObject } from "@/utils/helpers"; import { createDocumentResource } from "frappe-ui"; import { defineStore } from "pinia"; +import { markRaw } from "vue"; import { toast } from "vue-sonner"; const useComponentStore = defineStore("componentStore", { @@ -35,7 +36,7 @@ const useComponentStore = defineStore("componentStore", { }) .then((data: BuilderComponent) => { this.componentDocMap.set(data.name, data); - this.componentMap.set(data.name, getBlockInstance(data.block)); + this.componentMap.set(data.name, markRaw(getBlockInstance(data.block))); toast.success("Component saved!"); }); }, @@ -98,7 +99,7 @@ const useComponentStore = defineStore("componentStore", { }, setComponentMap(componentDoc: BuilderComponent) { this.componentDocMap.set(componentDoc.name, componentDoc); - this.componentMap.set(componentDoc.name, getBlockInstance(componentDoc.block)); + this.componentMap.set(componentDoc.name, markRaw(getBlockInstance(componentDoc.block))); }, async fetchComponent(componentName: string) { const webComponentDoc = await createDocumentResource({ From f9902f808ae4087cf937bf4e714797a78a6fba43 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 22 Dec 2024 23:18:48 +0530 Subject: [PATCH 12/27] refactor: Explicitly import dialog --- frontend/src/components/Modals/NewBlockTemplate.vue | 2 +- frontend/src/components/Modals/NewComponent.vue | 1 + frontend/src/components/Modals/NewFolder.vue | 1 + frontend/src/components/Modals/SelectFolder.vue | 1 + frontend/src/main.ts | 3 +-- frontend/src/pages/PageBuilderDashboard.vue | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Modals/NewBlockTemplate.vue b/frontend/src/components/Modals/NewBlockTemplate.vue index 772fcd255..26d6cd711 100644 --- a/frontend/src/components/Modals/NewBlockTemplate.vue +++ b/frontend/src/components/Modals/NewBlockTemplate.vue @@ -62,7 +62,7 @@ diff --git a/frontend/src/components/BlockEditor.vue b/frontend/src/components/BlockEditor.vue index e72954a2b..9a96af5df 100644 --- a/frontend/src/components/BlockEditor.vue +++ b/frontend/src/components/BlockEditor.vue @@ -1,39 +1,36 @@ From 9862a56311c7b1d51fa831509feeeee1626c9634 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 23 Dec 2024 23:31:28 +0530 Subject: [PATCH 15/27] refactor: Remove extra div --- frontend/src/components/BlockLayers.vue | 116 ++++++++++++------------ 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/frontend/src/components/BlockLayers.vue b/frontend/src/components/BlockLayers.vue index 29ce668b9..053269e08 100644 --- a/frontend/src/components/BlockLayers.vue +++ b/frontend/src/components/BlockLayers.vue @@ -8,69 +8,65 @@ @add="updateParent" :disabled="blocks.length && (blocks[0].isRoot() || blocks[0].isChildOfComponentBlock())"> @@ -89,7 +85,7 @@ type LayerInstance = InstanceType; const store = useStore(); const childLayers = ref([]); -const childLayer = (el) => { +const childLayer = (el: LayerInstance) => { if (el) { childLayers.value.push(el); } From cca62d60690f7be4ad21317bb64df786b3c565ca Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 26 Dec 2024 13:40:42 +0530 Subject: [PATCH 16/27] fix: Context menu action reactivity --- frontend/src/components/BlockEditor.vue | 2 +- frontend/src/utils/useCanvasHistory.ts | 26 ++++++++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/BlockEditor.vue b/frontend/src/components/BlockEditor.vue index 9a96af5df..3ed099f4d 100644 --- a/frontend/src/components/BlockEditor.vue +++ b/frontend/src/components/BlockEditor.vue @@ -236,11 +236,11 @@ const handleDoubleClick = (ev: MouseEvent) => { }; const handleMove = (ev: MouseEvent) => { - const pauseId = store.activeCanvas?.history?.pause(); if (store.mode === "text") { store.editableBlock = props.block; } if (!movable.value || props.block.isRoot()) return; + const pauseId = store.activeCanvas?.history?.pause(); const target = ev.target as HTMLElement; const startX = ev.clientX; const startY = ev.clientY; diff --git a/frontend/src/utils/useCanvasHistory.ts b/frontend/src/utils/useCanvasHistory.ts index ad681c48e..12b5eb71a 100644 --- a/frontend/src/utils/useCanvasHistory.ts +++ b/frontend/src/utils/useCanvasHistory.ts @@ -1,7 +1,7 @@ import Block from "@/utils/block"; import { generateId, getBlockInstance, getBlockString } from "@/utils/helpers"; import { debounceFilter, pausableFilter, watchIgnorable } from "@vueuse/core"; -import { nextTick, ref, Ref } from "vue"; +import { ref, Ref } from "vue"; type CanvasState = { block: string; @@ -136,19 +136,17 @@ export function useCanvasHistory(source: Ref, selectedBlockIds: Ref { - if (pauseId && pauseIdSet.has(pauseId)) { - pauseIdSet.delete(pauseId); - } else if (!force) { - return; - } - if (pauseIdSet.size && !force) { - return; - } - pauseIdSet.clear(); - resumeTracking(); - if (commitNow) commit(); - }); + if (pauseId && pauseIdSet.has(pauseId)) { + pauseIdSet.delete(pauseId); + } else if (!force) { + return; + } + if (pauseIdSet.size && !force) { + return; + } + pauseIdSet.clear(); + resumeTracking(); + if (commitNow) commit(); } function batch(callback: () => void) { From 3a4eb5a323e65fd29c7114fdb95cf630bcf2116c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 27 Dec 2024 11:08:58 +0530 Subject: [PATCH 17/27] refactor: validateVisit edge cases - If user is logged in and does not have access to builder, redirct to app - if user is not logged in redirect to login. Once user logs in redirect back to builder page. --- frontend/src/router.ts | 48 ++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/frontend/src/router.ts b/frontend/src/router.ts index b6553e752..36b48ef2b 100644 --- a/frontend/src/router.ts +++ b/frontend/src/router.ts @@ -10,43 +10,49 @@ function validatePermission(next: NavigationGuardNext) { next(); } else { alert("You do not have permission to access this page"); - window.location.href = "/login"; + if (isUserLoggedIn()) { + window.location.href = "/app"; + } else { + window.location.href = "/login?redirect-to=/builder"; + } } } -const validateVisit = function ( +const validateVisit = async function ( to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, ) { - if (document.cookie.includes("user_id") && !document.cookie.includes("user_id=Guest")) { - sessionUser.value = decodeURIComponent(document.cookie.split("user_id=")[1].split(";")[0]); + if (isUserLoggedIn()) { + sessionUser.value = getSessionUser(); if (hasPermission === null) { - createResource({ - url: "frappe.client.has_permission", - caches: "has_permission", - }) - .submit({ + try { + const response = await createResource({ + url: "frappe.client.has_permission", + }).submit({ doctype: "Builder Page", docname: null, perm_type: "write", - }) - .then((res: { has_permission: boolean }) => { - hasPermission = res.has_permission; - validatePermission(next); - }) - .catch(() => { - hasPermission = false; - validatePermission(next); }); - } else { - validatePermission(next); + hasPermission = response.has_permission; + return validatePermission(next); + } catch (e) { + hasPermission = false; + return validatePermission(next); + } } - } else { - validatePermission(next); } + return validatePermission(next); }; +function isUserLoggedIn() { + return document.cookie.includes("user_id") && !document.cookie.includes("user_id=Guest"); +} + +function getSessionUser() { + return decodeURIComponent(document.cookie.split("user_id=")[1].split(";")[0]) || "Guest"; +} + const routes = [ { path: "/home", From 3d67831bb276741fc9c014ab313423296137a159 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 27 Dec 2024 15:47:40 +0530 Subject: [PATCH 18/27] chore: Update frappe-ui --- frappe-ui | 2 +- frontend/package.json | 2 +- yarn.lock | 81 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/frappe-ui b/frappe-ui index 858d8f7aa..0f32e2e95 160000 --- a/frappe-ui +++ b/frappe-ui @@ -1 +1 @@ -Subproject commit 858d8f7aa521789d648195dc477d115abe6642d8 +Subproject commit 0f32e2e95677198fe84323f17954d7b306711065 diff --git a/frontend/package.json b/frontend/package.json index a36a70775..64a569e3b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,7 +24,7 @@ "ace-builds": "^1.22.0", "autoprefixer": "^10.4.2", "feather-icons": "^4.28.0", - "frappe-ui": "0.1.80", + "frappe-ui": "0.1.93", "opentype.js": "^1.3.4", "pinia": "^2.0.28", "postcss": "^8.4.5", diff --git a/yarn.lock b/yarn.lock index 1819aa803..43d672d7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1611,6 +1611,15 @@ birpc@^0.1.1: resolved "https://registry.yarnpkg.com/birpc/-/birpc-0.1.1.tgz#10b243ffe5a21ccaf4cbaf5a09a6f0cc035c4312" integrity sha512-B64AGL4ug2IS2jvV/zjTYDD1L+2gOJTT7Rv+VaK7KVQtQOo/xZbCDsh7g727ipckmU+QJYRqo5RcifVr0Kgcmg== +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blob-util@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" @@ -1663,7 +1672,7 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer@^5.7.1: +buffer@^5.5.0, buffer@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -1809,6 +1818,11 @@ cli-cursor@^5.0.0: dependencies: restore-cursor "^5.0.0" +cli-spinners@^2.5.0: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + cli-table3@~0.6.1: version "0.6.5" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" @@ -1834,6 +1848,11 @@ cli-truncate@^4.0.0: slice-ansi "^5.0.0" string-width "^7.0.0" +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -2061,7 +2080,7 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" -dayjs@^1.10.4: +dayjs@^1.10.4, dayjs@^1.11.13: version "1.11.13" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== @@ -2097,6 +2116,13 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -3209,7 +3235,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3333,6 +3359,11 @@ is-installed-globally@~0.4.0: global-dirs "^3.0.0" is-path-inside "^3.0.2" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-negative-zero@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" @@ -3696,7 +3727,7 @@ lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@^4.0.0: +log-symbols@^4.0.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -4106,6 +4137,21 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +ora@5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + orderedmap@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2" @@ -4617,6 +4663,15 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -4761,7 +4816,7 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" -safe-buffer@^5.0.1, safe-buffer@^5.1.2: +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5078,6 +5133,13 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -5464,7 +5526,7 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -util-deprecate@^1.0.2: +util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -5593,6 +5655,13 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + webfontloader@^1.6.28: version "1.6.28" resolved "https://registry.yarnpkg.com/webfontloader/-/webfontloader-1.6.28.tgz#db786129253cb6e8eae54c2fb05f870af6675bae" From aac5fa753f049578dd5c6841b48b98afa874f2be Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 27 Dec 2024 21:23:10 +0530 Subject: [PATCH 19/27] fix: Use Set for selectionIds to avoid duplicates --- frontend/src/utils/useBlockSelection.ts | 24 ++++++++++++------------ frontend/src/utils/useCanvasHistory.ts | 8 ++++---- frontend/src/utils/useCanvasUtils.ts | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/frontend/src/utils/useBlockSelection.ts b/frontend/src/utils/useBlockSelection.ts index 6d4424c89..7cb3ddf1d 100644 --- a/frontend/src/utils/useBlockSelection.ts +++ b/frontend/src/utils/useBlockSelection.ts @@ -2,7 +2,7 @@ import Block from "@/utils/block"; import { computed, Ref, ref } from "vue"; export function useBlockSelection(rootBlock: Ref) { - const selectedBlockIds = ref([]); + const selectedBlockIds = ref>(new Set()); function findBlock(blockId: string, blocks?: Block[]): Block | null { if (!blocks) { @@ -23,36 +23,37 @@ export function useBlockSelection(rootBlock: Ref) { } const selectedBlocks = computed(() => { - return selectedBlockIds.value.map((id) => findBlock(id)).filter((b) => b) as Block[]; + return Array.from(selectedBlockIds.value) + .map((id: string) => findBlock(id)) + .filter((b): b is Block => !!b); }); const isSelected = (block: Block) => { - return selectedBlockIds.value.includes(block.blockId); + return selectedBlockIds.value.has(block.blockId); }; const selectBlock = (block: Block, multiSelect = false) => { if (multiSelect) { - selectedBlockIds.value.push(block.blockId); + selectedBlockIds.value.add(block.blockId); } else { - selectedBlockIds.value = [block.blockId]; + selectedBlockIds.value = new Set([block.blockId]); } }; const toggleBlockSelection = (block: Block) => { - const index = selectedBlockIds.value.indexOf(block.blockId); - if (index >= 0) { - selectedBlockIds.value.splice(index, 1); + if (selectedBlockIds.value.has(block.blockId)) { + selectedBlockIds.value.delete(block.blockId); } else { selectBlock(block, true); } }; const clearSelection = () => { - selectedBlockIds.value = []; + selectedBlockIds.value = new Set(); }; const selectBlockRange = (newSelectedBlock: Block) => { - const lastSelectedBlockId = selectedBlockIds.value[selectedBlockIds.value.length - 1]; + const lastSelectedBlockId = Array.from(selectedBlockIds.value)[selectedBlockIds.value.size - 1]; const lastSelectedBlock = findBlock(lastSelectedBlockId); const lastSelectedBlockParent = lastSelectedBlock?.parentBlock; if (!lastSelectedBlock || !lastSelectedBlockParent) { @@ -69,8 +70,7 @@ export function useBlockSelection(rootBlock: Ref) { const end = Math.max(lastSelectedBlockIndex, newSelectedBlockIndex); if (lastSelectedBlockParent === newSelectedBlockParent) { const blocks = lastSelectedBlockParent.children.slice(start, end + 1); - selectedBlockIds.value = selectedBlockIds.value.concat(...blocks.map((b) => b.blockId)); - selectedBlockIds.value = Array.from(new Set(selectedBlockIds.value)); + blocks.forEach((b) => selectedBlockIds.value.add(b.blockId)); } }; diff --git a/frontend/src/utils/useCanvasHistory.ts b/frontend/src/utils/useCanvasHistory.ts index 12b5eb71a..fc4b3e50e 100644 --- a/frontend/src/utils/useCanvasHistory.ts +++ b/frontend/src/utils/useCanvasHistory.ts @@ -5,14 +5,14 @@ import { ref, Ref } from "vue"; type CanvasState = { block: string; - selectedBlockIds: string[]; + selectedBlockIds: Set; }; type PauseId = string & { __brand: "PauseId" }; const CAPACITY = 500; const DEBOUNCE_DELAY = 100; -export function useCanvasHistory(source: Ref, selectedBlockIds: Ref) { +export function useCanvasHistory(source: Ref, selectedBlockIds: Ref>) { const undoStack = ref([]) as Ref; const redoStack = ref([]) as Ref; const last = ref(createHistoryRecord()); @@ -61,7 +61,7 @@ export function useCanvasHistory(source: Ref, selectedBlockIds: Ref, selectedBlockIds: Ref { - selectedBlockIds.value = [...value.selectedBlockIds]; + selectedBlockIds.value = new Set(value.selectedBlockIds); }); last.value = value; } diff --git a/frontend/src/utils/useCanvasUtils.ts b/frontend/src/utils/useCanvasUtils.ts index 3e7ae7eee..0127e1f8d 100644 --- a/frontend/src/utils/useCanvasUtils.ts +++ b/frontend/src/utils/useCanvasUtils.ts @@ -13,7 +13,7 @@ export function useCanvasUtils( canvasContainer: Ref, canvas: Ref, rootBlock: Ref, - selectedBlockIds: Ref, + selectedBlockIds: Ref>, canvasHistory: Ref, ) { const isDirty = ref(false); @@ -192,9 +192,9 @@ export function useCanvasUtils( function selectBlock(_block: Block, multiSelect = false) { if (multiSelect) { - selectedBlockIds.value.push(_block.blockId); + selectedBlockIds.value.add(_block.blockId); } else { - selectedBlockIds.value.splice(0, selectedBlockIds.value.length, _block.blockId); + selectedBlockIds.value = new Set([_block.blockId]); } } From 6f403b6527b0f22b95cdbb6c43d90f982659e6f5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 29 Dec 2024 17:03:03 +0530 Subject: [PATCH 20/27] fix: Misc --- frontend/src/index.css | 4 ++++ frontend/src/utils/block.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/frontend/src/index.css b/frontend/src/index.css index 15c427f5f..638befb14 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -44,4 +44,8 @@ p:empty::before { ::-webkit-scrollbar-thumb { @apply rounded-full; @apply bg-surface-gray-3; +} + +[data-theme='dark'] img { + filter: revert; } \ No newline at end of file diff --git a/frontend/src/utils/block.ts b/frontend/src/utils/block.ts index 82db9d8ba..34f24f4d4 100644 --- a/frontend/src/utils/block.ts +++ b/frontend/src/utils/block.ts @@ -669,6 +669,9 @@ class Block implements BlockOptions { const component = this.referenceComponent; if (component) { resetWithComponent(this, this.extendedFromComponent as string, component.children); + // TODO: Remove this + const store = useStore(); + store.activeCanvas?.history?.resume(undefined, true, true); } } syncWithComponent() { From 2c9dca170fb10efbfb5ec3e2ffa6ccf9b986c543 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 29 Dec 2024 17:32:52 +0530 Subject: [PATCH 21/27] feat: Option to disable desktop breakpoint - Command click to only show clicked breakpoint --- frontend/src/components/BuilderCanvas.vue | 23 +++++++++++++++++------ frontend/src/utils/helpers.ts | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/BuilderCanvas.vue b/frontend/src/components/BuilderCanvas.vue index 8f570aea6..274b66e2a 100644 --- a/frontend/src/components/BuilderCanvas.vue +++ b/frontend/src/components/BuilderCanvas.vue @@ -26,7 +26,7 @@ class="w-auto cursor-pointer p-2" v-for="breakpoint in canvasProps.breakpoints" :key="breakpoint.device" - @click.stop="breakpoint.visible = !breakpoint.visible"> + @click.stop="(ev) => selectBreakpoint(ev, breakpoint)">
{ - return canvasProps.breakpoints.filter( - (breakpoint) => breakpoint.visible || breakpoint.device === "desktop", - ); + return canvasProps.breakpoints.filter((breakpoint) => breakpoint.visible); }); onMounted(() => { @@ -270,4 +268,17 @@ defineExpose({ removeBlock, selectBlockRange, }); + +function selectBreakpoint(ev: MouseEvent, breakpoint: BreakpointConfig) { + if (isCtrlOrCmd(ev)) { + canvasProps.breakpoints.forEach((bp) => { + bp.visible = bp.device === breakpoint.device; + }); + } else { + breakpoint.visible = !breakpoint.visible; + if (canvasProps.breakpoints.filter((bp) => bp.visible).length === 0) { + breakpoint.visible = true; + } + } +} diff --git a/frontend/src/utils/helpers.ts b/frontend/src/utils/helpers.ts index 938184864..406c77b11 100644 --- a/frontend/src/utils/helpers.ts +++ b/frontend/src/utils/helpers.ts @@ -278,7 +278,7 @@ function getBlockCopy(block: BlockOptions | Block, retainId = false): Block { return getBlockInstance(b, retainId); } -function isCtrlOrCmd(e: KeyboardEvent) { +function isCtrlOrCmd(e: KeyboardEvent | MouseEvent) { return e.ctrlKey || e.metaKey; } From e29e0dda5cfbe9a9dae2439e414fd2add26f84c4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 29 Dec 2024 17:58:18 +0530 Subject: [PATCH 22/27] perf: Do not re-render breakpoint canvas on toggling --- frontend/src/components/BuilderCanvas.vue | 13 ++++++++----- frontend/src/types/Builder/BuilderCanvas.ts | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/BuilderCanvas.vue b/frontend/src/components/BuilderCanvas.vue index 274b66e2a..3bb7750a3 100644 --- a/frontend/src/components/BuilderCanvas.vue +++ b/frontend/src/components/BuilderCanvas.vue @@ -43,7 +43,8 @@ background: canvasProps.background, width: `${breakpoint.width}px`, }" - v-for="breakpoint in visibleBreakpoints" + v-for="breakpoint in renderedBreakpoints" + v-show="breakpoint.visible" :key="breakpoint.device">
{ - return canvasProps.breakpoints.filter((breakpoint) => breakpoint.visible); -}); - onMounted(() => { const canvasContainerEl = canvasContainer.value as unknown as HTMLElement; const canvasEl = canvas.value as unknown as HTMLElement; @@ -280,5 +278,10 @@ function selectBreakpoint(ev: MouseEvent, breakpoint: BreakpointConfig) { breakpoint.visible = true; } } + if (breakpoint.visible) { + breakpoint.renderedOnce = true; + } } + +const renderedBreakpoints = computed(() => canvasProps.breakpoints.filter((bp) => bp.renderedOnce)); diff --git a/frontend/src/types/Builder/BuilderCanvas.ts b/frontend/src/types/Builder/BuilderCanvas.ts index 3424ebdb0..e70a25cfd 100644 --- a/frontend/src/types/Builder/BuilderCanvas.ts +++ b/frontend/src/types/Builder/BuilderCanvas.ts @@ -7,6 +7,7 @@ export interface BreakpointConfig { displayName: string; width: number; visible: boolean; + renderedOnce: boolean; } export interface CanvasProps { From 8ed14293a7f93628d493bdd1658bd4d4f99a9948 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 29 Dec 2024 19:07:58 +0530 Subject: [PATCH 23/27] perf: Only load layers if required Reduces the load on first render and makes it extremely fast while navigating using arrow keys --- frontend/src/components/BlockLayers.vue | 5 +++-- frontend/src/components/BuilderLeftPanel.vue | 19 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/BlockLayers.vue b/frontend/src/components/BlockLayers.vue index 053269e08..b9250f384 100644 --- a/frontend/src/components/BlockLayers.vue +++ b/frontend/src/components/BlockLayers.vue @@ -64,7 +64,7 @@ {{ store.activeBreakpoint }} -
+
@@ -161,7 +161,7 @@ const canShowChildLayer = (block: Block) => { }; watch( - () => store.activeCanvas?.selectedBlocks, + () => store.activeCanvas?.selectedBlockIds, () => { if (store.activeCanvas?.selectedBlocks.length) { store.activeCanvas?.selectedBlocks.forEach((block: Block) => { @@ -176,6 +176,7 @@ watch( }); } }, + { immediate: true }, ); // @ts-ignore diff --git a/frontend/src/components/BuilderLeftPanel.vue b/frontend/src/components/BuilderLeftPanel.vue index 207133fcd..a69dd4f3b 100644 --- a/frontend/src/components/BuilderLeftPanel.vue +++ b/frontend/src/components/BuilderLeftPanel.vue @@ -75,7 +75,7 @@ import PageScript from "@/components/PageScript.vue"; import Block from "@/utils/block"; import convertHTMLToBlocks from "@/utils/convertHTMLToBlocks"; import { createResource } from "frappe-ui"; -import { Ref, inject, ref, watch, watchEffect } from "vue"; +import { Ref, inject, nextTick, ref, watch, watchEffect } from "vue"; import useStore from "../store"; import BlockLayers from "./BlockLayers.vue"; import BuilderAssets from "./BuilderAssets.vue"; @@ -159,16 +159,15 @@ watch( ); watch( - () => store.activeCanvas?.selectedBlocks, - () => { - document.querySelectorAll(`[data-block-layer-id].block-selected`).forEach((el) => { - el.classList.remove("block-selected"); + () => store.activeCanvas?.selectedBlockIds, + async () => { + await nextTick(); + const selectedBlocks = document.querySelectorAll(`[data-block-layer-id].block-selected`); + selectedBlocks.forEach((el) => el.classList.remove("block-selected")); + Array.from(store.activeCanvas?.selectedBlockIds || new Set([])).forEach((blockId: string) => { + const blockElement = document.querySelector(`[data-block-layer-id="${blockId}"]`); + blockElement?.classList.add("block-selected"); }); - if (store.activeCanvas?.selectedBlocks.length) { - store.activeCanvas?.selectedBlocks.forEach((block: Block) => { - document.querySelector(`[data-block-layer-id="${block.blockId}"]`)?.classList.add("block-selected"); - }); - } }, ); From b1f356d7e199d6f553f007cb9676085057de0e0b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 29 Dec 2024 19:16:14 +0530 Subject: [PATCH 24/27] fix(UX): Reset hoveredBreakpoint and activeBreakpoint on new breapoint selection --- frontend/src/components/BuilderCanvas.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/BuilderCanvas.vue b/frontend/src/components/BuilderCanvas.vue index 3bb7750a3..64286201b 100644 --- a/frontend/src/components/BuilderCanvas.vue +++ b/frontend/src/components/BuilderCanvas.vue @@ -279,6 +279,8 @@ function selectBreakpoint(ev: MouseEvent, breakpoint: BreakpointConfig) { } } if (breakpoint.visible) { + store.hoveredBreakpoint = breakpoint.device; + store.activeBreakpoint = breakpoint.device; breakpoint.renderedOnce = true; } } From c06ba304c5d2620f3ec119876bcc644b4425dcc0 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 29 Dec 2024 20:05:29 +0530 Subject: [PATCH 25/27] fix(UX): Fade transition while loading page --- frontend/src/components/BuilderCanvas.vue | 11 +++++++++ frontend/src/components/Icons/Loading.vue | 30 ++++++----------------- frontend/src/store.ts | 14 ++++++----- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/BuilderCanvas.vue b/frontend/src/components/BuilderCanvas.vue index 64286201b..7c84cd232 100644 --- a/frontend/src/components/BuilderCanvas.vue +++ b/frontend/src/components/BuilderCanvas.vue @@ -287,3 +287,14 @@ function selectBreakpoint(ev: MouseEvent, breakpoint: BreakpointConfig) { const renderedBreakpoints = computed(() => canvasProps.breakpoints.filter((bp) => bp.renderedOnce)); + diff --git a/frontend/src/components/Icons/Loading.vue b/frontend/src/components/Icons/Loading.vue index 1b5074a2f..524ccc29d 100644 --- a/frontend/src/components/Icons/Loading.vue +++ b/frontend/src/components/Icons/Loading.vue @@ -1,29 +1,15 @@