@cu-mkp/editioncrafter 1.3.1-beta.1 → 1.3.1-beta.11

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.
@@ -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,43 @@
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, useContext, 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'
10
+ import TagFilterContext from '../../EditionCrafter/context/TagFilterContext'
12
11
  import TagFilters from './TagFilters'
13
12
 
14
13
  function getData(db) {
15
14
  const docStmt = db.prepare(`
16
15
  SELECT
17
- documents.id AS id,
18
- documents.name AS name,
19
- documents.local_id AS local_id
16
+ d.id AS id,
17
+ d.name AS name,
18
+ d.local_id AS local_id,
19
+ json_group_array(document_taggings.tag) as tags
20
20
  FROM
21
- documents
21
+ documents d
22
+ LEFT JOIN document_taggings ON d.id = document_taggings.document
23
+ GROUP BY d.id, d.name, d.local_id
22
24
  `)
23
-
24
25
  return getObjs(docStmt)
25
26
  }
26
27
 
28
+ function getDocTags(db) {
29
+ const tagsStmt = db.prepare(`
30
+ SELECT
31
+ tags.id AS id
32
+ FROM
33
+ tags
34
+ LEFT JOIN taxonomies ON tags.taxonomy_id = taxonomies.id
35
+ WHERE
36
+ taxonomies.is_surface = 0 OR taxonomies.is_surface IS NULL
37
+ `)
38
+ return getObjs(tagsStmt)
39
+ }
40
+
27
41
  function parseFolioID(folioID) {
28
42
  if (!folioID) {
29
43
  return null
@@ -50,27 +64,48 @@ function getSelection(path) {
50
64
  function SurfaceBrowser(props) {
51
65
  const { db, open, toggleOpen } = props
52
66
  const documents = useMemo(() => getData(db), [db])
67
+ const docTags = useMemo(() => getDocTags(db)?.map(tag => (tag.id)), [db])
53
68
  const [pageCount, setPageCount] = useState({})
54
69
  const [totalPages, setTotalPages] = useState(0)
55
70
  const [tags, setTags] = useState([])
56
71
  const [showFilters, setShowFilters] = useState(false)
72
+ const [query, setQuery] = useState(undefined)
73
+
74
+ const filterDocs = useCallback((docs, tags) => {
75
+ return docs.filter((doc) => {
76
+ const docID = doc.id
77
+ for (const tag of tags) {
78
+ if (docTags.includes(tag) && !JSON.parse(doc.tags)?.includes(tag)) {
79
+ const newCount = pageCount
80
+ newCount[docID] = 0
81
+ setPageCount(newCount)
82
+ return false
83
+ }
84
+ }
85
+ return true
86
+ })
87
+ }, [docTags, pageCount])
88
+
89
+ const filteredDocs = useMemo(() => (filterDocs(documents, tags)), [filterDocs, documents, tags])
90
+
91
+ const { clearTags } = useContext(TagFilterContext)
57
92
 
58
93
  const navigate = useNavigate()
59
94
  const location = useLocation()
60
95
  const selection = useMemo(() => getSelection(location.pathname), [location])
61
96
 
62
- const navigateToSelection = (nextSelection) => {
97
+ const navigateToSelection = useCallback((nextSelection) => {
63
98
  const folioID = nextSelection?.left ? `${nextSelection.left.localID}_${nextSelection.left.surfaceID}` : null
64
99
  const folioID2 = nextSelection?.right ? `${nextSelection.right.localID}_${nextSelection.right.surfaceID}` : null
65
100
  const navParams = `/ec/${folioID || '-1'}/${folioID ? 'f' : 'g'}/${folioID2 || '-1'}/${folioID2 ? 'f' : 'g'}`
66
101
  navigate(navParams + location.search)
67
- }
102
+ }, [location.search, navigate])
68
103
 
69
- const updatePageCount = (documentID, numPages) => {
104
+ const updatePageCount = useCallback((documentID, numPages) => {
70
105
  const newCount = pageCount
71
106
  newCount[documentID] = numPages
72
107
  setPageCount(newCount)
73
- }
108
+ }, [pageCount])
74
109
 
75
110
  useEffect(() => {
76
111
  let p = 0
@@ -80,7 +115,7 @@ function SurfaceBrowser(props) {
80
115
  setTotalPages(p)
81
116
  }, [pageCount, tags])
82
117
 
83
- const documentDetails = documents.map((doc) => {
118
+ const documentDetails = useMemo(() => filteredDocs.map((doc) => {
84
119
  return (
85
120
  <DocumentDetail
86
121
  key={`document-detail-${doc.id}`}
@@ -91,11 +126,11 @@ function SurfaceBrowser(props) {
91
126
  selection={selection}
92
127
  navigateToSelection={navigateToSelection}
93
128
  updatePageCount={count => updatePageCount(doc.id, count)}
94
- tags={tags}
129
+ tags={tags?.filter(tag => !docTags.includes(tag))}
95
130
  >
96
131
  </DocumentDetail>
97
132
  )
98
- })
133
+ }), [db, docTags, filteredDocs, navigateToSelection, selection, tags, updatePageCount])
99
134
 
100
135
  return (
101
136
  <Collapse in={open} horizontal>
@@ -108,34 +143,51 @@ function SurfaceBrowser(props) {
108
143
  <Divider></Divider>
109
144
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
110
145
  <Typography>Contents</Typography>
111
- <Button
112
- startIcon={<TuneIcon />}
113
- onClick={() => setShowFilters(current => (!current))}
114
- >
115
- Filter
146
+ <div>
116
147
  { tags && tags.length
117
148
  ? (
118
- <div style={{
119
- fontSize: 'small',
120
- backgroundColor: 'red',
121
- borderRadius: '999px',
122
- display: 'flex',
123
- justifyContent: 'center',
124
- alignItems: 'center',
125
- padding: '3px',
126
- color: 'white',
127
- height: '16px',
128
- width: '16px',
129
- position: 'absolute',
130
- top: '0',
131
- left: '-12px',
132
- }}
149
+ <Button
150
+ onClick={() => {
151
+ setTags([])
152
+ clearTags()
153
+ }}
154
+ style={{
155
+ marginRight: '24px',
156
+ }}
133
157
  >
134
- {tags.length}
135
- </div>
158
+ Clear All
159
+ </Button>
136
160
  )
137
161
  : null}
138
- </Button>
162
+ <Button
163
+ startIcon={<TuneIcon />}
164
+ onClick={() => setShowFilters(current => (!current))}
165
+ >
166
+ Filter
167
+ { tags && tags.length
168
+ ? (
169
+ <div style={{
170
+ fontSize: 'small',
171
+ backgroundColor: 'red',
172
+ borderRadius: '999px',
173
+ display: 'flex',
174
+ justifyContent: 'center',
175
+ alignItems: 'center',
176
+ padding: '3px',
177
+ color: 'white',
178
+ height: '16px',
179
+ width: '16px',
180
+ position: 'absolute',
181
+ top: '0',
182
+ left: '-12px',
183
+ }}
184
+ >
185
+ {tags.length}
186
+ </div>
187
+ )
188
+ : null}
189
+ </Button>
190
+ </div>
139
191
  </div>
140
192
  <Typography>
141
193
  {totalPages}
@@ -152,18 +204,22 @@ function SurfaceBrowser(props) {
152
204
  </IconButton>
153
205
  </ButtonGroup> */}
154
206
  { 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
- />
207
+ <div className="tag-filters">
208
+ <Input placeholder="Search for filters" value={query} onChange={(e) => { setQuery(e.target.value) }} className="tag-filters-search" />
209
+ <TagFilters
210
+ db={db}
211
+ filters={tags}
212
+ query={query}
213
+ onToggleSelected={(tagId) => {
214
+ if (tags.includes(tagId)) {
215
+ setTags(current => (current.filter(t => (t !== tagId))))
216
+ }
217
+ else {
218
+ setTags(current => ([...current, tagId]))
219
+ }
220
+ }}
221
+ />
222
+ </div>
167
223
  ) }
168
224
  <Box className="surface-browser-document-details">
169
225
  { documentDetails }