@@ -3,7 +3,7 @@ import {createBrowserRouter, Outlet, RouterProvider, useLocation} from "react-ro
3
3
import { strings } from "./main" ;
4
4
import { useContext , useEffect , useMemo , useRef , useState } from "react" ;
5
5
import { db } from "./backend/db" ;
6
- import { SourceDescription , Document , Agent , Person } from "./backend/gedcomx-extensions" ;
6
+ import { SourceDescription , Document , Agent , Person , EventExtended } from "./backend/gedcomx-extensions" ;
7
7
import { Home } from "./components/Home" ;
8
8
import Persons from "./components/Persons" ;
9
9
import Statistics from "./components/Statistics" ;
@@ -15,6 +15,13 @@ import {PlaceOverview, PlaceView} from "./components/Places";
15
15
import ErrorBoundary from "./components/ErrorBoundary" ;
16
16
import { ReactLink , ReactNavLink , VanillaLink } from "./components/GeneralComponents" ;
17
17
import { Imprint } from "./components/Imprint" ;
18
+ import { EventOverview , EventView } from "./components/Events" ;
19
+ import emojis from './backend/emojies.json' ;
20
+
21
+ let personCache = {
22
+ id : undefined ,
23
+ person : undefined
24
+ }
18
25
19
26
const router = createBrowserRouter ( [
20
27
{
@@ -23,11 +30,17 @@ const router = createBrowserRouter([
23
30
path : "*" , errorElement : < ErrorBoundary /> , children : [
24
31
{ index : true , Component : Home } ,
25
32
{
26
- path : "persons/:id?" , Component : Persons , loader : ( { params} ) => {
33
+ path : "person/:id?" , Component : Persons , loader : ( { params} ) => {
34
+ if ( personCache . person !== undefined && personCache . id === params . id ) {
35
+ return personCache . person ;
36
+ }
37
+
38
+ personCache . id = params . id ;
39
+
27
40
if ( ! params . id ) {
28
41
// find a person whose id does not start with "missing-id-" if possible
29
42
// persons with missing ids are not connected to any other persons, as they cannot be referenced in relationships
30
- return db . persons . toArray ( ) . then ( ps => ps . sort ( ( a , b ) => {
43
+ personCache . person = db . persons . toArray ( ) . then ( ps => ps . sort ( ( a , b ) => {
31
44
// Check if either string starts with "missing-id-"
32
45
const aStartsWithMissingId = a . id . startsWith ( "missing-id-" ) ;
33
46
const bStartsWithMissingId = b . id . startsWith ( "missing-id-" ) ;
@@ -42,13 +55,16 @@ const router = createBrowserRouter([
42
55
return a . id . localeCompare ( b . id ) ;
43
56
}
44
57
} ) [ 0 ] ) . then ( p => p ? new Person ( p ) : Promise . reject ( new Error ( strings . errors . noData ) ) ) ;
58
+ } else {
59
+ personCache . person = db . personWithId ( params . id ) ;
45
60
}
46
- return db . personWithId ( params . id ) ;
61
+
62
+ return personCache . person ;
47
63
}
48
64
} ,
49
65
{ path : "stats" , Component : Statistics } ,
50
66
{
51
- path : "sources " , children : [
67
+ path : "sourceDescription " , children : [
52
68
{
53
69
index : true ,
54
70
Component : SourceDescriptionOverview ,
@@ -62,7 +78,7 @@ const router = createBrowserRouter([
62
78
]
63
79
} ,
64
80
{
65
- path : "documents " , children : [
81
+ path : "document " , children : [
66
82
{
67
83
index : true ,
68
84
Component : DocumentOverview ,
@@ -72,7 +88,7 @@ const router = createBrowserRouter([
72
88
]
73
89
} ,
74
90
{
75
- path : "agents " , children : [
91
+ path : "agent " , children : [
76
92
{
77
93
index : true ,
78
94
Component : AgentOverview ,
@@ -81,17 +97,27 @@ const router = createBrowserRouter([
81
97
{ path : ":id" , Component : AgentView , loader : ( { params} ) => db . elementWithId ( params . id , "agent" ) }
82
98
]
83
99
} ,
84
- { path : "imprint" , Component : Imprint } ,
85
100
{
86
- path : "places" , children : [
101
+ path : "event" , children : [
102
+ {
103
+ index : true ,
104
+ Component : EventOverview ,
105
+ loader : ( ) => db . events . toArray ( ) . then ( e => e . length ? e . map ( d => new EventExtended ( d ) ) : Promise . reject ( new Error ( strings . errors . noData ) ) )
106
+ } ,
107
+ { path : ":id" , Component : EventView , loader : ( { params} ) => db . elementWithId ( params . id , "event" ) }
108
+ ]
109
+ } ,
110
+ {
111
+ path : "place" , children : [
87
112
{
88
113
index : true ,
89
114
Component : PlaceOverview ,
90
115
loader : ( ) => db . places . toArray ( ) . then ( p => p . length ? p . map ( d => new PlaceDescription ( d ) ) : Promise . reject ( new Error ( strings . errors . noData ) ) )
91
116
} ,
92
117
{ path : ":id" , Component : PlaceView , loader : ( { params} ) => db . elementWithId ( params . id , "place" ) }
93
118
]
94
- }
119
+ } ,
120
+ { path : "imprint" , Component : Imprint }
95
121
]
96
122
} ]
97
123
} ] , { basename : "/family-tree" } ) ;
@@ -103,10 +129,7 @@ export default function App() {
103
129
interface ILayoutContext {
104
130
setRightTitle : ( string ) => void ,
105
131
setHeaderChildren : ( ReactNode ) => void ,
106
- sidebarVisible : boolean ,
107
- isDark : boolean ,
108
- allowExternalContent : boolean ,
109
- toggleExternalContent : ( boolean ) => void ,
132
+ sidebarVisible : boolean
110
133
}
111
134
112
135
export const LayoutContext = React . createContext < ILayoutContext > ( undefined ) ;
@@ -116,44 +139,51 @@ function Layout() {
116
139
const [ headerChildren , setChildren ] = useState ( [ ] ) ;
117
140
const [ navBarExtended , toggleNavBar ] = useState ( false ) ;
118
141
const [ sidebarExtended , toggleSidebar ] = useState ( matchMedia ( "(min-width: 768px)" ) . matches ) ;
119
- const [ allowExternalContent , toggleExternalContent ] = useState ( false ) ;
120
142
const dialog = useRef < HTMLDialogElement > ( ) ;
121
143
const location = useLocation ( ) ;
122
- const darkQuery = matchMedia ( "(prefers-color-scheme: dark)" ) ;
123
- const [ isDark , toggleDark ] = useState ( darkQuery . matches ) ;
124
- darkQuery . addEventListener ( "change" , e => toggleDark ( e . matches ) ) ;
125
144
const query = matchMedia ( "(max-width: 639px)" ) ;
126
145
const [ isSmallScreen , setSmallScreen ] = useState ( query . matches ) ;
127
146
query . addEventListener ( "change" , e => setSmallScreen ( e . matches ) ) ;
128
147
129
148
const nav = < nav className = "row-start-2 row-span-2 dark:text-white" >
130
149
< ul className = { `flex flex-col gap-2 ${ isSmallScreen ? "" : "ml-2" } text-lg` } >
131
- < li > < ReactNavLink to = "" > { "🏠" + ( navBarExtended ? ` ${ strings . home . title } ` : "" ) } </ ReactNavLink > </ li >
132
- < li > < ReactNavLink to = "persons" > { "🌳" + ( navBarExtended ? ` ${ strings . gedcomX . person . persons } ` : "" ) } </ ReactNavLink >
150
+ < li > < ReactNavLink to = "" >
151
+ { emojis . home + ( navBarExtended ? ` ${ strings . home . title } ` : "" ) }
152
+ </ ReactNavLink > </ li >
153
+ < li > < ReactNavLink to = "person" >
154
+ { emojis . tree + ( navBarExtended ? ` ${ strings . gedcomX . person . persons } ` : "" ) }
155
+ </ ReactNavLink >
133
156
</ li >
134
- < li > < ReactNavLink to = "stats" > { "📊" + ( navBarExtended ? ` ${ strings . statistics . title } ` : "" ) } </ ReactNavLink > </ li >
135
- < li > < ReactNavLink
136
- to = "sources" > { "📚" + ( navBarExtended ? ` ${ strings . gedcomX . sourceDescription . sourceDescriptions } ` : "" ) } </ ReactNavLink >
157
+ < li > < ReactNavLink to = "stats" >
158
+ { emojis . stats + ( navBarExtended ? ` ${ strings . statistics . title } ` : "" ) }
159
+ </ ReactNavLink > </ li >
160
+ < li > < ReactNavLink to = "sourceDescription" >
161
+ { emojis . source . default + ( navBarExtended ? ` ${ strings . gedcomX . sourceDescription . sourceDescriptions } ` : "" ) }
162
+ </ ReactNavLink >
137
163
</ li >
138
- < li > < ReactNavLink
139
- to = "documents" > { "📄" + ( navBarExtended ? ` ${ strings . gedcomX . document . documents } ` : "" ) } </ ReactNavLink > </ li >
140
- < li > < ReactNavLink to = "agents" > { "👤" + ( navBarExtended ? ` ${ strings . gedcomX . agent . agents } ` : "" ) } </ ReactNavLink >
164
+ < li > < ReactNavLink to = "document" >
165
+ { emojis . document . default + ( navBarExtended ? ` ${ strings . gedcomX . document . documents } ` : "" ) }
166
+ </ ReactNavLink > </ li >
167
+ < li > < ReactNavLink to = "agent" >
168
+ { emojis . agent . agent + ( navBarExtended ? ` ${ strings . gedcomX . agent . agents } ` : "" ) }
169
+ </ ReactNavLink >
141
170
</ li >
142
- < li > < ReactNavLink
143
- to = "places" > { "🌎" + ( navBarExtended ? ` ${ strings . gedcomX . placeDescription . places } ` : "" ) } </ ReactNavLink > </ li >
171
+ < li > < ReactNavLink to = "place" >
172
+ { emojis . place + ( navBarExtended ? ` ${ strings . gedcomX . placeDescription . places } ` : "" ) }
173
+ </ ReactNavLink > </ li >
174
+ < li > < ReactNavLink to = "event" >
175
+ { emojis . event . default + ( navBarExtended ? ` ${ strings . gedcomX . event . events } ` : "" ) }
176
+ </ ReactNavLink > </ li >
144
177
</ ul >
145
178
</ nav >
146
179
147
180
const layoutContext = useMemo ( ( ) => {
148
181
return {
149
182
setRightTitle : setTitleRight ,
150
183
setHeaderChildren : setChildren ,
151
- sidebarVisible : sidebarExtended ,
152
- isDark : isDark ,
153
- allowExternalContent : allowExternalContent ,
154
- toggleExternalContent : toggleExternalContent
184
+ sidebarVisible : sidebarExtended
155
185
}
156
- } , [ allowExternalContent , isDark , sidebarExtended ] )
186
+ } , [ sidebarExtended ] )
157
187
158
188
useEffect ( ( ) => {
159
189
if ( navBarExtended && ! dialog . current ?. open ) dialog . current ?. showModal ( ) ;
@@ -164,18 +194,18 @@ function Layout() {
164
194
165
195
return < >
166
196
< div className = "row-start-1 ml-4 font-bold text-xl h-full my-1 dark:text-white" >
167
- < button onClick = { ( ) => toggleNavBar ( ! navBarExtended ) } > { navBarExtended ? "⬅️" : "➡️" } </ button >
197
+ < button onClick = { ( ) => toggleNavBar ( ! navBarExtended ) } > { navBarExtended ? emojis . left : emojis . right } </ button >
168
198
</ div >
169
199
{ isSmallScreen ? < dialog ref = { dialog } className = "rounded-2xl" > { nav } </ dialog > : nav }
170
200
171
- < header className = "row-start-1 text-xl flex flex-row items-center justify-center gap-4 dark:text-white w-full" >
201
+ < header className = "row-start-1 flex flex-row items-center justify-center gap-4 dark:text-white w-full" >
172
202
{ headerChildren }
173
203
</ header >
174
204
175
205
{ titleRight && < div className = "row-start-1 text-right lg:text-center font-bold text-xl my-1 mr-4 dark:text-white" >
176
206
{ sidebarExtended && < span className = { `mr-4 hidden md:inline` } > { titleRight } </ span > }
177
207
< span className = { `lg:hidden` } >
178
- < button onClick = { ( ) => toggleSidebar ( ! sidebarExtended ) } > { sidebarExtended ? "➡️" : "⬅️" } </ button >
208
+ < button onClick = { ( ) => toggleSidebar ( ! sidebarExtended ) } > { sidebarExtended ? emojis . right : emojis . left } </ button >
179
209
</ span >
180
210
</ div > }
181
211
@@ -228,7 +258,7 @@ export function Sidebar(props) {
228
258
229
259
if ( layoutContext . sidebarVisible ) {
230
260
return < aside
231
- className = { `row-start-2 md:row-span-2 mx-4 sm:ml-0 col-start-1 sm:col-start-2 md:col-start-3 col-span-3 sm:col-span-2 md:col-span-1 max-h-64 md:max-h-full md:max-w-xs overflow-y-auto overflow-x-scroll flex gap-4 flex-col dark:text-white` } >
261
+ className = { `row-start-2 md:row-span-2 mx-4 sm:ml-0 col-start-1 sm:col-start-2 md:col-start-3 col-span-3 sm:col-span-2 md:col-span-1 max-h-64 md:max-h-full md:max-w-xs overflow-y-auto overflow-x-scroll flex gap-6 flex-col dark:text-white` } >
232
262
{ props . children }
233
263
</ aside >
234
264
}
0 commit comments