mirror of
				https://github.com/khairul169/code-share.git
				synced 2025-10-31 03:39:34 +07:00 
			
		
		
		
	feat: update layout
This commit is contained in:
		
							parent
							
								
									f1f6d65798
								
							
						
					
					
						commit
						e007dd4ac4
					
				| @ -18,7 +18,7 @@ const BaseInput = forwardRef<HTMLInputElement, BaseInputProps>( | |||||||
|         type={type} |         type={type} | ||||||
|         className={cn( |         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", |           "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} |         ref={ref} | ||||||
|         {...props} |         {...props} | ||||||
|  | |||||||
| @ -58,26 +58,28 @@ const ResizablePanel = forwardRef((props: ResizablePanelProps, ref: any) => { | |||||||
|   ); |   ); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const ResizableHandle = ({ | type ResizableHandleProps = React.ComponentProps< | ||||||
|   withHandle, |   typeof ResizablePrimitive.PanelResizeHandle | ||||||
|   className, | > & { | ||||||
|   ...props |  | ||||||
| }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & { |  | ||||||
|   withHandle?: boolean; |   withHandle?: boolean; | ||||||
| }) => ( | }; | ||||||
|   <ResizablePrimitive.PanelResizeHandle | 
 | ||||||
|     className={cn( | const ResizableHandle = (props: ResizableHandleProps) => { | ||||||
|       "relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90", |   const { withHandle, className, ...restProps } = props; | ||||||
|       className |   return ( | ||||||
|     )} |     <ResizablePrimitive.PanelResizeHandle | ||||||
|     {...props} |       className={cn( | ||||||
|   > |         "relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90", | ||||||
|     {withHandle && ( |         withHandle && "w-3 bg-black group", | ||||||
|       <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border border-slate-200 bg-slate-200 dark:border-slate-800 dark:bg-slate-800"> |         className | ||||||
|         <GripVertical className="h-2.5 w-2.5" /> |       )} | ||||||
|       </div> |       {...restProps} | ||||||
|     )} |     > | ||||||
|   </ResizablePrimitive.PanelResizeHandle> |       {withHandle && ( | ||||||
| ); |         <div className="z-10 flex h-full max-h-[100px] w-1.5 items-center justify-center rounded-full bg-white/30 group-hover:bg-white/40"></div> | ||||||
|  |       )} | ||||||
|  |     </ResizablePrimitive.PanelResizeHandle> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; | ||||||
|  | |||||||
| @ -16,17 +16,9 @@ type Props = { | |||||||
|   onChange?: (idx: number) => void; |   onChange?: (idx: number) => void; | ||||||
|   onClose?: (idx: number) => void; |   onClose?: (idx: number) => void; | ||||||
|   className?: string; |   className?: string; | ||||||
|   containerClassName?: string; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const Tabs = ({ | const Tabs = ({ tabs, current = 0, onChange, onClose, className }: Props) => { | ||||||
|   tabs, |  | ||||||
|   current = 0, |  | ||||||
|   onChange, |  | ||||||
|   onClose, |  | ||||||
|   className, |  | ||||||
|   containerClassName, |  | ||||||
| }: Props) => { |  | ||||||
|   const tabContainerRef = useRef<HTMLDivElement>(null); |   const tabContainerRef = useRef<HTMLDivElement>(null); | ||||||
| 
 | 
 | ||||||
|   const onWheel = (e: WheelEvent) => { |   const onWheel = (e: WheelEvent) => { | ||||||
| @ -64,43 +56,44 @@ const Tabs = ({ | |||||||
|     container.scrollTo({ left: scrollX, behavior: "smooth" }); |     container.scrollTo({ left: scrollX, behavior: "smooth" }); | ||||||
|   }, [tabs, current]); |   }, [tabs, current]); | ||||||
| 
 | 
 | ||||||
|   const tabView = useMemo(() => { |   return tabs.length > 0 ? ( | ||||||
|     const tab = tabs[current]; |     <nav | ||||||
|     const element = tab?.render ? tab.render() : null; |       ref={tabContainerRef} | ||||||
|     return element; |  | ||||||
|   }, [tabs, current]); |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <div |  | ||||||
|       className={cn( |       className={cn( | ||||||
|         "w-full flex flex-col items-stretch bg-slate-800", |         "flex items-stretch overflow-x-auto h-10 gap-1 hide-scrollbar", | ||||||
|         className |         className | ||||||
|       )} |       )} | ||||||
|     > |     > | ||||||
|       {tabs.length > 0 ? ( |       {tabs.map((tab, idx) => ( | ||||||
|         <nav |         <TabItem | ||||||
|           ref={tabContainerRef} |           key={idx} | ||||||
|           className="flex items-stretch overflow-x-auto w-full h-10 min-h-10 hide-scrollbar" |           index={idx} | ||||||
|         > |           title={tab.title} | ||||||
|           {tabs.map((tab, idx) => ( |           icon={tab.icon} | ||||||
|             <TabItem |           isActive={idx === current} | ||||||
|               key={idx} |           onSelect={() => onChange && onChange(idx)} | ||||||
|               index={idx} |           onClose={!tab.locked && onClose ? () => onClose(idx) : null} | ||||||
|               title={tab.title} |         /> | ||||||
|               icon={tab.icon} |       ))} | ||||||
|               isActive={idx === current} |     </nav> | ||||||
|               onSelect={() => onChange && onChange(idx)} |   ) : null; | ||||||
|               onClose={!tab.locked && onClose ? () => onClose(idx) : null} | }; | ||||||
|             /> |  | ||||||
|           ))} |  | ||||||
|         </nav> |  | ||||||
|       ) : null} |  | ||||||
| 
 | 
 | ||||||
|       <main className={cn("flex-1 overflow-hidden", containerClassName)}> | type TabViewProps = { | ||||||
|         {tabView} |   tabs: Tab[]; | ||||||
|       </main> |   current: number; | ||||||
|     </div> |   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 <main className={cn("overflow-hidden", className)}>{tabView}</main>; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| type TabItemProps = { | type TabItemProps = { | ||||||
| @ -128,8 +121,8 @@ const TabItem = ({ | |||||||
|     <div |     <div | ||||||
|       data-idx={index} |       data-idx={index} | ||||||
|       className={cn( |       className={cn( | ||||||
|         "group border-b-2 border-transparent truncate flex-shrink-0 text-white/70 transition-all hover:text-white text-center max-w-[140px] md:max-w-[180px] text-sm flex items-center gap-0 relative z-[1]", |         "group truncate flex-shrink-0 h-full bg-white/10 rounded-lg text-white/70 transition-all hover:text-white text-center max-w-[140px] md:max-w-[180px] text-sm flex items-center gap-0 relative z-[1]", | ||||||
|         isActive ? "border-slate-500 text-white" : "" |         isActive ? "bg-white/20 text-white" : "" | ||||||
|       )} |       )} | ||||||
|       onClick={onSelect} |       onClick={onSelect} | ||||||
|     > |     > | ||||||
|  | |||||||
| @ -23,12 +23,9 @@ const ViewProjectPage = () => { | |||||||
|   return ( |   return ( | ||||||
|     <ProjectContext.Provider value={{ project, isCompact, isEmbed }}> |     <ProjectContext.Provider value={{ project, isCompact, isEmbed }}> | ||||||
|       <ResizablePanelGroup |       <ResizablePanelGroup | ||||||
|         autoSaveId="main-panel" |         autoSaveId={!isEmbed ? "main-panel" : null} | ||||||
|         direction={{ sm: "vertical", md: "horizontal" }} |         direction={{ sm: "vertical", md: "horizontal" }} | ||||||
|         className={cn( |         className={cn("w-full !h-screen")} | ||||||
|           "w-full !h-dvh bg-slate-600", |  | ||||||
|           !isCompact && !isEmbed ? "md:p-4" : "" |  | ||||||
|         )} |  | ||||||
|       > |       > | ||||||
|         <ResizablePanel |         <ResizablePanel | ||||||
|           defaultSize={hidePreview ? 100 : 60} |           defaultSize={hidePreview ? 100 : 60} | ||||||
| @ -38,14 +35,7 @@ const ViewProjectPage = () => { | |||||||
|         > |         > | ||||||
|           <Editor /> |           <Editor /> | ||||||
|         </ResizablePanel> |         </ResizablePanel> | ||||||
|         <ResizableHandle |         <ResizableHandle withHandle={!isEmbed && !isCompact} /> | ||||||
|           withHandle |  | ||||||
|           className={ |  | ||||||
|             !isCompact && !isEmbed |  | ||||||
|               ? "bg-slate-800 md:bg-transparent hover:bg-slate-500 transition-colors md:mx-1 w-2 md:data-[panel-group-direction=vertical]:h-2 md:rounded-lg" |  | ||||||
|               : "bg-slate-800" |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
|         <WebPreview |         <WebPreview | ||||||
|           defaultSize={40} |           defaultSize={40} | ||||||
|           defaultCollapsed={hidePreview} |           defaultCollapsed={hidePreview} | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ export const data = async (ctx: PageContext) => { | |||||||
|     throw render(404, "Project not found!"); |     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) => |   const initialFiles = files.filter((i) => | ||||||
|     filesParam != null ? filesParam.includes(i.path) : i.isPinned |     filesParam != null ? filesParam.includes(i.path) : i.isPinned | ||||||
|   ); |   ); | ||||||
|  | |||||||
| @ -4,12 +4,11 @@ import { | |||||||
|   ResizablePanel, |   ResizablePanel, | ||||||
|   ResizablePanelGroup, |   ResizablePanelGroup, | ||||||
| } from "~/components/ui/resizable"; | } 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 FileViewer from "./file-viewer"; | ||||||
| import trpc from "~/lib/trpc"; | import trpc from "~/lib/trpc"; | ||||||
| import EditorContext from "../context/editor"; | import EditorContext from "../context/editor"; | ||||||
| import type { FileSchema } from "~/server/db/schema/file"; | import type { FileSchema } from "~/server/db/schema/file"; | ||||||
| import Panel from "~/components/ui/panel"; |  | ||||||
| import { useProjectContext } from "../context/project"; | import { useProjectContext } from "../context/project"; | ||||||
| import Sidebar from "./sidebar"; | import Sidebar from "./sidebar"; | ||||||
| import ConsoleLogger from "./console-logger"; | import ConsoleLogger from "./console-logger"; | ||||||
| @ -22,11 +21,14 @@ import SettingsDialog from "./settings-dialog"; | |||||||
| import FileIcon from "~/components/ui/file-icon"; | import FileIcon from "~/components/ui/file-icon"; | ||||||
| import { api } from "~/lib/api"; | import { api } from "~/lib/api"; | ||||||
| import { useMutation } from "@tanstack/react-query"; | 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 Editor = () => { | ||||||
|   const { project, initialFiles } = useData<Data>(); |   const { project, initialFiles } = useData<Data>(); | ||||||
|   const trpcUtils = trpc.useUtils(); |   const trpcUtils = trpc.useUtils(); | ||||||
|   const projectCtx = useProjectContext(); |   const { isEmbed } = useProjectContext(); | ||||||
|   const [breakpoint] = useBreakpoint(); |   const [breakpoint] = useBreakpoint(); | ||||||
| 
 | 
 | ||||||
|   const [curTabIdx, setCurTabIdx] = useState(0); |   const [curTabIdx, setCurTabIdx] = useState(0); | ||||||
| @ -178,8 +180,7 @@ const Editor = () => { | |||||||
|     return tabs; |     return tabs; | ||||||
|   }, [curOpenFiles, openedFiles, breakpoint]); |   }, [curOpenFiles, openedFiles, breakpoint]); | ||||||
| 
 | 
 | ||||||
|   const PanelComponent = |   const currentTab = Math.min(Math.max(curTabIdx, 0), tabs.length - 1); | ||||||
|     !projectCtx.isCompact || !projectCtx.isEmbed ? Panel : "div"; |  | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <EditorContext.Provider |     <EditorContext.Provider | ||||||
| @ -189,9 +190,9 @@ const Editor = () => { | |||||||
|         onDeleteFile, |         onDeleteFile, | ||||||
|       }} |       }} | ||||||
|     > |     > | ||||||
|       <PanelComponent className="h-full relative flex flex-col"> |       <div className="h-full relative flex flex-col"> | ||||||
|         <ResizablePanelGroup |         <ResizablePanelGroup | ||||||
|           autoSaveId="veditor-panel" |           autoSaveId={!isEmbed ? "veditor-panel" : null} | ||||||
|           direction="horizontal" |           direction="horizontal" | ||||||
|           className="flex-1 order-2 md:order-1" |           className="flex-1 order-2 md:order-1" | ||||||
|         > |         > | ||||||
| @ -206,15 +207,36 @@ const Editor = () => { | |||||||
|           <ResizableHandle className="w-0" /> |           <ResizableHandle className="w-0" /> | ||||||
| 
 | 
 | ||||||
|           <ResizablePanel defaultSize={{ md: 100, lg: 75 }}> |           <ResizablePanel defaultSize={{ md: 100, lg: 75 }}> | ||||||
|             <ResizablePanelGroup autoSaveId="code-editor" direction="vertical"> |             <ResizablePanelGroup | ||||||
|  |               autoSaveId={!isEmbed ? "code-editor" : null} | ||||||
|  |               direction="vertical" | ||||||
|  |             > | ||||||
|               <ResizablePanel defaultSize={{ sm: 100, md: 80 }} minSize={20}> |               <ResizablePanel defaultSize={{ sm: 100, md: 80 }} minSize={20}> | ||||||
|                 <Tabs |                 <div className="w-full h-full flex flex-col items-stretch bg-slate-800"> | ||||||
|                   tabs={tabs} |                   <div className="flex items-center"> | ||||||
|                   current={Math.min(Math.max(curTabIdx, 0), tabs.length - 1)} |                     <Tabs | ||||||
|                   onChange={setCurTabIdx} |                       tabs={tabs} | ||||||
|                   onClose={onCloseFile} |                       current={currentTab} | ||||||
|                   className="h-full" |                       onChange={setCurTabIdx} | ||||||
|                 /> |                       onClose={onCloseFile} | ||||||
|  |                       className="flex-1 p-1 md:h-12 md:p-1.5 md:gap-1.5" | ||||||
|  |                     /> | ||||||
|  |                     <Button | ||||||
|  |                       variant="ghost" | ||||||
|  |                       className="dark:hover:bg-slate-700" | ||||||
|  |                       onClick={() => | ||||||
|  |                         window.open(`${BASE_URL}/${project.slug}`, "_blank") | ||||||
|  |                       } | ||||||
|  |                     > | ||||||
|  |                       <FaExternalLinkAlt /> | ||||||
|  |                     </Button> | ||||||
|  |                   </div> | ||||||
|  |                   <TabView | ||||||
|  |                     tabs={tabs} | ||||||
|  |                     current={currentTab} | ||||||
|  |                     className="flex-1" | ||||||
|  |                   /> | ||||||
|  |                 </div> | ||||||
|               </ResizablePanel> |               </ResizablePanel> | ||||||
| 
 | 
 | ||||||
|               {breakpoint >= 2 ? ( |               {breakpoint >= 2 ? ( | ||||||
| @ -236,7 +258,7 @@ const Editor = () => { | |||||||
|         </ResizablePanelGroup> |         </ResizablePanelGroup> | ||||||
| 
 | 
 | ||||||
|         <StatusBar className="order-1 md:order-2" /> |         <StatusBar className="order-1 md:order-2" /> | ||||||
|       </PanelComponent> |       </div> | ||||||
| 
 | 
 | ||||||
|       <SettingsDialog /> |       <SettingsDialog /> | ||||||
|     </EditorContext.Provider> |     </EditorContext.Provider> | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ const FileListing = () => { | |||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Fragment> |     <Fragment> | ||||||
|       <div className="h-10 flex items-center pl-4 pr-1"> |       <div className="h-10 md:h-12 flex items-center pl-4 pr-1"> | ||||||
|         <p className="text-xs uppercase truncate flex-1">{project.title}</p> |         <p className="text-xs uppercase truncate flex-1">{project.title}</p> | ||||||
|         <ActionButton |         <ActionButton | ||||||
|           icon={FiFilePlus} |           icon={FiFilePlus} | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ type Props = { | |||||||
| const FileViewer = ({ id }: Props) => { | const FileViewer = ({ id }: Props) => { | ||||||
|   const { project } = useProjectContext(); |   const { project } = useProjectContext(); | ||||||
|   const { initialFiles } = useData<Data>(); |   const { initialFiles } = useData<Data>(); | ||||||
|   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, { |   const { data, isLoading, refetch } = trpc.file.getById.useQuery(id, { | ||||||
|     initialData, |     initialData, | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ import { | |||||||
|   DialogHeader, |   DialogHeader, | ||||||
|   DialogTitle, |   DialogTitle, | ||||||
| } from "~/components/ui/dialog"; | } from "~/components/ui/dialog"; | ||||||
| import { useMemo, useState } from "react"; | import { Fragment, useMemo, useState } from "react"; | ||||||
| import { useProjectContext } from "../context/project"; | import { useProjectContext } from "../context/project"; | ||||||
| import { useForm, useFormReturn } from "~/hooks/useForm"; | import { useForm, useFormReturn } from "~/hooks/useForm"; | ||||||
| import Input from "~/components/ui/input"; | import Input from "~/components/ui/input"; | ||||||
| @ -21,8 +21,10 @@ import { | |||||||
| import trpc from "~/lib/trpc"; | import trpc from "~/lib/trpc"; | ||||||
| import { toast } from "~/lib/utils"; | import { toast } from "~/lib/utils"; | ||||||
| import Checkbox from "~/components/ui/checkbox"; | 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 { navigate } from "vike/client/router"; | ||||||
|  | import { useFieldArray } from "react-hook-form"; | ||||||
|  | import { FaTrashAlt } from "react-icons/fa"; | ||||||
| 
 | 
 | ||||||
| const defaultValues: ProjectSettingsSchema = { | const defaultValues: ProjectSettingsSchema = { | ||||||
|   title: "", |   title: "", | ||||||
| @ -102,8 +104,9 @@ const SettingsDialog = () => { | |||||||
|             tabs={tabs} |             tabs={tabs} | ||||||
|             current={tab} |             current={tab} | ||||||
|             onChange={setTab} |             onChange={setTab} | ||||||
|             containerClassName="mt-4" |             className="rounded-md overflow-hidden" | ||||||
|           /> |           /> | ||||||
|  |           <TabView current={tab} tabs={tabs} className="mt-4" /> | ||||||
| 
 | 
 | ||||||
|           <div className="flex flex-col sm:flex-row items-stretch sm:items-center sm:justify-end gap-4 mt-8"> |           <div className="flex flex-col sm:flex-row items-stretch sm:items-center sm:justify-end gap-4 mt-8"> | ||||||
|             <Button variant="outline" onClick={onClose}> |             <Button variant="outline" onClick={onClose}> | ||||||
| @ -157,17 +160,55 @@ const CSSTab = ({ form }: TabProps) => { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const JSTab = ({ form }: TabProps) => { | const JSTab = ({ form }: TabProps) => { | ||||||
|  |   const packages = useFieldArray({ | ||||||
|  |     control: form.control, | ||||||
|  |     name: "settings.js.packages", | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="space-y-3"> |     <div> | ||||||
|       <Select |       <Select | ||||||
|         form={form} |         form={form} | ||||||
|         name="settings.js.transpiler" |         name="settings.js.transpiler" | ||||||
|         label="Transpiler" |         label="Transpiler" | ||||||
|         items={jsTranspilerList} |         items={jsTranspilerList} | ||||||
|       /> |       /> | ||||||
|       <p className="text-sm"> |       <p className="text-sm mt-1"> | ||||||
|         * Set transpiler to <strong>SWC</strong> to use JSX |         * Set transpiler to <strong>SWC</strong> to use JSX | ||||||
|       </p> |       </p> | ||||||
|  | 
 | ||||||
|  |       <p className="text-sm mt-8">External Packages</p> | ||||||
|  |       <div> | ||||||
|  |         {packages.fields.map((field, index) => ( | ||||||
|  |           <div key={field.id} className="flex items-center gap-2 mt-2"> | ||||||
|  |             <Input | ||||||
|  |               key={field.id} | ||||||
|  |               form={form} | ||||||
|  |               name={`settings.js.packages.${index}.name`} | ||||||
|  |               placeholder={`Package alias`} | ||||||
|  |               className="flex-1" | ||||||
|  |             /> | ||||||
|  | 
 | ||||||
|  |             <Input | ||||||
|  |               key={field.id} | ||||||
|  |               form={form} | ||||||
|  |               name={`settings.js.packages.${index}.url`} | ||||||
|  |               placeholder={`URL`} | ||||||
|  |               className="flex-1" | ||||||
|  |             /> | ||||||
|  | 
 | ||||||
|  |             <Button size="icon" onClick={() => packages.remove(index)}> | ||||||
|  |               <FaTrashAlt /> | ||||||
|  |             </Button> | ||||||
|  |           </div> | ||||||
|  |         ))} | ||||||
|  |       </div> | ||||||
|  |       <Button | ||||||
|  |         onClick={() => packages.append({ name: "", url: "" })} | ||||||
|  |         className="mt-3" | ||||||
|  |       > | ||||||
|  |         Add Package | ||||||
|  |       </Button> | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -17,11 +17,11 @@ import { useProjectContext } from "../context/project"; | |||||||
| 
 | 
 | ||||||
| const StatusBar = ({ className }: React.ComponentProps<"div">) => { | const StatusBar = ({ className }: React.ComponentProps<"div">) => { | ||||||
|   const { user, urlPathname } = usePageContext(); |   const { user, urlPathname } = usePageContext(); | ||||||
|   const { isCompact, project } = useProjectContext(); |   const { isEmbed, project } = useProjectContext(); | ||||||
|   const sidebarExpanded = useStore(sidebarStore, (i) => i.expanded); |   const sidebarExpanded = useStore(sidebarStore, (i) => i.expanded); | ||||||
|   const previewExpanded = useStore(previewStore, (i) => i.open); |   const previewExpanded = useStore(previewStore, (i) => i.open); | ||||||
| 
 | 
 | ||||||
|   if (isCompact) { |   if (isEmbed) { | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| /* eslint-disable react/display-name */ | /* eslint-disable react/display-name */ | ||||||
| import Panel from "~/components/ui/panel"; |  | ||||||
| import { ComponentProps, useCallback, useEffect, useRef } from "react"; | import { ComponentProps, useCallback, useEffect, useRef } from "react"; | ||||||
| import { useProjectContext } from "../context/project"; | import { useProjectContext } from "../context/project"; | ||||||
| import { Button } from "~/components/ui/button"; | import { Button } from "~/components/ui/button"; | ||||||
| @ -56,8 +55,6 @@ const WebPreview = ({ url, ...props }: WebPreviewProps) => { | |||||||
|     refresh(); |     refresh(); | ||||||
|   }, [refresh, togglePanel]); |   }, [refresh, togglePanel]); | ||||||
| 
 | 
 | ||||||
|   const PanelComponent = !project.isCompact || !project.isEmbed ? Panel : "div"; |  | ||||||
| 
 |  | ||||||
|   return ( |   return ( | ||||||
|     <ResizablePanel |     <ResizablePanel | ||||||
|       ref={panelRef} |       ref={panelRef} | ||||||
| @ -65,8 +62,8 @@ const WebPreview = ({ url, ...props }: WebPreviewProps) => { | |||||||
|       onCollapse={() => previewStore.setState({ open: false })} |       onCollapse={() => previewStore.setState({ open: false })} | ||||||
|       {...props} |       {...props} | ||||||
|     > |     > | ||||||
|       <PanelComponent className="h-full flex flex-col bg-slate-800"> |       <div className="h-full flex flex-col bg-slate-800"> | ||||||
|         <div className="h-10 hidden md:flex items-center pl-4"> |         <div className="h-10 md:h-12 hidden md:flex items-center pl-4"> | ||||||
|           <p className="flex-1 truncate text-xs uppercase">Preview</p> |           <p className="flex-1 truncate text-xs uppercase">Preview</p> | ||||||
|           <Button |           <Button | ||||||
|             variant="ghost" |             variant="ghost" | ||||||
| @ -75,13 +72,16 @@ const WebPreview = ({ url, ...props }: WebPreviewProps) => { | |||||||
|           > |           > | ||||||
|             <FaRedo /> |             <FaRedo /> | ||||||
|           </Button> |           </Button> | ||||||
|           <Button | 
 | ||||||
|             variant="ghost" |           {!project.isEmbed ? ( | ||||||
|             className="dark:hover:bg-slate-700" |             <Button | ||||||
|             onClick={() => window.open(url || "#", "_blank")} |               variant="ghost" | ||||||
|           > |               className="dark:hover:bg-slate-700" | ||||||
|             <FaExternalLinkAlt /> |               onClick={() => window.open(url || "#", "_blank")} | ||||||
|           </Button> |             > | ||||||
|  |               <FaExternalLinkAlt /> | ||||||
|  |             </Button> | ||||||
|  |           ) : null} | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         {url != null ? ( |         {url != null ? ( | ||||||
| @ -92,7 +92,7 @@ const WebPreview = ({ url, ...props }: WebPreviewProps) => { | |||||||
|             sandbox="allow-scripts allow-forms" |             sandbox="allow-scripts allow-forms" | ||||||
|           /> |           /> | ||||||
|         ) : null} |         ) : null} | ||||||
|       </PanelComponent> |       </div> | ||||||
|     </ResizablePanel> |     </ResizablePanel> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -55,20 +55,20 @@ const projectRouter = router({ | |||||||
|         isNull(project.deletedAt) |         isNull(project.deletedAt) | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|       const result = await db.query.project.findFirst({ |       const projectData = await db.query.project.findFirst({ | ||||||
|         where, |         where, | ||||||
|         with: { |         with: { | ||||||
|           user: { columns: { password: false } }, |           user: { columns: { password: false } }, | ||||||
|         }, |         }, | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       if (!hasPermission(ctx, result, "r")) { |       if (!projectData || !hasPermission(ctx, projectData, "r")) { | ||||||
|         throw new TRPCError({ code: "FORBIDDEN" }); |         return null; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       const isMutable = hasPermission(ctx, result, "w"); |       const isMutable = hasPermission(ctx, projectData, "w"); | ||||||
| 
 | 
 | ||||||
|       return { ...result, isMutable }; |       return { ...projectData!, isMutable }; | ||||||
|     }), |     }), | ||||||
| 
 | 
 | ||||||
|   create: procedure |   create: procedure | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user