diff --git a/pages/project/@slug/components/editor.tsx b/pages/project/@slug/components/editor.tsx
index b5c2b63..399de08 100644
--- a/pages/project/@slug/components/editor.tsx
+++ b/pages/project/@slug/components/editor.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
import {
ResizableHandle,
ResizablePanel,
@@ -10,26 +10,21 @@ 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 { previewStore } from "../stores/web-preview";
import { useProjectContext } from "../context/project";
-import { ImperativePanelHandle } from "react-resizable-panels";
import Sidebar from "./sidebar";
-import useCommandKey from "~/hooks/useCommandKey";
-import { Button } from "~/components/ui/button";
-import { FaCompress, FaCompressArrowsAlt } from "react-icons/fa";
import ConsoleLogger from "./console-logger";
import { useData } from "~/renderer/hooks";
import { Data } from "../+data";
import { useBreakpoint } from "~/hooks/useBreakpoint";
+import StatusBar from "./status-bar";
+import { FiTerminal } from "react-icons/fi";
const Editor = () => {
const { project, pinnedFiles } = useData
();
const trpcUtils = trpc.useUtils();
const projectCtx = useProjectContext();
- const sidebarPanel = useRef(null);
const [breakpoint] = useBreakpoint();
- const [sidebarExpanded, setSidebarExpanded] = useState(false);
const [curTabIdx, setCurTabIdx] = useState(0);
const [curOpenFiles, setOpenFiles] = useState(
pinnedFiles.map((i) => i.id)
@@ -53,22 +48,6 @@ const Editor = () => {
},
});
- const toggleSidebar = useCallback(() => {
- const sidebar = sidebarPanel.current;
- if (!sidebar) {
- return;
- }
-
- if (sidebar.isExpanded()) {
- sidebar.collapse();
- } else {
- sidebar.expand();
- sidebar.resize(25);
- }
- }, [sidebarPanel]);
-
- useCommandKey("b", toggleSidebar);
-
useEffect(() => {
if (!pinnedFiles?.length || curOpenFiles.length > 0) {
return;
@@ -139,22 +118,33 @@ const Editor = () => {
[openedFilesData]
);
- const refreshPreview = useCallback(() => {
- previewStore.getState().refresh();
- }, []);
+ const tabs = useMemo(() => {
+ let tabs: Tab[] = [];
- const openFileList = useMemo(() => {
- return curOpenFiles.map((fileId) => {
- const fileData = openedFiles?.find((i) => i.id === fileId);
+ // opened files
+ tabs = tabs.concat(
+ curOpenFiles.map((fileId) => {
+ const fileData = openedFiles?.find((i) => i.id === fileId);
- return {
- title: fileData?.filename || "...",
- render: () => (
-
- ),
- };
- }) satisfies Tab[];
- }, [curOpenFiles, openedFiles, refreshPreview]);
+ return {
+ title: fileData?.filename || "...",
+ render: () => ,
+ };
+ })
+ );
+
+ // show console tab on mobile
+ if (breakpoint < 2) {
+ tabs.push({
+ title: "Console",
+ icon: ,
+ render: () => ,
+ locked: true,
+ });
+ }
+
+ return tabs;
+ }, [curOpenFiles, openedFiles, breakpoint]);
const PanelComponent = !projectCtx.isCompact ? Panel : "div";
@@ -166,20 +156,19 @@ const Editor = () => {
onDeleteFile,
}}
>
-
-
-
+
+ setSidebarExpanded(true)}
- onCollapse={() => setSidebarExpanded(false)}
- >
-
-
+ />
@@ -187,8 +176,8 @@ const Editor = () => {
@@ -212,13 +201,7 @@ const Editor = () => {
-
+
);
diff --git a/pages/project/@slug/components/file-viewer.tsx b/pages/project/@slug/components/file-viewer.tsx
index e80be35..0deb112 100644
--- a/pages/project/@slug/components/file-viewer.tsx
+++ b/pages/project/@slug/components/file-viewer.tsx
@@ -4,22 +4,28 @@ import trpc from "~/lib/trpc";
import { useData } from "~/renderer/hooks";
import { Data } from "../+data";
import Spinner from "~/components/ui/spinner";
+import { previewStore } from "../stores/web-preview";
type Props = {
id: number;
- onFileContentChange?: () => void;
};
-const FileViewer = ({ id, onFileContentChange }: Props) => {
+const FileViewer = ({ id }: Props) => {
const { pinnedFiles } = useData();
const initialData = pinnedFiles.find((i) => i.id === id);
const { data, isLoading, refetch } = trpc.file.getById.useQuery(id, {
initialData,
});
+
+ const onFileContentChange = () => {
+ // refresh preview
+ previewStore.getState().refresh();
+ };
+
const updateFileContent = trpc.file.update.useMutation({
onSuccess: () => {
- if (onFileContentChange) onFileContentChange();
+ onFileContentChange();
refetch();
},
});
diff --git a/pages/project/@slug/components/sidebar.tsx b/pages/project/@slug/components/sidebar.tsx
index a0d7049..77728e5 100644
--- a/pages/project/@slug/components/sidebar.tsx
+++ b/pages/project/@slug/components/sidebar.tsx
@@ -1,22 +1,53 @@
+import { ComponentProps, useCallback, useEffect, useRef } from "react";
import FileListing from "./file-listing";
-import { FaUserCircle } from "react-icons/fa";
-import { Button } from "~/components/ui/button";
+import { ImperativePanelHandle } from "react-resizable-panels";
+import useCommandKey from "~/hooks/useCommandKey";
+import { sidebarStore } from "../stores/sidebar";
+import { ResizablePanel } from "~/components/ui/resizable";
+import { useBreakpointValue } from "~/hooks/useBreakpointValue";
+
+type SidebarProps = ComponentProps;
+
+const Sidebar = (props: SidebarProps) => {
+ const sidebarPanel = useRef(null);
+ const defaultSize = useBreakpointValue(props.defaultSize);
+
+ const toggleSidebar = useCallback(
+ (toggle?: boolean) => {
+ const sidebar = sidebarPanel.current;
+ if (!sidebar) {
+ return;
+ }
+
+ const expand = toggle != null ? toggle : !sidebar.isCollapsed();
+
+ if (expand) {
+ sidebar.collapse();
+ } else {
+ sidebar.expand();
+ sidebar.resize(defaultSize);
+ }
+ },
+ [sidebarPanel, defaultSize]
+ );
+
+ useCommandKey("b", toggleSidebar);
+ useEffect(() => {
+ sidebarStore.setState({ toggle: toggleSidebar });
+ }, [toggleSidebar]);
-const Sidebar = () => {
return (
-
+ sidebarStore.setState({ expanded: true })}
+ onCollapse={() => sidebarStore.setState({ expanded: false })}
+ {...props}
+ >
+
+
);
};
diff --git a/pages/project/@slug/components/status-bar.tsx b/pages/project/@slug/components/status-bar.tsx
new file mode 100644
index 0000000..cda7e44
--- /dev/null
+++ b/pages/project/@slug/components/status-bar.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import { FiSidebar, FiSmartphone, FiUser } from "react-icons/fi";
+import { useStore } from "zustand";
+import { Button } from "~/components/ui/button";
+import { cn } from "~/lib/utils";
+import { usePageContext } from "~/renderer/context";
+import { sidebarStore } from "../stores/sidebar";
+import ActionButton from "~/components/ui/action-button";
+import { previewStore } from "../stores/web-preview";
+import { useProjectContext } from "../context/project";
+
+const StatusBar = ({ className }: React.ComponentProps<"div">) => {
+ const { user, urlPathname } = usePageContext();
+ const { isCompact } = useProjectContext();
+ const sidebarExpanded = useStore(sidebarStore, (i) => i.expanded);
+ const previewExpanded = useStore(previewStore, (i) => i.open);
+
+ if (isCompact) {
+ return null;
+ }
+
+ return (
+
+
sidebarStore.getState().toggle()}
+ />
+ previewStore.getState().toggle()}
+ />
+
+
+
+
+ );
+};
+
+export default StatusBar;
diff --git a/pages/project/@slug/components/web-preview.tsx b/pages/project/@slug/components/web-preview.tsx
index 646d78c..88b1361 100644
--- a/pages/project/@slug/components/web-preview.tsx
+++ b/pages/project/@slug/components/web-preview.tsx
@@ -1,54 +1,92 @@
/* eslint-disable react/display-name */
import Panel from "~/components/ui/panel";
-import { useCallback, useEffect, useRef } from "react";
+import { ComponentProps, useCallback, useEffect, useRef } from "react";
import { useProjectContext } from "../context/project";
import { Button } from "~/components/ui/button";
import { FaRedo } from "react-icons/fa";
import { previewStore } from "../stores/web-preview";
+import { ImperativePanelHandle } from "react-resizable-panels";
+import useCommandKey from "~/hooks/useCommandKey";
+import { ResizablePanel } from "~/components/ui/resizable";
+import { useConsoleLogger } from "~/hooks/useConsoleLogger";
-type WebPreviewProps = {
+type WebPreviewProps = ComponentProps & {
url?: string | null;
};
-const WebPreview = ({ url }: WebPreviewProps) => {
+const WebPreview = ({ url, ...props }: WebPreviewProps) => {
const frameRef = useRef(null);
+ const panelRef = useRef(null);
const project = useProjectContext();
+ // hook into the console
+ useConsoleLogger();
+
const refresh = useCallback(() => {
if (frameRef.current) {
frameRef.current.src = `${url}?t=${Date.now()}`;
}
}, [url]);
+ const togglePanel = useCallback(
+ (toggle?: boolean) => {
+ const panel = panelRef.current;
+ if (!panel) {
+ return;
+ }
+
+ const expand = toggle != null ? toggle : !panel.isCollapsed();
+
+ if (expand) {
+ panel.collapse();
+ } else {
+ panel.expand();
+ panel.resize(
+ typeof props.defaultSize === "number" ? props.defaultSize : 25
+ );
+ }
+ },
+ [panelRef, props.defaultSize]
+ );
+
+ useCommandKey("p", togglePanel);
+
useEffect(() => {
- previewStore.setState({ refresh });
+ previewStore.setState({ refresh, toggle: togglePanel });
refresh();
- }, [refresh]);
+ }, [refresh, togglePanel]);
const PanelComponent = !project.isCompact ? Panel : "div";
return (
-
-
+ previewStore.setState({ open: true })}
+ onCollapse={() => previewStore.setState({ open: false })}
+ {...props}
+ >
+
+
- {url != null ? (
-
- ) : null}
-
+ {url != null ? (
+
+ ) : null}
+
+
);
};
diff --git a/pages/project/@slug/stores/sidebar.ts b/pages/project/@slug/stores/sidebar.ts
new file mode 100644
index 0000000..74e53ec
--- /dev/null
+++ b/pages/project/@slug/stores/sidebar.ts
@@ -0,0 +1,11 @@
+import { createStore } from "zustand";
+
+type SidebarStore = {
+ expanded: boolean;
+ toggle: (toggle?: boolean) => void;
+};
+
+export const sidebarStore = createStore(() => ({
+ expanded: false,
+ toggle() {},
+}));
diff --git a/pages/project/@slug/stores/web-preview.ts b/pages/project/@slug/stores/web-preview.ts
index df1dcf8..f416abb 100644
--- a/pages/project/@slug/stores/web-preview.ts
+++ b/pages/project/@slug/stores/web-preview.ts
@@ -1,9 +1,13 @@
import { createStore } from "zustand";
type PreviewStore = {
+ open: boolean;
+ toggle: (toggle?: boolean) => void;
refresh: () => void;
};
export const previewStore = createStore(() => ({
+ open: false,
+ toggle() {},
refresh: () => {},
}));