171 lines
5.2 KiB
TypeScript
171 lines
5.2 KiB
TypeScript
import { first } from 'rxjs/operators';
|
|
|
|
import { Placement } from 'app/plugins/panel/canvas/panelcfg.gen';
|
|
import { LayerActionID } from 'app/plugins/panel/canvas/types';
|
|
|
|
import { ElementState } from './element';
|
|
import { FrameState } from './frame';
|
|
import { RootElement } from './root';
|
|
import { Scene } from './scene';
|
|
|
|
// TODO: Consider whether or not reorderElements + updateElements should be moved to TreeNavigationEditor
|
|
// Reorder elements in the DOM when rearranging them in the tree navigation editor
|
|
export const reorderElements = (src: ElementState, dest: ElementState, dragToGap: boolean, destPosition: number) => {
|
|
switch (dragToGap) {
|
|
case true:
|
|
switch (destPosition) {
|
|
case -1:
|
|
// top of the tree
|
|
if (src.parent instanceof FrameState) {
|
|
// move outside the frame
|
|
if (dest.parent) {
|
|
updateElements(src, dest.parent, dest.parent.elements.length);
|
|
src.updateData(dest.parent.scene.context);
|
|
}
|
|
} else {
|
|
dest.parent?.reorderTree(src, dest, true);
|
|
}
|
|
break;
|
|
default:
|
|
if (dest.parent) {
|
|
updateElements(src, dest.parent, dest.parent.elements.indexOf(dest));
|
|
src.updateData(dest.parent.scene.context);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case false:
|
|
if (dest instanceof FrameState) {
|
|
if (src.parent === dest) {
|
|
// same frame parent
|
|
src.parent?.reorderTree(src, dest, true);
|
|
} else {
|
|
updateElements(src, dest);
|
|
src.updateData(dest.scene.context);
|
|
}
|
|
} else if (src.parent === dest.parent) {
|
|
src.parent?.reorderTree(src, dest);
|
|
} else {
|
|
if (dest.parent) {
|
|
updateElements(src, dest.parent);
|
|
src.updateData(dest.parent.scene.context);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
// Reorders canvas elements
|
|
const updateElements = (src: ElementState, dest: FrameState | RootElement, idx: number | null = null) => {
|
|
src.parent?.doAction(LayerActionID.Delete, src);
|
|
src.parent = dest;
|
|
|
|
const elementContainer = src.div?.getBoundingClientRect();
|
|
src.setPlacementFromConstraint(elementContainer, dest.div?.getBoundingClientRect());
|
|
|
|
const destIndex = idx ?? dest.elements.length - 1;
|
|
dest.elements.splice(destIndex, 0, src);
|
|
dest.scene.save();
|
|
|
|
dest.reinitializeMoveable();
|
|
};
|
|
|
|
// Finds the element state if it exists for a given DOM element
|
|
export const findElementByTarget = (target: Element, sceneElements: ElementState[]): ElementState | undefined => {
|
|
// We will probably want to add memoization to this as we are calling on drag / resize
|
|
|
|
const stack = [...sceneElements];
|
|
while (stack.length > 0) {
|
|
const currentElement = stack.shift();
|
|
|
|
if (currentElement && currentElement.div && currentElement.div === target) {
|
|
return currentElement;
|
|
}
|
|
|
|
const nestedElements = currentElement instanceof FrameState ? currentElement.elements : [];
|
|
for (const nestedElement of nestedElements) {
|
|
stack.unshift(nestedElement);
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
};
|
|
|
|
// Nest selected elements into a frame object
|
|
export const frameSelection = (scene: Scene) => {
|
|
scene.selection.pipe(first()).subscribe((currentSelectedElements) => {
|
|
const currentLayer = currentSelectedElements[0].parent!;
|
|
|
|
const newLayer = new FrameState(
|
|
{
|
|
type: 'frame',
|
|
name: scene.getNextElementName(true),
|
|
elements: [],
|
|
},
|
|
scene,
|
|
currentSelectedElements[0].parent
|
|
);
|
|
|
|
const framePlacement = generateFrameContainer(currentSelectedElements);
|
|
|
|
newLayer.options.placement = framePlacement;
|
|
|
|
currentSelectedElements.forEach((element: ElementState) => {
|
|
const elementContainer = element.div?.getBoundingClientRect();
|
|
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
element.setPlacementFromConstraint(elementContainer, framePlacement as DOMRect);
|
|
currentLayer.doAction(LayerActionID.Delete, element);
|
|
newLayer.doAction(LayerActionID.Duplicate, element, false, false);
|
|
});
|
|
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
newLayer.setPlacementFromConstraint(framePlacement as DOMRect, currentLayer.div?.getBoundingClientRect());
|
|
|
|
currentLayer.elements.push(newLayer);
|
|
|
|
scene.byName.set(newLayer.getName(), newLayer);
|
|
|
|
scene.save();
|
|
});
|
|
};
|
|
|
|
// Helper function to generate the a frame object based on the selected elements' dimensions
|
|
const generateFrameContainer = (elements: ElementState[]): Placement => {
|
|
let minTop = Infinity;
|
|
let minLeft = Infinity;
|
|
let maxRight = 0;
|
|
let maxBottom = 0;
|
|
|
|
elements.forEach((element: ElementState) => {
|
|
const elementContainer = element.div?.getBoundingClientRect();
|
|
|
|
if (!elementContainer) {
|
|
return;
|
|
}
|
|
|
|
if (minTop > elementContainer.top) {
|
|
minTop = elementContainer.top;
|
|
}
|
|
|
|
if (minLeft > elementContainer.left) {
|
|
minLeft = elementContainer.left;
|
|
}
|
|
|
|
if (maxRight < elementContainer.right) {
|
|
maxRight = elementContainer.right;
|
|
}
|
|
|
|
if (maxBottom < elementContainer.bottom) {
|
|
maxBottom = elementContainer.bottom;
|
|
}
|
|
});
|
|
|
|
return {
|
|
top: minTop,
|
|
left: minLeft,
|
|
width: maxRight - minLeft,
|
|
height: maxBottom - minTop,
|
|
};
|
|
};
|