Claude commited on
Commit
4002561
·
unverified ·
1 Parent(s): 1d5cfba

feat(frontend): Sprint Fix 4 — React Router, accessibility, translations

Browse files

5 issues fixed:

- #24: Add react-router-dom v7, replace useState-based view routing with
URL-based Routes: /, /admin, /reader/:manuscriptId, /editor/:pageId.
F5 reloads to same page, browser back button works, shareable URLs.
- #18: Fix Editor→Back navigation: navigate(-1) returns to Reader (was
always going to Home due to setState({ name: 'home' }))
- #15: RetroCheckbox now uses a real <input type="checkbox"> (sr-only)
with proper htmlFor/id, keyboard accessible (Tab + Space)
- #16: Accessibility improvements across retro components:
- RetroMenuBar: <div> → <nav aria-label="Menu principal">
- RetroIcon: add aria-label={label} to button
- RetroInput: add htmlFor + auto-generated id
- RetroSelect: add htmlFor + auto-generated id
- #35: TranslationPanel shows FR and EN based on profile active_layers
(was hardcoded to French only)

TypeScript compiles clean. 563 backend tests pass, 0 regressions.

https://claude.ai/code/session_01UB4he7RdRPHLvNjky4X8Sw

frontend/package.json CHANGED
@@ -10,7 +10,8 @@
10
  "dependencies": {
11
  "openseadragon": "^4.1.0",
12
  "react": "^18.3.1",
13
- "react-dom": "^18.3.1"
 
14
  },
15
  "devDependencies": {
16
  "@types/openseadragon": "^3.0.10",
 
10
  "dependencies": {
11
  "openseadragon": "^4.1.0",
12
  "react": "^18.3.1",
13
+ "react-dom": "^18.3.1",
14
+ "react-router-dom": "^7.14.0"
15
  },
16
  "devDependencies": {
17
  "@types/openseadragon": "^3.0.10",
frontend/src/App.tsx CHANGED
@@ -1,49 +1,16 @@
1
- import { useState } from 'react'
2
  import Admin from './pages/Admin.tsx'
3
  import Editor from './pages/Editor.tsx'
4
  import Home from './pages/Home.tsx'
5
  import Reader from './pages/Reader.tsx'
6
 
7
- type View =
8
- | { name: 'home' }
9
- | { name: 'reader'; manuscriptId: string; profileId: string }
10
- | { name: 'admin' }
11
- | { name: 'editor'; pageId: string }
12
-
13
  export default function App() {
14
- const [view, setView] = useState<View>({ name: 'home' })
15
-
16
- if (view.name === 'reader') {
17
- return (
18
- <Reader
19
- manuscriptId={view.manuscriptId}
20
- profileId={view.profileId}
21
- onBack={() => setView({ name: 'home' })}
22
- onEdit={(pageId) => setView({ name: 'editor', pageId })}
23
- />
24
- )
25
- }
26
-
27
- if (view.name === 'admin') {
28
- return <Admin onHome={() => setView({ name: 'home' })} />
29
- }
30
-
31
- if (view.name === 'editor') {
32
- return (
33
- <Editor
34
- pageId={view.pageId}
35
- onBack={() => setView({ name: 'home' })}
36
- />
37
- )
38
- }
39
-
40
  return (
41
- <Home
42
- onOpenManuscript={(manuscriptId, profileId) =>
43
- setView({ name: 'reader', manuscriptId, profileId })
44
- }
45
- onOpenPage={(pageId) => setView({ name: 'editor', pageId })}
46
- onAdmin={() => setView({ name: 'admin' })}
47
- />
48
  )
49
  }
 
1
+ import { Routes, Route } from 'react-router-dom'
2
  import Admin from './pages/Admin.tsx'
3
  import Editor from './pages/Editor.tsx'
4
  import Home from './pages/Home.tsx'
5
  import Reader from './pages/Reader.tsx'
6
 
 
 
 
 
 
 
7
  export default function App() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  return (
9
+ <Routes>
10
+ <Route path="/" element={<Home />} />
11
+ <Route path="/admin" element={<Admin />} />
12
+ <Route path="/reader/:manuscriptId" element={<Reader />} />
13
+ <Route path="/editor/:pageId" element={<Editor />} />
14
+ </Routes>
 
15
  )
16
  }
frontend/src/components/TranslationPanel.tsx CHANGED
@@ -22,25 +22,50 @@ interface Props {
22
  translation: Translation | null
23
  editorial: EditorialInfo
24
  visible: boolean
 
 
25
  }
26
 
27
- const TranslationPanel: FC<Props> = ({ translation, editorial, visible }) => {
28
  if (!visible) return null
29
 
 
 
 
30
  return (
31
  <div className="p-2">
32
  <div className="flex items-center justify-between mb-2">
33
- <span className="text-retro-xs font-bold">Traduction (FR)</span>
34
  <RetroBadge variant={STATUS_VARIANTS[editorial.status]}>
35
  {STATUS_LABELS[editorial.status]}
36
  </RetroBadge>
37
  </div>
38
- {translation?.fr ? (
39
- <p className="text-retro-sm whitespace-pre-wrap font-retro leading-relaxed">
40
- {translation.fr}
41
- </p>
42
- ) : (
43
- <p className="text-retro-sm text-retro-darkgray">Traduction non disponible.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  )}
45
  </div>
46
  )
 
22
  translation: Translation | null
23
  editorial: EditorialInfo
24
  visible: boolean
25
+ /** Active layers from profile — controls which languages are shown */
26
+ activeLayers?: string[]
27
  }
28
 
29
+ const TranslationPanel: FC<Props> = ({ translation, editorial, visible, activeLayers }) => {
30
  if (!visible) return null
31
 
32
+ const showFr = !activeLayers || activeLayers.includes('translation_fr')
33
+ const showEn = !activeLayers || activeLayers.includes('translation_en')
34
+
35
  return (
36
  <div className="p-2">
37
  <div className="flex items-center justify-between mb-2">
38
+ <span className="text-retro-xs font-bold">Traduction</span>
39
  <RetroBadge variant={STATUS_VARIANTS[editorial.status]}>
40
  {STATUS_LABELS[editorial.status]}
41
  </RetroBadge>
42
  </div>
43
+ {showFr && (
44
+ <div className="mb-2">
45
+ <div className="text-retro-xs font-bold text-retro-darkgray mb-1">FR</div>
46
+ {translation?.fr ? (
47
+ <p className="text-retro-sm whitespace-pre-wrap font-retro leading-relaxed">
48
+ {translation.fr}
49
+ </p>
50
+ ) : (
51
+ <p className="text-retro-sm text-retro-darkgray">Traduction FR non disponible.</p>
52
+ )}
53
+ </div>
54
+ )}
55
+ {showEn && (
56
+ <div>
57
+ <div className="text-retro-xs font-bold text-retro-darkgray mb-1">EN</div>
58
+ {translation?.en ? (
59
+ <p className="text-retro-sm whitespace-pre-wrap font-retro leading-relaxed">
60
+ {translation.en}
61
+ </p>
62
+ ) : (
63
+ <p className="text-retro-sm text-retro-darkgray">Traduction EN non disponible.</p>
64
+ )}
65
+ </div>
66
+ )}
67
+ {!showFr && !showEn && (
68
+ <p className="text-retro-sm text-retro-darkgray">Aucune couche de traduction active.</p>
69
  )}
70
  </div>
71
  )
frontend/src/components/retro/RetroCheckbox.tsx CHANGED
@@ -1,3 +1,5 @@
 
 
1
  interface Props {
2
  /** Label text next to the checkbox */
3
  label: string
@@ -18,9 +20,11 @@ export default function RetroCheckbox({
18
  disabled = false,
19
  className = '',
20
  }: Props) {
 
 
21
  return (
22
  <label
23
- onClick={() => { if (!disabled) onChange(!checked) }}
24
  className={`
25
  inline-flex items-center gap-[6px]
26
  text-retro-sm font-retro
@@ -29,7 +33,16 @@ export default function RetroCheckbox({
29
  ${className}
30
  `}
31
  >
 
 
 
 
 
 
 
 
32
  <span
 
33
  className={`
34
  inline-flex items-center justify-center
35
  w-[13px] h-[13px]
 
1
+ import { useId } from 'react'
2
+
3
  interface Props {
4
  /** Label text next to the checkbox */
5
  label: string
 
20
  disabled = false,
21
  className = '',
22
  }: Props) {
23
+ const id = useId()
24
+
25
  return (
26
  <label
27
+ htmlFor={id}
28
  className={`
29
  inline-flex items-center gap-[6px]
30
  text-retro-sm font-retro
 
33
  ${className}
34
  `}
35
  >
36
+ <input
37
+ id={id}
38
+ type="checkbox"
39
+ checked={checked}
40
+ onChange={(e) => onChange(e.target.checked)}
41
+ disabled={disabled}
42
+ className="sr-only"
43
+ />
44
  <span
45
+ aria-hidden="true"
46
  className={`
47
  inline-flex items-center justify-center
48
  w-[13px] h-[13px]
frontend/src/components/retro/RetroIcon.tsx CHANGED
@@ -22,6 +22,7 @@ export default function RetroIcon({
22
  <button
23
  type="button"
24
  onClick={onClick}
 
25
  className={`
26
  flex flex-col items-center gap-1
27
  p-2 w-[80px]
 
22
  <button
23
  type="button"
24
  onClick={onClick}
25
+ aria-label={label}
26
  className={`
27
  flex flex-col items-center gap-1
28
  p-2 w-[80px]
frontend/src/components/retro/RetroInput.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import type { InputHTMLAttributes } from 'react'
2
 
3
  interface Props extends InputHTMLAttributes<HTMLInputElement> {
4
  /** Optional label rendered above the input */
@@ -6,14 +6,18 @@ interface Props extends InputHTMLAttributes<HTMLInputElement> {
6
  }
7
 
8
  export default function RetroInput({ label, className = '', ...rest }: Props) {
 
 
 
9
  return (
10
  <div className="flex flex-col gap-[2px]">
11
  {label && (
12
- <label className="text-retro-xs font-retro font-medium text-retro-black">
13
  {label}
14
  </label>
15
  )}
16
  <input
 
17
  className={`
18
  px-2 py-[3px]
19
  text-retro-sm font-retro
 
1
+ import { useId, type InputHTMLAttributes } from 'react'
2
 
3
  interface Props extends InputHTMLAttributes<HTMLInputElement> {
4
  /** Optional label rendered above the input */
 
6
  }
7
 
8
  export default function RetroInput({ label, className = '', ...rest }: Props) {
9
+ const generatedId = useId()
10
+ const inputId = rest.id ?? generatedId
11
+
12
  return (
13
  <div className="flex flex-col gap-[2px]">
14
  {label && (
15
+ <label htmlFor={inputId} className="text-retro-xs font-retro font-medium text-retro-black">
16
  {label}
17
  </label>
18
  )}
19
  <input
20
+ id={inputId}
21
  className={`
22
  px-2 py-[3px]
23
  text-retro-sm font-retro
frontend/src/components/retro/RetroMenuBar.tsx CHANGED
@@ -17,7 +17,8 @@ interface Props {
17
 
18
  export default function RetroMenuBar({ items = [], right, className = '' }: Props) {
19
  return (
20
- <div
 
21
  className={`
22
  flex items-center
23
  bg-retro-gray
@@ -51,6 +52,6 @@ export default function RetroMenuBar({ items = [], right, className = '' }: Prop
51
  {right}
52
  </div>
53
  )}
54
- </div>
55
  )
56
  }
 
17
 
18
  export default function RetroMenuBar({ items = [], right, className = '' }: Props) {
19
  return (
20
+ <nav
21
+ aria-label="Menu principal"
22
  className={`
23
  flex items-center
24
  bg-retro-gray
 
52
  {right}
53
  </div>
54
  )}
55
+ </nav>
56
  )
57
  }
frontend/src/components/retro/RetroSelect.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import type { SelectHTMLAttributes } from 'react'
2
 
3
  interface Props extends SelectHTMLAttributes<HTMLSelectElement> {
4
  /** Optional label rendered above the select */
@@ -8,14 +8,18 @@ interface Props extends SelectHTMLAttributes<HTMLSelectElement> {
8
  }
9
 
10
  export default function RetroSelect({ label, options, className = '', ...rest }: Props) {
 
 
 
11
  return (
12
  <div className="flex flex-col gap-[2px]">
13
  {label && (
14
- <label className="text-retro-xs font-retro font-medium text-retro-black">
15
  {label}
16
  </label>
17
  )}
18
  <select
 
19
  className={`
20
  px-2 py-[3px]
21
  text-retro-sm font-retro
 
1
+ import { useId, type SelectHTMLAttributes } from 'react'
2
 
3
  interface Props extends SelectHTMLAttributes<HTMLSelectElement> {
4
  /** Optional label rendered above the select */
 
8
  }
9
 
10
  export default function RetroSelect({ label, options, className = '', ...rest }: Props) {
11
+ const generatedId = useId()
12
+ const selectId = rest.id ?? generatedId
13
+
14
  return (
15
  <div className="flex flex-col gap-[2px]">
16
  {label && (
17
+ <label htmlFor={selectId} className="text-retro-xs font-retro font-medium text-retro-black">
18
  {label}
19
  </label>
20
  )}
21
  <select
22
+ id={selectId}
23
  className={`
24
  px-2 py-[3px]
25
  text-retro-sm font-retro
frontend/src/main.tsx CHANGED
@@ -1,10 +1,13 @@
1
  import React from 'react'
2
  import ReactDOM from 'react-dom/client'
 
3
  import App from './App.tsx'
4
  import './index.css'
5
 
6
  ReactDOM.createRoot(document.getElementById('root')!).render(
7
  <React.StrictMode>
8
- <App />
 
 
9
  </React.StrictMode>,
10
  )
 
1
  import React from 'react'
2
  import ReactDOM from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router-dom'
4
  import App from './App.tsx'
5
  import './index.css'
6
 
7
  ReactDOM.createRoot(document.getElementById('root')!).render(
8
  <React.StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
  </React.StrictMode>,
13
  )
frontend/src/pages/Admin.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { type FormEvent, useEffect, useRef, useState } from 'react'
 
2
  import {
3
  fetchCorpora,
4
  fetchManuscripts,
@@ -36,10 +37,6 @@ import {
36
 
37
  type IngestSubTab = 'urls' | 'manifest' | 'files'
38
 
39
- interface Props {
40
- onHome: () => void
41
- }
42
-
43
  // ── Feedback helpers ───────────────────────────────────────────────────────
44
 
45
  function ErrorMsg({ message }: { message: string }) {
@@ -542,7 +539,9 @@ function CorpusDetail({ corpus, onDeleted }: { corpus: Corpus; onDeleted: () =>
542
 
543
  // ── Admin (main component) ─────────────────────────────────────────────────
544
 
545
- export default function Admin({ onHome }: Props) {
 
 
546
  const [corpora, setCorpora] = useState<Corpus[]>([])
547
  const [selectedCorpusId, setSelectedCorpusId] = useState<string | null>(null)
548
  const [showCreate, setShowCreate] = useState(false)
 
1
  import { type FormEvent, useEffect, useRef, useState } from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
  import {
4
  fetchCorpora,
5
  fetchManuscripts,
 
37
 
38
  type IngestSubTab = 'urls' | 'manifest' | 'files'
39
 
 
 
 
 
40
  // ── Feedback helpers ───────────────────────────────────────────────────────
41
 
42
  function ErrorMsg({ message }: { message: string }) {
 
539
 
540
  // ── Admin (main component) ─────────────────────────────────────────────────
541
 
542
+ export default function Admin() {
543
+ const navigate = useNavigate()
544
+ const onHome = () => navigate('/')
545
  const [corpora, setCorpora] = useState<Corpus[]>([])
546
  const [selectedCorpusId, setSelectedCorpusId] = useState<string | null>(null)
547
  const [showCreate, setShowCreate] = useState(false)
frontend/src/pages/Editor.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { useCallback, useEffect, useRef, useState } from 'react'
 
2
  import {
3
  applyCorrections,
4
  getHistory,
@@ -16,11 +17,6 @@ import {
16
  RetroBadge,
17
  } from '../components/retro'
18
 
19
- interface Props {
20
- pageId: string
21
- onBack: () => void
22
- }
23
-
24
  type Panel = 'transcription' | 'commentary' | 'regions' | 'history'
25
 
26
  const PANEL_LABELS: Record<Panel, string> = {
@@ -30,7 +26,9 @@ const PANEL_LABELS: Record<Panel, string> = {
30
  history: 'Historique',
31
  }
32
 
33
- export default function Editor({ pageId, onBack }: Props) {
 
 
34
  const [master, setMaster] = useState<PageMaster | null>(null)
35
  const [history, setHistory] = useState<VersionInfo[]>([])
36
  const [activePanel, setActivePanel] = useState<Panel>('transcription')
@@ -144,7 +142,7 @@ export default function Editor({ pageId, onBack }: Props) {
144
  <RetroWindow title="Erreur" className="w-80">
145
  <div className="p-4 text-retro-sm">
146
  {error}
147
- <div className="mt-2"><RetroButton onClick={onBack}>Retour</RetroButton></div>
148
  </div>
149
  </RetroWindow>
150
  </div>
@@ -159,7 +157,7 @@ export default function Editor({ pageId, onBack }: Props) {
159
  {/* ── Menu bar ───────────────────────────────────────────────── */}
160
  <RetroMenuBar
161
  items={[
162
- { label: 'IIIF Studio', onClick: onBack },
163
  { label: `Editeur — ${master?.folio_label ?? pageId}` },
164
  ]}
165
  right={
 
1
  import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import { useNavigate, useParams } from 'react-router-dom'
3
  import {
4
  applyCorrections,
5
  getHistory,
 
17
  RetroBadge,
18
  } from '../components/retro'
19
 
 
 
 
 
 
20
  type Panel = 'transcription' | 'commentary' | 'regions' | 'history'
21
 
22
  const PANEL_LABELS: Record<Panel, string> = {
 
26
  history: 'Historique',
27
  }
28
 
29
+ export default function Editor() {
30
+ const { pageId = '' } = useParams()
31
+ const navigate = useNavigate()
32
  const [master, setMaster] = useState<PageMaster | null>(null)
33
  const [history, setHistory] = useState<VersionInfo[]>([])
34
  const [activePanel, setActivePanel] = useState<Panel>('transcription')
 
142
  <RetroWindow title="Erreur" className="w-80">
143
  <div className="p-4 text-retro-sm">
144
  {error}
145
+ <div className="mt-2"><RetroButton onClick={() => navigate(-1)}>Retour</RetroButton></div>
146
  </div>
147
  </RetroWindow>
148
  </div>
 
157
  {/* ── Menu bar ───────────────────────────────────────────────── */}
158
  <RetroMenuBar
159
  items={[
160
+ { label: 'IIIF Studio', onClick: () => navigate('/') },
161
  { label: `Editeur — ${master?.folio_label ?? pageId}` },
162
  ]}
163
  right={
frontend/src/pages/Home.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { useEffect, useState } from 'react'
 
2
  import SearchBar from '../components/SearchBar.tsx'
3
  import { RetroMenuBar, RetroWindow, RetroIcon } from '../components/retro'
4
  import {
@@ -16,13 +17,8 @@ const PROFILE_GLYPHS: Record<string, string> = {
16
  'modern-handwritten': '\u{270D}',
17
  }
18
 
19
- interface Props {
20
- onOpenManuscript: (manuscriptId: string, profileId: string) => void
21
- onOpenPage?: (pageId: string) => void
22
- onAdmin: () => void
23
- }
24
-
25
- export default function Home({ onOpenManuscript, onOpenPage, onAdmin }: Props) {
26
  const [corpora, setCorpora] = useState<Corpus[]>([])
27
  const [loading, setLoading] = useState(true)
28
  const [error, setError] = useState<string | null>(null)
@@ -43,7 +39,7 @@ export default function Home({ onOpenManuscript, onOpenPage, onAdmin }: Props) {
43
 
44
  const cached = manuscripts[corpus.id]
45
  if (cached) {
46
- if (cached.length === 1) onOpenManuscript(cached[0].id, corpus.profile_id)
47
  return
48
  }
49
 
@@ -52,7 +48,7 @@ export default function Home({ onOpenManuscript, onOpenPage, onAdmin }: Props) {
52
  try {
53
  const ms = await fetchManuscripts(corpus.id)
54
  setManuscripts((prev) => ({ ...prev, [corpus.id]: ms }))
55
- if (ms.length === 1) onOpenManuscript(ms[0].id, corpus.profile_id)
56
  } catch (e: unknown) {
57
  setExpandError(e instanceof Error ? e.message : 'Erreur de chargement')
58
  } finally {
@@ -95,10 +91,10 @@ export default function Home({ onOpenManuscript, onOpenPage, onAdmin }: Props) {
95
  <RetroMenuBar
96
  items={[
97
  { label: 'IIIF Studio' },
98
- { label: 'Administration', onClick: onAdmin },
99
  ]}
100
  right={
101
- <SearchBar onSelectResult={onOpenPage ? (r) => onOpenPage(r.page_id) : undefined} />
102
  }
103
  />
104
 
@@ -176,7 +172,7 @@ export default function Home({ onOpenManuscript, onOpenPage, onAdmin }: Props) {
176
  <button
177
  type="button"
178
  key={ms.id}
179
- onClick={() => onOpenManuscript(ms.id, selectedCorpus.profile_id)}
180
  className="
181
  w-full text-left px-3 py-[6px]
182
  text-retro-sm font-retro
 
1
  import { useEffect, useState } from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
  import SearchBar from '../components/SearchBar.tsx'
4
  import { RetroMenuBar, RetroWindow, RetroIcon } from '../components/retro'
5
  import {
 
17
  'modern-handwritten': '\u{270D}',
18
  }
19
 
20
+ export default function Home() {
21
+ const navigate = useNavigate()
 
 
 
 
 
22
  const [corpora, setCorpora] = useState<Corpus[]>([])
23
  const [loading, setLoading] = useState(true)
24
  const [error, setError] = useState<string | null>(null)
 
39
 
40
  const cached = manuscripts[corpus.id]
41
  if (cached) {
42
+ if (cached.length === 1) navigate(`/reader/${cached[0].id}?profile=${corpus.profile_id}`)
43
  return
44
  }
45
 
 
48
  try {
49
  const ms = await fetchManuscripts(corpus.id)
50
  setManuscripts((prev) => ({ ...prev, [corpus.id]: ms }))
51
+ if (ms.length === 1) navigate(`/reader/${ms[0].id}?profile=${corpus.profile_id}`)
52
  } catch (e: unknown) {
53
  setExpandError(e instanceof Error ? e.message : 'Erreur de chargement')
54
  } finally {
 
91
  <RetroMenuBar
92
  items={[
93
  { label: 'IIIF Studio' },
94
+ { label: 'Administration', onClick: () => navigate('/admin') },
95
  ]}
96
  right={
97
+ <SearchBar onSelectResult={(r) => navigate(`/editor/${r.page_id}`)} />
98
  }
99
  />
100
 
 
172
  <button
173
  type="button"
174
  key={ms.id}
175
+ onClick={() => navigate(`/reader/${ms.id}?profile=${selectedCorpus.profile_id}`)}
176
  className="
177
  w-full text-left px-3 py-[6px]
178
  text-retro-sm font-retro
frontend/src/pages/Reader.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { useCallback, useEffect, useState } from 'react'
 
2
  import type OpenSeadragon from 'openseadragon'
3
  import {
4
  fetchPages,
@@ -17,14 +18,11 @@ import TranslationPanel from '../components/TranslationPanel.tsx'
17
  import CommentaryPanel from '../components/CommentaryPanel.tsx'
18
  import { RetroMenuBar, RetroWindow, RetroButton, RetroBadge } from '../components/retro'
19
 
20
- interface Props {
21
- manuscriptId: string
22
- profileId: string
23
- onBack: () => void
24
- onEdit?: (pageId: string) => void
25
- }
26
-
27
- export default function Reader({ manuscriptId, profileId, onBack, onEdit }: Props) {
28
  const [pages, setPages] = useState<Page[]>([])
29
  const [currentIndex, setCurrentIndex] = useState(0)
30
  const [master, setMaster] = useState<PageMaster | null>(null)
@@ -108,7 +106,7 @@ export default function Reader({ manuscriptId, profileId, onBack, onEdit }: Prop
108
  <div className="p-4 text-retro-sm">
109
  Aucune page dans ce manuscrit.
110
  <div className="mt-2">
111
- <RetroButton onClick={onBack}>Retour</RetroButton>
112
  </div>
113
  </div>
114
  </RetroWindow>
@@ -125,7 +123,7 @@ export default function Reader({ manuscriptId, profileId, onBack, onEdit }: Prop
125
  {/* ── Menu bar ───────────────────────────────────────────────── */}
126
  <RetroMenuBar
127
  items={[
128
- { label: 'IIIF Studio', onClick: onBack },
129
  { label: profile?.label ?? profileId },
130
  ]}
131
  right={
@@ -147,11 +145,9 @@ export default function Reader({ manuscriptId, profileId, onBack, onEdit }: Prop
147
  >
148
  Next
149
  </RetroButton>
150
- {onEdit && (
151
- <RetroButton size="sm" onClick={() => onEdit(currentPage.id)}>
152
- Editer
153
- </RetroButton>
154
- )}
155
  </div>
156
  }
157
  />
@@ -246,7 +242,8 @@ export default function Reader({ manuscriptId, profileId, onBack, onEdit }: Prop
246
  <TranslationPanel
247
  translation={master.translation}
248
  editorial={master.editorial}
249
- visible={visibleLayers.has('translation_fr')}
 
250
  />
251
  <CommentaryPanel
252
  commentary={master.commentary}
 
1
  import { useCallback, useEffect, useState } from 'react'
2
+ import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
3
  import type OpenSeadragon from 'openseadragon'
4
  import {
5
  fetchPages,
 
18
  import CommentaryPanel from '../components/CommentaryPanel.tsx'
19
  import { RetroMenuBar, RetroWindow, RetroButton, RetroBadge } from '../components/retro'
20
 
21
+ export default function Reader() {
22
+ const { manuscriptId = '' } = useParams()
23
+ const [searchParams] = useSearchParams()
24
+ const profileId = searchParams.get('profile') ?? ''
25
+ const navigate = useNavigate()
 
 
 
26
  const [pages, setPages] = useState<Page[]>([])
27
  const [currentIndex, setCurrentIndex] = useState(0)
28
  const [master, setMaster] = useState<PageMaster | null>(null)
 
106
  <div className="p-4 text-retro-sm">
107
  Aucune page dans ce manuscrit.
108
  <div className="mt-2">
109
+ <RetroButton onClick={() => navigate('/')}>Retour</RetroButton>
110
  </div>
111
  </div>
112
  </RetroWindow>
 
123
  {/* ── Menu bar ───────────────────────────────────────────────── */}
124
  <RetroMenuBar
125
  items={[
126
+ { label: 'IIIF Studio', onClick: () => navigate('/') },
127
  { label: profile?.label ?? profileId },
128
  ]}
129
  right={
 
145
  >
146
  Next
147
  </RetroButton>
148
+ <RetroButton size="sm" onClick={() => navigate(`/editor/${currentPage.id}`)}>
149
+ Editer
150
+ </RetroButton>
 
 
151
  </div>
152
  }
153
  />
 
242
  <TranslationPanel
243
  translation={master.translation}
244
  editorial={master.editorial}
245
+ visible={visibleLayers.has('translation_fr') || visibleLayers.has('translation_en')}
246
+ activeLayers={profile?.active_layers}
247
  />
248
  <CommentaryPanel
249
  commentary={master.commentary}