mirror of
https://github.com/khairul169/code-share.git
synced 2026-10-24 11:53:58 +07:00
feat: add project schema
This commit is contained in:
@@ -6,22 +6,21 @@ import {
|
||||
import WebPreview from "./components/web-preview";
|
||||
import Editor from "./components/editor";
|
||||
import ProjectContext from "./context/project";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useParams, useSearchParams } from "~/renderer/hooks";
|
||||
import { BASE_URL } from "~/lib/consts";
|
||||
import { cn, getPreviewUrl } from "~/lib/utils";
|
||||
import { useData, useSearchParams } from "~/renderer/hooks";
|
||||
import { withClientOnly } from "~/renderer/client-only";
|
||||
import Spinner from "~/components/ui/spinner";
|
||||
import { Data } from "./+data";
|
||||
|
||||
const ViewProjectPage = () => {
|
||||
const { project } = useData<Data>();
|
||||
const searchParams = useSearchParams();
|
||||
const params = useParams();
|
||||
const isCompact =
|
||||
searchParams.get("compact") === "1" || searchParams.get("embed") === "1";
|
||||
const slug = params["slug"];
|
||||
const previewUrl = BASE_URL + `/api/preview/${slug}/index.html`;
|
||||
const previewUrl = getPreviewUrl(project, "index.html");
|
||||
|
||||
return (
|
||||
<ProjectContext.Provider value={{ slug, isCompact }}>
|
||||
<ProjectContext.Provider value={{ project, isCompact }}>
|
||||
<ResizablePanelGroup
|
||||
autoSaveId="main-panel"
|
||||
direction={{ sm: "vertical", md: "horizontal" }}
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
import { PageContext } from "vike/types";
|
||||
import { render } from "vike/abort";
|
||||
import trpcServer from "~/server/api/trpc/trpc";
|
||||
|
||||
export const data = async (ctx: PageContext) => {
|
||||
const trpc = await trpcServer(ctx);
|
||||
const pinnedFiles = await trpc.file.getAll({ isPinned: true });
|
||||
const files = await trpc.file.getAll();
|
||||
|
||||
return { files, pinnedFiles };
|
||||
const project = await trpc.project.getById(ctx.routeParams?.slug!);
|
||||
if (!project) {
|
||||
throw render(404, "Project not found!");
|
||||
}
|
||||
|
||||
const files = await trpc.file.getAll({ projectId: project.id });
|
||||
const pinnedFiles = files.filter((i) => i.isPinned);
|
||||
|
||||
return {
|
||||
title: project.title,
|
||||
description: `Check ${project.title} on CodeShare!`,
|
||||
project,
|
||||
files,
|
||||
pinnedFiles,
|
||||
};
|
||||
};
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default "/@slug";
|
||||
@@ -23,9 +23,9 @@ import { Data } from "../+data";
|
||||
import { useBreakpoint } from "~/hooks/useBreakpoint";
|
||||
|
||||
const Editor = () => {
|
||||
const { pinnedFiles } = useData<Data>();
|
||||
const { project, pinnedFiles } = useData<Data>();
|
||||
const trpcUtils = trpc.useUtils();
|
||||
const project = useProjectContext();
|
||||
const projectCtx = useProjectContext();
|
||||
const sidebarPanel = useRef<ImperativePanelHandle>(null);
|
||||
const [breakpoint] = useBreakpoint();
|
||||
|
||||
@@ -36,7 +36,7 @@ const Editor = () => {
|
||||
);
|
||||
|
||||
const openedFilesData = trpc.file.getAll.useQuery(
|
||||
{ id: curOpenFiles },
|
||||
{ projectId: project.id, id: curOpenFiles },
|
||||
{ enabled: curOpenFiles.length > 0, initialData: pinnedFiles }
|
||||
);
|
||||
const [openedFiles, setOpenedFiles] = useState<any[]>(pinnedFiles);
|
||||
@@ -156,7 +156,7 @@ const Editor = () => {
|
||||
}) satisfies Tab[];
|
||||
}, [curOpenFiles, openedFiles, refreshPreview]);
|
||||
|
||||
const PanelComponent = !project.isCompact ? Panel : "div";
|
||||
const PanelComponent = !projectCtx.isCompact ? Panel : "div";
|
||||
|
||||
return (
|
||||
<EditorContext.Provider
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"use client";
|
||||
|
||||
import React, { Fragment, useMemo, useState } from "react";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
import { UseDiscloseReturn, useDisclose } from "~/hooks/useDisclose";
|
||||
import {
|
||||
FiChevronRight,
|
||||
@@ -21,27 +19,28 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from "~/components/ui/dropdown-menu";
|
||||
import { cn, getUrl } from "~/lib/utils";
|
||||
import { cn, getPreviewUrl } from "~/lib/utils";
|
||||
import FileIcon from "~/components/ui/file-icon";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { useData, useParams } from "~/renderer/hooks";
|
||||
import { useData } from "~/renderer/hooks";
|
||||
import Spinner from "~/components/ui/spinner";
|
||||
import { Data } from "../+data";
|
||||
|
||||
const FileListing = () => {
|
||||
const pageData = useData<Data>();
|
||||
const { project, files: initialFiles } = useData<Data>();
|
||||
const { onOpenFile, onFileChanged } = useEditorContext();
|
||||
const createFileDlg = useDisclose<CreateFileSchema>();
|
||||
const files = trpc.file.getAll.useQuery(undefined, {
|
||||
initialData: pageData.files,
|
||||
});
|
||||
const files = trpc.file.getAll.useQuery(
|
||||
{ projectId: project.id },
|
||||
{ initialData: initialFiles }
|
||||
);
|
||||
|
||||
const fileList = useMemo(() => groupFiles(files.data, null), [files.data]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="h-10 flex items-center pl-4 pr-1">
|
||||
<p className="text-xs uppercase truncate flex-1">My Project</p>
|
||||
<p className="text-xs uppercase truncate flex-1">{project.title}</p>
|
||||
<ActionButton
|
||||
icon={FiFilePlus}
|
||||
onClick={() => createFileDlg.onOpen()}
|
||||
@@ -104,7 +103,7 @@ type FileItemProps = {
|
||||
};
|
||||
|
||||
const FileItem = ({ file, createFileDlg }: FileItemProps) => {
|
||||
const { slug } = useParams();
|
||||
const { project } = useData<Data>();
|
||||
const { onOpenFile, onDeleteFile } = useEditorContext();
|
||||
const [isCollapsed, setCollapsed] = useState(false);
|
||||
const trpcUtils = trpc.useUtils();
|
||||
@@ -186,16 +185,13 @@ const FileItem = ({ file, createFileDlg }: FileItemProps) => {
|
||||
Copy Path
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => copy(getUrl(`api/preview/${slug}`, file.path))}
|
||||
onClick={() => copy(getPreviewUrl(project, file))}
|
||||
>
|
||||
Copy URL
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
window.open(
|
||||
getUrl(`api/preview/${slug}`, file.path),
|
||||
"_blank"
|
||||
)
|
||||
window.open(getPreviewUrl(project, file), "_blank")
|
||||
}
|
||||
>
|
||||
Open in new tab
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { getFileExt } from "~/lib/utils";
|
||||
import CodeEditor from "../../../../components/ui/code-editor";
|
||||
import trpc from "~/lib/trpc";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import FileListing from "./file-listing";
|
||||
import { FaUserCircle } from "react-icons/fa";
|
||||
import { Button } from "~/components/ui/button";
|
||||
@@ -7,6 +6,7 @@ const Sidebar = () => {
|
||||
return (
|
||||
<aside className="flex flex-col items-stretch h-full">
|
||||
<FileListing />
|
||||
|
||||
<div className="h-12 bg-[#1a1b26] pl-12">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createContext, useContext } from "react";
|
||||
import type { ProjectSchema } from "~/server/db/schema/project";
|
||||
|
||||
type TProjectContext = {
|
||||
slug: string;
|
||||
project: ProjectSchema;
|
||||
isCompact?: boolean;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user