import { ApiError } from "@/api"; import { CodeMirrorEditor } from "@/components/codemirror-editor"; import { Button } from "@/components/ui/button.tsx"; import { Dialog, DialogTrigger, } from "@/components/ui/dialog"; import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, } from "@/components/ui/sidebar.tsx"; import { cn } from "@/lib/utils"; import { Link } from "@tanstack/react-router"; import { ArrowLeft, ChevronDown, ChevronUp, Hammer, Loader2, } from "lucide-react"; import { useEffect, useRef } from "react"; import { useStore } from "zustand"; import { useTemplate, useTemplateFile, useUpdateTemplateFile } from "./api"; import { BuildTemplateDialog } from "./build-template-dialog"; import { templateEditorRoute } from "./routes"; import { type TemplateEditorStore, TemplateEditorStoreContext, createTemplateEditorStore, useTemplateEditorStore, } from "./template-editor-store"; import type { Template } from "./types"; function TemplateEditor() { const { templateName, _splat } = templateEditorRoute.useParams(); const { data: template, isLoading, error } = useTemplate(templateName); if (isLoading) { return ( ); } if (error || !template) { if (error === ApiError.NotFound) { return ( Template does not exist Create template ); } let message = ""; switch (error) { case ApiError.Network: message = "We are having trouble contacting the server."; break; default: message = "An error occurred on our end."; break; } return ( {message} Refresh ); } return <_TemplateEditor template={template} currentFilePath={_splat ?? ""} />; } function _TemplateEditor({ template, currentFilePath, }: { template: Template; currentFilePath: string }) { const store = useRef(null); if (!store.current) { store.current = createTemplateEditorStore({ template, currentFilePath }); } const setCurrentFilePath = useStore( store.current, (state) => state.setCurrentFilePath, ); useEffect(() => { setCurrentFilePath(currentFilePath); }, [setCurrentFilePath, currentFilePath]); return ( ); } function Editor() { const templateName = useTemplateEditorStore((state) => state.template.name); const currentPath = useTemplateEditorStore((state) => state.currentFilePath); const { isLoading, data: fileContent, error, } = useTemplateFile(templateName, currentPath); const saveTimeout = useRef | null>(null); const { updateTemplateFile } = useUpdateTemplateFile(templateName); useEffect( () => () => { if (saveTimeout.current) { clearTimeout(saveTimeout.current); } }, [], ); if (!currentPath) { return ( Select a file from the sidebar ); } if (isLoading) { return ( Loading file⦠); } if (error || fileContent === undefined) { let message = ""; switch (error) { case ApiError.NotFound: message = "This file does not exist in the template."; break; case ApiError.Network: message = "Having trouble contacting the server."; break; default: message = "An error occured on our end."; } return ( {message} ); } function onValueChanged(path: string, value: string) { if (saveTimeout.current) { clearTimeout(saveTimeout.current); } saveTimeout.current = setTimeout(() => { updateTemplateFile(path, value); }, 500); } return ( ); } function EditorTopBar() { const currentFilePath = useTemplateEditorStore( (state) => state.currentFilePath, ); const isBuildInProgress = useTemplateEditorStore( (state) => state.isBuildInProgress, ); return ( {currentFilePath} {isBuildInProgress ? ( ) : ( )}{" "} Build ); } function EditorSidebar() { const templateName = useTemplateEditorStore((state) => state.template.name); return ( All templates {templateName} Files ); } function EditorSidebarFileTree() { const template = useTemplateEditorStore((state) => state.template); const currentFilePath = useTemplateEditorStore( (state) => state.currentFilePath, ); return ( {Object.values(template.files).map((file) => ( {file.path} ))} ); } function TemplateBuildOutputPanel() { const isBuildOutputVisible = useTemplateEditorStore( (state) => state.isBuildOutputVisible, ); const toggleBuildOutput = useTemplateEditorStore( (state) => state.toggleBuildOutput, ); return ( Build output {isBuildOutputVisible ? : } {isBuildOutputVisible ? : null} ); } function BuildOutput() { const buildOutput = useTemplateEditorStore((state) => state.buildOutput); const el = useRef(null); // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { if (el.current) { el.current.scrollTo(0, el.current.scrollHeight); } }, [buildOutput]); return ( {buildOutput} ); } export { TemplateEditor };
Template does not exist
{message}
Select a file from the sidebar
Loading fileā¦
{currentFilePath}
{templateName}
Build output
{buildOutput}