mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-21 18:55:36 +02:00
Compare commits
3 commits
2414acf0df
...
c85cce579b
| Author | SHA1 | Date | |
|---|---|---|---|
| c85cce579b | |||
| 44f67390be | |||
| c4f1c7a137 |
6 changed files with 87 additions and 50 deletions
|
|
@ -90,8 +90,8 @@ fn save_with(
|
|||
.arg(&out_file)
|
||||
.arg(format!("--browser-executable-path={chrome}"))
|
||||
.arg("--browser-headless")
|
||||
.arg("--browser-wait-until=networkAlmostIdle")
|
||||
// Extra delay after networkAlmostIdle: Cloudflare Fonts injects @font-face
|
||||
.arg("--browser-wait-until=networkidle2")
|
||||
// Extra delay after networkidle2: Cloudflare Fonts injects @font-face
|
||||
// CSS after HTML parse, so the font hook needs more time to see it.
|
||||
.arg("--browser-wait-delay=2000")
|
||||
// Realistic UA: some origins block headless Chrome's default UA string.
|
||||
|
|
|
|||
40
crates/archivr-server/static/assets/index-BEbGvi2K.js
Normal file
40
crates/archivr-server/static/assets/index-BEbGvi2K.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue