1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-21 18:55:36 +02:00

fix: sync URL to active view and settings sub-tab

This commit is contained in:
TheGeneralist 2026-07-01 19:11:42 +02:00
commit c85cce579b
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4
5 changed files with 85 additions and 48 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Archivr</title>
<script type="module" crossorigin src="/assets/index-CbWD4KAy.js"></script>
<script type="module" crossorigin src="/assets/index-BEbGvi2K.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-MrdP6h9x.css">
</head>
<body>

View file

@ -15,6 +15,22 @@ import ContextRail from './components/ContextRail'
export const AuthContext = createContext(null);
const VIEWS = ['archive','runs','admin','tags','collections','settings']
const SETTINGS_TABS = ['profile','tokens','instance']
function parseLocation() {
const parts = window.location.pathname.split('/').filter(Boolean)
const view = VIEWS.includes(parts[0]) ? parts[0] : 'archive'
const settingsTab = (view === 'settings' && SETTINGS_TABS.includes(parts[1])) ? parts[1] : 'profile'
return { view, settingsTab }
}
function locationPath(view, settingsTab) {
if (view === 'archive') return '/'
if (view === 'settings') return `/settings/${settingsTab}`
return `/${view}`
}
export default function App() {
const [authState, setAuthState] = useState('loading');
const [currentUser, setCurrentUser] = useState(null);
@ -36,13 +52,25 @@ export default function App() {
return () => window.removeEventListener('auth:expired', handler);
}, []);
// Sync URL state on back/forward
useEffect(() => {
const handler = () => {
const { view, settingsTab } = parseLocation()
setView(view)
setSettingsTab(settingsTab)
}
window.addEventListener('popstate', handler)
return () => window.removeEventListener('popstate', handler)
}, [])
const [archives, setArchives] = useState([])
const [archiveId, setArchiveId] = useState(null)
const [entries, setEntries] = useState([])
const [selectedEntryUid, setSelectedEntryUid] = useState(null)
const [selectedEntry, setSelectedEntry] = useState(null)
const [tagFilter, setTagFilter] = useState(null)
const [view, setView] = useState('archive')
const [view, setView] = useState(() => parseLocation().view)
const [settingsTab, setSettingsTab] = useState(() => parseLocation().settingsTab)
const [searchQuery, setSearchQuery] = useState('')
const [resultCount, setResultCount] = useState('')
const [searchBusy, setSearchBusy] = useState(false)
@ -105,10 +133,12 @@ export default function App() {
return () => clearTimeout(timer)
}, [searchQuery, archiveId]) // tagFilter handled separately below
// Tag filter change: switch to archive view, reload
// Tag filter applied: switch to archive view and reload.
// Only reset view when tagFilter is non-null; archive change alone (tagFilter=null)
// must not stomp the URL-initialised view on first load.
useEffect(() => {
if (archiveId === null) return
setView('archive')
if (tagFilter !== null) setView('archive')
loadEntries(archiveId, searchQuery, tagFilter)
}, [tagFilter, archiveId]) // intentional: searchQuery excluded to avoid double-fire
@ -123,6 +153,14 @@ export default function App() {
}
}, [archiveId])
// Sync view + settingsTab URL
useEffect(() => {
const path = locationPath(view, settingsTab)
if (window.location.pathname !== path) {
history.pushState(null, '', path)
}
}, [view, settingsTab])
const selectEntry = useCallback((entry) => {
setSelectedEntryUid(entry ? entry.entry_uid : null)
setSelectedEntry(entry)
@ -230,7 +268,7 @@ export default function App() {
<CollectionsView archiveId={archiveId} />
)}
{view === 'settings' && (
<SettingsView />
<SettingsView tab={settingsTab} onTabChange={setSettingsTab} />
)}
</div>
<ContextRail

View file

@ -8,10 +8,9 @@ import {
const ROLE_ADMIN = 4
export default function SettingsView() {
export default function SettingsView({ tab, onTabChange }) {
const { currentUser, setCurrentUser } = useContext(AuthContext) ?? {}
const isAdmin = currentUser && ((currentUser.role_bits & ROLE_ADMIN) !== 0)
const [tab, setTab] = useState('profile')
const tabs = ['profile', 'tokens', ...(isAdmin ? ['instance'] : [])]
const tabLabels = { profile: 'Profile', tokens: 'API Tokens', instance: 'Instance' }
@ -23,7 +22,7 @@ export default function SettingsView() {
{tabs.map(t => (
<button key={t}
className={`view-tab${tab === t ? ' is-active' : ''}`}
onClick={() => setTab(t)}>
onClick={() => onTabChange(t)}>
{tabLabels[t]}
</button>
))}