@npio/internals
Version:
A free visual website editor, powered with your own SolidJS components.
262 lines (226 loc) • 6.09 kB
text/typescript
import { NitroFont, NitroFontFace, NitroPreset } from "@prisma/client";
import { isServer } from "solid-js/web";
import type { Page, PageRevision } from "../server";
import { Element, FontFaceFile, State } from "../types";
import type { Settings } from "./server/settings";
const track = function (value: any) {
if (isServer || value == null) return value;
const type = typeof value;
if (type !== "object") return value;
if (Array.isArray(value)) {
value.forEach(track);
return value;
}
Object.values(value).forEach(track);
return value;
};
type OmitDates<T> = Omit<T, "createdAt" | "updatedAt">;
type NormalizableFonts = (OmitDates<NitroFont> & {
faces: OmitDates<NitroFontFace>[];
})[];
export const normalizeFonts = function ({
settings,
fonts,
}: {
settings?: Settings;
fonts: NormalizableFonts;
}) {
let defaultId: string | undefined;
const normalizedFonts = fonts.map(function (font) {
const normalizedFaces = font.faces.map(function (face) {
const normalizedFiles = (JSON.parse(face.files) as FontFaceFile[]).sort(
function (fileA, fileB) {
const sortA = fontTypesSorting[fileA.format] || 1;
const sortB = fontTypesSorting[fileB.format] || 1;
return sortB - sortA;
},
);
return {
id: face.id,
preload: face.preload,
weight: face.weight!,
style: face.style,
files: normalizedFiles,
};
});
if (settings?.defaultFont.value === font.id) {
defaultId = font.publicId;
}
return {
...font,
fallbacks: font.fallback.split(","),
driver: font.driver || undefined,
faces: normalizedFaces,
};
});
return {
defaultFont: defaultId,
fonts: normalizedFonts,
} satisfies Partial<State>;
};
export const normalizePreset = (preset: NitroPreset) => {
return {
id: preset.id,
name: preset.name,
blueprint: preset.blueprint,
data: JSON.parse(preset.data),
};
};
export const normalizePresets = function ({
presets,
}: {
presets: NitroPreset[];
}) {
return presets.reduce(
(acc, preset) => {
acc[preset.id] = normalizePreset(preset);
return acc;
},
{} as State["presets"],
);
};
export const normalizeProjectSettings = function ({
settings,
}: {
settings: Settings;
}) {
return {
backgroundColor: track(settings.pageBackgroundColor.value),
metaImage: settings.metaImage.value?.mediaIds[0],
favicon: settings.favicon.value?.mediaIds[0],
};
};
export const normalizeSettings = function ({
settings,
pageRevision,
}: {
pageRevision: PageRevision;
settings: Settings;
}) {
return {
notFound: settings.notFoundPage.value === pageRevision.pageId,
metaDescription: settings.metaDescription.value,
colors: track(settings.colors.value) || {},
richtext: {
textColors: track(settings.rteTextColors.value) || {},
},
blueprintDefaults: {
project: track(settings.blueprintDefaults.value) || {},
// TODO: Remove page blueprint defaults
//page: JSON.parse(pageRevision.blueprintDefaults),
},
...normalizeProjectSettings({ settings }),
} satisfies Partial<State>;
};
export const normalizePage = function ({
pageRevision,
page,
passiveBlueprints = {},
}: {
pageRevision: PageRevision;
page?: Page;
passiveBlueprints?: Record<string, boolean>;
}) {
const blueprintIds: Record<string, boolean> = {};
const dynamicElements: string[] = [];
const elements = Object.values(pageRevision.elements).reduce(
(acc, el) => {
let dynamic = false;
if (el.blueprintId) {
blueprintIds[el.blueprintId] = false;
if (!passiveBlueprints[el.blueprintId]) {
dynamicElements.push(el.id);
dynamic = true;
}
}
// TODO: Implement non-dynamic layouts
if (el.layoutId) {
dynamicElements.push(el.id);
dynamic = true;
}
const { createdAt, updatedAt, ...rest } = el;
acc[el.id] = {
...rest,
dynamic,
};
return acc;
},
{} as Record<string, Element>,
);
const setIds: Record<string, boolean> = {};
const setDynamic = function (id: string) {
if (setIds[id]) {
return;
}
setIds[id] = true;
const element = elements[id];
elements[id].dynamic = true;
if (element.blueprintId) {
blueprintIds[element.blueprintId] = true;
}
if (element.parentSlotId == null) return;
const parentElement =
pageRevision.slots[element.parentSlotId].parentElementId;
if (parentElement == null) return;
setDynamic(parentElement);
};
for (const id of dynamicElements) {
setDynamic(id);
}
return {
title: pageRevision.title,
urlPath: pageRevision.urlPath,
includeInFeed: page?.includeInFeed,
slots: pageRevision.slots,
layoutSlots: page?.layoutSlots ?? {},
elements,
blueprintIds,
};
};
export const createIdMap = <T extends { id: string }, M = undefined>(
list: T[],
map?: (v: T) => M,
) => {
const result: Record<string, M extends undefined ? T : M> = {};
for (const item of list) {
result[item.id] = (map ? map(item) : item) as any;
}
return result;
};
export const normalizeState = function ({
pageRevision,
page,
fonts,
presets,
settings,
passiveBlueprints = {},
}: {
pageRevision: PageRevision;
page?: Page;
settings: Settings;
presets: NitroPreset[];
fonts: NormalizableFonts;
passiveBlueprints?: Record<string, boolean>;
}): State {
const settingsData = normalizeSettings({
pageRevision,
settings,
});
const pageData = normalizePage({ page, pageRevision, passiveBlueprints });
const presetData = normalizePresets({ presets });
const fontData = normalizeFonts({ fonts, settings });
const result = {
cacheKey: 0,
presets: presetData,
...pageData,
...fontData,
...settingsData,
} satisfies State;
return result;
};
export const fontTypesSorting: Record<string, number> = {
woff2: 4,
woff: 3,
opentype: 2,
truetype: 1,
};