Browse Source

Merge branch 'codex/pr-6692-with-test' into dev

pull/6735/head
Artur Arseniev 6 months ago
parent
commit
b7da3f58e8
  1. 101
      packages/core/src/utils/sorter/BaseComponentNode.ts
  2. 7
      packages/core/src/utils/sorter/CanvasNewComponentNode.ts
  3. 72
      packages/core/test/specs/utils/sorter/sorterLayers.ts

101
packages/core/src/utils/sorter/BaseComponentNode.ts

@ -16,40 +16,7 @@ export abstract class BaseComponentNode extends SortableTreeNode<Component> {
* BaseComponentNode, or null if there are no children.
*/
getChildren(): BaseComponentNode[] | null {
return this.getDisplayedChildren();
}
/**
* Get the list of displayed children, i.e., components that have a valid HTML element.
* Cached values are used to avoid recalculating the display status unnecessarily.
* @returns {BaseComponentNode[] | null} - The list of displayed children wrapped in
* BaseComponentNode, or null if there are no displayed children.
*/
private getDisplayedChildren(): BaseComponentNode[] | null {
const children = this.model.components();
const displayedChildren = children.filter((child) => this.isChildDisplayed(child));
return displayedChildren.map((comp: Component) => new (this.constructor as any)(comp));
}
/**
* Check if a child is displayed, using cached value if available.
* @param {Component} child - The child component to check.
* @returns {boolean} - Whether the child is displayed.
*/
private isChildDisplayed(child: Component): boolean {
// Check if display status is cached
if (this.displayCache.has(child)) {
return this.displayCache.get(child)!;
}
const element = child.getEl();
const displayed = isDisplayed(element);
// Cache the display status
this.displayCache.set(child, displayed);
return displayed;
return this.model.components().map((comp: Component) => new (this.constructor as any)(comp));
}
/**
@ -65,13 +32,13 @@ export abstract class BaseComponentNode extends SortableTreeNode<Component> {
/**
* Add a child component to this node at the specified index.
* @param {BaseComponentNode} node - The child node to add.
* @param {number} displayIndex - The visual index at which to insert the child.
* @param {number} index - The index at which to insert the child.
* @param {{ action: string }} options - Options for the operation, with the default action being 'add-component'.
* @returns {BaseComponentNode} - The newly added child node wrapped in BaseComponentNode.
*/
addChildAt(
node: BaseComponentNode,
displayIndex: number,
index: number,
options: { action: string } = { action: 'add-component' },
): BaseComponentNode {
const insertingTextableIntoText = this.model?.isInstanceOf?.('text') && node?.model?.get?.('textable');
@ -82,7 +49,7 @@ export abstract class BaseComponentNode extends SortableTreeNode<Component> {
}
const newModel = this.model.components().add(node.model, {
at: this.getRealIndex(displayIndex),
at: index,
action: options.action,
});
@ -91,11 +58,11 @@ export abstract class BaseComponentNode extends SortableTreeNode<Component> {
/**
* Remove a child component at the specified index.
* @param {number} displayIndex - The visual index of the child to remove.
* @param {number} index - The visual index of the child to remove.
* @param {{ temporary: boolean }} options - Whether to temporarily remove the child.
*/
removeChildAt(displayIndex: number, options: { temporary: boolean } = { temporary: false }): void {
const child = this.model.components().at(this.getRealIndex(displayIndex));
removeChildAt(index: number, options: { temporary: boolean } = { temporary: false }): void {
const child = this.model.components().at(index);
if (child) {
this.model.components().remove(child, options as any);
}
@ -107,49 +74,27 @@ export abstract class BaseComponentNode extends SortableTreeNode<Component> {
* @returns {number} - The index of the child node, or -1 if not found.
*/
indexOfChild(node: BaseComponentNode): number {
return this.getDisplayIndex(node);
return this.getIndex(node);
}
/**
* Get the index of the given node within the displayed children.
* Get the index of the given node.
* @param {BaseComponentNode} node - The node to find.
* @returns {number} - The display index of the node, or -1 if not found.
* @returns {number} - The index of the node, or -1 if not found.
*/
private getDisplayIndex(node: BaseComponentNode): number {
const displayedChildren = this.getDisplayedChildren();
return displayedChildren ? displayedChildren.findIndex((displayedNode) => displayedNode.model === node.model) : -1;
}
/**
* Convert a display index to the actual index within the component's children array.
* @param {number} index - The display index to convert.
* @returns {number} - The corresponding real index, or -1 if not found.
*/
getRealIndex(index: number): number {
if (index === -1) return -1;
let displayedCount = 0;
const children = this.model.components();
for (let i = 0; i < children.length; i++) {
const child = children.at(i);
const displayed = this.isChildDisplayed(child);
if (displayed) displayedCount++;
if (displayedCount === index + 1) return i;
}
return -1;
private getIndex(node: BaseComponentNode): number {
const Children = this.getChildren();
return Children ? Children.findIndex((Node) => Node.model === node.model) : -1;
}
/**
* Check if a source node can be moved to a specified index within this component.
* @param {BaseComponentNode} source - The source node to move.
* @param {number} index - The display index to move the source to.
* @param {number} index - The index to move the source to.
* @returns {boolean} - True if the move is allowed, false otherwise.
*/
canMove(source: BaseComponentNode, index: number): boolean {
return this.model.em.Components.canMove(this.model, source.model, this.getRealIndex(index)).result;
return this.model.em.Components.canMove(this.model, source.model, index).result;
}
equals(node?: BaseComponentNode): node is BaseComponentNode {
@ -229,19 +174,3 @@ export abstract class BaseComponentNode extends SortableTreeNode<Component> {
return this.model.get?.('textable');
}
}
/**
* Function to check if an element is displayed in the DOM.
* @param {HTMLElement | undefined} element - The element to check.
* @returns {boolean} - Whether the element is displayed.
*/
function isDisplayed(element: HTMLElement | undefined): boolean {
if (!element) return false;
return (
typeof element === 'object' &&
element.nodeType === Node.ELEMENT_NODE &&
window.getComputedStyle(element).display !== 'none' &&
element.offsetWidth > 0 &&
element.offsetHeight > 0
);
}

7
packages/core/src/utils/sorter/CanvasNewComponentNode.ts

@ -8,7 +8,6 @@ type CanMoveSource = Component | ContentType;
export default class CanvasNewComponentNode extends CanvasComponentNode {
canMove(source: CanvasNewComponentNode, index: number): boolean {
const realIndex = this.getRealIndex(index);
const { model: symbolModel, content, dragDef } = source._dragSource;
const canMoveSymbol = !symbolModel || !this.isSourceSameSymbol(symbolModel);
@ -16,11 +15,11 @@ export default class CanvasNewComponentNode extends CanvasComponentNode {
if (Array.isArray(sourceContent)) {
return (
canMoveSymbol && sourceContent.every((contentItem, i) => this.canMoveSingleContent(contentItem, realIndex + i))
canMoveSymbol && sourceContent.every((contentItem, i) => this.canMoveSingleContent(contentItem, index + i))
);
}
return canMoveSymbol && this.canMoveSingleContent(sourceContent, realIndex);
return canMoveSymbol && this.canMoveSingleContent(sourceContent, index);
}
private canMoveSingleContent(contentItem: ContentElement | Component, index: number): boolean {
@ -57,7 +56,7 @@ export default class CanvasNewComponentNode extends CanvasComponentNode {
// @ts-ignore
model = this.model?.getView?.()?.insertComponent?.(content, { action: 'add-component' });
} else {
model = this.model.components().add(content, { at: this.getRealIndex(index), action: 'add-component' });
model = this.model.components().add(content, { at: index, action: 'add-component' });
}
return new (this.constructor as any)(model);
}

72
packages/core/test/specs/utils/sorter/sorterLayers.ts

@ -0,0 +1,72 @@
import Component from '../../../../src/dom_components/model/Component';
import Editor from '../../../../src/editor';
import LayersComponentNode from '../../../../src/utils/sorter/LayersComponentNode';
import { setupTestEditor } from '../../../common';
describe('Layers sorter', () => {
let editor: Editor;
let fixtures: HTMLElement;
const setCanvasVisibility = (cmp: Component, visible: boolean) => {
const el = cmp.getEl();
Object.defineProperty(el, 'offsetWidth', {
configurable: true,
get: () => (visible ? 120 : 0),
});
Object.defineProperty(el, 'offsetHeight', {
configurable: true,
get: () => (visible ? 24 : 0),
});
};
const getChildIds = (cmp: Component) => cmp.components().map((child) => child.getAttributes().id);
beforeEach(() => {
({ editor, fixtures } = setupTestEditor({ withCanvas: true }));
});
afterEach(() => {
jest.restoreAllMocks();
editor.destroy();
});
test('moves the hidden source after the target without removing other hidden siblings', () => {
editor.setComponents(`
<div id="hidden-a" style="display: none;"></div>
<div id="visible-a">Visible A</div>
<div id="visible-b">Visible B</div>
<div id="hidden-b" style="display: none;"></div>
`);
const wrapper = editor.getWrapper()!;
const source = wrapper.find('#hidden-a')[0];
const target = wrapper.find('#visible-a')[0];
const visibleSibling = wrapper.find('#visible-b')[0];
const trailingHidden = wrapper.find('#hidden-b')[0];
editor.select(source);
editor.Layers.setRoot(wrapper);
fixtures.appendChild(editor.Layers.render());
const sorter = source.viewLayer!.sorter;
const sourceLayer = source.viewLayer!.el;
setCanvasVisibility(source, false);
setCanvasVisibility(target, true);
setCanvasVisibility(visibleSibling, true);
setCanvasVisibility(trailingHidden, false);
sorter.startSort([{ element: sourceLayer }]);
sorter.dropLocationDeterminer.lastMoveData = {
targetNode: new LayersComponentNode(wrapper),
index: 2,
};
sorter.endDrag();
expect(getChildIds(wrapper)).toEqual(['visible-a', 'hidden-a', 'visible-b', 'hidden-b']);
});
});
Loading…
Cancel
Save