fix: imports/exports

This commit is contained in:
Andras Bacsai committed 2022-09-13 13:49:32 +02:00
1 parent f35a3dbcd4
commit 6bd52dc72f
29 files changed
+513 -1740

No files matched your search

+15 -15
View File
@@ -1,30 +1,30 @@
import { useCallback, useMemo, useState } from "react";
import store from "immerhin";
import * as db from "@webstudio-is/project";
import type { Project } from "@webstudio-is/project";
import {
type OnChangeChildren,
createElementsTree,
type Data,
globalStyles,
type OnChangeChildren,
type Tree,
useAllUserProps,
globalStyles,
createElementsTree,
} from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useShortcuts } from "./shared/use-shortcuts";
import {
usePopulateRootInstance,
useInsertInstance,
useDeleteInstance,
useReparentInstance,
usePublishSelectedInstanceData,
usePublishRootInstance,
useUpdateSelectedInstance,
usePublishSelectedInstanceDataRect,
usePublishHoveredInstanceRect,
useInsertInstance,
usePopulateRootInstance,
usePublishHoveredInstanceData,
usePublishHoveredInstanceRect,
usePublishRootInstance,
usePublishSelectedInstanceData,
usePublishSelectedInstanceDataRect,
usePublishTextEditingInstanceId,
useReparentInstance,
useSetHoveredInstance,
useUnselectInstance,
usePublishTextEditingInstanceId,
useUpdateSelectedInstance,
} from "./shared/instance";
import { useUpdateStyle } from "./shared/style";
import { useTrackSelectedElement } from "./shared/use-track-selected-element";
@@ -83,7 +83,7 @@ const useSubscribePreviewMode = () => {
type DesignModeProps = {
treeId: Tree["id"];
project: db.project.Project;
project: Project;
};
const DesignMode = ({ treeId, project }: DesignModeProps) => {
@@ -112,7 +112,7 @@ const DesignMode = ({ treeId, project }: DesignModeProps) => {
};
type CanvasProps = {
data: Data & { project: db.project.Project };
data: Data & { project: Project };
};
export const Canvas = ({ data }: CanvasProps): JSX.Element | null => {
@@ -1,9 +1,9 @@
import useInterval from "react-use/lib/useInterval";
import { sync } from "immerhin";
import { enqueue } from "./queue";
import { project } from "@webstudio-is/project";
import type { Project } from "@webstudio-is/project";
export const useSync = ({ project }: { project: project.Project }) => {
export const useSync = ({ project }: { project: Project }) => {
useInterval(() => {
const entries = sync();
if (entries.length === 0) return;
+7 -7
View File
@@ -1,8 +1,8 @@
import { useCallback } from "react";
import { useSubscribe, usePublish, type Publish } from "~/shared/pubsub";
import * as db from "@webstudio-is/project";
import { type Publish, usePublish, useSubscribe } from "~/shared/pubsub";
import type { Project } from "@webstudio-is/project";
import type { Config } from "~/config";
import { Box, Flex, Grid, type CSS } from "@webstudio-is/design-system";
import { Box, type CSS, Flex, Grid } from "@webstudio-is/design-system";
import interStyles from "~/shared/font-faces/inter.css";
import { SidebarLeft } from "./features/sidebar-left";
import { Inspector } from "./features/inspector";
@@ -16,19 +16,19 @@ import designerStyles from "./designer.css";
import { Footer } from "./features/footer";
import { TreePrevew } from "./features/tree-preview";
import {
useUpdateCanvasWidth,
useSubscribeBreakpoints,
useUpdateCanvasWidth,
} from "./features/breakpoints";
import {
CanvasIframe,
useReadCanvasRect,
Workspace,
CanvasIframe,
} from "./features/workspace";
import { usePublishShortcuts } from "./shared/shortcuts";
import {
useDragAndDropState,
useIsPreviewMode,
useRootInstance,
useDragAndDropState,
} from "~/shared/nano-states";
import { useClientSettings } from "./shared/client-settings";
import { Navigator } from "./features/sidebar-left";
@@ -211,7 +211,7 @@ const NavigatorPanel = ({ publish, isPreviewMode }: NavigatorPanelProps) => {
type DesignerProps = {
config: Config;
project: db.project.Project;
project: Project;
};
export const Designer = ({ config, project }: DesignerProps) => {
@@ -1,16 +1,16 @@
import { useEffect, useState } from "react";
import { type Breakpoint } from "@webstudio-is/react-sdk";
import { useSubscribe, type Publish } from "~/shared/pubsub";
import { type Publish, useSubscribe } from "~/shared/pubsub";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuPortal,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuArrow,
DropdownMenuSeparator,
DropdownMenuCheckboxItem,
__DEPRECATED__Text,
DropdownMenu,
DropdownMenuArrow,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuTrigger,
Flex,
} from "@webstudio-is/design-system";
import { useSelectedBreakpoint } from "../../shared/nano-states";
@@ -20,12 +20,12 @@ import { ZoomSetting } from "./zoom-setting";
import { TriggerButton } from "./trigger-button";
import { WidthSetting } from "./width-setting";
import {
useSubscribeZoomFromShortcut,
useSubscribeSelectBreakpointFromShortcut,
useSubscribeZoomFromShortcut,
} from "./use-subscribe-shortcuts";
import { ConfirmationDialog } from "./confirmation-dialog";
import { useBreakpoints } from "~/shared/nano-states";
import { sort } from "@webstudio-is/project";
import { utils } from "@webstudio-is/project";
declare module "~/shared/pubsub" {
export interface PubsubMap {
@@ -105,7 +105,7 @@ export const Breakpoints = ({ publish }: BreakpointsProps) => {
nextBreakpoints.splice(index, 1);
setBreakpoints(nextBreakpoints);
if (breakpointToDelete === selectedBreakpoint) {
setSelectedBreakpoint(sort(nextBreakpoints)[0]);
setSelectedBreakpoint(utils.breakpoints.sort(nextBreakpoints)[0]);
}
publish({
type: "breakpointDelete",
@@ -163,7 +163,7 @@ export const Breakpoints = ({ publish }: BreakpointsProps) => {
)}
{view === "selector" && (
<>
{sort(breakpoints).map((breakpoint) => {
{utils.breakpoints.sort(breakpoints).map((breakpoint) => {
return (
<DropdownMenuCheckboxItem
checked={breakpoint === selectedBreakpoint}
@@ -2,8 +2,7 @@ import { useEffect } from "react";
import { useSubscribe } from "~/shared/pubsub";
import { useSelectedBreakpoint } from "~/designer/shared/nano-states";
import { useBreakpoints } from "~/shared/nano-states";
import { sort } from "@webstudio-is/project";
import { utils } from "@webstudio-is/project";
export const useSubscribeBreakpoints = () => {
const [breakpoints, setBreakpoints] = useBreakpoints();
const [selectedBreakpoint, setSelectedBreakpoint] = useSelectedBreakpoint();
@@ -13,7 +12,7 @@ export const useSubscribeBreakpoints = () => {
useEffect(() => {
// Set the initial selected breakpoint
if (selectedBreakpoint === undefined && breakpoints.length !== 0) {
setSelectedBreakpoint(sort(breakpoints)[0]);
setSelectedBreakpoint(utils.breakpoints.sort(breakpoints)[0]);
}
// Breakpoints must have changed, lets update the selected breakpoint
@@ -2,13 +2,13 @@ import { useSubscribe } from "~/shared/pubsub";
import { useBreakpoints } from "~/shared/nano-states";
import { useZoom, useSelectedBreakpoint } from "../../shared/nano-states";
import { minZoom } from "./zoom-setting";
import { sort } from "@webstudio-is/project";
import { utils } from "@webstudio-is/project";
export const useSubscribeSelectBreakpointFromShortcut = () => {
const [breakpoints] = useBreakpoints();
const [, setSelectedBreakpoint] = useSelectedBreakpoint();
useSubscribe("selectBreakpointFromShortcut", (index) => {
const breakpoint = sort(breakpoints)[index - 1];
const breakpoint = utils.breakpoints.sort(breakpoints)[index - 1];
if (breakpoint) setSelectedBreakpoint(breakpoint);
});
};
@@ -3,9 +3,9 @@ import {
useCanvasWidth,
useSelectedBreakpoint,
} from "~/designer/shared/nano-states";
import { Slider, __DEPRECATED__Text, Flex } from "@webstudio-is/design-system";
import { __DEPRECATED__Text, Flex, Slider } from "@webstudio-is/design-system";
import { useBreakpoints, useIsPreviewMode } from "~/shared/nano-states";
import { sort } from "@webstudio-is/project";
import { utils } from "@webstudio-is/project";
// Doesn't make sense to allow resizing the canvas lower/higher than this.
export const minWidth = 50;
@@ -18,7 +18,10 @@ const useNextBreakpoint = () => {
const [selectedBreakpoint] = useSelectedBreakpoint();
const [breakpoints] = useBreakpoints();
const sortedBreakpoints = useMemo(() => sort(breakpoints), [breakpoints]);
const sortedBreakpoints = useMemo(
() => utils.breakpoints.sort(breakpoints),
[breakpoints]
);
return useMemo(() => {
if (selectedBreakpoint === undefined) return;
@@ -1,24 +1,24 @@
import { useState, useEffect } from "react";
import { useEffect, useState } from "react";
import { useFetcher } from "@remix-run/react";
import { RocketIcon, ExternalLinkIcon } from "@webstudio-is/icons";
import { ExternalLinkIcon, RocketIcon } from "@webstudio-is/icons";
import {
__DEPRECATED__Text,
Button,
Flex,
Popover,
PopoverContent,
PopoverTrigger,
PopoverPortal,
TextField,
Text,
__DEPRECATED__Text,
Label,
Link,
Popover,
PopoverContent,
PopoverPortal,
PopoverTrigger,
Text,
TextField,
useId,
} from "@webstudio-is/design-system";
import { useIsPublishDialogOpen } from "../../shared/nano-states";
import env from "~/shared/env";
import * as db from "@webstudio-is/project";
type PublishButtonProps = { project: db.project.Project };
import type { Project } from "@webstudio-is/project";
type PublishButtonProps = { project: Project };
const getHost = () => {
if (env.PUBLISHER_ENDPOINT && env.PUBLISHER_HOST) {
@@ -60,7 +60,7 @@ const Content = ({ project }: PublishButtonProps) => {
gap: "$0",
}}
>
<Text truncate>{`${domain}.${getHost()}`} </Text>
<Text truncate>{`${domain}.${getHost()}`}</Text>
<ExternalLinkIcon />
</Link>
)}
@@ -4,15 +4,15 @@ import {
IconButton,
Popover,
PopoverContent,
PopoverTrigger,
PopoverPortal,
PopoverTrigger,
TextField,
} from "@webstudio-is/design-system";
import { Share1Icon } from "@webstudio-is/icons";
import * as db from "@webstudio-is/project";
import type { Project } from "@webstudio-is/project";
import { useIsShareDialogOpen } from "../../shared/nano-states";
type ShareButtonProps = { path: string; project: db.project.Project };
type ShareButtonProps = { path: string; project: Project };
const Content = ({ path, project }: ShareButtonProps) => {
if (typeof location === "undefined") {
@@ -7,12 +7,12 @@ import { PublishButton } from "./publish";
import { SyncStatus } from "./sync-status";
import { Menu } from "./menu";
import { Breakpoints } from "../breakpoints";
import * as db from "@webstudio-is/project";
import type { Project } from "@webstudio-is/project";
type TopbarProps = {
config: Config;
css: CSS;
project: db.project.Project;
project: Project;
publish: Publish;
};
export const Topbar = ({ config, css, project, publish }: TopbarProps) => {
+4 -4
View File
@@ -1,17 +1,17 @@
import { useLoaderData } from "@remix-run/react";
import {
redirect,
type LoaderFunction,
type ActionFunction,
json,
type LoaderFunction,
redirect,
} from "@remix-run/node";
import { Dashboard, links } from "~/dashboard";
import * as db from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
import config, { type Config } from "~/config";
import { ensureUserCookie } from "~/shared/session";
import { authenticator } from "~/services/auth.server";
import { zfd } from "zod-form-data";
import { User, type Project } from "@webstudio-is/prisma-client";
import { type Project, User } from "@webstudio-is/prisma-client";
export { links };
const schema = zfd.formData({
+4 -3
View File
@@ -1,13 +1,14 @@
import { useLoaderData } from "@remix-run/react";
import { LoaderFunction } from "@remix-run/node";
import { Designer, links } from "~/designer";
import * as db from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
import type { Project } from "@webstudio-is/project";
import config from "~/config";
import env from "~/env.server";
import { ErrorMessage } from "~/shared/error";
import { action, useAction } from "./_assets";
export { links, action };
export { action, links };
export const loader: LoaderFunction = async ({ params }) => {
if (params.id === undefined) throw new Error("Project id undefined");
@@ -20,7 +21,7 @@ export const loader: LoaderFunction = async ({ params }) => {
type Data = {
config: typeof config;
project: db.project.Project;
project: Project;
};
type Error = {
+3 -3
View File
@@ -1,8 +1,8 @@
import { redirect, type LoaderFunction } from "@remix-run/node";
import { type LoaderFunction, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Root, type Data } from "@webstudio-is/react-sdk";
import { type Data, Root } from "@webstudio-is/react-sdk";
import config from "~/config";
import * as db from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
import { Canvas as CanvasDocument } from "~/shared/documents/canvas";
import env, { Env } from "~/env.server";
@@ -1,6 +1,6 @@
import type { LoaderFunction } from "@remix-run/node";
import { Breakpoint } from "@webstudio-is/react-sdk";
import * as projectdomain from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
export type ErrorData = {
errors: string;
@@ -10,7 +10,7 @@ export const loader: LoaderFunction = async ({
params,
}): Promise<Array<Breakpoint> | ErrorData> => {
try {
const project = await projectdomain.project.loadById(params.projectId);
const project = await db.project.loadById(params.projectId);
if (project === null) {
throw new Error(`Project ${params.projectId} not found`);
}
@@ -19,7 +19,7 @@ export const loader: LoaderFunction = async ({
`Project ${params.projectId} needs to be published first`
);
}
const data = await projectdomain.breakpoints.load(project.prodTreeId);
const data = await db.breakpoints.load(project.prodTreeId);
if (data === null) {
throw new Error(
`Breakpoints not found for project ${params.projectId} and tree ID ${project.prodTreeId}`
@@ -1,6 +1,6 @@
import type { LoaderFunction } from "@remix-run/node";
import type { Breakpoint } from "@webstudio-is/react-sdk";
import * as projectdomain from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
export type ErrorData = {
errors: string;
@@ -10,7 +10,7 @@ export const loader: LoaderFunction = async ({
params,
}): Promise<Array<Breakpoint> | ErrorData> => {
try {
const project = await projectdomain.project.loadById(params.projectId);
const project = await db.project.loadById(params.projectId);
if (project === null) {
throw new Error(`Project ${params.projectId} not found`);
}
@@ -19,7 +19,7 @@ export const loader: LoaderFunction = async ({
`Project ${params.projectId} needs to be published first`
);
}
const data = await projectdomain.breakpoints.load(project.prodTreeId);
const data = await db.breakpoints.load(project.prodTreeId);
if (data === null) {
throw new Error(
`Breakpoints not found for project ${params.projectId} and tree ID ${project.prodTreeId}`
+4 -4
View File
@@ -1,13 +1,13 @@
import { type ActionFunction } from "@remix-run/node";
import * as projectdomain from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
import { type SyncItem } from "immerhin";
import { type Tree } from "@webstudio-is/react-sdk";
import { type Project } from "@webstudio-is/prisma-client";
const updaters = {
root: projectdomain.tree.patchRoot,
props: projectdomain.props.patch,
breakpoints: projectdomain.breakpoints.patch,
root: db.tree.patchRoot,
props: db.props.patch,
breakpoints: db.breakpoints.patch,
};
type UpdaterKey = keyof typeof updaters;
@@ -1,6 +1,7 @@
import { type LoaderFunction, redirect } from "@remix-run/node";
import type { User } from "@webstudio-is/prisma-client";
import * as projectdomain from "@webstudio-is/project";
import { db as database } from "@webstudio-is/project";
import type { Project } from "@webstudio-is/project";
import * as db from "~/shared/db";
import { ensureUserCookie } from "~/shared/session";
import config from "~/config";
@@ -12,11 +13,11 @@ const ensureProject = async ({
}: {
userId: User["id"];
domain: string;
}): Promise<projectdomain.project.Project> => {
const projects = await projectdomain.project.loadManyByUserId(userId);
}): Promise<Project> => {
const projects = await database.project.loadManyByUserId(userId);
if (projects.length !== 0) return projects[0];
return await projectdomain.project.clone(domain, userId);
return await database.project.clone(domain, userId);
};
/**
@@ -1,6 +1,6 @@
import { type LoaderFunction } from "@remix-run/node";
import { type InstanceProps } from "@webstudio-is/react-sdk";
import * as projectdomain from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
type ErrorData = {
errors: string;
};
@@ -9,8 +9,8 @@ export const loader: LoaderFunction = async ({
params,
}): Promise<Array<InstanceProps> | ErrorData> => {
try {
const project = await projectdomain.project.loadById(params.projectId);
return await projectdomain.props.loadByProject(project, "production");
const project = await db.project.loadById(params.projectId);
return await db.props.loadByProject(project, "production");
} catch (error) {
if (error instanceof Error) {
return {
@@ -1,6 +1,6 @@
import { type LoaderFunction } from "@remix-run/node";
import type { Tree } from "@webstudio-is/react-sdk";
import * as projectdomain from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
export type ErrorData = {
errors: string;
};
@@ -9,8 +9,8 @@ export const loader: LoaderFunction = async ({
params,
}): Promise<Tree | null | ErrorData> => {
try {
const project = await projectdomain.project.loadById(params.projectId);
return await projectdomain.tree.loadByProject(project, "production");
const project = await db.project.loadById(params.projectId);
return await db.tree.loadByProject(project, "production");
} catch (error) {
if (error instanceof Error) {
return {
+10 -10
View File
@@ -1,21 +1,21 @@
import { type Data } from "@webstudio-is/react-sdk";
import * as projectdomain from "@webstudio-is/project";
export type CanvasData = Data & { project: projectdomain.project.Project };
import { db } from "@webstudio-is/project";
import type { Project } from "@webstudio-is/project";
export type CanvasData = Data & { project: Project };
export type ErrorData = {
errors: string;
};
const loadData = async (projectId: projectdomain.project.Project["id"]) => {
const project = await projectdomain.project.loadById(projectId);
const loadData = async (projectId: Project["id"]) => {
const project = await db.project.loadById(projectId);
if (project === null) throw new Error(`Project "${projectId}" not found`);
const [tree, props, breakpoints] = await Promise.all([
projectdomain.tree.loadByProject(project, "development"),
projectdomain.props.loadByProject(project, "development"),
projectdomain.breakpoints.load(project.devTreeId),
db.tree.loadByProject(project, "development"),
db.props.loadByProject(project, "development"),
db.breakpoints.load(project.devTreeId),
]);
if (tree === null) {
@@ -39,7 +39,7 @@ const loadData = async (projectId: projectdomain.project.Project["id"]) => {
export const loadCanvasData = async ({
projectId,
}: {
projectId: projectdomain.project.Project["id"];
projectId: Project["id"];
}): Promise<CanvasData | ErrorData> => {
return await loadData(projectId);
};
@@ -49,7 +49,7 @@ export type PreviewData = Data;
export const loadPreviewData = async ({
projectId,
}: {
projectId: projectdomain.project.Project["id"];
projectId: Project["id"];
}): Promise<PreviewData | ErrorData> => {
const { tree, props, breakpoints } = await loadData(projectId);
return { tree, props, breakpoints };
+6 -6
View File
@@ -1,4 +1,4 @@
import * as projectdomain from "@webstudio-is/project";
import { db } from "@webstudio-is/project";
/**
* Conceptually publishing is cloning all data that affects user site
@@ -18,17 +18,17 @@ export const publish = async ({
throw new Error("Domain required");
}
const project = await projectdomain.project.loadById(projectId);
const project = await db.project.loadById(projectId);
if (project === null) {
throw new Error(`Project "${projectId}" not found`);
}
const tree = await projectdomain.tree.clone(project.devTreeId);
await projectdomain.props.clone({
const tree = await db.tree.clone(project.devTreeId);
await db.props.clone({
previousTreeId: project.devTreeId,
nextTreeId: tree.id,
});
await projectdomain.breakpoints.clone({
await db.breakpoints.clone({
previousTreeId: project.devTreeId,
nextTreeId: tree.id,
});
@@ -36,7 +36,7 @@ export const publish = async ({
if (project.prodTreeId) {
prodTreeIdHistory.push(project.prodTreeId);
}
const updatedProject = await projectdomain.project.update({
const updatedProject = await db.project.update({
id: projectId,
domain,
prodTreeId: tree.id,
-1
View File
@@ -28,4 +28,3 @@ export const loadProject = async ({
breakpoints,
};
};
-82
View File
@@ -1,82 +0,0 @@
import {
initialBreakpoints,
type Tree,
type Breakpoint,
BreakpointsSchema,
} from "@webstudio-is/react-sdk";
import ObjectId from "bson-objectid";
import { applyPatches, type Patch } from "immer";
import { prisma, Project } from "@webstudio-is/prisma-client";
export const load = async (treeId?: Tree["id"]) => {
if (typeof treeId !== "string") {
throw new Error("Tree ID required");
}
const breakpoint = await prisma.breakpoints.findUnique({
where: { treeId },
});
if (breakpoint === null) {
throw new Error("Breakpoint not found");
}
const values: Array<Breakpoint> = JSON.parse(breakpoint.values);
BreakpointsSchema.parse(values);
return {
...breakpoint,
values,
};
};
export const getBreakpointsWithId = () =>
initialBreakpoints.map((breakpoint) => ({
...breakpoint,
id: ObjectId().toString(),
}));
export const create = async (
treeId: Project["id"],
values: Array<Breakpoint>
) => {
BreakpointsSchema.parse(values);
const data = {
treeId,
values: JSON.stringify(values),
};
await prisma.breakpoints.create({ data });
return {
...data,
breakpoints: values,
};
};
export const clone = async ({
previousTreeId,
nextTreeId,
}: {
previousTreeId: Tree["id"];
nextTreeId: Tree["id"];
}) => {
const breakpoints = await load(previousTreeId);
if (breakpoints === null) {
throw new Error(`Didn't find breakpoints with tree id "${previousTreeId}"`);
}
await create(nextTreeId, breakpoints.values);
};
export const patch = async (
{ treeId }: { treeId: Tree["id"]; projectId: Project["id"] },
patches: Array<Patch>
) => {
const breakpoints = await load(treeId);
if (breakpoints === null) return;
const nextValues = applyPatches(breakpoints.values, patches);
BreakpointsSchema.parse(nextValues);
await prisma.breakpoints.update({
where: { treeId },
data: { values: JSON.stringify(nextValues) },
});
};
+6 -7
View File
@@ -1,9 +1,8 @@
import * as project from "./project";
import * as breakpoints from "./breakpoints";
import * as tree from "./tree";
import * as props from "./props";
import * as dbFunctions from "./db";
import * as breakpointsUtils from "./shared/breakpoints";
import * as treeUtils from "./shared/tree-utils";
export const db = {project, breakpoints, tree, props}
export const utils = { breakpoints: breakpointsUtils, tree: treeUtils };
export const db = dbFunctions;
export breakpoints from "./shared/breakpoints";
export tree from "./shared/tree-utils";
export * from "./db/types";
-210
View File
@@ -1,210 +0,0 @@
import slugify from "slugify";
import { nanoid } from "nanoid";
import { z } from "zod";
import {
User,
prisma,
Prisma,
Project as BaseProject,
} from "@webstudio-is/prisma-client";
import { formatAsset } from "@webstudio-is/asset-uploader";
import type { Asset } from "@webstudio-is/asset-uploader";
import * as db from "./index";
const TreeHistorySchema = z.array(z.string());
export type Project = Omit<BaseProject, "prodTreeIdHistory" | "assets"> & {
prodTreeIdHistory: z.infer<typeof TreeHistorySchema>;
assets?: Array<Asset>;
};
const parseProject = (project: BaseProject): Project => {
const prodTreeIdHistory = JSON.parse(project.prodTreeIdHistory);
return {
...project,
prodTreeIdHistory: TreeHistorySchema.parse(prodTreeIdHistory),
assets: project?.assets?.map(formatAsset),
};
};
export const loadById = async (projectId?: Project["id"]) => {
if (typeof projectId !== "string") {
throw new Error("Project ID required");
}
const project = await prisma.project.findUnique({
where: { id: projectId },
include: {
assets: {
orderBy: {
createdAt: "desc",
},
},
},
});
if (!project) return null;
return parseProject(project);
};
export const loadByDomain = async (domain: string): Promise<Project | null> => {
const project = await prisma.project.findUnique({
where: { domain },
include: {
assets: {
orderBy: {
createdAt: "desc",
},
},
},
});
if (!project) return null;
return parseProject(project);
};
export const loadManyByUserId = async (
userId: User["id"]
): Promise<Array<Project>> => {
const projects = await prisma.project.findMany({
where: {
user: {
id: userId,
},
},
});
return projects.map(parseProject) as Project[];
};
const slugifyOptions = { lower: true, strict: true };
const MIN_DOMAIN_LENGTH = 10;
const MIN_TITLE_LENGTH = 2;
const generateDomain = (title: string) => {
const slugifiedTitle = slugify(title, slugifyOptions);
const domain = `${slugifiedTitle}-${nanoid(
// If user entered a long title already, we just add 5 chars generated id
// Otherwise we add the amount of chars to satisfy min length
Math.max(MIN_DOMAIN_LENGTH - slugifiedTitle.length - 1, 5)
)}`;
return domain;
};
export const create = async ({
userId,
title,
}: {
userId: string;
title: string;
}) => {
if (title.length < MIN_TITLE_LENGTH) {
throw new Error(`Minimum ${MIN_TITLE_LENGTH} characters required`);
}
const domain = generateDomain(title);
const breakpoints = db.breakpoints.getBreakpointsWithId();
const tree = await db.tree.create(db.tree.createRootInstance(breakpoints));
const project = await prisma.project.create({
data: {
userId,
title,
domain,
devTreeId: tree.id,
},
});
await db.breakpoints.create(tree.id, breakpoints);
return parseProject(project);
};
export const clone = async (clonableDomain: string, userId: string) => {
const clonableProject = await loadByDomain(clonableDomain);
if (clonableProject === null) {
throw new Error(`Not found project "${clonableDomain}"`);
}
if (clonableProject.prodTreeId === null) {
throw new Error("Expected project to be published first");
}
const tree = await db.tree.clone(clonableProject.prodTreeId);
const domain = generateDomain(clonableProject.title);
const [project] = await Promise.all([
prisma.project.create({
data: {
userId: userId,
title: clonableProject.title,
domain,
devTreeId: tree.id,
},
include: {
assets: {
orderBy: {
createdAt: "desc",
},
},
},
}),
db.props.clone({
previousTreeId: clonableProject.prodTreeId,
nextTreeId: tree.id,
}),
db.breakpoints.clone({
previousTreeId: clonableProject.prodTreeId,
nextTreeId: tree.id,
}),
]);
const parsedProject = parseProject(project);
if (parsedProject === null) {
throw new Error(`Not found project "${clonableDomain}"`);
}
return parsedProject;
};
export const update = async ({
id,
...data
}: {
id: string;
domain?: string;
prodTreeId?: string;
devTreeId?: string;
prodTreeIdHistory: Array<string>;
}) => {
if (data.domain) {
data.domain = slugify(data.domain, slugifyOptions);
if (data.domain.length < MIN_DOMAIN_LENGTH) {
throw new Error(`Minimum ${MIN_DOMAIN_LENGTH} characters required`);
}
}
TreeHistorySchema.parse(data.prodTreeIdHistory);
try {
const project = await prisma.project.update({
data: {
...data,
prodTreeIdHistory: JSON.stringify(data.prodTreeIdHistory),
},
where: { id },
include: {
assets: {
orderBy: {
createdAt: "desc",
},
},
},
});
return project;
} catch (error) {
if (
error instanceof Prisma.PrismaClientKnownRequestError &&
error.code === "P2002"
) {
throw new Error(`Domain "${data.domain}" is already used`);
}
throw error;
}
};
-98
View File
@@ -1,98 +0,0 @@
import {
type Tree,
type AllUserProps,
UserPropsSchema,
} from "@webstudio-is/react-sdk";
import { applyPatches, type Patch } from "immer";
import { prisma } from "@webstudio-is/prisma-client";
import { Project } from "./project";
export const loadByProject = async (
project: Project | null,
env: "production" | "development" = "development"
) => {
if (project === null) {
throw new Error("Project required");
}
const treeId = env === "production" ? project.prodTreeId : project.devTreeId;
if (treeId === null) {
throw new Error("Site needs to be published, production tree ID is null.");
}
return loadByTreeId(treeId);
};
export const loadByTreeId = async (treeId: Tree["id"]) => {
const instancePropsEntries = await prisma.instanceProps.findMany({
where: { treeId },
});
return instancePropsEntries.map((instanceProps) => {
const props = JSON.parse(instanceProps.props);
UserPropsSchema.parse(props);
return {
...instanceProps,
props,
};
});
};
export const clone = async ({
previousTreeId,
nextTreeId,
}: {
previousTreeId: string;
nextTreeId: string;
}) => {
const props = await prisma.instanceProps.findMany({
where: { treeId: previousTreeId },
});
if (props.length === 0) return;
const data = props.map(({ id: _id, treeId: _treeId, ...rest }) => ({
...rest,
treeId: nextTreeId,
}));
await prisma.$transaction(
data.map((prop) =>
prisma.instanceProps.create({
data: prop,
})
)
);
};
export const patch = async (
{ treeId }: { treeId: Tree["id"]; projectId: Project["id"] },
patches: Array<Patch>
) => {
const allProps = await loadByTreeId(treeId);
// We should get rid of this by applying patches on the client to the original
// model instead of the instanceId map.
// The map is handy for accessing props, but probably should be just cached interface, not the main data structure.
const allPropsMapByInstanceId = allProps.reduce((acc, prop) => {
acc[prop.instanceId] = prop;
return acc;
}, {} as AllUserProps);
const nextProps = applyPatches<AllUserProps>(
allPropsMapByInstanceId,
patches
);
await Promise.all(
Object.values(nextProps).map(({ id, instanceId, treeId, props }) => {
UserPropsSchema.parse(props);
const propsString = JSON.stringify(props);
return prisma.instanceProps.upsert({
where: { id: id },
create: { id: id, instanceId, treeId, props: propsString },
update: {
props: propsString,
},
});
})
);
};
-86
View File
@@ -1,86 +0,0 @@
import {
type Instance,
type Breakpoint,
type Tree,
InstanceSchema,
} from "@webstudio-is/react-sdk";
import { applyPatches, type Patch } from "immer";
import { prisma } from "@webstudio-is/prisma-client";
import { Tree as DbTree } from "@prisma/client";
import { Project } from "./project";
import { sort } from "./shared/breakpoints";
import { createInstance, populateInstance } from "./shared/tree-utils";
export const createRootInstance = (breakpoints: Array<Breakpoint>) => {
// Take the smallest breakpoint as default
const defaultBreakpoint = sort(breakpoints)[0];
if (defaultBreakpoint === undefined) {
throw new Error("A breakpoint with minWidth 0 is required");
}
const instance = createInstance({ component: "Body" });
return populateInstance(instance, defaultBreakpoint.id);
};
export const create = async (root: Instance): Promise<DbTree> => {
InstanceSchema.parse(root);
const rootString = JSON.stringify(root);
return await prisma.tree.create({
data: { root: rootString },
});
};
export const loadById = async (treeId: string): Promise<Tree | null> => {
const tree = await prisma.tree.findUnique({
where: { id: treeId },
});
if (tree === null) return null;
const root = JSON.parse(tree.root);
InstanceSchema.parse(root);
return {
...tree,
root,
};
};
export const loadByProject = async (
project: Project | null,
env: "production" | "development" = "development"
) => {
if (project === null) {
throw new Error("Project required");
}
const treeId = env === "production" ? project.prodTreeId : project.devTreeId;
if (treeId === null) {
throw new Error("Site needs to be published, production tree ID is null.");
}
return await loadById(treeId);
};
export const clone = async (treeId: string) => {
const tree = await loadById(treeId);
if (tree === null) {
throw new Error(`Tree ${treeId} not found`);
}
return await create(tree.root);
};
export const patchRoot = async (
{ treeId }: { treeId: Tree["id"] },
patches: Array<Patch>
) => {
const tree = await loadById(treeId);
if (tree === null) {
throw new Error(`Tree ${treeId} not found`);
}
const root = applyPatches(tree.root, patches);
InstanceSchema.parse(root);
await prisma.tree.update({
data: { root: JSON.stringify(root) },
where: { id: treeId },
});
};
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -28,5 +28,5 @@ export const units = [
"vmin",
"vw",
"x",
"%"
] as const;
"%",
] as const;