From 3958e1cc3ddaeb74e42da446940f6f484e9396d5 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Fri, 26 Jul 2024 21:17:34 -0500 Subject: [PATCH] panel management --- packages/panorama-daemon/src/apps/index.ts | 2 +- ui/src/App.tsx | 40 ++--- ui/src/components/Header.tsx | 4 +- ui/src/components/NodeDisplay.tsx | 132 +++++++------- ui/src/components/PanelDisplay.tsx | 9 + ui/src/components/SearchBar.tsx | 190 ++++++++++----------- ui/src/components/Sidebar.tsx | 56 +++--- ui/src/components/nodes/Query.tsx | 13 ++ ui/src/lib/panelManagement.ts | 29 ++++ 9 files changed, 254 insertions(+), 221 deletions(-) create mode 100644 ui/src/components/PanelDisplay.tsx create mode 100644 ui/src/components/nodes/Query.tsx create mode 100644 ui/src/lib/panelManagement.ts diff --git a/packages/panorama-daemon/src/apps/index.ts b/packages/panorama-daemon/src/apps/index.ts index 0f65d16..6cbc9ef 100644 --- a/packages/panorama-daemon/src/apps/index.ts +++ b/packages/panorama-daemon/src/apps/index.ts @@ -38,7 +38,7 @@ export async function loadApps(): Promise> { const app = await loadApp(child); apps.set(app.name, app); } catch (e) { - console.error("Error setting up " + child + ": " + e.message) + console.error(`Error setting up ${child}: ${e.message}`); } } } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 73debe1..c1523b3 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -6,30 +6,29 @@ import "@fontsource/inter/700.css"; import "./global.scss"; import "katex/dist/katex.min.css"; import { useEffect } from "react"; -import NodeDisplay from "./components/NodeDisplay"; +import PanelDisplay from "./components/PanelDisplay"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import TimeAgo from "javascript-time-ago"; import en from "javascript-time-ago/locale/en"; import Sidebar from "./components/Sidebar"; -import { atom, useAtom, useAtomValue } from "jotai"; -import { OrderedSet } from "immutable"; +import { useAtomValue } from "jotai"; import { PANORAMA_DAEMON_URL } from "./lib/constants"; +import { panelsOpenedAtom, usePanelControls } from "./lib/panelManagement"; const queryClient = new QueryClient(); TimeAgo.addDefaultLocale(en); -export const nodesOpenedAtom = atom>(OrderedSet()); +// export const nodesOpenedAtom = atom>(OrderedSet()); function App() { - const nodesOpened = useAtomValue(nodesOpenedAtom); - const { openNode } = useNodeControls(); + const panelsOpened = useAtomValue(panelsOpenedAtom); + const { openNode } = usePanelControls(); // Open today's journal entry if it's not already opened useEffect(() => { (async () => { - console.log("ndoes", nodesOpened); - if (nodesOpened.size === 0) { + if (panelsOpened.size === 0) { console.log("Opening today's entry."); const resp = await fetch( `${PANORAMA_DAEMON_URL}/journal/get_todays_journal_id`, @@ -39,10 +38,10 @@ function App() { openNode(data.node_id); } })(); - }, [nodesOpened, openNode]); + }, [openNode]); - const nodes = [...nodesOpened.reverse().values()].map((nodeId, idx) => ( - + const panels = [...panelsOpened.reverse().values()].map((panelInfo, idx) => ( + )); return ( @@ -52,7 +51,7 @@ function App() {
-
{nodes}
+
{panels}
@@ -60,20 +59,3 @@ function App() { } export default App; - -export function useNodeControls() { - const [nodesOpened, setNodesOpened] = useAtom(nodesOpenedAtom); - return { - isOpen: (node_id: string) => nodesOpened.has(node_id), - toggleNode: (node_id: string) => { - if (nodesOpened.has(node_id)) setNodesOpened(nodesOpened.remove(node_id)); - else setNodesOpened(nodesOpened.remove(node_id).add(node_id)); - }, - openNode: (node_id: string) => { - setNodesOpened(nodesOpened.remove(node_id).add(node_id)); - }, - closeNode: (node_id: string) => { - setNodesOpened(nodesOpened.remove(node_id)); - }, - }; -} diff --git a/ui/src/components/Header.tsx b/ui/src/components/Header.tsx index bbac0d7..82d18b6 100644 --- a/ui/src/components/Header.tsx +++ b/ui/src/components/Header.tsx @@ -5,7 +5,6 @@ import ListIcon from "@mui/icons-material/List"; import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; import { useSetAtom } from "jotai"; import { sidebarExpandedAtom } from "./Sidebar"; -import { useNodeControls } from "../App"; import { useCallback, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { getVersion } from "@tauri-apps/api/app"; @@ -19,9 +18,10 @@ import { useInteractions, } from "@floating-ui/react"; import { PANORAMA_DAEMON_URL } from "../lib/constants"; +import { usePanelControls } from "../lib/panelManagement"; export default function Header() { - const { openNode } = useNodeControls(); + const { openNode } = usePanelControls(); const setSidebarExpanded = useSetAtom(sidebarExpandedAtom); const versionData = useQuery({ queryKey: ["appVersion"], diff --git a/ui/src/components/NodeDisplay.tsx b/ui/src/components/NodeDisplay.tsx index 3be2470..36c4b04 100644 --- a/ui/src/components/NodeDisplay.tsx +++ b/ui/src/components/NodeDisplay.tsx @@ -5,85 +5,85 @@ import { getNode } from "../lib/getNode"; import FirstPageIcon from "@mui/icons-material/FirstPage"; import MoreVertIcon from "@mui/icons-material/MoreVert"; import CloseIcon from "@mui/icons-material/Close"; -import { useNodeControls } from "../App"; +import { usePanelControls } from "../lib/panelManagement"; export interface NodeDisplayProps { - id: string; - idx?: number | undefined; + nodeId: string; + idx?: number | undefined; } -export default function NodeDisplay({ id, idx }: NodeDisplayProps) { - const query = useQuery({ - queryKey: ["fetchNode", id], - queryFn: getNode, - }); +export default function NodeDisplay({ nodeId, idx }: NodeDisplayProps) { + const query = useQuery({ + queryKey: ["fetchNode", nodeId], + queryFn: getNode, + }); - const { isSuccess, status, data: nodeDescriptor } = query; + const { isSuccess, status, data: nodeDescriptor } = query; - let Component = undefined; - let data = undefined; - if (isSuccess) { - Component = nodeDescriptor.render; - data = nodeDescriptor.data; - } + let Component = undefined; + let data = undefined; + if (isSuccess) { + Component = nodeDescriptor.render; + data = nodeDescriptor.data; + } - return ( -
-
- {isSuccess ? ( - - ) : ( - <> - ID {id} ({status}) - - )} -
+ return ( +
+
+ {isSuccess ? ( + + ) : ( + <> + ID {nodeId} ({status}) + + )} +
-
- {Component && } -
+
+ {Component && } +
-
{id}
-
- ); +
{nodeId}
+
+ ); } function NodeDisplayHeaderLoaded({ idx, id, data }) { - const { openNode, closeNode } = useNodeControls(); - const updatedAt = data.updated_at && Date.parse(data.updated_at); + const { openNode, closeNode } = usePanelControls(); + const updatedAt = data.updated_at && Date.parse(data.updated_at); - return ( - <> - {idx === 0 || ( - - )} - - Type {data.type}{" "} - {updatedAt && ( - <> - · Last updated - - )} - -
+ return ( + <> + {idx === 0 || ( + + )} + + Type {data.type}{" "} + {updatedAt && ( + <> + · Last updated + + )} + +
- + - - - ); + + + ); } diff --git a/ui/src/components/PanelDisplay.tsx b/ui/src/components/PanelDisplay.tsx new file mode 100644 index 0000000..7e00e2e --- /dev/null +++ b/ui/src/components/PanelDisplay.tsx @@ -0,0 +1,9 @@ +import type { PanelInfo } from "../lib/panelManagement"; + +export interface PanelDisplayProps { + info: PanelInfo; +} + +export default function PanelDisplay({ info }: PanelDisplayProps) { + return <>Panel {JSON.stringify(info)}; +} diff --git a/ui/src/components/SearchBar.tsx b/ui/src/components/SearchBar.tsx index 57fbde7..c746fcd 100644 --- a/ui/src/components/SearchBar.tsx +++ b/ui/src/components/SearchBar.tsx @@ -1,116 +1,116 @@ import styles from "./SearchBar.module.scss"; import { - FloatingOverlay, - FloatingPortal, - autoUpdate, - offset, - useDismiss, - useFloating, - useFocus, - useInteractions, + FloatingOverlay, + FloatingPortal, + autoUpdate, + offset, + useDismiss, + useFloating, + useFocus, + useInteractions, } from "@floating-ui/react"; import { useCallback, useEffect, useState } from "react"; import { atom, useAtom, useSetAtom } from "jotai"; -import { useNodeControls } from "../App"; import { useDebounce, useDebouncedCallback } from "use-debounce"; +import { usePanelControls } from "../lib/panelManagement"; const searchQueryAtom = atom(""); const showMenuAtom = atom(false); export default function SearchBar() { - const [showMenu, setShowMenu] = useAtom(showMenuAtom); - const [searchQuery, setSearchQuery] = useAtom(searchQueryAtom); - const [searchResults, setSearchResults] = useState([]); + const [showMenu, setShowMenu] = useAtom(showMenuAtom); + const [searchQuery, setSearchQuery] = useAtom(searchQueryAtom); + const [searchResults, setSearchResults] = useState([]); - const { refs, context, floatingStyles } = useFloating({ - placement: "bottom-start", - open: showMenu, - onOpenChange: setShowMenu, - whileElementsMounted: autoUpdate, - middleware: [offset(10)], - }); - const focus = useFocus(context); - const { getReferenceProps, getFloatingProps } = useInteractions([ - focus, - useDismiss(context), - ]); + const { refs, context, floatingStyles } = useFloating({ + placement: "bottom-start", + open: showMenu, + onOpenChange: setShowMenu, + whileElementsMounted: autoUpdate, + middleware: [offset(10)], + }); + const focus = useFocus(context); + const { getReferenceProps, getFloatingProps } = useInteractions([ + focus, + useDismiss(context), + ]); - const performSearch = useCallback(() => { - const trimmed = searchQuery.trim(); - if (trimmed === "") return; + const performSearch = useCallback(() => { + const trimmed = searchQuery.trim(); + if (trimmed === "") return; - (async () => { - const params = new URLSearchParams(); - params.set("query", trimmed); - const resp = await fetch( - `http://localhost:5195/node/search?${params.toString()}`, - ); - const data = await resp.json(); - setSearchResults(data.results); - })(); - }, [searchQuery]); + (async () => { + const params = new URLSearchParams(); + params.set("query", trimmed); + const resp = await fetch( + `http://localhost:5195/node/search?${params.toString()}`, + ); + const data = await resp.json(); + setSearchResults(data.results); + })(); + }, [searchQuery]); - return ( - <> -
- setShowMenu(true)} - ref={refs.setReference} - value={searchQuery} - onChange={(evt) => { - setSearchQuery(evt.target.value); - if (evt.target.value) performSearch(); - else setSearchResults([]); - }} - {...getReferenceProps()} - /> -
+ return ( + <> +
+ setShowMenu(true)} + ref={refs.setReference} + value={searchQuery} + onChange={(evt) => { + setSearchQuery(evt.target.value); + if (evt.target.value) performSearch(); + else setSearchResults([]); + }} + {...getReferenceProps()} + /> +
- {showMenu && ( - - -
- -
-
-
- )} - - ); + {showMenu && ( + + +
+ +
+
+
+ )} + + ); } function SearchMenu({ results }) { - const setSearchQuery = useSetAtom(searchQueryAtom); - const setShowMenu = useSetAtom(showMenuAtom); - const { openNode } = useNodeControls(); + const setSearchQuery = useSetAtom(searchQueryAtom); + const setShowMenu = useSetAtom(showMenuAtom); + const { openNode } = usePanelControls(); - return ( -
- {results.map((result) => { - return ( - - ); - })} -
- ); + return ( +
+ {results.map((result) => { + return ( + + ); + })} +
+ ); } diff --git a/ui/src/components/Sidebar.tsx b/ui/src/components/Sidebar.tsx index 0c98878..b0be087 100644 --- a/ui/src/components/Sidebar.tsx +++ b/ui/src/components/Sidebar.tsx @@ -3,39 +3,39 @@ import styles from "./Sidebar.module.scss"; import classNames from "classnames"; import EmailIcon from "@mui/icons-material/Email"; import SettingsIcon from "@mui/icons-material/Settings"; -import { useNodeControls } from "../App"; +import { usePanelControls } from "../lib/panelManagement"; export const sidebarExpandedAtom = atom(false); export default function Sidebar() { - const sidebarExpanded = useAtomValue(sidebarExpandedAtom); - const { toggleNode, isOpen } = useNodeControls(); + const sidebarExpanded = useAtomValue(sidebarExpandedAtom); + const { toggleNodePanel, isNodeOpen } = usePanelControls(); - return ( -
- + return ( +
+ -
+
-
- - Settings -
-
- ); +
+ + Settings +
+
+ ); } diff --git a/ui/src/components/nodes/Query.tsx b/ui/src/components/nodes/Query.tsx new file mode 100644 index 0000000..8fdba9a --- /dev/null +++ b/ui/src/components/nodes/Query.tsx @@ -0,0 +1,13 @@ +export interface QueryTableProps {} + +export default function QueryTable({}: QueryTableProps) { + return ( +
+
+ +
+ +
+
+ ); +} diff --git a/ui/src/lib/panelManagement.ts b/ui/src/lib/panelManagement.ts new file mode 100644 index 0000000..75ed321 --- /dev/null +++ b/ui/src/lib/panelManagement.ts @@ -0,0 +1,29 @@ +import { OrderedSet } from "immutable"; +import { atom, useAtom } from "jotai"; + +export type PanelInfo = { type: "node"; nodeId: string }; + +export const panelsOpenedAtom = atom>( + OrderedSet(), +); + +export function usePanelControls() { + const [nodesOpened, setNodesOpened] = useAtom(panelsOpenedAtom); + + return { + isNodeOpen: (nodeId: string) => nodesOpened.has({ type: "node", nodeId }), + toggleNodePanel: (nodeId: string) => { + const info: PanelInfo = { type: "node", nodeId }; + if (nodesOpened.has(info)) setNodesOpened(nodesOpened.remove(info)); + else setNodesOpened(nodesOpened.remove(info).add(info)); + }, + openNode: (nodeId: string) => { + const info: PanelInfo = { type: "node", nodeId }; + setNodesOpened(nodesOpened.remove(info).add(info)); + }, + closeNode: (nodeId: string) => { + const info: PanelInfo = { type: "node", nodeId }; + setNodesOpened(nodesOpened.remove(info)); + }, + }; +}