From 2ec2384830567161f3253c5621fa863e2d58d5a4 Mon Sep 17 00:00:00 2001 From: semiaddict Date: Tue, 10 Jan 2023 16:53:49 +0100 Subject: [PATCH] fix: freeze Content components while editing text Closes: #576 --- .../components/ComponentWrapper.vue | 4 + packages/editor/modules/app_preview/index.js | 20 ++- .../editor/modules/app_preview/store/index.js | 134 ++++++++++++------ .../modules/app_preview/store/plugin.js | 26 ++++ .../components/controls/HtmlControl.vue | 77 +++++----- 5 files changed, 180 insertions(+), 81 deletions(-) create mode 100644 packages/editor/modules/app_preview/store/plugin.js diff --git a/packages/editor/modules/app_preview/components/ComponentWrapper.vue b/packages/editor/modules/app_preview/components/ComponentWrapper.vue index 266ad1603..d603c4809 100644 --- a/packages/editor/modules/app_preview/components/ComponentWrapper.vue +++ b/packages/editor/modules/app_preview/components/ComponentWrapper.vue @@ -57,6 +57,7 @@ preview, dragging, resizing, + frozen, 'drag-over': dragOver, }" @click="onClick" @@ -181,6 +182,9 @@ export default { locked() { return this.store.isComponentLocked(this.component); }, + frozen() { + return this.store.isComponentFrozen(this.component); + }, interactable() { return ( (this.model.$isPositionable || this.model.$isResizable) && diff --git a/packages/editor/modules/app_preview/index.js b/packages/editor/modules/app_preview/index.js index a076716fb..6275f411c 100644 --- a/packages/editor/modules/app_preview/index.js +++ b/packages/editor/modules/app_preview/index.js @@ -2,6 +2,7 @@ import { readonly } from "vue"; import { storeToRefs } from "pinia"; import AbstractModule from "@metascore-library/core/services/module-manager/AbstractModule"; import useStore from "./store"; +import storePlugin from "./store/plugin"; import AppComponents from "@metascore-library/core/modules/app_components"; import AppRenderer from "@metascore-library/core/modules/app_renderer"; import ContextMenu from "@metascore-library/core/modules/contextmenu"; @@ -31,7 +32,7 @@ export default class AppPreviewModule extends AbstractModule { MediaPlayer, ]; - constructor({ app }) { + constructor({ app, pinia }) { super(arguments); // Override the app_components' component-wrapper. @@ -43,6 +44,8 @@ export default class AppPreviewModule extends AbstractModule { app.component("AppZoomController", AppZoomController); app.component("AppDimensionsController", AppDimensionsController); app.component("AppPreviewToggler", AppPreviewToggler); + + pinia.use(storePlugin); } get preview() { @@ -88,6 +91,21 @@ export default class AppPreviewModule extends AbstractModule { return store.unlockComponent(component); } + isComponentFrozen(component) { + const store = useStore(); + return store.isComponentFrozen(component); + } + + freezeComponent(component) { + const store = useStore(); + return store.freezeComponent(component); + } + + unfreezeComponent(component) { + const store = useStore(); + return store.unfreezeComponent(component); + } + componentHasSelectedDescendents(component) { const store = useStore(); return store.componentHasSelectedDescendents(component); diff --git a/packages/editor/modules/app_preview/store/index.js b/packages/editor/modules/app_preview/store/index.js index 0a5372f28..8d6c78933 100644 --- a/packages/editor/modules/app_preview/store/index.js +++ b/packages/editor/modules/app_preview/store/index.js @@ -19,8 +19,9 @@ export default defineStore("app-preview", { zoom: 1, preview: false, iframe: null, - selectedComponents: [], - lockedComponents: [], + selectedComponents: {}, + lockedComponents: {}, + frozenComponents: {}, activeSnapTargets: [], }; }, @@ -33,24 +34,28 @@ export default defineStore("app-preview", { }; }, isComponentSelected() { - return (component) => { + return ({ type, id }) => { const { getComponent } = useModule("app_components"); - return this.selectedComponents.some(({ type, id }) => { - return ( - component.type === type && - component.id === id && - getComponent(type, id) - ); - }); + return ( + this.selectedComponents[type]?.includes(id) && getComponent(type, id) + ); }; }, getSelectedComponents() { const { getComponent } = useModule("app_components"); - return this.selectedComponents - .map(({ type, id }) => { - return getComponent(type, id); - }) - .filter((c) => c); + return Object.entries(this.selectedComponents).reduce( + (acc, [type, ids]) => { + return [ + ...acc, + ...ids + .map((id) => { + return getComponent(type, id); + }) + .filter((c) => c), + ]; + }, + [] + ); }, componentHasSelectedDescendents() { return (component) => { @@ -66,24 +71,42 @@ export default defineStore("app-preview", { }; }, isComponentLocked() { - return (component) => { + return ({ type, id }) => { const { getComponent } = useModule("app_components"); - return this.lockedComponents.some(({ type, id }) => { - return ( - component.type === type && - component.id === id && - getComponent(type, id) - ); - }); + return ( + this.lockedComponents[type]?.includes(id) && getComponent(type, id) + ); }; }, getLockedComponents() { const { getComponent } = useModule("app_components"); - return this.lockedComponents - .map(({ type, id }) => { - return getComponent(type, id); - }) - .filter((c) => c); + return Object.entries(this.lockedComponents).reduce( + (acc, [type, ids]) => { + return [ + ...acc, + ...ids + .map((id) => { + return getComponent(type, id); + }) + .filter((c) => c), + ]; + }, + [] + ); + }, + isComponentFrozen() { + return ({ type, id }) => { + return ( + type in this.frozenComponents && id in this.frozenComponents[type] + ); + }; + }, + getFrozenComponent() { + return ({ type, id }) => { + if (this.isComponentFrozen({ type, id })) { + return this.frozenComponents[type][id]; + } + }; }, }, actions: { @@ -92,24 +115,25 @@ export default defineStore("app-preview", { this.deselectAllComponents(); } if (!this.isComponentSelected(component)) { - this.selectedComponents.push({ - type: component.type, - id: component.id, - }); + this.selectedComponents[component.type] = + this.selectedComponents[component.type] || []; + this.selectedComponents[component.type].push(component.id); } }, deselectComponent(component) { - this.selectedComponents = this.selectedComponents.filter( - ({ type, id }) => { - return !(component.type === type && component.id === id); - } - ); + if (this.isComponentSelected(component)) { + this.selectedComponents[component.type] = this.selectedComponents[ + component.type + ].filter((id) => { + return component.id !== id; + }); + } }, deselectComponents(components) { components.map(this.deselectComponent); }, deselectAllComponents() { - this.selectedComponents = []; + this.selectedComponents = {}; }, moveComponentSelection(reverse = false) { const selected = this.getSelectedComponents; @@ -137,25 +161,43 @@ export default defineStore("app-preview", { }, lockComponent(component) { if (!this.isComponentLocked(component)) { - this.lockedComponents.push({ - type: component.type, - id: component.id, - }); + this.lockedComponents[component.type] = + this.lockedComponents[component.type] || []; + this.lockedComponents[component.type].push(component.id); } }, lockComponents(components) { components.map(this.lockComponent); }, unlockComponent(component) { - this.lockedComponents = this.lockedComponents.filter(({ type, id }) => { - return !(component.type === type && component.id === id); - }); + if (this.isComponentLocked(component)) { + this.lockedComponents[component.type] = this.lockedComponents[ + component.type + ].filter((id) => { + return component.id !== id; + }); + } }, unlockComponents(components) { components.map(this.unlockComponent); }, unlockAllComponents() { - this.lockedComponents = []; + this.lockedComponents = {}; + }, + freezeComponent(component) { + if (!this.isComponentFrozen(component)) { + const { getComponent } = useModule("app_components"); + this.frozenComponents[component.type] = + this.frozenComponents[component.type] || {}; + this.frozenComponents[component.type][component.id] = structuredClone( + getComponent(component.type, component.id) + ); + } + }, + unfreezeComponent(component) { + if (this.isComponentFrozen(component)) { + delete this.frozenComponents[component.type][component.id]; + } }, copyComponents(components) { const { setData: setClipboardData } = useModule("clipboard"); diff --git a/packages/editor/modules/app_preview/store/plugin.js b/packages/editor/modules/app_preview/store/plugin.js new file mode 100644 index 000000000..30e464dfb --- /dev/null +++ b/packages/editor/modules/app_preview/store/plugin.js @@ -0,0 +1,26 @@ +import useStore from "./"; + +export default function ({ store }) { + if (store.$id !== "app-components") return; + + const original_get = store.get; + + return { + /** + * Override the app-components's "get" action. + * + * @param {string} type The component's type + * @param {string} id The component's id + * @returns {object} The component's data + */ + get(type, id) { + const { isComponentFrozen, getFrozenComponent } = useStore(); + if (isComponentFrozen({ type, id })) { + // Get frozen component data. + return getFrozenComponent({ type, id }); + } + + return original_get(type, id); + }, + }; +} diff --git a/packages/editor/modules/component_form/components/controls/HtmlControl.vue b/packages/editor/modules/component_form/components/controls/HtmlControl.vue index 77109ad74..e39931ab5 100644 --- a/packages/editor/modules/component_form/components/controls/HtmlControl.vue +++ b/packages/editor/modules/component_form/components/controls/HtmlControl.vue @@ -68,14 +68,23 @@ export default { iframe: appPreveiwIframe, getComponentElement, preview, + freezeComponent, + unfreezeComponent, } = useModule("app_preview"); - return { store, appPreveiwIframe, getComponentElement, preview }; + return { + store, + appPreveiwIframe, + getComponentElement, + preview, + freezeComponent, + unfreezeComponent, + }; }, data() { return { settingUpEditor: false, - componentInner: null, - contentsEl: null, + editingComponent: null, + editingComponentEl: null, editor: null, }; }, @@ -88,9 +97,6 @@ export default { this.$emit("update:modelValue", value); }, }, - componentEl() { - return this.getComponentElement(this.component); - }, editing: { get() { return this.store.editingTextContent; @@ -119,23 +125,14 @@ export default { } this.stopEditing(); }, - componentEl: { + editingComponentEl: { handler(value, oldValue) { if (value) { value.addEventListener("dblclick", this.onComponentDblclick); - - this.componentInnerEl = value.querySelector( - ":scope > .metaScore-component--inner" - ); - - this.contentsEl = - this.componentInnerEl.querySelector(":scope > .contents"); } else { if (oldValue) { oldValue.removeEventListener("dblclick", this.onComponentDblclick); } - this.componentInnerEl = null; - this.contentsEl = null; } }, immediate: true, @@ -147,8 +144,8 @@ export default { }, }, async beforeUnmount() { - if (this.componentEl) { - this.componentEl.removeEventListener( + if (this.editingComponentEl) { + this.editingComponentEl.removeEventListener( "dblclick", this.onComponentDblclick ); @@ -157,6 +154,14 @@ export default { await this.stopEditing(); }, methods: { + getInnerElement() { + return this.editingComponentEl?.querySelector( + ":scope > .metaScore-component--inner" + ); + }, + getContentsElement() { + return this.getInnerElement()?.querySelector(":scope > .contents"); + }, onComponentDblclick() { if (!this.disabled) this.startEditing(); }, @@ -177,10 +182,14 @@ export default { this.editing = true; this.settingUpEditor = true; + this.editingComponent = this.component; + this.editingComponentEl = this.getComponentElement(this.editingComponent); + + this.freezeComponent(this.editingComponent); const { default: createEditor } = await import("../../ckeditor"); - createEditor(this.contentsEl, { + createEditor(this.getContentsElement(), { language: this.$i18n.locale, extraFonts: this.extraFonts, }) @@ -194,14 +203,11 @@ export default { }); // Prevent key events from propagating. - this.componentInnerEl.addEventListener( - "keydown", - this.onComponentInnerElKeyEvent - ); - this.componentInnerEl.addEventListener( - "keyup", - this.onComponentInnerElKeyEvent - ); + const inner_el = this.getInnerElement(); + if (inner_el) { + inner_el.addEventListener("keydown", this.onComponentInnerElKeyEvent); + inner_el.addEventListener("keyup", this.onComponentInnerElKeyEvent); + } }, onEditorCreate(editor) { editor.editing.view.change((writer) => { @@ -253,7 +259,10 @@ export default { evt.return = value + offset; }, onEditorSourceEditingModeChange(evt, name, isSourceEditingMode) { - this.componentEl.classList.toggle("sourceediting", isSourceEditingMode); + this.editingComponentEl.classList.toggle( + "sourceediting", + isSourceEditingMode + ); }, async stopEditing() { if (!this.editing) return; @@ -264,17 +273,17 @@ export default { this.editor = null; } - if (this.componentInnerEl) { - this.componentInnerEl.removeEventListener( + const inner_el = this.getInnerElement(); + if (inner_el) { + inner_el.removeEventListener( "keydown", this.onComponentInnerElKeyEvent ); - this.componentInnerEl.removeEventListener( - "keyup", - this.onComponentInnerElKeyEvent - ); + inner_el.removeEventListener("keyup", this.onComponentInnerElKeyEvent); } + this.unfreezeComponent(this.editingComponent); + this.editing = false; }, },