nitropage
Version:
A free and open source, extensible visual page builder based on SolidStart.
436 lines (389 loc) • 10.3 kB
text/typescript
import type {
Accessor,
Component,
ComponentProps,
Context,
FlowComponent,
JSX,
Owner,
ParentComponent,
ValidComponent,
} from "solid-js";
import type { SetStoreFunction } from "solid-js/store";
import type { NitroMedia } from "@prisma/client";
export type BlueprintDataInputProps<V = any, C = any> = {
element?: Element;
setValue: (produce: (state: V) => V) => void;
setOverride: (produce: (override: boolean) => boolean) => void;
value?: V;
data: BlueprintData<V, C>;
override: boolean;
key: string;
context: "element" | "preset" | "project";
};
export type BlueprintDataInput<V = any, C = any> = Component<
BlueprintDataInputProps<V, C>
>;
export type BlueprintDataWrapper = FlowComponent;
export type ToolbarOption = {
bold?: boolean;
italic?: boolean;
underline?: boolean;
color?: boolean;
background?: boolean;
script?: boolean;
align?: boolean;
indent?: boolean;
listOrdered?: boolean;
listBullet?: boolean;
link?: boolean;
clean?: boolean;
};
export type BlueprintData<V = any, C = any> = {
id?: string;
title?: () => string;
rte?: {
// disabled this option for now is I need to figure out a good api first
// formats?: string[];
toolbar?: ToolbarOption;
};
defaultValue: () => V;
custom?: C;
getter?: (value: V | undefined, state: State) => unknown;
wrapper?: BlueprintDataWrapper;
loadWrappers?: { id: string; wrapper: BlueprintDataWrapper }[];
context?: Context<any>;
required?: boolean;
input: BlueprintDataInput<V>;
type?: String;
};
export type FontFaceFile = {
format: string;
mime: string;
name: string;
};
export type FontFace = {
id: string;
files: FontFaceFile[];
weight?: number;
style?: string;
preload?: boolean;
};
export type Font = {
id?: string;
publicId: string;
family: string;
external?: boolean;
externalItalic?: boolean;
externalWeights?: string[];
fallbacks: string[];
preload?: boolean;
cdn?: boolean | null;
faces: FontFace[];
driver?: string;
};
export type InputFromBlueprintData<T extends BlueprintData> = T["input"];
export type BlueprintDataRecord = Record<string, BlueprintData>;
type CustomProps<Props> = Omit<
Props,
"children" | "editor" | "dragging" | "ref" | "Slot" | "dragSlot"
>;
type ComponentData<
T extends CustomBlueprintComponent = CustomBlueprintComponent,
Props = CustomProps<ComponentProps<T>["data"]>,
> = {
[P in keyof Props]: Props[P];
};
export type BlueprintDataPropDefault<T extends BlueprintDataRecord> = {
[P in keyof T]: ReturnType<T[P]["defaultValue"]>;
};
export type BlueprintDataPropGetter<T extends BlueprintData> =
T["getter"] extends Function
? ReturnType<T["getter"]>
: ReturnType<T["defaultValue"]>;
export type BlueprintDataRecordPropGetter<T extends BlueprintDataRecord> = {
[P in keyof T]: BlueprintDataPropGetter<T[P]>;
};
export type BlueprintDataPropsDefault<
T extends BlueprintFn,
R extends BaseBlueprint = Awaited<ReturnType<T>>,
> = BlueprintDataPropDefault<
R["data"] extends BlueprintDataRecord ? R["data"] : BlueprintDataRecord
>;
export type BlueprintDataPropsGetter<
T extends BlueprintFn,
R extends BaseBlueprint = Awaited<ReturnType<T>>,
> = BlueprintDataRecordPropGetter<
R["data"] extends BlueprintDataRecord ? R["data"] : BlueprintDataRecord
>;
export type BlueprintSlot = {
/**
* @deprecated
*/
max?: number;
blueprint?: string;
/**
* @deprecated
*/
acceptBlueprint?: (blueprint: string) => boolean | void | undefined;
};
export type BlueprintSlots = Record<string, BlueprintSlot>;
export type BaseBlueprint<
SD extends BlueprintDataRecord = BlueprintDataRecord,
> = {
component?: CustomBlueprintComponent;
title?: () => string;
category?: string;
/**
* @deprecated
*/
acceptParentType?: "root" | "element";
/**
* @deprecated
*/
acceptParent?: (blueprint: string) => boolean | void | undefined;
slots?: BlueprintSlots;
data: SD;
previewData?: () => Partial<BlueprintDataPropDefault<SD>>;
};
export interface Blueprint extends BaseBlueprint {
filename?: string;
slots: BlueprintSlots;
title: () => string;
category: string;
id: string;
version?: number;
}
export type BlueprintFn = {
(opts: { admin: boolean }): BaseBlueprint | Promise<BaseBlueprint>;
_id?: string;
};
export type ElementData = { value: any; override?: boolean };
export type ElementDataRecord = Record<string, ElementData>;
export type Element = {
id: string;
createdAt?: Date;
updatedAt?: Date;
historyId?: string | null;
blueprintId?: string | null;
layoutId?: string | null;
dynamic?: boolean;
data: ElementDataRecord;
presetId?: string | null;
parentSlotId?: string | null;
slots: Record<string, string>;
};
export type ElementSlot = {
id: string;
key: string;
layoutSlotId?: string | null;
parentElementId: string;
elements: string[];
};
export type LayoutSlot = {
id: string;
title: string;
/** Slots consuming elements from this slot */
consumerSlots: string[];
/** Slots inserting elements into this slot */
elementSlots: string[];
};
export type Preset = {
id: string;
new?: boolean;
blueprint: string;
name: string;
data: ElementDataRecord;
};
export type SlotComponent<
ElementProps = JSX.HTMLAttributes<HTMLElement>,
S = string,
> = Component<
{
optional?: boolean;
component?: ValidComponent;
key: S;
direction?: "vertical" | "horizontal";
expand?: boolean;
} & ElementProps
>;
export type RteComponent<
B extends BaseBlueprint = BaseBlueprint,
D extends BlueprintDataKeys<B> = BlueprintDataKeys<B>,
> = Component<{
component?: ValidComponent;
class?: string;
key?:
| D
| ((data: BlueprintDataRecordPropGetter<B["data"]>) => string | undefined);
}>;
type DragSlotConfig = {
slot?: string;
direction?: "vertical" | "horizontal";
};
export type DragSlotFunction = (
el: HTMLElement,
config: () => DragSlotConfig,
) => any;
export type BlueprintComponentProps<
B extends BaseBlueprint,
S = GetBlueprintSlots<B>,
> = {
admin?: boolean;
preview?: boolean;
handles?: JSX.Element;
blueprint: B;
writing?: boolean;
element: {
id: string;
};
// TODO: Maybe rename to rootProps
elementProps: {
"data-element": string | boolean;
id?: string;
ref?: (el: HTMLElement) => void;
};
slotElements: (name: S) => string[];
Slot: SlotComponent<JSX.HTMLAttributes<HTMLElement>, S>;
Rte: RteComponent<B>;
lax?: boolean;
};
export type GetBlueprintSlots<T extends BaseBlueprint> =
T["slots"] extends Record<any, any> ? keyof T["slots"] : string;
export type BlueprintDataKeys<T extends BaseBlueprint> =
T["data"] extends Record<any, any> ? keyof T["data"] : string;
export type CustomBlueprintComponent<
T = {},
B extends BaseBlueprint = BaseBlueprint,
> = Component<{ data: T } & BlueprintComponentProps<B>>;
export type SidebarElementsTab = "element" | "preset" | "presets" | "project";
export type EditorUiState = {
sidebar: {
tab: "elements" | "page" | "project";
elementsTab: SidebarElementsTab;
expandElementsTree: boolean;
createElement: {
type: "blueprints" | "layouts";
category: string;
};
};
viewport: {
tool:
| "idle"
| "writing"
| "selecting"
| "creating"
| "locked"
| "moving"
| "deleting";
darkMode?: boolean;
zoom: number;
};
pageId: string;
selectedRevisionId: string;
saving: boolean;
movingElementId: false | string;
createMoveTarget: {
elementId?: string;
slot?: string;
};
hoveredElementId: string | undefined;
creatingElementId: string | undefined;
deletingElementId: string | undefined;
selectedElementId: string | undefined;
selectedBlueprint: string | undefined;
modification: {
dirty: boolean;
initialState?: string;
initialSettings?: string;
};
};
export interface RgbColor {
r: number;
g: number;
b: number;
}
export type Colors = Record<string, { light: RgbColor; dark?: RgbColor }>;
export type Color = {
css?: string;
rgb?: RgbColor;
id?: string;
invert?: boolean;
alpha?: number;
};
export type RteTextColors = Record<string, Color>;
export type BlueprintDefaults = Record<string, Record<string, ElementData>>;
export type State = {
admin?: boolean;
normalizationDate?: Date;
title: string;
cacheKey: number;
urlPath: string;
defaultFont?: string;
notFound?: boolean;
includeInFeed?: boolean;
metaDescription?: string;
metaImage?: string;
favicon?: string;
fonts: Font[];
colors: Colors;
backgroundColor?: Color;
richtext: {
textColors: RteTextColors;
};
// true -> blueprint needs to always be loaded
// false -> blueprint is static, can be skipped during hydration
blueprintIds?: Record<string, boolean>;
blueprintDefaults: {
project: BlueprintDefaults;
};
elements: Record<string, Element>;
slots: Record<string, ElementSlot>;
layoutSlots: Record<string, LayoutSlot>;
presets: Record<string, Preset>;
pendingDeletes?: {
presets: string[];
};
};
export type PageVisit = {
dynamicBlueprintIds: string[];
blueprintIds: string[];
state?: State | undefined;
layoutIds: string[];
};
export type BlueprintComponent<TS extends () => BlueprintFn> =
CustomBlueprintComponent<
BlueprintDataPropsGetter<ReturnType<TS>>,
Awaited<ReturnType<ReturnType<TS>>>
>;
export type Config = {
owner: Accessor<Owner>;
page: ParentComponent;
blueprints: Record<string, Blueprint>;
getBlueprints?: () => BlueprintModules;
initPromises: Record<string, Promise<void> | undefined>;
};
export type InitConfigParams = {
configParams: NPConfig;
config: Config;
setConfig: SetStoreFunction<Config>;
admin: boolean;
page?: PageVisit;
};
export type NPConfig = {
pageRoot: ParentComponent;
blueprints: () => BlueprintModules;
};
export type BlueprintModules = Record<string, () => Promise<any>>;
type MediaBase = Omit<
NitroMedia,
"projectId" | "name" | "extension" | "createdAt" | "updatedAt"
>;
export type Media = MediaBase & {
timestamp?: number;
url: string;
rawUrl: string;
name?: string;
extension?: string;
};
export type MediaValue = { mediaIds: string[] };