nitropage
Version:
A free and open source, extensible visual page builder based on SolidStart.
139 lines (116 loc) • 3.98 kB
text/typescript
import { Config, State } from "#../types";
import { EditorUiContext } from "#lib/context/admin";
import { ConfigContext, StateContext } from "#lib/context/page";
import { spliceValue } from "#utils/array";
import { startTransition, useContext } from "solid-js";
import { MoveEvent, SortableEvent, SortableOptions } from "sortablejs";
import { blueprintAllowed } from "./blueprint";
import { addSlotToState, createSlot, slotHasSpace } from "./slot";
export const useSortableElementMove = function () {
const [state, setState] = useContext(StateContext)!;
const [_, setEditorUi] = useContext(EditorUiContext)!;
const [declaration] = useContext(ConfigContext)!;
return function (childProduce: any, evt: SortableEvent) {
const builderInfo = getBuilderInfoFromSortableEvent(evt, state);
const { fromElementId, targetElementId, element } = builderInfo;
if (!element || !fromElementId || !targetElementId) return false;
const fromSlot = evt.from.dataset.slot || "default";
const targetSlot = evt.to.dataset.slot || "default";
let fromSlotId = state.elements[fromElementId].slots[fromSlot];
let targetSlotId = state.elements[targetElementId].slots[targetSlot];
const sameList = fromSlotId === targetSlotId;
setEditorUi((d) => (d.movingElementId = false));
if (sameList) {
startTransition(() => {
setState((d) => {
childProduce(d.slots[targetSlotId].elements);
});
});
return true;
}
if (!canMoveToOtherSlot(state, declaration, builderInfo)) {
return false;
}
if (!targetSlotId) {
const newSlot = createSlot(targetSlot, targetElementId);
targetSlotId = newSlot.id;
setState((d) => addSlotToState(d, newSlot));
}
startTransition(() => {
setState((d) => {
// Remove element from old list
spliceValue(element.id, d.slots[fromSlotId].elements);
// Add element to new list
d.slots[targetSlotId].elements.splice(evt.newIndex!, 0, element.id);
d.elements[element.id].parentSlotId = targetSlotId;
});
});
};
};
export const getBuilderInfoFromSortableEvent = function (
evt: MoveEvent | SortableEvent,
state: State,
) {
const domElement = (evt as SortableEvent).item || (evt as MoveEvent).dragged;
const elementId = domElement.dataset.element;
const fromElementId = evt.from.dataset.elementId;
const targetElementId = evt.to.dataset.elementId;
const element = elementId ? state.elements[elementId] : undefined;
const fromElement = fromElementId ? state.elements[fromElementId] : undefined;
const targetElement = targetElementId
? state.elements[targetElementId]
: undefined;
const fromSlotKey = evt.from.dataset.slot || "default";
const targetSlotKey = evt.to.dataset.slot || "default";
const fromSlotId = fromElement?.slots[fromSlotKey];
const targetSlotId = targetElement?.slots[targetSlotKey];
return {
element,
elementId,
elementBlueprint: element?.blueprintId,
fromElementId,
fromElement,
fromBlueprint: fromElement?.blueprintId,
fromSlotKey,
fromSlotId,
targetElement,
targetElementId,
targetBlueprint: targetElement?.blueprintId,
targetSlotKey,
targetSlotId,
};
};
export const canMoveToOtherSlot = function (
state: State,
declaration: Config,
builderInfo: ReturnType<typeof getBuilderInfoFromSortableEvent>,
) {
if (
!slotHasSpace(
state,
declaration,
builderInfo.targetElement,
builderInfo.targetSlotKey,
)
) {
return false;
}
if (
!blueprintAllowed(
declaration,
builderInfo.element?.blueprintId,
builderInfo.targetElement?.blueprintId,
builderInfo.targetSlotKey,
)
) {
return false;
}
return true;
};
export const nestedSortableConfig: Partial<SortableOptions> = {
forceFallback: true,
fallbackOnBody: true,
swapThreshold: 1,
emptyInsertThreshold: 1,
invertSwap: true,
};