diff --git a/docs/api/datasource.md b/docs/api/datasource.md index d330d9b44f..9206234076 100644 --- a/docs/api/datasource.md +++ b/docs/api/datasource.md @@ -46,14 +46,14 @@ If the `records` property is not an instance of `DataRecords`, it will be conver ### Parameters -* `props` **DataSourceProps** Properties to initialize the data source. +* `props` **DataSourceProps\** Properties to initialize the data source. * `opts` **DataSourceOptions** Options to initialize the data source. ## records Retrieves the collection of records associated with this data source. -Returns **DataRecords** The collection of data records. +Returns **DataRecords\** The collection of data records. ## em @@ -67,7 +67,7 @@ Adds a new record to the data source. ### Parameters -* `record` **DataRecordProps** The properties of the record to add. +* `record` **DRProps** The properties of the record to add. * `opts` **AddOptions?** Options to apply when adding the record. Returns **DataRecord** The added data record. @@ -80,14 +80,14 @@ Retrieves a record from the data source by its ID. * `id` **([string][6] | [number][7])** The ID of the record to retrieve. -Returns **(DataRecord | [undefined][8])** The data record, or `undefined` if no record is found with the given ID. +Returns **(DataRecord\ | [undefined][8])** The data record, or `undefined` if no record is found with the given ID. ## getRecords Retrieves all records from the data source. Each record is processed with the `getRecord` method to apply any read transformers. -Returns **[Array][9]<(DataRecord | [undefined][8])>** An array of data records. +Returns **[Array][9]<(DataRecord\ | [undefined][8])>** An array of data records. ## removeRecord @@ -98,7 +98,7 @@ Removes a record from the data source by its ID. * `id` **([string][6] | [number][7])** The ID of the record to remove. * `opts` **RemoveOptions?** Options to apply when removing the record. -Returns **(DataRecord | [undefined][8])** The removed data record, or `undefined` if no record is found with the given ID. +Returns **(DataRecord\ | [undefined][8])** The removed data record, or `undefined` if no record is found with the given ID. ## setRecords @@ -106,7 +106,7 @@ Replaces the existing records in the data source with a new set of records. ### Parameters -* `records` **[Array][9]\** An array of data record properties to set. +* `records` **[Array][9]\** An array of data record properties to set. Returns **[Array][9]\** An array of the added data records. diff --git a/packages/core/src/block_manager/view/BlockView.ts b/packages/core/src/block_manager/view/BlockView.ts index 9f215671f6..0fb3f6afad 100644 --- a/packages/core/src/block_manager/view/BlockView.ts +++ b/packages/core/src/block_manager/view/BlockView.ts @@ -5,12 +5,14 @@ import { on, off } from '../../utils/dom'; import { hasDnd } from '../../utils/mixins'; import { BlockManagerConfig } from '../config/config'; import Block from '../model/Block'; +import ComponentSorter from '../../utils/sorter/ComponentSorter'; +import CanvasNewComponentNode from '../../utils/sorter/CanvasNewComponentNode'; export interface BlockViewConfig { em?: EditorModel; pStylePrefix?: string; appendOnClick?: BlockManagerConfig['appendOnClick']; - getSorter?: any; + getSorter?: () => ComponentSorter; } export default class BlockView extends View { @@ -52,24 +54,30 @@ export default class BlockView extends View { } else if (isFunction(onClick)) { return onClick(model, em?.getEditor(), { event: ev }); } - const sorter = config.getSorter(); + const sorter = config.getSorter?.(); + if (!sorter) return; const content = model.get('content')!; + let dropModel = this.getTempDropModel(content); + const el = dropModel.view?.el; + const sources = el ? [{ element: el, dragSource: { content } }] : []; const selected = em.getSelected(); - sorter.setDropContent(content); - let target, valid, insertAt; + let target, + valid, + insertAt, + index = 0; // If there is a selected component, try first to append // the block inside, otherwise, try to place it as a next sibling if (selected) { - valid = sorter.validTarget(selected.getEl(), content); + valid = sorter.validTarget(selected.getEl(), sources, index); - if (valid.valid) { + if (valid) { target = selected; } else { const parent = selected.parent(); if (parent) { - valid = sorter.validTarget(parent.getEl(), content); - if (valid.valid) { + valid = sorter.validTarget(parent.getEl(), sources, index); + if (valid) { target = parent; insertAt = parent.components().indexOf(selected) + 1; } @@ -80,8 +88,8 @@ export default class BlockView extends View { // If no target found yet, try to append the block to the wrapper if (!target) { const wrapper = em.getWrapper()!; - valid = sorter.validTarget(wrapper.getEl(), content); - if (valid.valid) target = wrapper; + valid = sorter.validTarget(wrapper.getEl(), sources, index); + if (valid) target = wrapper; } const result = target && target.append(content, { at: insertAt })[0]; @@ -100,9 +108,11 @@ export default class BlockView extends View { em.refreshCanvas(); const sorter = config.getSorter(); sorter.__currentBlock = model; - sorter.setDragHelper(this.el, e); - sorter.setDropContent(this.model.get('content')); - sorter.startSort([this.el]); + const content = this.model.get('content'); + let dropModel = this.getTempDropModel(content); + const el = dropModel.view?.el; + const sources = el ? [{ element: el, dragSource: { content } }] : []; + sorter.startSort(sources); on(document, 'mouseup', this.endDrag); } @@ -124,9 +134,29 @@ export default class BlockView extends View { */ endDrag() { off(document, 'mouseup', this.endDrag); - const sorter = this.config.getSorter(); + const sorter = this.config.getSorter?.(); + if (sorter) { + sorter.endDrag(); + } + } - sorter.cancelDrag(); + /** + * Generates a temporary model of the content being dragged for use with the sorter. + * @returns The temporary model representing the dragged content. + */ + private getTempDropModel(content?: any) { + const comps = this.em.Components.getComponents(); + const opts = { + avoidChildren: 1, + avoidStore: 1, + avoidUpdateStyle: 1, + }; + const tempModel = comps.add(content, { ...opts, temporary: true }); + let dropModel = comps.remove(tempModel, { ...opts, temporary: true } as any); + // @ts-ignore + dropModel = dropModel instanceof Array ? dropModel[0] : dropModel; + dropModel.view?.$el.data('model', dropModel); + return dropModel; } render() { diff --git a/packages/core/src/block_manager/view/BlocksView.ts b/packages/core/src/block_manager/view/BlocksView.ts index 87ec697f54..b298a17c20 100644 --- a/packages/core/src/block_manager/view/BlocksView.ts +++ b/packages/core/src/block_manager/view/BlocksView.ts @@ -7,6 +7,8 @@ import Block from '../model/Block'; import Categories from '../../abstract/ModuleCategories'; import BlockView from './BlockView'; import CategoryView from '../../abstract/ModuleCategoryView'; +import CanvasNewComponentNode from '../../utils/sorter/CanvasNewComponentNode'; +import { DragDirection } from '../../utils/sorter/types'; export interface BlocksViewConfig { em: EditorModel; @@ -71,23 +73,30 @@ export default class BlocksView extends View { if (!this.sorter) { const utils = em.Utils; const canvas = em.Canvas; - - this.sorter = new utils.Sorter({ - // @ts-ignore - container: canvas.getBody(), - placer: canvas.getPlacerEl(), - containerSel: '*', - itemSel: '*', - pfx: this.ppfx, - onStart: this.onDrag, - onEndMove: this.onDrop, - onMove: this.onMove, - document: canvas.getFrameEl().contentDocument, - direction: 'a', - wmargin: 1, - nested: 1, + this.sorter = new utils.ComponentSorter({ em, - canvasRelative: 1, + treeClass: CanvasNewComponentNode, + containerContext: { + container: canvas.getBody(), + containerSel: '*', + itemSel: '*', + pfx: this.ppfx, + placeholderElement: canvas.getPlacerEl()!, + document: canvas.getBody().ownerDocument, + }, + dragBehavior: { + dragDirection: DragDirection.BothDirections, + nested: true, + }, + positionOptions: { + windowMargin: 1, + canvasRelative: true, + }, + eventHandlers: { + legacyOnStartSort: this.onDrag, + legacyOnEndMove: this.onDrop, + legacyOnMoveClb: this.onMove, + }, }); } diff --git a/packages/core/src/utils/sorter/ComponentSorter.ts b/packages/core/src/utils/sorter/ComponentSorter.ts index 833ee245e9..c5f05185f8 100644 --- a/packages/core/src/utils/sorter/ComponentSorter.ts +++ b/packages/core/src/utils/sorter/ComponentSorter.ts @@ -12,6 +12,7 @@ import { SorterEventHandlers, DragSource, } from './types'; +import Block from '../../block_manager/model/Block'; const targetSpotType = CanvasSpotBuiltInTypes.Target; @@ -22,6 +23,8 @@ const spotTarget = { export default class ComponentSorter extends Sorter { targetIsText: boolean = false; + // For event triggering + __currentBlock?: Block; constructor({ em, treeClass, diff --git a/packages/core/src/utils/sorter/Sorter.ts b/packages/core/src/utils/sorter/Sorter.ts index 747c7d02f6..c1505db177 100644 --- a/packages/core/src/utils/sorter/Sorter.ts +++ b/packages/core/src/utils/sorter/Sorter.ts @@ -71,18 +71,7 @@ export default class Sorter> { * @param {HTMLElement[]} sources[] * */ startSort(sources: { element?: HTMLElement; dragSource?: DragSource }[]) { - const validSources = sources.filter((source) => !!source.dragSource || this.findValidSourceElement(source.element)); - - const sourcesWithModel: { model: T; content?: any }[] = validSources.map((source) => { - return { - model: $(source.element)?.data('model'), - content: source.dragSource, - }; - }); - const sortedSources = sourcesWithModel.sort((a, b) => { - return sortDom(a.model, b.model); - }); - const sourceNodes = sortedSources.map((source) => new this.treeClass(source.model, source.content)); + const { sourceNodes, sourcesWithModel } = this.getSourceNodes(sources); this.sourceNodes = sourceNodes; this.dropLocationDeterminer.startSort(sourceNodes); this.bindDragEventHandlers(); @@ -104,6 +93,37 @@ export default class Sorter> { this.em.trigger('sorter:drag:start', sources[0], sourcesWithModel[0]); } + validTarget( + targetEl: HTMLElement | undefined, + sources: { element?: HTMLElement; dragSource?: DragSource }[], + index: number, + ): boolean { + if (!targetEl) return false; + const targetModel = $(targetEl).data('model'); + if (!targetModel) return false; + + const targetNode = new this.treeClass(targetModel); + const { sourceNodes } = this.getSourceNodes(sources); + const canMove = sourceNodes.some((node) => targetNode.canMove(node, index)); + return canMove; + } + + private getSourceNodes(sources: { element?: HTMLElement; dragSource?: DragSource }[]) { + const validSources = sources.filter((source) => !!source.dragSource || this.findValidSourceElement(source.element)); + + const sourcesWithModel: { model: T; content?: any }[] = validSources.map((source) => { + return { + model: $(source.element)?.data('model'), + content: source.dragSource, + }; + }); + const sortedSources = sourcesWithModel.sort((a, b) => { + return sortDom(a.model, b.model); + }); + const sourceNodes = sortedSources.map((source) => new this.treeClass(source.model, source.content)); + return { sourceNodes, sourcesWithModel }; + } + /** * This method is should be called when the user scrolls within the container. */