231 lines
6.1 KiB
TypeScript
Raw Normal View History

2024-02-23 23:57:14 +00:00
import { useCallback, useEffect, useMemo, useState } from "react";
2024-02-21 02:01:35 +07:00
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
2024-02-22 12:14:58 +00:00
} from "~/components/ui/resizable";
import Tabs, { Tab } from "~/components/ui/tabs";
2024-02-21 02:01:35 +07:00
import FileViewer from "./file-viewer";
2024-02-22 12:14:58 +00:00
import trpc from "~/lib/trpc";
2024-02-21 02:01:35 +07:00
import EditorContext from "../context/editor";
2024-02-22 12:14:58 +00:00
import type { FileSchema } from "~/server/db/schema/file";
import Panel from "~/components/ui/panel";
2024-02-21 02:01:35 +07:00
import { useProjectContext } from "../context/project";
import Sidebar from "./sidebar";
import ConsoleLogger from "./console-logger";
import { useData } from "~/renderer/hooks";
import { Data } from "../+data";
2024-02-22 19:49:13 +00:00
import { useBreakpoint } from "~/hooks/useBreakpoint";
2024-02-23 23:57:14 +00:00
import StatusBar from "./status-bar";
import { FiTerminal } from "react-icons/fi";
import SettingsDialog from "./settings-dialog";
import FileIcon from "~/components/ui/file-icon";
2024-02-21 02:01:35 +07:00
const Editor = () => {
2024-02-24 00:27:52 +00:00
const { project, initialFiles } = useData<Data>();
2024-02-21 02:01:35 +07:00
const trpcUtils = trpc.useUtils();
2024-02-22 20:59:20 +00:00
const projectCtx = useProjectContext();
2024-02-22 19:49:13 +00:00
const [breakpoint] = useBreakpoint();
2024-02-21 02:01:35 +07:00
const [curTabIdx, setCurTabIdx] = useState(0);
const [curOpenFiles, setOpenFiles] = useState<number[]>(
2024-02-24 00:27:52 +00:00
initialFiles.map((i) => i.id)
2024-02-21 02:01:35 +07:00
);
2024-02-21 02:01:35 +07:00
const openedFilesData = trpc.file.getAll.useQuery(
2024-02-22 20:59:20 +00:00
{ projectId: project.id, id: curOpenFiles },
2024-02-24 00:27:52 +00:00
{ enabled: curOpenFiles.length > 0, initialData: initialFiles }
2024-02-21 02:01:35 +07:00
);
2024-02-24 00:27:52 +00:00
const [openedFiles, setOpenedFiles] = useState<any[]>(initialFiles);
2024-02-21 02:01:35 +07:00
const deleteFile = trpc.file.delete.useMutation({
onSuccess: (file) => {
trpcUtils.file.getAll.invalidate();
onFileChanged(file);
const openFileIdx = curOpenFiles.indexOf(file.id);
if (openFileIdx >= 0) {
onCloseFile(openFileIdx);
}
},
});
useEffect(() => {
2024-02-24 00:27:52 +00:00
if (!initialFiles?.length || curOpenFiles.length > 0) {
2024-02-21 02:01:35 +07:00
return;
}
2024-02-24 00:27:52 +00:00
initialFiles.forEach((file) => {
2024-02-21 02:01:35 +07:00
onOpenFile(file.id, false);
});
return () => {
setOpenFiles([]);
};
2024-02-21 02:01:35 +07:00
// eslint-disable-next-line react-hooks/exhaustive-deps
2024-02-24 00:27:52 +00:00
}, [initialFiles]);
2024-02-21 02:01:35 +07:00
useEffect(() => {
if (openedFilesData.data) {
setOpenedFiles(openedFilesData.data);
}
}, [openedFilesData.data]);
// useEffect(() => {
// // start API sandbox
// api(`/sandbox/${project.slug}/start`, { method: "POST" }).catch(() => {});
// }, [project]);
2024-02-26 10:48:17 +00:00
2024-02-21 02:01:35 +07:00
const onOpenFile = useCallback(
(fileId: number, autoSwitchTab = true) => {
const idx = curOpenFiles.indexOf(fileId);
if (idx >= 0) {
return setCurTabIdx(idx);
}
setOpenFiles((state) => {
if (autoSwitchTab) {
setCurTabIdx(state.length);
}
return [...state, fileId];
});
},
[curOpenFiles]
);
const onDeleteFile = useCallback(
(fileId: number) => {
if (
window.confirm("Are you sure want to delete this files?") &&
!deleteFile.isPending
) {
deleteFile.mutate(fileId);
}
},
[deleteFile]
);
const onCloseFile = useCallback(
(idx: number) => {
const _f = [...curOpenFiles];
_f.splice(idx, 1);
setOpenFiles(_f);
if (curTabIdx === idx) {
setCurTabIdx(Math.max(0, idx - 1));
}
},
[curOpenFiles, curTabIdx]
);
const onFileChanged = useCallback(
(_file: Omit<FileSchema, "content">) => {
openedFilesData.refetch();
},
[openedFilesData]
);
2024-02-23 23:57:14 +00:00
const tabs = useMemo(() => {
let tabs: Tab[] = [];
// opened files
tabs = tabs.concat(
curOpenFiles.map((fileId) => {
const fileData = openedFiles?.find((i) => i.id === fileId);
const filename = fileData?.filename || "...";
2024-02-23 23:57:14 +00:00
return {
title: filename,
icon: <FileIcon file={{ isDirectory: false, filename }} />,
2024-02-23 23:57:14 +00:00
render: () => <FileViewer id={fileId} />,
};
})
);
// show console tab on mobile
if (breakpoint < 2) {
tabs.push({
title: "Console",
icon: <FiTerminal />,
render: () => <ConsoleLogger />,
locked: true,
});
}
2024-02-21 02:01:35 +07:00
// tabs.push({
// title: "API",
// icon: <FiServer />,
// render: () => <APIManager />,
// locked: true,
// });
2024-02-26 10:48:17 +00:00
2024-02-23 23:57:14 +00:00
return tabs;
}, [curOpenFiles, openedFiles, breakpoint]);
2024-02-21 02:01:35 +07:00
const PanelComponent =
!projectCtx.isCompact || !projectCtx.isEmbed ? Panel : "div";
2024-02-21 02:01:35 +07:00
return (
<EditorContext.Provider
value={{
onOpenFile,
onFileChanged,
onDeleteFile,
}}
>
2024-02-23 23:57:14 +00:00
<PanelComponent className="h-full relative flex flex-col">
<ResizablePanelGroup
autoSaveId="veditor-panel"
direction="horizontal"
className="flex-1 order-2 md:order-1"
>
<Sidebar
defaultSize={{ md: 50, lg: 25 }}
defaultCollapsed={{ md: true, lg: false }}
2024-02-21 02:01:35 +07:00
minSize={10}
collapsible
collapsedSize={0}
2024-02-23 23:57:14 +00:00
/>
2024-02-21 02:01:35 +07:00
2024-02-24 00:27:52 +00:00
<ResizableHandle className="w-0" />
2024-02-21 02:01:35 +07:00
<ResizablePanel defaultSize={{ md: 100, lg: 75 }}>
<ResizablePanelGroup autoSaveId="code-editor" direction="vertical">
2024-02-22 19:49:13 +00:00
<ResizablePanel defaultSize={{ sm: 100, md: 80 }} minSize={20}>
2024-02-21 02:01:35 +07:00
<Tabs
2024-02-23 23:57:14 +00:00
tabs={tabs}
current={Math.min(Math.max(curTabIdx, 0), tabs.length - 1)}
2024-02-21 02:01:35 +07:00
onChange={setCurTabIdx}
onClose={onCloseFile}
className="h-full"
2024-02-21 02:01:35 +07:00
/>
</ResizablePanel>
2024-02-22 19:49:13 +00:00
{breakpoint >= 2 ? (
<>
2024-02-24 00:27:52 +00:00
<ResizableHandle className="!h-0" />
2024-02-22 19:49:13 +00:00
<ResizablePanel
defaultSize={{ sm: 0, md: 20 }}
collapsible
collapsedSize={0}
minSize={10}
>
<ConsoleLogger />
</ResizablePanel>
</>
) : null}
2024-02-21 02:01:35 +07:00
</ResizablePanelGroup>
</ResizablePanel>
</ResizablePanelGroup>
2024-02-23 23:57:14 +00:00
<StatusBar className="order-1 md:order-2" />
2024-02-21 02:01:35 +07:00
</PanelComponent>
<SettingsDialog />
2024-02-21 02:01:35 +07:00
</EditorContext.Provider>
);
};
export default Editor;