From 9e0fa4aa7e57f60efb11780b19c5d3781675effa Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Sat, 2 May 2026 23:14:49 -0400 Subject: [PATCH] Add collapsable sidebar with icons; move source picker to status bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New Sidebar component (modelled on pf_app): collapses 200px→48px via hamburger toggle, persists state to df_sidebar in localStorage; each nav item has an SVG icon with label that fades out when collapsed; user avatar + sign-out at bottom - New StatusBar component: source picker + dark-mode toggle across the top of the content area - Fix Pivot theme: setAttribute('theme') moved to after flush() so viewer.restore() can no longer reset it back to light Co-Authored-By: Claude Sonnet 4.6 --- ui/src/App.jsx | 126 +++------------------- ui/src/components/Sidebar.jsx | 183 ++++++++++++++++++++++++++++++++ ui/src/components/StatusBar.jsx | 53 +++++++++ ui/src/pages/Pivot.jsx | 2 +- 4 files changed, 253 insertions(+), 111 deletions(-) create mode 100644 ui/src/components/Sidebar.jsx create mode 100644 ui/src/components/StatusBar.jsx diff --git a/ui/src/App.jsx b/ui/src/App.jsx index e4f3025..65afbc5 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react' -import { BrowserRouter, Routes, Route, NavLink, Navigate } from 'react-router-dom' +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import { api, setCredentials, clearCredentials } from './api' -import useTheme from './theme.jsx' +import StatusBar from './components/StatusBar.jsx' +import Sidebar from './components/Sidebar.jsx' import Login from './pages/Login' import Sources from './pages/Sources' import Import from './pages/Import' @@ -13,25 +14,12 @@ import Pivot from './pages/Pivot' import Remap from './pages/Remap' import Stacks from './pages/Stacks' -const NAV = [ - { to: '/sources', label: 'Sources' }, - { to: '/import', label: 'Import' }, - { to: '/rules', label: 'Rules' }, - { to: '/mappings', label: 'Mappings' }, - { to: '/remap', label: 'Remap' }, - { to: '/records', label: 'Records' }, - { to: '/pivot', label: 'Pivot' }, - { to: '/stacks', label: 'Stacks' }, - { to: '/log', label: 'Log' }, -] - export default function App() { - const { dark, setDark } = useTheme() const [authed, setAuthed] = useState(false) const [loginUser, setLoginUser] = useState('') const [sources, setSources] = useState([]) const [source, setSource] = useState(() => localStorage.getItem('selectedSource') || '') - const [sidebarOpen, setSidebarOpen] = useState(false) + const [sidebarExpanded, setSidebarExpanded] = useState(() => localStorage.getItem('df_sidebar') !== 'collapsed') // Sets of names whose dfv view is out of sync with current definitions const [staleSources, setStaleSources] = useState(new Set()) const [staleStacks, setStaleStacks] = useState(new Set()) @@ -121,108 +109,26 @@ export default function App() { if (source) localStorage.setItem('selectedSource', source) }, [source]) + useEffect(() => { + localStorage.setItem('df_sidebar', sidebarExpanded ? 'expanded' : 'collapsed') + }, [sidebarExpanded]) + if (!authed) return - const sidebar = ( -
- {/* Header */} -
-
- Dataflow -
- - -
-
-
- {loginUser} - -
-
- - {/* Source selector */} -
-
- - setSidebarOpen(false)}>+ -
- -
- - {/* Nav */} - -
- ) - return (
- {/* Mobile overlay */} - {sidebarOpen && ( -
setSidebarOpen(false)} /> - )} - - {/* Sidebar — fixed on mobile, static on desktop */} -
- {sidebar} -
+ {/* Main */} -
- {/* Mobile top bar */} -
- - Dataflow -
+
+ {(staleSources.size > 0 || staleStacks.size > 0) && (
diff --git a/ui/src/components/Sidebar.jsx b/ui/src/components/Sidebar.jsx new file mode 100644 index 0000000..1c5683f --- /dev/null +++ b/ui/src/components/Sidebar.jsx @@ -0,0 +1,183 @@ +import { NavLink } from 'react-router-dom' + +const NAV = [ + { + to: '/sources', + label: 'Sources', + icon: ( + + + + + + ), + }, + { + to: '/import', + label: 'Import', + icon: ( + + + + + + ), + }, + { + to: '/rules', + label: 'Rules', + icon: ( + + + + + + ), + }, + { + to: '/mappings', + label: 'Mappings', + icon: ( + + + + + + + ), + }, + { + to: '/remap', + label: 'Remap', + icon: ( + + + + + + + ), + }, + { + to: '/records', + label: 'Records', + icon: ( + + + + + + ), + }, + { + to: '/pivot', + label: 'Pivot', + icon: ( + + + + + + + ), + }, + { + to: '/stacks', + label: 'Stacks', + icon: ( + + + + + + ), + }, + { + to: '/log', + label: 'Log', + icon: ( + + + + + ), + }, +] + +export default function Sidebar({ expanded, setExpanded, loginUser, onLogout }) { + return ( +
+ {/* Header */} +
+ + + Dataflow + +
+ + {/* Nav */} + + + {/* User / logout */} +
+
+ {loginUser ? loginUser[0].toUpperCase() : '?'} +
+
+ {loginUser} + +
+
+
+ ) +} diff --git a/ui/src/components/StatusBar.jsx b/ui/src/components/StatusBar.jsx new file mode 100644 index 0000000..f430104 --- /dev/null +++ b/ui/src/components/StatusBar.jsx @@ -0,0 +1,53 @@ +import { NavLink } from 'react-router-dom' +import useTheme from '../theme.jsx' + +export default function StatusBar({ sources = [], source, setSource }) { + const { dark, setDark } = useTheme() + + return ( +
+ Source + + + + +
+ +
+
+ ) +} diff --git a/ui/src/pages/Pivot.jsx b/ui/src/pages/Pivot.jsx index 9bb22a8..a85e956 100644 --- a/ui/src/pages/Pivot.jsx +++ b/ui/src/pages/Pivot.jsx @@ -253,7 +253,6 @@ export default function Pivot({ source }) { viewer.addEventListener('perspective-click', perspClickHandlerRef.current) await viewer.load(worker) - viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') const plugin = await viewer.getPlugin() const savedLayout = localStorage.getItem(LAYOUT_KEY(selectedView)) @@ -267,6 +266,7 @@ export default function Pivot({ source }) { await plugin.restore(DEFAULT_PLUGIN_CONFIG) } await viewer.flush() + viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') setStatus('ready') } catch (err) {