nitropage
Version:
A free and open source, extensible visual page builder based on SolidStart.
179 lines (151 loc) • 4.84 kB
text/typescript
import { State } from "#../types";
import { createId } from "@paralleldrive/cuid2";
import { NitroPage } from "@prisma/client";
import type { Prisma } from "@prisma/client/extension";
import { isEqual, pick } from "es-toolkit";
import { logger } from "../../log";
import { useDatabase } from "../../prisma";
import { DEBUG_PAGE_UPDATE } from "../../util";
import { normalizeUrlPath } from "../../util/string";
import { omitDisabledElementData } from "./elementData";
type CreateMany<T> = Exclude<Prisma.Args<T, "createMany">["data"], any[]>[];
const createPageRevision = async ({
state: state,
prevState,
page,
deletedPresets,
}: {
state: State;
prevState?: State;
page: NitroPage;
deletedPresets: string[];
}) => {
const db = useDatabase();
for (const id of Object.keys(state.elements)) {
const item = state.elements[id];
item.data = omitDisabledElementData(item.data);
}
const revisionKeys = [
"elements",
"slots",
"title",
"urlPath",
] as const satisfies (keyof State)[];
const localState = pick(state, revisionKeys);
if (isEqual(localState, pick(prevState ?? ({} as never), revisionKeys)))
return;
const layouts = (
await db.nitroPage.findMany({
select: { id: true },
where: {
type: "layout",
},
})
).map((p) => p.id);
const layoutSlots = (
await db.nitroLayoutSlot.findMany({ select: { id: true } })
).map((p) => p.id);
if (DEBUG_PAGE_UPDATE) logger.info("Create page revision");
const pageRevision = await db.nitroPageRevision.create({
data: {
title: localState.title,
urlPath: normalizeUrlPath(localState.urlPath),
savedAt: new Date(),
page: {
connect: {
id: page.id,
},
},
},
});
const insertSlots = async (
items: [id: string, parentElementId?: string][],
) => {
const slotDataItems: CreateMany<typeof db.nitroElementSlot> = [];
const idMap: Record<string, string> = {};
for (const [id, parentElementId] of items) {
const stateSlot = localState.slots[id];
if (!stateSlot.elements.length && !stateSlot.layoutSlotId) continue;
const parentElement = localState.elements[stateSlot.parentElementId];
const parentLayoutSlotId = parentElement.layoutId
? stateSlot.key
: undefined;
if (parentLayoutSlotId && !layoutSlots.includes(parentLayoutSlotId)) {
logger.warn("Skipped saving slot, parent layout slot didn't exist");
continue;
}
const nextId = createId();
idMap[id] = nextId;
slotDataItems.push({
id: nextId,
key: stateSlot.key,
layoutSlotId: stateSlot.layoutSlotId,
parentElementId: parentElementId,
parentPageRevisionId: !parentElementId ? pageRevision.id : undefined,
parentLayoutSlotId,
});
}
await db.nitroElementSlot.createMany({
data: slotDataItems,
});
for (const [id] of items) {
const nextId = idMap[id];
if (!nextId) continue;
const stateSlot = localState.slots[id];
await insertSlotElements(stateSlot.elements, nextId);
}
};
const insertSlotElements = async function (
elementIds: string[],
slotId?: string,
) {
if (!elementIds.length) return;
const elementDataItems: CreateMany<typeof db.nitroElement> = [];
const slots: [string, string][] = [];
let position = -1;
for (const prevId of elementIds) {
const element = localState.elements[prevId];
if (element.layoutId && !layouts.includes(element.layoutId)) {
logger.warn("Skipped saving layout element, layout didn't exist");
continue;
}
if (element.presetId && deletedPresets.includes(element.presetId)) {
element.presetId = undefined;
}
if (!element.historyId) {
const history = await db.nitroElementHistory.create({
data: {
pageId: page.id,
},
});
element.historyId = history.id;
} else {
element.id = createId();
}
elementDataItems.push({
id: element.id,
blueprintId: element.blueprintId,
layoutId: element.layoutId,
position: position++,
parentSlotId: slotId,
presetId: element.presetId,
historyId: element.historyId!,
pageId: pageRevision.id,
data: JSON.stringify(element.data),
});
const newSlots = Object.values(element.slots).map(
(s) => [s, element.id] as [string, string],
);
slots.push(...newSlots);
}
await db.nitroElement.createMany({
data: elementDataItems,
});
await insertSlots(slots);
};
await insertSlots(
Object.values(localState.elements.root.slots).map((s) => [s]),
);
return pageRevision;
};
export default createPageRevision;