freat: initroduce katalogos
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Recollection view switcher: Logos or Flux based on pathname.
|
||||
* Single container, conditional render — only the active view is mounted (no CSS flip).
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { useLocation, useParams } from 'react-router-dom'
|
||||
import { LogosPage } from './logos/LogosPage'
|
||||
import { FluxRoute } from './flux/FluxRoute'
|
||||
|
||||
export function FlippingCardView() {
|
||||
const { pathname } = useLocation()
|
||||
const { recollectionId } = useParams<{ recollectionId: string }>()
|
||||
const isFlux = pathname.endsWith('/flux')
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
{isFlux ? (
|
||||
recollectionId ? (
|
||||
<FluxRoute />
|
||||
) : null
|
||||
) : (
|
||||
<LogosPage />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
/**
|
||||
* Layout for a single recollection: shared menubar and flipping card (Logos front / Flux back).
|
||||
* Layout for a single recollection: shared menubar and nested routes for Logos / Katalogos / Flux.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { Link, Outlet, useParams } from 'react-router-dom'
|
||||
import { usePlatform } from '@/app/kosmos/KosmosContext'
|
||||
import { RecollectionMenubarProvider } from './RecollectionMenubarContext'
|
||||
import { RecollectionActionsProvider } from './RecollectionActionsContext'
|
||||
import { RecollectionTitleContent } from './RecollectionTitleContent'
|
||||
import { RecollectionMenubar } from './RecollectionMenubar'
|
||||
import { FlippingCardView } from './FlippingCardView'
|
||||
|
||||
export function RecollectionLayout() {
|
||||
const { recollectionId } = useParams<{ recollectionId: string }>()
|
||||
@@ -40,7 +39,7 @@ export function RecollectionLayout() {
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<RecollectionMenubar />
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<FlippingCardView />
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</RecollectionActionsProvider>
|
||||
|
||||
@@ -5,21 +5,12 @@
|
||||
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import { FluxIcon, LogosIcon } from '@/lib/icons'
|
||||
import { FluxIcon, LogosIcon, RecollectionsIcon } from '@/lib/icons'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
|
||||
const VIEW_TOGGLE_KEYS = { key: 'v', shiftKey: true }
|
||||
|
||||
function matchKey(ev: KeyboardEvent, want: { key: string; shiftKey: boolean }) {
|
||||
const mod = ev.ctrlKey || ev.metaKey
|
||||
return ev.key.toLowerCase() === want.key && !!mod && ev.shiftKey === want.shiftKey
|
||||
}
|
||||
|
||||
function shortcutLabel() {
|
||||
if (typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform)) {
|
||||
return '⌘⇧V'
|
||||
}
|
||||
return 'Ctrl+Shift+V'
|
||||
const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform)
|
||||
return isMac ? '⌘⇧↑ / ⌘⇧↓' : 'Ctrl+Shift+↑ / Ctrl+Shift+↓'
|
||||
}
|
||||
|
||||
export function RecollectionViewSwitcher() {
|
||||
@@ -28,30 +19,53 @@ export function RecollectionViewSwitcher() {
|
||||
const navigate = useNavigate()
|
||||
const base = recollectionId ? `/recollections/${recollectionId}` : ''
|
||||
|
||||
const isFlux = pathname.endsWith('/flux')
|
||||
type Mode = 'logos' | 'katalogos' | 'flux'
|
||||
const mode: Mode = pathname.endsWith('/flux')
|
||||
? 'flux'
|
||||
: pathname.endsWith('/katalogos')
|
||||
? 'katalogos'
|
||||
: 'logos'
|
||||
|
||||
const goRelative = useCallback(
|
||||
(delta: 1 | -1) => {
|
||||
if (!base) return
|
||||
const order: Mode[] = ['logos', 'katalogos', 'flux']
|
||||
const currentIndex = order.indexOf(mode)
|
||||
const nextMode = order[(currentIndex + (delta === 1 ? 1 : order.length - 1)) % order.length]
|
||||
navigate(`${base}/${nextMode}`)
|
||||
},
|
||||
[base, mode, navigate]
|
||||
)
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
if (!base) return
|
||||
navigate(isFlux ? `${base}/logos` : `${base}/flux`)
|
||||
}, [base, isFlux, navigate])
|
||||
goRelative(1)
|
||||
}, [goRelative])
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (ev: KeyboardEvent) => {
|
||||
if (matchKey(ev, VIEW_TOGGLE_KEYS)) {
|
||||
ev.preventDefault()
|
||||
toggle()
|
||||
const isMod = ev.ctrlKey || ev.metaKey
|
||||
const isShortcut = isMod && ev.shiftKey && (ev.key === 'ArrowDown' || ev.key === 'ArrowUp')
|
||||
if (!isShortcut) return
|
||||
ev.preventDefault()
|
||||
if (ev.key === 'ArrowDown') {
|
||||
goRelative(1)
|
||||
} else if (ev.key === 'ArrowUp') {
|
||||
goRelative(-1)
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKeyDown, true)
|
||||
return () => window.removeEventListener('keydown', onKeyDown, true)
|
||||
}, [toggle])
|
||||
}, [goRelative])
|
||||
|
||||
if (!base) return null
|
||||
|
||||
const shortcut = shortcutLabel()
|
||||
const tooltipText = isFlux
|
||||
? `Flux · Switch to Logos (${shortcut})`
|
||||
: `Logos · Switch to Flux (${shortcut})`
|
||||
const tooltipText =
|
||||
mode === 'logos'
|
||||
? `Logos · Next: Katalogos (${shortcut})`
|
||||
: mode === 'katalogos'
|
||||
? `Katalogos · Next: Flux (${shortcut})`
|
||||
: `Flux · Next: Logos (${shortcut})`
|
||||
|
||||
return (
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
@@ -61,20 +75,34 @@ export function RecollectionViewSwitcher() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggle}
|
||||
aria-label={isFlux ? 'Switch to Logos' : 'Switch to Flux'}
|
||||
aria-label={
|
||||
mode === 'logos'
|
||||
? 'Switch to Katalogos'
|
||||
: mode === 'katalogos'
|
||||
? 'Switch to Flux'
|
||||
: 'Switch to Logos'
|
||||
}
|
||||
className="h-7 w-7 shrink-0 overflow-hidden rounded-full border border-border/60 bg-background shadow-md transition-colors hover:bg-accent hover:text-accent-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<div
|
||||
className="flex w-full flex-col transition-transform duration-200 ease-out"
|
||||
style={{
|
||||
height: '200%',
|
||||
transform: isFlux ? 'translateY(-50%)' : 'translateY(0)',
|
||||
height: '300%',
|
||||
transform:
|
||||
mode === 'logos'
|
||||
? 'translateY(0)'
|
||||
: mode === 'katalogos'
|
||||
? 'translateY(-33.3333%)'
|
||||
: 'translateY(-66.6667%)',
|
||||
}}
|
||||
>
|
||||
<div className="flex h-9 w-full items-center justify-center">
|
||||
<LogosIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex h-9 w-full items-center justify-center">
|
||||
<RecollectionsIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex h-9 w-full items-center justify-center">
|
||||
<FluxIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
@@ -88,11 +116,21 @@ export function RecollectionViewSwitcher() {
|
||||
<div className="h-9 min-w-[3.5rem] overflow-hidden">
|
||||
<div
|
||||
className="flex w-full flex-col transition-transform duration-200 ease-out"
|
||||
style={{ transform: isFlux ? 'translateY(-50%)' : 'translateY(0)' }}
|
||||
style={{
|
||||
transform:
|
||||
mode === 'logos'
|
||||
? 'translateY(0)'
|
||||
: mode === 'katalogos'
|
||||
? 'translateY(-33.3333%)'
|
||||
: 'translateY(-66.6667%)',
|
||||
}}
|
||||
>
|
||||
<span className="flex h-9 flex-shrink-0 items-center text-sm font-medium text-foreground">
|
||||
Logos
|
||||
</span>
|
||||
<span className="flex h-9 flex-shrink-0 items-center text-sm font-medium text-foreground">
|
||||
Katalogos
|
||||
</span>
|
||||
<span className="flex h-9 flex-shrink-0 items-center text-sm font-medium text-foreground">
|
||||
Flux
|
||||
</span>
|
||||
|
||||
31
frontend/src/app/recollections/katalogos/KatalogosPage.tsx
Normal file
31
frontend/src/app/recollections/katalogos/KatalogosPage.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Katalogos page: third view for a recollection.
|
||||
* Simple placeholder layout, matching Logos/Flux flex and typography.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { usePlatform } from '@/app/kosmos/KosmosContext'
|
||||
|
||||
export function KatalogosPage() {
|
||||
const { recollectionId } = useParams<{ recollectionId: string }>()
|
||||
const { recollections } = usePlatform()
|
||||
|
||||
const recollection = recollectionId ? recollections.find((p) => p.id === recollectionId) : null
|
||||
const title = recollection?.name ?? 'Untitled'
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-1 flex-col overflow-auto bg-background">
|
||||
<div className="mx-auto w-full max-w-3xl p-4">
|
||||
<h1 className="mb-2 truncate font-serif text-2xl font-semibold tracking-tight text-foreground">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="mb-6 text-sm text-muted-foreground">Katalogos</p>
|
||||
<div className="rounded-lg border border-dashed border-muted-foreground/30 bg-muted/10 p-4 text-sm text-muted-foreground">
|
||||
Katalogos view coming soon.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import { registerBuiltinConfigTypes } from './lib/graph/configTypes'
|
||||
import { KosmosPage } from './app/kosmos/KosmosPage'
|
||||
import { RecollectionsPage } from './app/recollections/RecollectionsPage'
|
||||
import { RecollectionLayout } from './app/recollections/RecollectionLayout'
|
||||
import { LogosPage } from './app/recollections/logos/LogosPage'
|
||||
import { FluxRoute } from './app/recollections/flux/FluxRoute'
|
||||
import { KatalogosPage } from './app/recollections/katalogos/KatalogosPage'
|
||||
import './lib/prismSetup'
|
||||
import 'prismjs/themes/prism.css'
|
||||
import './styles.css'
|
||||
@@ -26,8 +29,9 @@ createRoot(document.getElementById('root')!).render(
|
||||
<Route path="recollections" element={<RecollectionsPage />} />
|
||||
<Route path="recollections/:recollectionId" element={<RecollectionLayout />}>
|
||||
<Route index element={<Navigate to="logos" replace />} />
|
||||
<Route path="logos" element={<></>} />
|
||||
<Route path="flux" element={<></>} />
|
||||
<Route path="logos" element={<LogosPage />} />
|
||||
<Route path="katalogos" element={<KatalogosPage />} />
|
||||
<Route path="flux" element={<FluxRoute />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
Reference in New Issue
Block a user