@cu-mkp/editioncrafter 1.3.0 → 1.3.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -107,8 +107,6 @@ function htmlToReactParserOptions(selectedZone, selectedTags) {
107
107
  function TranscriptionView(props) {
108
108
  const [searchParams] = useSearchParams()
109
109
 
110
- const { tags } = useContext(TagFilterContext)
111
-
112
110
  const {
113
111
  side,
114
112
  folioID,
@@ -118,6 +116,9 @@ function TranscriptionView(props) {
118
116
  documentViewActions,
119
117
  } = props
120
118
 
119
+ const { tagsLeft, tagsRight } = useContext(TagFilterContext)
120
+ const tags = side === 'left' ? tagsLeft : tagsRight
121
+
121
122
  if (folioID === '-1') {
122
123
  return (
123
124
  <Watermark
@@ -131,7 +131,7 @@ function* resolveFolio(pathSegments) {
131
131
  folioIDs.push(thirdID)
132
132
 
133
133
  for (const folioID of folioIDs) {
134
- const folioData = document.folioIndex[folioID]
134
+ const folioData = document.folioIndex[folioID] || document.folioIndex[decodeURI(folioID)]
135
135
  if (folioData && !folioData.loading) {
136
136
  // wait for folio to load and then advance state
137
137
  const folio = yield loadFolio(folioData)
@@ -1,16 +1,19 @@
1
1
  function InsertLeft() {
2
2
  return (
3
3
  <svg width="28" height="21" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
4
- <g id="fluent:document-one-page-20-regular" clipPath="url(#clip0_527_47401)">
4
+ <g
5
+ // id="fluent:document-one-page-20-regular"
6
+ // clipPath="url(#clip0_527_47401)"
7
+ >
5
8
  <path id="Vector" d="M1.26984 0H6.34921C6.68599 0 7.00898 0.158035 7.24712 0.43934C7.48526 0.720644 7.61905 1.10218 7.61905 1.5V10.5C7.61905 10.8978 7.48526 11.2794 7.24712 11.5607C7.00898 11.842 6.68599 12 6.34921 12H1.26984C0.933058 12 0.610069 11.842 0.371928 11.5607C0.133786 11.2794 0 10.8978 0 10.5V1.5C0 1.10218 0.133786 0.720644 0.371928 0.43934C0.610069 0.158035 0.933058 0 1.26984 0ZM1.26984 0.75C1.10145 0.75 0.939955 0.829018 0.820885 0.96967C0.701814 1.11032 0.634921 1.30109 0.634921 1.5V10.5C0.634921 10.6989 0.701814 10.8897 0.820885 11.0303C0.939955 11.171 1.10145 11.25 1.26984 11.25H6.34921C6.5176 11.25 6.67909 11.171 6.79816 11.0303C6.91723 10.8897 6.98413 10.6989 6.98413 10.5V1.5C6.98413 1.30109 6.91723 1.11032 6.79816 0.96967C6.67909 0.829018 6.5176 0.75 6.34921 0.75H1.26984Z" fill="white" />
6
9
  <path id="Vector_2" opacity="0.4" d="M9.6507 0H14.7301C15.0668 0 15.3898 0.158035 15.628 0.43934C15.8661 0.720644 15.9999 1.10218 15.9999 1.5V10.5C15.9999 10.8978 15.8661 11.2794 15.628 11.5607C15.3898 11.842 15.0668 12 14.7301 12H9.6507C9.31392 12 8.99093 11.842 8.75279 11.5607C8.51465 11.2794 8.38086 10.8978 8.38086 10.5V1.5C8.38086 1.10218 8.51465 0.720644 8.75279 0.43934C8.99093 0.158035 9.31392 0 9.6507 0ZM9.6507 0.75C9.48231 0.75 9.32081 0.829018 9.20174 0.96967C9.08267 1.11032 9.01578 1.30109 9.01578 1.5V10.5C9.01578 10.6989 9.08267 10.8897 9.20174 11.0303C9.32081 11.171 9.48231 11.25 9.6507 11.25H14.7301C14.8985 11.25 15.06 11.171 15.179 11.0303C15.2981 10.8897 15.365 10.6989 15.365 10.5V1.5C15.365 1.30109 15.2981 1.11032 15.179 0.96967C15.06 0.829018 14.8985 0.75 14.7301 0.75H9.6507Z" fill="white" />
7
10
  <path id="Vector_3" d="M5.82826 6.11765C5.82826 6.16445 5.81101 6.20934 5.7803 6.24243C5.74959 6.27553 5.70794 6.29412 5.66451 6.29412H4.02709V8.05882C4.02709 8.10563 4.00984 8.15051 3.97913 8.18361C3.94842 8.2167 3.90677 8.23529 3.86335 8.23529C3.81992 8.23529 3.77827 8.2167 3.74756 8.18361C3.71686 8.15051 3.6996 8.10563 3.6996 8.05882V6.29412H2.06218C2.01875 6.29412 1.9771 6.27553 1.9464 6.24243C1.91569 6.20934 1.89844 6.16445 1.89844 6.11765C1.89844 6.07084 1.91569 6.02596 1.9464 5.99286C1.9771 5.95977 2.01875 5.94118 2.06218 5.94118H3.6996V4.17647C3.6996 4.12967 3.71686 4.08478 3.74756 4.05169C3.77827 4.01859 3.81992 4 3.86335 4C3.90677 4 3.94842 4.01859 3.97913 4.05169C4.00984 4.08478 4.02709 4.12967 4.02709 4.17647V5.94118H5.66451C5.70794 5.94118 5.74959 5.95977 5.7803 5.99286C5.81101 6.02596 5.82826 6.07084 5.82826 6.11765Z" fill="white" />
8
11
  </g>
9
- <defs>
12
+ {/* <defs>
10
13
  <clipPath id="clip0_527_47401">
11
14
  <rect width="16" height="12" fill="white" />
12
15
  </clipPath>
13
- </defs>
16
+ </defs> */}
14
17
  </svg>
15
18
  )
16
19
  }
@@ -1,16 +1,18 @@
1
1
  function InsertRight() {
2
2
  return (
3
3
  <svg width="28" height="21" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
4
- <g clipPath="url(#clip0_527_47406)">
4
+ <g
5
+ // clipPath="url(#clip0_527_47406)"
6
+ >
5
7
  <path opacity="0.4" d="M1.27 0H6.35C6.68682 0 7.00985 0.158035 7.24802 0.43934C7.4862 0.720644 7.62 1.10218 7.62 1.5V10.5C7.62 10.8978 7.4862 11.2794 7.24802 11.5607C7.00985 11.842 6.68682 12 6.35 12H1.27C0.933175 12 0.610146 11.842 0.371974 11.5607C0.133803 11.2794 0 10.8978 0 10.5V1.5C0 1.10218 0.133803 0.720644 0.371974 0.43934C0.610146 0.158035 0.933175 0 1.27 0ZM1.27 0.75C1.10159 0.75 0.940073 0.829018 0.820987 0.96967C0.701902 1.11032 0.635 1.30109 0.635 1.5V10.5C0.635 10.6989 0.701902 10.8897 0.820987 11.0303C0.940073 11.171 1.10159 11.25 1.27 11.25H6.35C6.51841 11.25 6.67993 11.171 6.79901 11.0303C6.9181 10.8897 6.985 10.6989 6.985 10.5V1.5C6.985 1.30109 6.9181 1.11032 6.79901 0.96967C6.67993 0.829018 6.51841 0.75 6.35 0.75H1.27Z" fill="white" />
6
8
  <path d="M9.64988 0H14.7299C15.0667 0 15.3897 0.158035 15.6279 0.43934C15.8661 0.720644 15.9999 1.10218 15.9999 1.5V10.5C15.9999 10.8978 15.8661 11.2794 15.6279 11.5607C15.3897 11.842 15.0667 12 14.7299 12H9.64988C9.31306 12 8.99003 11.842 8.75186 11.5607C8.51369 11.2794 8.37988 10.8978 8.37988 10.5V1.5C8.37988 1.10218 8.51369 0.720644 8.75186 0.43934C8.99003 0.158035 9.31306 0 9.64988 0ZM9.64988 0.75C9.48147 0.75 9.31996 0.829018 9.20087 0.96967C9.08178 1.11032 9.01488 1.30109 9.01488 1.5V10.5C9.01488 10.6989 9.08178 10.8897 9.20087 11.0303C9.31996 11.171 9.48147 11.25 9.64988 11.25H14.7299C14.8983 11.25 15.0598 11.171 15.1789 11.0303C15.298 10.8897 15.3649 10.6989 15.3649 10.5V1.5C15.3649 1.30109 15.298 1.11032 15.1789 0.96967C15.0598 0.829018 14.8983 0.75 14.7299 0.75H9.64988Z" fill="white" />
7
9
  <path d="M14.2096 6.10028C14.2096 6.1404 14.1924 6.17887 14.1616 6.20724C14.1309 6.23561 14.0893 6.25154 14.0458 6.25154H12.4082V7.76415C12.4082 7.80426 12.391 7.84274 12.3603 7.87111C12.3295 7.89947 12.2879 7.91541 12.2445 7.91541C12.201 7.91541 12.1594 7.89947 12.1287 7.87111C12.0979 7.84274 12.0807 7.80426 12.0807 7.76415V6.25154H10.4431C10.3996 6.25154 10.358 6.23561 10.3273 6.20724C10.2966 6.17887 10.2793 6.1404 10.2793 6.10028C10.2793 6.06017 10.2966 6.02169 10.3273 5.99332C10.358 5.96496 10.3996 5.94902 10.4431 5.94902H12.0807V4.43642C12.0807 4.3963 12.0979 4.35783 12.1287 4.32946C12.1594 4.30109 12.201 4.28516 12.2445 4.28516C12.2879 4.28516 12.3295 4.30109 12.3603 4.32946C12.391 4.35783 12.4082 4.3963 12.4082 4.43642V5.94902H14.0458C14.0893 5.94902 14.1309 5.96496 14.1616 5.99332C14.1924 6.02169 14.2096 6.06017 14.2096 6.10028Z" fill="white" />
8
10
  </g>
9
- <defs>
11
+ {/* <defs>
10
12
  <clipPath id="clip0_527_47406">
11
13
  <rect width="16" height="12" fill="white" />
12
14
  </clipPath>
13
- </defs>
15
+ </defs> */}
14
16
  </svg>
15
17
  )
16
18
  }
@@ -195,29 +195,32 @@ function DocumentDetail(props) {
195
195
  updatePageCount(surfaces?.length)
196
196
  }, [surfaces, updatePageCount, tags])
197
197
 
198
- return (
199
- <Accordion>
200
- <AccordionSummary
201
- expandIcon={<ExpandMoreIcon />}
202
- aria-controls={`document-detail-${documentID}-content`}
203
- id={`document-detail-${documentID}`}
204
- className="accordion-summary"
205
- >
206
- <Typography>{documentName}</Typography>
207
- <Typography>{surfaces?.length || ''}</Typography>
208
- </AccordionSummary>
209
- <AccordionDetails
210
- className="accordion-detail"
211
- >
212
- <ThumbnailGrid
213
- navigateToSelection={navigateToSelection}
214
- documentLocalID={documentLocalID}
215
- surfaces={surfaces}
216
- selection={selection}
217
- >
218
- </ThumbnailGrid>
219
- </AccordionDetails>
220
- </Accordion>
198
+ return ((!tags.length || surfaces.length)
199
+ ? (
200
+ <Accordion>
201
+ <AccordionSummary
202
+ expandIcon={<ExpandMoreIcon />}
203
+ aria-controls={`document-detail-${documentID}-content`}
204
+ id={`document-detail-${documentID}`}
205
+ className="accordion-summary"
206
+ >
207
+ <Typography>{documentName}</Typography>
208
+ <Typography>{surfaces?.length || ''}</Typography>
209
+ </AccordionSummary>
210
+ <AccordionDetails
211
+ className="accordion-detail"
212
+ >
213
+ <ThumbnailGrid
214
+ navigateToSelection={navigateToSelection}
215
+ documentLocalID={documentLocalID}
216
+ surfaces={surfaces}
217
+ selection={selection}
218
+ >
219
+ </ThumbnailGrid>
220
+ </AccordionDetails>
221
+ </Accordion>
222
+ )
223
+ : null
221
224
  )
222
225
  }
223
226
 
@@ -0,0 +1,293 @@
1
+ import { Checkbox, FormControlLabel, FormGroup, Typography } from '@material-ui/core'
2
+ import { useCallback, useEffect, useMemo, useState } from 'react'
3
+ import { getObjs } from '../../common/lib/sql'
4
+
5
+ function getData(db) {
6
+ const rolesStmt = db.prepare(`
7
+ SELECT * FROM roles
8
+ `)
9
+ const agentStmt = db.prepare(`
10
+ SELECT
11
+ people.name as name,
12
+ roles.name as role,
13
+ people.id as person_id,
14
+ roles.id as role_id
15
+ FROM
16
+ agents
17
+ LEFT JOIN people
18
+ ON people.id = agents.person
19
+ LEFT JOIN roles
20
+ ON roles.id = agents.role
21
+ GROUP BY
22
+ roles.name, people.name, people.id, roles.id
23
+ `)
24
+
25
+ const keywordsStmt = db.prepare(`
26
+ SELECT * FROM keywords
27
+ `)
28
+
29
+ const typesStmt = db.prepare(`
30
+ SELECT type FROM keywords GROUP BY type
31
+ `)
32
+
33
+ const langStmt = db.prepare(`SELECT * FROM languages`)
34
+ const locStmt = db.prepare(`SELECT * FROM locations`)
35
+
36
+ return {
37
+ agents: getObjs(agentStmt),
38
+ roles: getObjs(rolesStmt),
39
+ types: getObjs(typesStmt),
40
+ keywords: getObjs(keywordsStmt),
41
+ languages: getObjs(langStmt),
42
+ locations: getObjs(locStmt),
43
+ }
44
+ }
45
+
46
+ function DocumentFilters(props) {
47
+ const { filters, query } = props
48
+ const fullData = useMemo(() => getData(props.db), [props.db])
49
+ const [expanded, setExpanded] = useState(fullData.roles?.map(() => (false)))
50
+ const [keywordExpanded, setKeywordExpanded] = useState(fullData.types?.map(() => (false)))
51
+ const [langExpanded, setLangExpanded] = useState(false)
52
+ const [locExpanded, setLocExpanded] = useState(false)
53
+ const [displayedTags, setDisplayedTags] = useState({})
54
+
55
+ const data = useMemo(() => {
56
+ const agents = fullData.agents?.filter(ag => (!query || !query.length || ag.name?.toLowerCase().includes(query.toLowerCase())))
57
+ const keywords = fullData.keywords?.filter(term => (!query || !query.length || term.name?.toLowerCase().includes(query.toLowerCase())))
58
+ const languages = fullData.languages?.filter(lang => (!query || !query.length || lang.name.toLowerCase().includes(query.toLowerCase())))
59
+ const locations = fullData.locations?.filter(loc => (!query || !query.length || loc.name.toLowerCase().includes(query.toLowerCase())))
60
+ return ({
61
+ agents,
62
+ keywords,
63
+ languages,
64
+ locations,
65
+ types: fullData.types,
66
+ roles: fullData.roles,
67
+ })
68
+ }, [fullData, query])
69
+
70
+ useEffect(() => {
71
+ const tags = {}
72
+ for (let i = 0; i < data.roles.length; i++) {
73
+ const tax = data.roles[i]
74
+ const tagList = expanded[i] ? data.agents.filter(t => (t.role_id === tax.id)) : data.agents.filter(t => (t.role_id === tax.id))?.slice(0, 5)
75
+ tags[tax.id] = tagList
76
+ }
77
+ setDisplayedTags(tags)
78
+ }, [expanded, data])
79
+
80
+ const onToggleAgent = useCallback((role, person) => {
81
+ const current = [...filters.agents.data]
82
+ if (current.find(f => (f.role === role && f.person === person))) {
83
+ filters.agents.onUpdate(current => (current.filter(item => (item.role !== role || item.person !== person))))
84
+ }
85
+ else {
86
+ filters.agents.onUpdate(current => [...current, { role, person }])
87
+ }
88
+ }, [filters])
89
+
90
+ const onToggleKeyword = useCallback((type, term) => {
91
+ const current = [...filters.keywords.data]
92
+ if (current.find(f => (f.type === type && f.term === term))) {
93
+ filters.keywords.onUpdate(current => (current.filter(item => (item.type !== type || item.term !== term))))
94
+ }
95
+ else {
96
+ filters.keywords.onUpdate(current => [...current, { type, term }])
97
+ }
98
+ }, [filters])
99
+
100
+ const onToggleLanguage = useCallback((item_id) => {
101
+ const current = [...filters.langs.data]
102
+ if (current.includes(item_id)) {
103
+ filters.langs.onUpdate(current => (current.filter(i => (i !== item_id))))
104
+ }
105
+ else {
106
+ filters.langs.onUpdate(current => [...current, item_id])
107
+ }
108
+ }, [filters])
109
+
110
+ const onToggleLocation = useCallback((item_id) => {
111
+ const current = [...filters.locations.data]
112
+ if (current.includes(item_id)) {
113
+ filters.locations.onUpdate(current => (current.filter(i => (i !== item_id))))
114
+ }
115
+ else {
116
+ filters.locations.onUpdate(current => [...current, item_id])
117
+ }
118
+ }, [filters])
119
+
120
+ return (
121
+ <div className="tag-list">
122
+ <FormGroup>
123
+ { data.roles.map((tax, idx) => {
124
+ const tagList = displayedTags[tax.id]
125
+ return tagList?.length
126
+ ? (
127
+ <div key={tax.id}>
128
+ <Typography>{tax.name}</Typography>
129
+ <ul>
130
+ { tagList?.map(tag => (
131
+ <FormControlLabel
132
+ as="li"
133
+ control={(
134
+ <Checkbox
135
+ checked={!!filters.agents.data.find(ag => (ag.role === tax.id && ag.person === tag.person_id))}
136
+ onChange={() => {
137
+ onToggleAgent(tax.id, tag.person_id)
138
+ }}
139
+ />
140
+ )}
141
+ key={tag.person_id}
142
+ label={tag.name}
143
+ />
144
+ ))}
145
+ </ul>
146
+ { (data.agents.filter(t => (t.role_id === tax.id))?.length && data.agents.filter(t => (t.role_id === tax.id)).length >= 6)
147
+ ? (
148
+ <button
149
+ className="tag-filter-button"
150
+ type="button"
151
+ onClick={() => {
152
+ const newState = [...expanded]
153
+ newState[idx] = !expanded[idx]
154
+ setExpanded(newState)
155
+ }}
156
+ >
157
+ { expanded[idx] ? 'Show less' : 'Show more'}
158
+ </button>
159
+ )
160
+ : null }
161
+ </div>
162
+ )
163
+ : null
164
+ })}
165
+ { data.types.map((tax, idx) => {
166
+ const tagList = data.keywords.filter(term => (term.type === tax.type))
167
+ return tagList?.length
168
+ ? (
169
+ <div key={tax.type}>
170
+ <Typography>
171
+ {tax.type[0].toUpperCase()}
172
+ {tax.type.slice(1)}
173
+ </Typography>
174
+ <ul>
175
+ { tagList?.map(tag => (
176
+ <FormControlLabel
177
+ as="li"
178
+ control={(
179
+ <Checkbox
180
+ checked={!!filters.keywords.data.find(item => (item.type === tax.type && item.term === tag.id))}
181
+ onChange={() => {
182
+ onToggleKeyword(tax.type, tag.id)
183
+ }}
184
+ />
185
+ )}
186
+ key={tag.id}
187
+ label={tag.name}
188
+ />
189
+ ))}
190
+ </ul>
191
+ { tagList.length >= 6
192
+ ? (
193
+ <button
194
+ className="tag-filter-button"
195
+ type="button"
196
+ onClick={() => {
197
+ const newState = [...keywordExpanded]
198
+ newState[idx] = !keywordExpanded[idx]
199
+ setKeywordExpanded(newState)
200
+ }}
201
+ >
202
+ { keywordExpanded[idx] ? 'Show less' : 'Show more'}
203
+ </button>
204
+ )
205
+ : null}
206
+ </div>
207
+ )
208
+ : null
209
+ })}
210
+ { data.languages?.length
211
+ ? (
212
+ <div>
213
+ <Typography>
214
+ Language
215
+ </Typography>
216
+ <ul>
217
+ { data.languages.map(tag => (
218
+ <FormControlLabel
219
+ as="li"
220
+ control={(
221
+ <Checkbox
222
+ checked={filters.langs.data.includes(tag.id)}
223
+ onChange={() => {
224
+ onToggleLanguage(tag.id)
225
+ }}
226
+ />
227
+ )}
228
+ key={tag.id}
229
+ label={tag.name}
230
+ />
231
+ ))}
232
+ </ul>
233
+ { data.languages.length >= 6
234
+ ? (
235
+ <button
236
+ className="tag-filter-button"
237
+ type="button"
238
+ onClick={() => {
239
+ setLangExpanded(current => !current)
240
+ }}
241
+ >
242
+ { langExpanded ? 'Show less' : 'Show more'}
243
+ </button>
244
+ )
245
+ : null}
246
+ </div>
247
+ )
248
+ : null }
249
+ { data.locations?.length
250
+ ? (
251
+ <div>
252
+ <Typography>
253
+ Location of Publication
254
+ </Typography>
255
+ <ul>
256
+ { data.locations.map(tag => (
257
+ <FormControlLabel
258
+ as="li"
259
+ control={(
260
+ <Checkbox
261
+ checked={filters.locations.data.includes(tag.id)}
262
+ onChange={() => {
263
+ onToggleLocation(tag.id)
264
+ }}
265
+ />
266
+ )}
267
+ key={tag.id}
268
+ label={tag.name}
269
+ />
270
+ ))}
271
+ </ul>
272
+ { data.locations.length >= 6
273
+ ? (
274
+ <button
275
+ className="tag-filter-button"
276
+ type="button"
277
+ onClick={() => {
278
+ setLocExpanded(current => !current)
279
+ }}
280
+ >
281
+ { locExpanded ? 'Show less' : 'Show more'}
282
+ </button>
283
+ )
284
+ : null}
285
+ </div>
286
+ )
287
+ : null }
288
+ </FormGroup>
289
+ </div>
290
+ )
291
+ }
292
+
293
+ export default DocumentFilters
@@ -1,29 +1,42 @@
1
- import { Box, Button, ButtonGroup, Collapse, Divider, IconButton, Typography } from '@material-ui/core'
2
- import { red } from '@material-ui/core/colors'
1
+ import { Box, Button, Collapse, Divider, IconButton, Input, Typography } from '@material-ui/core'
3
2
  import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
4
- import GridOnIcon from '@material-ui/icons/GridOn'
5
- import ListIcon from '@material-ui/icons/List'
6
3
  import TuneIcon from '@material-ui/icons/Tune'
7
- import { useEffect, useMemo, useState } from 'react'
4
+ import { useCallback, useEffect, useMemo, useState } from 'react'
8
5
 
9
6
  import { useLocation, useNavigate } from 'react-router-dom'
10
7
  import { getObjs } from '../../common/lib/sql'
11
8
  import DocumentDetail from './DocumentDetail'
9
+ // import DocumentFilters from './DocumentFilters'
12
10
  import TagFilters from './TagFilters'
13
11
 
14
12
  function getData(db) {
15
13
  const docStmt = db.prepare(`
16
14
  SELECT
17
- documents.id AS id,
18
- documents.name AS name,
19
- documents.local_id AS local_id
15
+ d.id AS id,
16
+ d.name AS name,
17
+ d.local_id AS local_id,
18
+ json_group_array(document_taggings.tag) as tags
20
19
  FROM
21
- documents
20
+ documents d
21
+ LEFT JOIN document_taggings ON d.id = document_taggings.document
22
+ GROUP BY d.id, d.name, d.local_id
22
23
  `)
23
-
24
24
  return getObjs(docStmt)
25
25
  }
26
26
 
27
+ function getDocTags(db) {
28
+ const tagsStmt = db.prepare(`
29
+ SELECT
30
+ tags.id AS id
31
+ FROM
32
+ tags
33
+ LEFT JOIN taxonomies ON tags.taxonomy_id = taxonomies.id
34
+ WHERE
35
+ taxonomies.is_surface = 0 OR taxonomies.is_surface IS NULL
36
+ `)
37
+ return getObjs(tagsStmt)
38
+ }
39
+
27
40
  function parseFolioID(folioID) {
28
41
  if (!folioID) {
29
42
  return null
@@ -50,27 +63,46 @@ function getSelection(path) {
50
63
  function SurfaceBrowser(props) {
51
64
  const { db, open, toggleOpen } = props
52
65
  const documents = useMemo(() => getData(db), [db])
66
+ const docTags = useMemo(() => getDocTags(db)?.map(tag => (tag.id)), [db])
53
67
  const [pageCount, setPageCount] = useState({})
54
68
  const [totalPages, setTotalPages] = useState(0)
55
69
  const [tags, setTags] = useState([])
56
70
  const [showFilters, setShowFilters] = useState(false)
71
+ const [query, setQuery] = useState(undefined)
72
+
73
+ const filterDocs = useCallback((docs, tags) => {
74
+ return docs.filter((doc) => {
75
+ const docID = doc.id
76
+ for (const tag of tags) {
77
+ if (docTags.includes(tag) && !JSON.parse(doc.tags)?.includes(tag)) {
78
+ const newCount = pageCount
79
+ newCount[docID] = 0
80
+ setPageCount(newCount)
81
+ return false
82
+ }
83
+ }
84
+ return true
85
+ })
86
+ }, [docTags, pageCount])
87
+
88
+ const filteredDocs = useMemo(() => (filterDocs(documents, tags)), [filterDocs, documents, tags])
57
89
 
58
90
  const navigate = useNavigate()
59
91
  const location = useLocation()
60
92
  const selection = useMemo(() => getSelection(location.pathname), [location])
61
93
 
62
- const navigateToSelection = (nextSelection) => {
94
+ const navigateToSelection = useCallback((nextSelection) => {
63
95
  const folioID = nextSelection?.left ? `${nextSelection.left.localID}_${nextSelection.left.surfaceID}` : null
64
96
  const folioID2 = nextSelection?.right ? `${nextSelection.right.localID}_${nextSelection.right.surfaceID}` : null
65
97
  const navParams = `/ec/${folioID || '-1'}/${folioID ? 'f' : 'g'}/${folioID2 || '-1'}/${folioID2 ? 'f' : 'g'}`
66
98
  navigate(navParams + location.search)
67
- }
99
+ }, [location.search, navigate])
68
100
 
69
- const updatePageCount = (documentID, numPages) => {
101
+ const updatePageCount = useCallback((documentID, numPages) => {
70
102
  const newCount = pageCount
71
103
  newCount[documentID] = numPages
72
104
  setPageCount(newCount)
73
- }
105
+ }, [pageCount])
74
106
 
75
107
  useEffect(() => {
76
108
  let p = 0
@@ -80,7 +112,7 @@ function SurfaceBrowser(props) {
80
112
  setTotalPages(p)
81
113
  }, [pageCount, tags])
82
114
 
83
- const documentDetails = documents.map((doc) => {
115
+ const documentDetails = useMemo(() => filteredDocs.map((doc) => {
84
116
  return (
85
117
  <DocumentDetail
86
118
  key={`document-detail-${doc.id}`}
@@ -91,11 +123,11 @@ function SurfaceBrowser(props) {
91
123
  selection={selection}
92
124
  navigateToSelection={navigateToSelection}
93
125
  updatePageCount={count => updatePageCount(doc.id, count)}
94
- tags={tags}
126
+ tags={tags?.filter(tag => !docTags.includes(tag))}
95
127
  >
96
128
  </DocumentDetail>
97
129
  )
98
- })
130
+ }), [db, docTags, filteredDocs, navigateToSelection, selection, tags, updatePageCount])
99
131
 
100
132
  return (
101
133
  <Collapse in={open} horizontal>
@@ -152,18 +184,22 @@ function SurfaceBrowser(props) {
152
184
  </IconButton>
153
185
  </ButtonGroup> */}
154
186
  { showFilters && (
155
- <TagFilters
156
- db={db}
157
- filters={tags}
158
- onToggleSelected={(tagId) => {
159
- if (tags.includes(tagId)) {
160
- setTags(current => (current.filter(t => (t !== tagId))))
161
- }
162
- else {
163
- setTags(current => ([...current, tagId]))
164
- }
165
- }}
166
- />
187
+ <div className="tag-filters">
188
+ <Input placeholder="Search for filters" value={query} onChange={(e) => { setQuery(e.target.value) }} className="tag-filters-search" />
189
+ <TagFilters
190
+ db={db}
191
+ filters={tags}
192
+ query={query}
193
+ onToggleSelected={(tagId) => {
194
+ if (tags.includes(tagId)) {
195
+ setTags(current => (current.filter(t => (t !== tagId))))
196
+ }
197
+ else {
198
+ setTags(current => ([...current, tagId]))
199
+ }
200
+ }}
201
+ />
202
+ </div>
167
203
  ) }
168
204
  <Box className="surface-browser-document-details">
169
205
  { documentDetails }