From e007dd4ac43327a32ba1e93cb1abf3c5bf6a140a Mon Sep 17 00:00:00 2001 From: Khairul Hidayat Date: Sun, 3 Mar 2024 18:24:00 +0700 Subject: [PATCH] feat: update layout --- components/ui/input.tsx | 2 +- components/ui/resizable.tsx | 42 +++++----- components/ui/tabs.tsx | 79 +++++++++---------- pages/project/@slug/+Page.tsx | 16 +--- pages/project/@slug/+data.ts | 2 +- pages/project/@slug/components/editor.tsx | 54 +++++++++---- .../project/@slug/components/file-listing.tsx | 2 +- .../project/@slug/components/file-viewer.tsx | 2 +- .../@slug/components/settings-dialog.tsx | 51 ++++++++++-- pages/project/@slug/components/status-bar.tsx | 4 +- .../project/@slug/components/web-preview.tsx | 26 +++--- server/routers/project.ts | 10 +-- 12 files changed, 169 insertions(+), 121 deletions(-) diff --git a/components/ui/input.tsx b/components/ui/input.tsx index 8143f36..0177986 100644 --- a/components/ui/input.tsx +++ b/components/ui/input.tsx @@ -18,7 +18,7 @@ const BaseInput = forwardRef( type={type} className={cn( "flex h-10 w-full rounded-md border border-slate-200 px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/40 bg-transparent dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300", - inputClassName + label || error ? inputClassName : className )} ref={ref} {...props} diff --git a/components/ui/resizable.tsx b/components/ui/resizable.tsx index 9edf9ee..3788b83 100644 --- a/components/ui/resizable.tsx +++ b/components/ui/resizable.tsx @@ -58,26 +58,28 @@ const ResizablePanel = forwardRef((props: ResizablePanelProps, ref: any) => { ); }); -const ResizableHandle = ({ - withHandle, - className, - ...props -}: React.ComponentProps & { +type ResizableHandleProps = React.ComponentProps< + typeof ResizablePrimitive.PanelResizeHandle +> & { withHandle?: boolean; -}) => ( - div]:rotate-90", - className - )} - {...props} - > - {withHandle && ( -
- -
- )} -
-); +}; + +const ResizableHandle = (props: ResizableHandleProps) => { + const { withHandle, className, ...restProps } = props; + return ( + div]:rotate-90", + withHandle && "w-3 bg-black group", + className + )} + {...restProps} + > + {withHandle && ( +
+ )} +
+ ); +}; export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; diff --git a/components/ui/tabs.tsx b/components/ui/tabs.tsx index f11d685..79b01ae 100644 --- a/components/ui/tabs.tsx +++ b/components/ui/tabs.tsx @@ -16,17 +16,9 @@ type Props = { onChange?: (idx: number) => void; onClose?: (idx: number) => void; className?: string; - containerClassName?: string; }; -const Tabs = ({ - tabs, - current = 0, - onChange, - onClose, - className, - containerClassName, -}: Props) => { +const Tabs = ({ tabs, current = 0, onChange, onClose, className }: Props) => { const tabContainerRef = useRef(null); const onWheel = (e: WheelEvent) => { @@ -64,43 +56,44 @@ const Tabs = ({ container.scrollTo({ left: scrollX, behavior: "smooth" }); }, [tabs, current]); - const tabView = useMemo(() => { - const tab = tabs[current]; - const element = tab?.render ? tab.render() : null; - return element; - }, [tabs, current]); - - return ( -
0 ? ( + + ) : null; +}; -
- {tabView} -
-
- ); +type TabViewProps = { + tabs: Tab[]; + current: number; + className?: string; +}; + +export const TabView = ({ tabs, current, className }: TabViewProps) => { + const tabView = useMemo(() => { + const tab = tabs[current]; + const element = tab?.render ? tab.render() : null; + + return element; + }, [tabs, current]); + + return
{tabView}
; }; type TabItemProps = { @@ -128,8 +121,8 @@ const TabItem = ({
diff --git a/pages/project/@slug/+Page.tsx b/pages/project/@slug/+Page.tsx index 9e5d83e..e7534ce 100644 --- a/pages/project/@slug/+Page.tsx +++ b/pages/project/@slug/+Page.tsx @@ -23,12 +23,9 @@ const ViewProjectPage = () => { return ( { > - + { throw render(404, "Project not found!"); } - const files = await trpc.file.getAll({ projectId: project.id }); + const files = await trpc.file.getAll({ projectId: project.id! }); const initialFiles = files.filter((i) => filesParam != null ? filesParam.includes(i.path) : i.isPinned ); diff --git a/pages/project/@slug/components/editor.tsx b/pages/project/@slug/components/editor.tsx index 7949b15..f0a934c 100644 --- a/pages/project/@slug/components/editor.tsx +++ b/pages/project/@slug/components/editor.tsx @@ -4,12 +4,11 @@ import { ResizablePanel, ResizablePanelGroup, } from "~/components/ui/resizable"; -import Tabs, { Tab } from "~/components/ui/tabs"; +import Tabs, { Tab, TabView } from "~/components/ui/tabs"; import FileViewer from "./file-viewer"; import trpc from "~/lib/trpc"; import EditorContext from "../context/editor"; import type { FileSchema } from "~/server/db/schema/file"; -import Panel from "~/components/ui/panel"; import { useProjectContext } from "../context/project"; import Sidebar from "./sidebar"; import ConsoleLogger from "./console-logger"; @@ -22,11 +21,14 @@ import SettingsDialog from "./settings-dialog"; import FileIcon from "~/components/ui/file-icon"; import { api } from "~/lib/api"; import { useMutation } from "@tanstack/react-query"; +import { Button } from "~/components/ui/button"; +import { FaExternalLinkAlt } from "react-icons/fa"; +import { BASE_URL } from "~/lib/consts"; const Editor = () => { const { project, initialFiles } = useData(); const trpcUtils = trpc.useUtils(); - const projectCtx = useProjectContext(); + const { isEmbed } = useProjectContext(); const [breakpoint] = useBreakpoint(); const [curTabIdx, setCurTabIdx] = useState(0); @@ -178,8 +180,7 @@ const Editor = () => { return tabs; }, [curOpenFiles, openedFiles, breakpoint]); - const PanelComponent = - !projectCtx.isCompact || !projectCtx.isEmbed ? Panel : "div"; + const currentTab = Math.min(Math.max(curTabIdx, 0), tabs.length - 1); return ( { onDeleteFile, }} > - +
@@ -206,15 +207,36 @@ const Editor = () => { - + - +
+
+ + +
+ +
{breakpoint >= 2 ? ( @@ -236,7 +258,7 @@ const Editor = () => {
- +
diff --git a/pages/project/@slug/components/file-listing.tsx b/pages/project/@slug/components/file-listing.tsx index 6668db9..9a8ceb3 100644 --- a/pages/project/@slug/components/file-listing.tsx +++ b/pages/project/@slug/components/file-listing.tsx @@ -39,7 +39,7 @@ const FileListing = () => { return ( -
+

{project.title}

{ const { project } = useProjectContext(); const { initialFiles } = useData(); - const initialData = initialFiles.find((i) => i.id === id); + const initialData = initialFiles.find((i) => i.id === id) as any; const { data, isLoading, refetch } = trpc.file.getById.useQuery(id, { initialData, diff --git a/pages/project/@slug/components/settings-dialog.tsx b/pages/project/@slug/components/settings-dialog.tsx index 207c9b5..a4c0957 100644 --- a/pages/project/@slug/components/settings-dialog.tsx +++ b/pages/project/@slug/components/settings-dialog.tsx @@ -6,7 +6,7 @@ import { DialogHeader, DialogTitle, } from "~/components/ui/dialog"; -import { useMemo, useState } from "react"; +import { Fragment, useMemo, useState } from "react"; import { useProjectContext } from "../context/project"; import { useForm, useFormReturn } from "~/hooks/useForm"; import Input from "~/components/ui/input"; @@ -21,8 +21,10 @@ import { import trpc from "~/lib/trpc"; import { toast } from "~/lib/utils"; import Checkbox from "~/components/ui/checkbox"; -import Tabs, { Tab } from "~/components/ui/tabs"; +import Tabs, { Tab, TabView } from "~/components/ui/tabs"; import { navigate } from "vike/client/router"; +import { useFieldArray } from "react-hook-form"; +import { FaTrashAlt } from "react-icons/fa"; const defaultValues: ProjectSettingsSchema = { title: "", @@ -102,8 +104,9 @@ const SettingsDialog = () => { tabs={tabs} current={tab} onChange={setTab} - containerClassName="mt-4" + className="rounded-md overflow-hidden" /> +
+
+ ))} +
+
); }; diff --git a/pages/project/@slug/components/status-bar.tsx b/pages/project/@slug/components/status-bar.tsx index eeb48cb..4373d7d 100644 --- a/pages/project/@slug/components/status-bar.tsx +++ b/pages/project/@slug/components/status-bar.tsx @@ -17,11 +17,11 @@ import { useProjectContext } from "../context/project"; const StatusBar = ({ className }: React.ComponentProps<"div">) => { const { user, urlPathname } = usePageContext(); - const { isCompact, project } = useProjectContext(); + const { isEmbed, project } = useProjectContext(); const sidebarExpanded = useStore(sidebarStore, (i) => i.expanded); const previewExpanded = useStore(previewStore, (i) => i.open); - if (isCompact) { + if (isEmbed) { return null; } diff --git a/pages/project/@slug/components/web-preview.tsx b/pages/project/@slug/components/web-preview.tsx index b72907a..ec74d8e 100644 --- a/pages/project/@slug/components/web-preview.tsx +++ b/pages/project/@slug/components/web-preview.tsx @@ -1,5 +1,4 @@ /* eslint-disable react/display-name */ -import Panel from "~/components/ui/panel"; import { ComponentProps, useCallback, useEffect, useRef } from "react"; import { useProjectContext } from "../context/project"; import { Button } from "~/components/ui/button"; @@ -56,8 +55,6 @@ const WebPreview = ({ url, ...props }: WebPreviewProps) => { refresh(); }, [refresh, togglePanel]); - const PanelComponent = !project.isCompact || !project.isEmbed ? Panel : "div"; - return ( { onCollapse={() => previewStore.setState({ open: false })} {...props} > - -
+
+

Preview

- + + {!project.isEmbed ? ( + + ) : null}
{url != null ? ( @@ -92,7 +92,7 @@ const WebPreview = ({ url, ...props }: WebPreviewProps) => { sandbox="allow-scripts allow-forms" /> ) : null} - +
); }; diff --git a/server/routers/project.ts b/server/routers/project.ts index a4652ec..0cebb85 100644 --- a/server/routers/project.ts +++ b/server/routers/project.ts @@ -55,20 +55,20 @@ const projectRouter = router({ isNull(project.deletedAt) ); - const result = await db.query.project.findFirst({ + const projectData = await db.query.project.findFirst({ where, with: { user: { columns: { password: false } }, }, }); - if (!hasPermission(ctx, result, "r")) { - throw new TRPCError({ code: "FORBIDDEN" }); + if (!projectData || !hasPermission(ctx, projectData, "r")) { + return null; } - const isMutable = hasPermission(ctx, result, "w"); + const isMutable = hasPermission(ctx, projectData, "w"); - return { ...result, isMutable }; + return { ...projectData!, isMutable }; }), create: procedure