mirror of
https://github.com/khairul169/code-share.git
synced 2025-04-29 17:19:37 +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