From 6bfc80b418cb4f6ab908e743f80ca6331c5e6317 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Thu, 16 Nov 2023 21:09:28 +0400 Subject: [PATCH 01/11] Support `nomodule` attribute for `canvas.scripts` options --- src/canvas/view/FrameView.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/canvas/view/FrameView.ts b/src/canvas/view/FrameView.ts index 6c93319331..615d54e356 100644 --- a/src/canvas/view/FrameView.ts +++ b/src/canvas/view/FrameView.ts @@ -306,8 +306,13 @@ export default class FrameView extends ModuleView { type: 'text/javascript', ...(isString(src) ? { src } : src), }); - scriptEl.onerror = scriptEl.onload = appendScript.bind(null, scripts); el.contentDocument?.head.appendChild(scriptEl); + + if (scriptEl.hasAttribute('nomodule') && 'noModule' in HTMLScriptElement.prototype) { + appendScript(scripts); + } else { + scriptEl.onerror = scriptEl.onload = appendScript.bind(null, scripts); + } } else { this.renderBody(); em && em.trigger(evLoad, evOpts); From 1185b1da4762485a452258544c73ff505b82bf78 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Thu, 16 Nov 2023 21:19:59 +0400 Subject: [PATCH 02/11] Add preinitialize to ComponentView --- src/dom_components/view/ComponentView.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dom_components/view/ComponentView.ts b/src/dom_components/view/ComponentView.ts index e33ca31eae..5103d20570 100644 --- a/src/dom_components/view/ComponentView.ts +++ b/src/dom_components/view/ComponentView.ts @@ -48,6 +48,10 @@ Component> { getTemplate?: Function; scriptContainer?: HTMLElement; + preinitialize(opt: any = {}) { + this.opts = opt; + } + initialize(opt: any = {}) { const model = this.model; const config = opt.config || {}; From f111aa6c1f65d01fbf61a06b53fdda6c183c5eeb Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Thu, 16 Nov 2023 21:56:59 +0400 Subject: [PATCH 03/11] Add createDoc to ComponentView --- src/dom_components/config/config.ts | 8 ++++++++ src/dom_components/view/ComponentView.ts | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/dom_components/config/config.ts b/src/dom_components/config/config.ts index 56da8668ea..e184c03a29 100644 --- a/src/dom_components/config/config.ts +++ b/src/dom_components/config/config.ts @@ -53,6 +53,13 @@ export interface DomComponentsConfig { * https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-elements */ voidElements?: string[]; + + /** + * Experimental: Use the frame document for DOM element creation. + * This option might be useful when elements require the local document context to + * work properly (eg. Web Components). + */ + useFrameDoc?: boolean; } export default { @@ -61,6 +68,7 @@ export default { draggableComponents: true, disableTextInnerChilds: false, processor: undefined, + useFrameDoc: false, voidElements: [ 'area', 'base', diff --git a/src/dom_components/view/ComponentView.ts b/src/dom_components/view/ComponentView.ts index 5103d20570..f2b2cecb18 100644 --- a/src/dom_components/view/ComponentView.ts +++ b/src/dom_components/view/ComponentView.ts @@ -97,6 +97,11 @@ Component> { return this.opts.config.frameView; } + get createDoc() { + const doc = this.frameView?.getDoc() || document; + return this.opts.config?.useFrameDoc ? doc : document; + } + __isDraggable() { const { model, config } = this; const { draggable } = model.attributes; @@ -516,6 +521,10 @@ Component> { this.$el.data({ model, collection, view }); } + _createElement(tagName: string): Node { + return this.createDoc.createElement(tagName); + } + /** * Render children components * @private From 3a8366253cb007b5ce1466358abec74960af0031 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Fri, 17 Nov 2023 01:11:58 +0400 Subject: [PATCH 04/11] Add new `canvas:frame:*` events --- src/canvas/types.ts | 30 ++++++++++++++++++++++++++++++ src/canvas/view/FrameView.ts | 15 ++++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/canvas/types.ts b/src/canvas/types.ts index d1798e1481..7f9d316234 100644 --- a/src/canvas/types.ts +++ b/src/canvas/types.ts @@ -100,6 +100,36 @@ export enum CanvasEvents { * }); */ pointer = 'canvas:pointer', + + /** + * @event `canvas:frame:load` Frame loaded in canvas. + * The event is triggered right after iframe's `onload`. + * @example + * editor.on('canvas:frame:load', ({ window }) => { + * console.log('Frame loaded', window); + * }); + */ + frameLoad = 'canvas:frame:load', + + /** + * @event `canvas:frame:load:head` Frame head loaded in canvas. + * The event is triggered right after iframe's finished to load the head elemenets (eg. scripts) + * @example + * editor.on('canvas:frame:load:head', ({ window }) => { + * console.log('Frame head loaded', window); + * }); + */ + frameLoadHead = 'canvas:frame:load:head', + + /** + * @event `canvas:frame:load:body` Frame body loaded in canvas. + * The event is triggered when the body is rendered with components. + * @example + * editor.on('canvas:frame:load:body', ({ window }) => { + * console.log('Frame completed the body render', window); + * }); + */ + frameLoadBody = 'canvas:frame:load:body', } /**{END_EVENTS}*/ diff --git a/src/canvas/view/FrameView.ts b/src/canvas/view/FrameView.ts index 615d54e356..56a15f7245 100644 --- a/src/canvas/view/FrameView.ts +++ b/src/canvas/view/FrameView.ts @@ -1,6 +1,6 @@ import { bindAll, debounce, isString, isUndefined } from 'underscore'; import { ModuleView } from '../../abstract'; -import { BoxRect } from '../../common'; +import { BoxRect, ObjectAny } from '../../common'; import CssRulesView from '../../css_composer/view/CssRulesView'; import ComponentWrapperView from '../../dom_components/view/ComponentWrapperView'; import Droppable from '../../utils/Droppable'; @@ -18,6 +18,7 @@ import { hasDnd, setViewEl } from '../../utils/mixins'; import Canvas from '../model/Canvas'; import Frame from '../model/Frame'; import FrameWrapView from './FrameWrapView'; +import CanvasEvents from '../types'; export default class FrameView extends ModuleView { /** @ts-ignore */ @@ -290,14 +291,14 @@ export default class FrameView extends ModuleView { const { $el, ppfx, em } = this; $el.attr({ class: `${ppfx}frame` }); this.renderScripts(); - em.trigger('frame:render', this); + em.trigger('frame:render', this); // deprecated return this; } renderScripts() { const { el, model, em } = this; const evLoad = 'frame:load'; - const evOpts = { el, model, view: this }; + const evOpts: ObjectAny = { el, model, view: this }; const canvas = this.getCanvasModel(); const appendScript = (scripts: any[]) => { if (scripts.length > 0) { @@ -314,8 +315,10 @@ export default class FrameView extends ModuleView { scriptEl.onerror = scriptEl.onload = appendScript.bind(null, scripts); } } else { + em?.trigger(CanvasEvents.frameLoadHead, evOpts); this.renderBody(); - em && em.trigger(evLoad, evOpts); + em?.trigger(CanvasEvents.frameLoadBody, evOpts); + em?.trigger(evLoad, evOpts); // deprecated } }; @@ -327,7 +330,9 @@ export default class FrameView extends ModuleView { doc.write(frameContent); doc.close(); } - em && em.trigger(`${evLoad}:before`, evOpts); + evOpts.window = this.getWindow(); + em?.trigger(`${evLoad}:before`, evOpts); // deprecated + em?.trigger(CanvasEvents.frameLoad, evOpts); appendScript([...canvas.get('scripts')]); }; } From 0e1de049609f93a6aa751a56a32d83198c48fad8 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sat, 18 Nov 2023 12:26:05 +0400 Subject: [PATCH 05/11] Add `delegate` property to Component --- src/commands/view/ComponentDelete.ts | 3 +- src/commands/view/DeleteComponent.ts | 74 --------------------------- src/common/index.ts | 2 + src/dom_components/model/Component.ts | 5 ++ src/dom_components/model/types.ts | 10 ++++ 5 files changed, 19 insertions(+), 75 deletions(-) delete mode 100644 src/commands/view/DeleteComponent.ts diff --git a/src/commands/view/ComponentDelete.ts b/src/commands/view/ComponentDelete.ts index 46233f73b0..a8d9cff6c5 100644 --- a/src/commands/view/ComponentDelete.ts +++ b/src/commands/view/ComponentDelete.ts @@ -15,7 +15,8 @@ const command: CommandObject<{ component?: Component }> = { component, }); } - component.remove(); + const cmp = component.delegate?.remove?.(component) || component; + cmp.remove(); }); ed.select(toSelect); diff --git a/src/commands/view/DeleteComponent.ts b/src/commands/view/DeleteComponent.ts deleted file mode 100644 index 3497fa92b3..0000000000 --- a/src/commands/view/DeleteComponent.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { bindAll, extend } from 'underscore'; -import { $ } from '../../common'; -import Component from '../../dom_components/model/Component'; -import { CommandObject } from './CommandAbstract'; -import SelectComponent from './SelectComponent'; - -export default extend({}, SelectComponent, { - init() { - bindAll(this, 'startDelete', 'stopDelete', 'onDelete'); - this.hoverClass = this.pfx + 'hover-delete'; - this.badgeClass = this.pfx + 'badge-red'; - }, - - enable() { - var that = this; - this.$el.find('*').mouseover(this.startDelete).mouseout(this.stopDelete).click(this.onDelete); - }, - - /** - * Start command - * @param {Object} e - * @private - */ - startDelete(e: any) { - e.stopPropagation(); - var $this = $(e.target); - - // Show badge if possible - if ($this.data('model').get('removable')) { - $this.addClass(this.hoverClass); - this.attachBadge($this.get(0)); - } - }, - - /** - * Stop command - * @param {Object} e - * @private - */ - stopDelete(e: any) { - e.stopPropagation(); - var $this = $(e.target); - $this.removeClass(this.hoverClass); - - // Hide badge if possible - if (this.badge) this.badge.css({ left: -1000, top: -1000 }); - }, - - /** - * Delete command - * @param {Object} e - * @private - */ - onDelete(e: any) { - e.stopPropagation(); - var $this = $(e.target); - - // Do nothing in case can't remove - if (!$this.data('model').get('removable')) return; - - $this.data('model').destroy(); - this.removeBadge(); - this.clean(); - }, - - /** - * Updates badge label - * @param {Object} model - * @private - * */ - updateBadgeLabel(model: Component) { - this.badge.html('Remove ' + model.getName()); - }, -} as CommandObject<{}, { [k: string]: any }>); diff --git a/src/common/index.ts b/src/common/index.ts index a8780b8224..d44186854b 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -19,6 +19,8 @@ export type ObjectAny = Record; export type ObjectStrings = Record; +export type Nullable = undefined | null | false; + // https://github.com/microsoft/TypeScript/issues/29729#issuecomment-1483854699 export type LiteralUnion = T | (U & NOOP); diff --git a/src/dom_components/model/Component.ts b/src/dom_components/model/Component.ts index c2a43c9152..dd4c7854f1 100644 --- a/src/dom_components/model/Component.ts +++ b/src/dom_components/model/Component.ts @@ -154,6 +154,7 @@ export default class Component extends StyleableModel { propagate: '', dmode: '', toolbar: null, + delegate: null, [keySymbol]: 0, [keySymbols]: 0, [keySymbolOvrd]: 0, @@ -182,6 +183,10 @@ export default class Component extends StyleableModel { return this.get('resizable')!; } + get delegate() { + return this.get('delegate'); + } + /** * Hook method, called once the model is created */ diff --git a/src/dom_components/model/types.ts b/src/dom_components/model/types.ts index cb986ae534..195c7dd20c 100644 --- a/src/dom_components/model/types.ts +++ b/src/dom_components/model/types.ts @@ -1,4 +1,5 @@ import Frame from '../../canvas/model/Frame'; +import { Nullable } from '../../common'; import EditorModel from '../../editor/model/Editor'; import Selectors from '../../selector_manager/model/Selectors'; import { TraitProperties } from '../../trait_manager/model/Trait'; @@ -13,6 +14,10 @@ export type DragMode = 'translate' | 'absolute' | ''; export type DraggableDroppableFn = (source: Component, target: Component, index?: number) => boolean | void; +export interface ComponentDelegateProps { + remove?: (cmp: Component) => Component | Nullable; +} + export interface ComponentProperties { /** * Component type, eg. `text`, `image`, `video`, etc. @@ -167,6 +172,11 @@ export interface ComponentProperties { * By default, when `toolbar` property is falsy the editor will add automatically commands `core:component-exit` (select parent component, added if there is one), `tlb-move` (added if `draggable`) , `tlb-clone` (added if `copyable`), `tlb-delete` (added if `removable`). */ toolbar?: ToolbarButtonProps[]; + + /** + * Delegate actions to other components. + */ + delegate?: ComponentDelegateProps; ///** // * Children components. Default: `null` // */ From 4bdc19611017b8bda018ee6db849a33b955331f7 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sat, 18 Nov 2023 12:54:46 +0400 Subject: [PATCH 06/11] Add move to delegate --- src/commands/index.ts | 43 ++++++++++++++----------------- src/dom_components/model/types.ts | 2 ++ 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/commands/index.ts b/src/commands/index.ts index 7db7adfdcc..1f723c3de1 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -46,7 +46,7 @@ import { isFunction, includes } from 'underscore'; import CommandAbstract, { Command, CommandOptions, CommandObject, CommandFunction } from './view/CommandAbstract'; import defaults, { CommandsConfig } from './config/config'; import { Module } from '../abstract'; -import { eventDrag } from '../dom_components/model/Component'; +import Component, { eventDrag } from '../dom_components/model/Component'; import Editor from '../editor/model/Editor'; import { ObjectAny } from '../common'; @@ -118,39 +118,36 @@ export default class CommandsModule extends Module trg.delegate?.move?.(trg) || trg).filter(Boolean); + const target = targets[0] as Component | undefined; + const nativeDrag = event?.type === 'dragstart'; const defComOptions = { preserveSelected: 1 }; const modes = ['absolute', 'translate']; - if (!sel || !sel.get('draggable')) { + if (!target?.get('draggable')) { return em.logWarning('The element is not draggable'); } - const mode = sel.get('dmode') || em.get('dmode'); + const mode = target.get('dmode') || em.get('dmode'); const hideTlb = () => em.stopDefault(defComOptions); const altMode = includes(modes, mode); - selAll.forEach(sel => sel.trigger('disable')); + targets.forEach(trg => trg.trigger('disable')); // Without setTimeout the ghost image disappears nativeDrag ? setTimeout(hideTlb, 0) : hideTlb(); - const onStart = (data: any) => { - em.trigger(`${eventDrag}:start`, data); - }; - const onDrag = (data: any) => { - em.trigger(eventDrag, data); - }; + const onStart = (data: any) => em.trigger(`${eventDrag}:start`, data); + const onDrag = (data: any) => em.trigger(eventDrag, data); const onEnd = (e: any, opts: any, data: any) => { - selAll.forEach(sel => sel.set('status', 'selected')); - ed.select(selAll); - sel.emitUpdate(); + targets.forEach(trg => trg.set('status', 'selected')); + ed.select(targets); + target.emitUpdate(); em.trigger(`${eventDrag}:end`, data); // Defer selectComponent in order to prevent canvas "freeze" #2692 @@ -165,7 +162,7 @@ export default class CommandsModule extends Module sel.set('status', 'freezed-selected')); + targets.forEach(sel => sel.set('status', 'freezed-selected')); }, }; diff --git a/src/dom_components/model/types.ts b/src/dom_components/model/types.ts index 195c7dd20c..89bb4a7fb4 100644 --- a/src/dom_components/model/types.ts +++ b/src/dom_components/model/types.ts @@ -16,6 +16,7 @@ export type DraggableDroppableFn = (source: Component, target: Component, index? export interface ComponentDelegateProps { remove?: (cmp: Component) => Component | Nullable; + move?: (cmp: Component) => Component | Nullable; } export interface ComponentProperties { @@ -177,6 +178,7 @@ export interface ComponentProperties { * Delegate actions to other components. */ delegate?: ComponentDelegateProps; + ///** // * Children components. Default: `null` // */ From 8e3c7458053b25aef8728971e0f08a6977f81cd6 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sat, 18 Nov 2023 14:05:39 +0400 Subject: [PATCH 07/11] Delegate copy --- src/commands/view/CopyComponent.ts | 2 +- src/commands/view/PasteComponent.ts | 9 ++++----- src/dom_components/model/Component.ts | 1 + src/dom_components/model/types.ts | 27 ++++++++++++++++++++++++++- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/commands/view/CopyComponent.ts b/src/commands/view/CopyComponent.ts index cfbf961f59..4cba918390 100644 --- a/src/commands/view/CopyComponent.ts +++ b/src/commands/view/CopyComponent.ts @@ -3,7 +3,7 @@ import { CommandObject } from './CommandAbstract'; export default { run(ed) { const em = ed.getModel(); - const models = [...ed.getSelectedAll()]; + const models = [...ed.getSelectedAll()].map(md => md.delegate?.copy?.(md) || md).filter(Boolean); models.length && em.set('clipboard', models); }, } as CommandObject; diff --git a/src/commands/view/PasteComponent.ts b/src/commands/view/PasteComponent.ts index faa0250fd8..db5188da14 100644 --- a/src/commands/view/PasteComponent.ts +++ b/src/commands/view/PasteComponent.ts @@ -5,11 +5,12 @@ import { CommandObject } from './CommandAbstract'; export default { run(ed, s, opts = {}) { const em = ed.getModel(); - const clp: Component[] = em.get('clipboard'); + const clp: Component[] | null = em.get('clipboard'); const lastSelected = ed.getSelected(); - if (clp && lastSelected) { - ed.getSelectedAll().forEach(selected => { + if (clp?.length && lastSelected) { + ed.getSelectedAll().forEach(sel => { + const selected = sel.delegate?.copy?.(sel) || sel; const { collection } = selected; if (!collection) return; @@ -18,13 +19,11 @@ export default { const addOpts = { at, action: opts.action || 'paste-component' }; if (contains(clp, selected) && selected.get('copyable')) { - // @ts-ignore added = collection.add(selected.clone(), addOpts); } else { const copyable = clp.filter(cop => cop.get('copyable')); const pasteable = copyable.filter(cop => ed.Components.canMove(selected.parent()!, cop).result); added = collection.add( - // @ts-ignore pasteable.map(cop => cop.clone()), addOpts ); diff --git a/src/dom_components/model/Component.ts b/src/dom_components/model/Component.ts index dd4c7854f1..d3fa914637 100644 --- a/src/dom_components/model/Component.ts +++ b/src/dom_components/model/Component.ts @@ -110,6 +110,7 @@ export const keyUpdateInside = `${keyUpdate}-inside`; * Eg. `toolbar: [ { attributes: {class: 'fa fa-arrows'}, command: 'tlb-move' }, ... ]`. * By default, when `toolbar` property is falsy the editor will add automatically commands `core:component-exit` (select parent component, added if there is one), `tlb-move` (added if `draggable`) , `tlb-clone` (added if `copyable`), `tlb-delete` (added if `removable`). * @property {Collection} [components=null] Children components. Default: `null` + * @property {Object} [delegate=null] Delegate commands to other components. Available commands `remove` | `move` | `copy`. eg. `{ remove: (cmp) => cmp.closestType('other-type') }` * * @module docsjs.Component */ diff --git a/src/dom_components/model/types.ts b/src/dom_components/model/types.ts index 89bb4a7fb4..5461bf44c1 100644 --- a/src/dom_components/model/types.ts +++ b/src/dom_components/model/types.ts @@ -14,9 +14,34 @@ export type DragMode = 'translate' | 'absolute' | ''; export type DraggableDroppableFn = (source: Component, target: Component, index?: number) => boolean | void; +/** + * Delegate commands to other components. + */ export interface ComponentDelegateProps { + /** + * Delegate remove command to another component. + * @example + * delegate: { + * remove: (cmp) => cmp.closestType('other-type'), + * } + */ remove?: (cmp: Component) => Component | Nullable; + /** + * Delegate move command to another component. + * @example + * delegate: { + * move: (cmp) => cmp.closestType('other-type'), + * } + */ move?: (cmp: Component) => Component | Nullable; + /** + * Delegate copy command to another component. + * @example + * delegate: { + * copy: (cmp) => cmp.closestType('other-type'), + * } + */ + copy?: (cmp: Component) => Component | Nullable; } export interface ComponentProperties { @@ -175,7 +200,7 @@ export interface ComponentProperties { toolbar?: ToolbarButtonProps[]; /** - * Delegate actions to other components. + * Delegate commands to other components. */ delegate?: ComponentDelegateProps; From 014aab6de87db9cf928e7053599cad130e43adf4 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sat, 18 Nov 2023 14:24:19 +0400 Subject: [PATCH 08/11] Add select delegate --- src/dom_components/model/Component.ts | 2 +- src/dom_components/model/types.ts | 8 ++++++++ src/editor/model/Editor.ts | 4 ++-- src/utils/mixins.ts | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/dom_components/model/Component.ts b/src/dom_components/model/Component.ts index d3fa914637..37e3656e13 100644 --- a/src/dom_components/model/Component.ts +++ b/src/dom_components/model/Component.ts @@ -584,7 +584,7 @@ export default class Component extends StyleableModel { * component.removeAttributes('some-attr'); * component.removeAttributes(['some-attr1', 'some-attr2']); */ - removeAttributes(attrs: string[] = [], opts: SetOptions = {}) { + removeAttributes(attrs: string | string[] = [], opts: SetOptions = {}) { const attrArr = Array.isArray(attrs) ? attrs : [attrs]; const compAttr = this.getAttributes(); attrArr.map(i => delete compAttr[i]); diff --git a/src/dom_components/model/types.ts b/src/dom_components/model/types.ts index 5461bf44c1..26a9822c94 100644 --- a/src/dom_components/model/types.ts +++ b/src/dom_components/model/types.ts @@ -42,6 +42,14 @@ export interface ComponentDelegateProps { * } */ copy?: (cmp: Component) => Component | Nullable; + /** + * Delegate select command to another component. + * @example + * delegate: { + * select: (cmp) => cmp.findType('other-type')[0], + * } + */ + select?: (cmp: Component) => Component | Nullable; } export interface ComponentProperties { diff --git a/src/editor/model/Editor.ts b/src/editor/model/Editor.ts index fc56eae1dc..1955bc5db1 100644 --- a/src/editor/model/Editor.ts +++ b/src/editor/model/Editor.ts @@ -480,7 +480,7 @@ export default class EditorModel extends Model { const { event } = opts; const ctrlKey = event && (event.ctrlKey || event.metaKey); const { shiftKey } = event || {}; - const els = (isArray(el) ? el : [el]).map(el => getModel(el, $)); + const els = (isArray(el) ? el : [el]).map(el => getModel(el, $)).map(cmp => cmp?.delegate?.select?.(cmp) || cmp); const selected = this.getSelectedAll(); const mltSel = this.getConfig().multipleSelection; let added; @@ -501,7 +501,7 @@ export default class EditorModel extends Model { if (opts.useValid) { let parent = model.parent(); while (parent && !parent.get('selectable')) parent = parent.parent(); - model = parent; + model = parent!; } else { return; } diff --git a/src/utils/mixins.ts b/src/utils/mixins.ts index 5adf2e1fff..1f1e648201 100644 --- a/src/utils/mixins.ts +++ b/src/utils/mixins.ts @@ -187,7 +187,7 @@ export const deepMerge = (...args: ObjectAny[]) => { * @param {HTMLElement|Component} el Component or HTML element * @return {Component} */ -const getModel = (el: any, $?: any) => { +const getModel = (el: any, $?: any): Component => { let model = el; if (!$ && el && el.__cashData) { model = el.__cashData.model; From 9a90baa3efd0dcbfa369321f7a9ec0c2dedd18ae Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sat, 18 Nov 2023 15:53:48 +0400 Subject: [PATCH 09/11] Refactor items move --- src/commands/index.ts | 41 ++++++++++++++++++++------------- src/navigator/view/ItemView.ts | 14 +++++++---- src/navigator/view/ItemsView.ts | 7 +----- src/utils/Sorter.ts | 4 ---- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/commands/index.ts b/src/commands/index.ts index 1f723c3de1..58fbbda5c4 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -79,6 +79,27 @@ const commandsDef = [ ['component-drag', 'ComponentDrag'], ]; +const defComOptions = { preserveSelected: 1 }; + +export const getOnComponentDragStart = (em: Editor) => (data: any) => em.trigger(`${eventDrag}:start`, data); + +export const getOnComponentDrag = (em: Editor) => (data: any) => em.trigger(eventDrag, data); + +export const getOnComponentDragEnd = + (em: Editor, targets: Component[], opts: { altMode?: boolean } = {}) => + (a: any, b: any, data: any) => { + targets.forEach(trg => trg.set('status', trg.get('selectable') ? 'selected' : '')); + em.setSelected(targets); + targets[0].emitUpdate(); + em.trigger(`${eventDrag}:end`, data); + + // Defer selectComponent in order to prevent canvas "freeze" #2692 + setTimeout(() => em.runDefault(defComOptions)); + + // Dirty patch to prevent parent selection on drop + (opts.altMode || data.cancelled) && em.set('_cmpDrag', 1); + }; + export default class CommandsModule extends Module { CommandAbstract = CommandAbstract; defaultCommands: Record = {}; @@ -127,7 +148,6 @@ export default class CommandsModule extends Module trg.delegate?.move?.(trg) || trg).filter(Boolean); const target = targets[0] as Component | undefined; const nativeDrag = event?.type === 'dragstart'; - const defComOptions = { preserveSelected: 1 }; const modes = ['absolute', 'translate']; if (!target?.get('draggable')) { @@ -142,20 +162,9 @@ export default class CommandsModule extends Module em.trigger(`${eventDrag}:start`, data); - const onDrag = (data: any) => em.trigger(eventDrag, data); - const onEnd = (e: any, opts: any, data: any) => { - targets.forEach(trg => trg.set('status', 'selected')); - ed.select(targets); - target.emitUpdate(); - em.trigger(`${eventDrag}:end`, data); - - // Defer selectComponent in order to prevent canvas "freeze" #2692 - setTimeout(() => em.runDefault(defComOptions)); - - // Dirty patch to prevent parent selection on drop - (altMode || data.cancelled) && em.set('_cmpDrag', 1); - }; + const onStart = getOnComponentDragStart(em); + const onDrag = getOnComponentDrag(em); + const onEnd = getOnComponentDragEnd(em, targets, { altMode }); if (altMode) { // TODO move grabbing func in editor/canvas from the Sorter @@ -182,7 +191,7 @@ export default class CommandsModule extends Module sel.set('status', 'freezed-selected')); + targets.filter(sel => sel.get('selectable')).forEach(sel => sel.set('status', 'freezed-selected')); }, }; diff --git a/src/navigator/view/ItemView.ts b/src/navigator/view/ItemView.ts index 9e6240e17c..625b58718b 100644 --- a/src/navigator/view/ItemView.ts +++ b/src/navigator/view/ItemView.ts @@ -1,12 +1,13 @@ import { bindAll, isString } from 'underscore'; import { View, ViewOptions } from '../../common'; -import Component, { eventDrag } from '../../dom_components/model/Component'; +import Component from '../../dom_components/model/Component'; import ComponentView from '../../dom_components/view/ComponentView'; import EditorModel from '../../editor/model/Editor'; import { isEnterKey, isEscKey } from '../../utils/dom'; import { getModel } from '../../utils/mixins'; import LayerManager from '../index'; import ItemsView from './ItemsView'; +import { getOnComponentDrag, getOnComponentDragEnd, getOnComponentDragStart } from '../../commands'; export type ItemViewProps = ViewOptions & { ItemView: ItemView; @@ -317,14 +318,17 @@ export default class ItemView extends View { * */ startSort(ev: MouseEvent) { ev.stopPropagation(); - const { em, sorter } = this; + const { em, sorter, model } = this; // Right or middel click if (ev.button && ev.button !== 0) return; if (sorter) { - sorter.onStart = (data: any) => em.trigger(`${eventDrag}:start`, data); - sorter.onMoveClb = (data: any) => em.trigger(eventDrag, data); - sorter.startSort(ev.target); + const toMove = model.delegate?.move?.(model) || model; + sorter.onStart = getOnComponentDragStart(em); + sorter.onMoveClb = getOnComponentDrag(em); + sorter.onEndMove = getOnComponentDragEnd(em, [toMove]); + const itemEl = (toMove as any).viewLayer?.el || ev.target; + sorter.startSort(itemEl); } } diff --git a/src/navigator/view/ItemsView.ts b/src/navigator/view/ItemsView.ts index fdca2cf4a5..a2ca13a413 100644 --- a/src/navigator/view/ItemsView.ts +++ b/src/navigator/view/ItemsView.ts @@ -1,5 +1,5 @@ import { View } from '../../common'; -import Component, { eventDrag } from '../../dom_components/model/Component'; +import Component from '../../dom_components/model/Component'; import EditorModel from '../../editor/model/Editor'; import ItemView from './ItemView'; @@ -33,11 +33,6 @@ export default class ItemsView extends View { containerSel: `.${this.className}`, itemSel: `.${pfx}layer`, ignoreViewChildren: 1, - onEndMove(created: any, sorter: any, data: any) { - const srcModel = sorter.getSourceModel(); - em.setSelected(srcModel, { forceChange: 1 }); - em.trigger(`${eventDrag}:end`, data); - }, avoidSelectOnEnd: 1, nested: 1, ppfx, diff --git a/src/utils/Sorter.ts b/src/utils/Sorter.ts index 9b7833c878..8939f0994f 100644 --- a/src/utils/Sorter.ts +++ b/src/utils/Sorter.ts @@ -1160,10 +1160,6 @@ export default class Sorter extends View { if (src) { srcModel = this.getSourceModel(); - if (this.selectOnEnd && srcModel && srcModel.set) { - srcModel.set('status', ''); - srcModel.set('status', 'selected'); - } } if (this.moved && target) { From 6334acc915253180040818ba7a08f48e122883d2 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sat, 18 Nov 2023 15:56:38 +0400 Subject: [PATCH 10/11] Remove delegate from JSON --- src/dom_components/model/Component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dom_components/model/Component.ts b/src/dom_components/model/Component.ts index 37e3656e13..d5a482c158 100644 --- a/src/dom_components/model/Component.ts +++ b/src/dom_components/model/Component.ts @@ -110,7 +110,7 @@ export const keyUpdateInside = `${keyUpdate}-inside`; * Eg. `toolbar: [ { attributes: {class: 'fa fa-arrows'}, command: 'tlb-move' }, ... ]`. * By default, when `toolbar` property is falsy the editor will add automatically commands `core:component-exit` (select parent component, added if there is one), `tlb-move` (added if `draggable`) , `tlb-clone` (added if `copyable`), `tlb-delete` (added if `removable`). * @property {Collection} [components=null] Children components. Default: `null` - * @property {Object} [delegate=null] Delegate commands to other components. Available commands `remove` | `move` | `copy`. eg. `{ remove: (cmp) => cmp.closestType('other-type') }` + * @property {Object} [delegate=null] Delegate commands to other components. Available commands `remove` | `move` | `copy` | `select`. eg. `{ remove: (cmp) => cmp.closestType('other-type') }` * * @module docsjs.Component */ @@ -1663,6 +1663,7 @@ export default class Component extends StyleableModel { delete obj.status; delete obj.open; // used in Layers delete obj._undoexc; + delete obj.delegate; if (!opts.fromUndo) { const symbol = obj[keySymbol]; From 149a9b2ad4e40eeec86f1b9ed87777a895c65f24 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Tue, 21 Nov 2023 20:45:20 +0400 Subject: [PATCH 11/11] Up jsdoc doc --- src/selector_manager/model/Selector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/selector_manager/model/Selector.ts b/src/selector_manager/model/Selector.ts index 4ce46c8786..a74c0e4e65 100644 --- a/src/selector_manager/model/Selector.ts +++ b/src/selector_manager/model/Selector.ts @@ -106,7 +106,7 @@ export default class Selector extends Model `my-selector` */ getName() {