@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.
@@ -1,5 +1,6 @@
1
- import { Checkbox, FormControlLabel, FormGroup, Typography } from '@material-ui/core'
2
- import { useContext, useEffect, useMemo, useState } from 'react'
1
+ import { Accordion, AccordionDetails, AccordionSummary, Checkbox, FormControlLabel, FormGroup, Typography } from '@material-ui/core'
2
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
3
+ import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
3
4
  import { getObjs } from '../../common/lib/sql'
4
5
  import TagFilterContext from '../../EditionCrafter/context/TagFilterContext'
5
6
 
@@ -9,6 +10,17 @@ function getData(db) {
9
10
  *
10
11
  FROM
11
12
  taxonomies;
13
+ ORDER BY
14
+ name ASC;
15
+ `)
16
+
17
+ const categoriesStmt = db.prepare(`
18
+ SELECT
19
+ *
20
+ FROM
21
+ categories
22
+ ORDER BY
23
+ name ASC;
12
24
  `)
13
25
 
14
26
  const tagsStmt = db.prepare(`
@@ -16,90 +28,234 @@ function getData(db) {
16
28
  tags.id AS id,
17
29
  tags.name AS name,
18
30
  tags.xml_id AS xml_id,
31
+ tags.parent_category_id AS parent_category_id,
19
32
  taxonomies.name as taxonomy,
20
33
  taxonomies.id as taxonomy_id
21
34
  FROM
22
35
  tags
23
36
  LEFT JOIN taxonomies
24
37
  ON tags.taxonomy_id = taxonomies.id
25
- INNER JOIN taggings
26
- ON taggings.tag_id = tags.id
27
- INNER JOIN elements
28
- ON elements.id = taggings.element_id
29
- WHERE
30
- elements.type = 'zone'
31
38
  GROUP BY
32
- tags.xml_id`)
39
+ tags.xml_id
40
+ ORDER BY
41
+ tags.name ASC`)
33
42
 
34
43
  return {
35
44
  tags: getObjs(tagsStmt),
45
+ categories: getObjs(categoriesStmt),
36
46
  taxonomies: getObjs(taxonomiesStmt),
37
47
  }
38
48
  }
39
49
 
50
+ function CategoryFilter(props) {
51
+ const {
52
+ name,
53
+ categoryId,
54
+ tags,
55
+ categories,
56
+ toggleTag,
57
+ onToggleSelected,
58
+ isSurface,
59
+ filters,
60
+ } = props
61
+
62
+ const hasDescendentTags = useCallback((catId) => {
63
+ if (tags?.filter(tag => (tag.parent_category_id === catId))?.length) {
64
+ return true
65
+ }
66
+
67
+ const subs = categories?.filter(cat => (cat.parent_category_id === catId))
68
+
69
+ for (const sub of subs) {
70
+ if (hasDescendentTags(sub.id)) {
71
+ return true
72
+ }
73
+ }
74
+
75
+ return false
76
+ }, [tags, categories])
77
+
78
+ const countDescendentTags = useCallback((catId) => {
79
+ let count = tags?.filter(tag => (tag.parent_category_id === catId))?.length
80
+
81
+ const subs = categories?.filter(cat => (cat.parent_category_id === catId))
82
+
83
+ for (const sub of subs) {
84
+ count = count + countDescendentTags(sub.id)
85
+ }
86
+
87
+ return count
88
+ }, [tags, categories])
89
+
90
+ const categoryTags = useMemo(() => {
91
+ return tags?.filter(tag => (tag.parent_category_id === categoryId))
92
+ }, [tags, categoryId])
93
+
94
+ const subcategories = useMemo(() => {
95
+ return categories?.filter(cat => (cat.parent_category_id === categoryId))
96
+ }, [categories, categoryId])
97
+
98
+ return hasDescendentTags(categoryId) && (
99
+ <Accordion>
100
+ <AccordionSummary
101
+ expandIcon={<ExpandMoreIcon />}
102
+ aria-controls={`category-tags-${name}-content`}
103
+ id={`category-tags-${name}`}
104
+ className="accordion-summary"
105
+ >
106
+ <Typography>{name}</Typography>
107
+ <Typography>{countDescendentTags(categoryId) || ''}</Typography>
108
+ </AccordionSummary>
109
+ <AccordionDetails
110
+ className="accordion-detail"
111
+ >
112
+ { !!categoryTags?.length && (
113
+ <ul>
114
+ { categoryTags?.map(tag => (
115
+ <FormControlLabel
116
+ as="li"
117
+ control={(
118
+ <Checkbox
119
+ checked={filters.includes(tag.id)}
120
+ onChange={() => {
121
+ onToggleSelected(tag.id)
122
+ if (isSurface) {
123
+ toggleTag(tag.xml_id, 'left')
124
+ toggleTag(tag.xml_id, 'right')
125
+ }
126
+ }}
127
+ />
128
+ )}
129
+ key={tag.id}
130
+ label={tag.name}
131
+ />
132
+ ))}
133
+ </ul>
134
+ )}
135
+ {
136
+ !!subcategories?.length && (
137
+ <>
138
+ {
139
+ subcategories.map(cat => (
140
+ <CategoryFilter
141
+ key={cat.id}
142
+ name={cat.name}
143
+ categoryId={cat.id}
144
+ tags={tags}
145
+ categories={categories}
146
+ toggleTag={toggleTag}
147
+ onToggleSelected={onToggleSelected}
148
+ isSurface={isSurface}
149
+ filters={filters}
150
+ />
151
+ ))
152
+ }
153
+ </>
154
+ )
155
+ }
156
+ </AccordionDetails>
157
+ </Accordion>
158
+ )
159
+ }
160
+
40
161
  function TagFilters(props) {
41
- const { onToggleSelected, filters } = props
162
+ const { onToggleSelected, filters, query } = props
42
163
  const data = useMemo(() => getData(props.db), [props.db])
43
- const [expanded, setExpanded] = useState(data.taxonomies?.map(() => (false)))
44
164
  const [displayedTags, setDisplayedTags] = useState({})
45
165
 
46
166
  const { toggleTag } = useContext(TagFilterContext)
47
167
 
48
168
  useEffect(() => {
49
169
  const tags = {}
170
+ const filteredTags = data.tags.filter(tag => (!query || !query.length || tag.name.toLowerCase().includes(query.toLowerCase())))
50
171
  for (let i = 0; i < data.taxonomies.length; i++) {
51
172
  const tax = data.taxonomies[i]
52
- const tagList = expanded[i] ? data.tags.filter(t => (t.taxonomy_id === tax.id)) : data.tags.filter(t => (t.taxonomy_id === tax.id))?.slice(0, 5)
53
- tags[tax.id] = tagList
173
+ const tagList = filteredTags.filter(t => t.taxonomy_id === tax.id)
174
+ if (tagList?.length) {
175
+ tags[tax.id] = tagList
176
+ }
54
177
  }
55
178
  setDisplayedTags(tags)
56
- }, [expanded, data])
179
+ }, [data, query])
57
180
 
58
181
  return (
59
- <div className="tag-filters">
60
- <div className="tag-list">
61
- <FormGroup>
62
- { data.taxonomies.map((tax, idx) => {
63
- const tagList = displayedTags[tax.id]
64
- return (
65
- <div key={tax.id}>
66
- <Typography>{tax.name}</Typography>
67
- <ul>
68
- { tagList?.map(tag => (
69
- <FormControlLabel
70
- as="li"
71
- control={(
72
- <Checkbox
73
- checked={filters.includes(tag.id)}
74
- onChange={() => {
75
- onToggleSelected(tag.id)
76
- toggleTag(tag.xml_id, 'left')
77
- toggleTag(tag.xml_id, 'right')
78
- }}
79
- />
80
- )}
81
- key={tag.id}
82
- label={tag.name}
83
- />
84
- ))}
85
- </ul>
86
- <button
87
- className="tag-filter-button"
88
- type="button"
89
- onClick={() => {
90
- const newState = [...expanded]
91
- newState[idx] = !expanded[idx]
92
- setExpanded(newState)
93
- }}
94
- >
95
- { !data.tags.filter(t => (t.taxonomy_id === tax.id))?.length || data.tags.filter(t => (t.taxonomy_id === tax.id)).length < 6 ? null : expanded[idx] ? 'Show less' : 'Show more'}
96
- </button>
182
+ <>
183
+ {
184
+ data?.tags?.length
185
+ ? (
186
+ <div className="tag-list">
187
+ <FormGroup>
188
+ { data.taxonomies.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 0)).map((tax) => {
189
+ const tagList = displayedTags[tax.id]
190
+ const topLevelTags = tagList?.filter(tag => (!tag.parent_category_id))
191
+ return (
192
+ tagList?.length
193
+ ? (
194
+ <Accordion>
195
+ <AccordionSummary
196
+ expandIcon={<ExpandMoreIcon />}
197
+ aria-controls={`tags-${tax.name}-content`}
198
+ id={`tags-${tax.name}`}
199
+ className="accordion-summary"
200
+ >
201
+ <Typography>{`${tax.name.slice(0, 1).toUpperCase()}${tax.name.slice(1)}`}</Typography>
202
+ <Typography>{tagList?.length || ''}</Typography>
203
+ </AccordionSummary>
204
+ <AccordionDetails
205
+ className="accordion-detail"
206
+ >
207
+ <div key={tax.id}>
208
+ { !!topLevelTags?.length && (
209
+ <ul>
210
+ { topLevelTags?.map(tag => (
211
+ <FormControlLabel
212
+ as="li"
213
+ control={(
214
+ <Checkbox
215
+ checked={filters.includes(tag.id)}
216
+ onChange={() => {
217
+ onToggleSelected(tag.id)
218
+ if (tax.is_surface) {
219
+ toggleTag(tag.xml_id, 'left')
220
+ toggleTag(tag.xml_id, 'right')
221
+ }
222
+ }}
223
+ />
224
+ )}
225
+ key={tag.id}
226
+ label={tag.name}
227
+ />
228
+ ))}
229
+ </ul>
230
+ )}
231
+ {
232
+ data.categories?.filter(cat => (cat.taxonomy_id === tax.id && !cat.parent_category_id))?.map(cat => (
233
+ <CategoryFilter
234
+ key={cat.id}
235
+ name={cat.name}
236
+ categoryId={cat.id}
237
+ tags={tagList}
238
+ categories={data.categories}
239
+ toggleTag={toggleTag}
240
+ onToggleSelected={onToggleSelected}
241
+ isSurface={tax.is_surface}
242
+ filters={filters}
243
+ />
244
+ ))
245
+ }
246
+ </div>
247
+ </AccordionDetails>
248
+ </Accordion>
249
+ )
250
+ : null
251
+ )
252
+ })}
253
+ </FormGroup>
97
254
  </div>
98
255
  )
99
- })}
100
- </FormGroup>
101
- </div>
102
- </div>
256
+ : null
257
+ }
258
+ </>
103
259
  )
104
260
  }
105
261
 
@@ -1,5 +1,5 @@
1
- import { useEffect, useMemo, useState } from 'react'
2
- import { BrowserRouter, HashRouter } from 'react-router-dom'
1
+ import { useEffect, useState } from 'react'
2
+ import { HashRouter } from 'react-router-dom'
3
3
  import initSqlJs from 'sql.js'
4
4
  import sqlJsInfo from 'sql.js/package.json'
5
5
  import Loading from '../common/components/Loading'
@@ -48,14 +48,14 @@ function getData(db) {
48
48
 
49
49
  function generateECProps(props, db) {
50
50
  const documents = getData(db)
51
- const { documentName, baseURL, transcriptionTypes } = props
51
+ const { documentName, baseURL, transcriptionTypes, manifestPath = '/iiif/manifest.json' } = props
52
52
  const documentInfo = {}
53
53
 
54
54
  for (const document of documents) {
55
55
  documentInfo[document.local_id] = {
56
56
  documentName: document.name,
57
57
  transcriptionTypes,
58
- iiifManifest: `${baseURL}/${document.local_id}/iiif/manifest.json`,
58
+ iiifManifest: `${baseURL}/${document.local_id}${manifestPath}`,
59
59
  }
60
60
  }
61
61
 
@@ -71,8 +71,6 @@ function TagExplore(props) {
71
71
  const [ecProps, setECProps] = useState(null)
72
72
  const [filters, setFilters] = useState(initialFilters)
73
73
 
74
- const Router = useMemo(() => props.serverNav ? BrowserRouter : HashRouter, [props.serverNav])
75
-
76
74
  useEffect(() => {
77
75
  const loadDb = async () => {
78
76
  const db = await initDb(props.dbUrl)
@@ -98,12 +96,12 @@ function TagExplore(props) {
98
96
 
99
97
  return (
100
98
  <div className="tag-explore">
101
- <Router>
99
+ <HashRouter>
102
100
  <TagFilterProvider>
103
101
  <TagExploreSidebar db={db} />
104
102
  <EditionCrafter {...ecProps} />
105
103
  </TagFilterProvider>
106
- </Router>
104
+ </HashRouter>
107
105
  </div>
108
106
  )
109
107
  }
@@ -26,13 +26,18 @@
26
26
  overflow-y: auto;
27
27
  position: absolute;
28
28
  top: 48px;
29
- left: 0;
29
+ left: 400px;
30
30
  background-color: #242629;
31
31
  z-index: 10000;
32
32
  padding: 12px 0 12px 12px;
33
- width: 100%;
33
+ width: 490px;
34
34
  height: 100%;
35
- max-height: 100dvh;
35
+ max-height: calc(97dvh - 48px);
36
+ border-left: solid white 1px;
37
+ }
38
+
39
+ .tag-explore .tag-filters .tag-filters-search {
40
+ margin-bottom: 24px;
36
41
  }
37
42
 
38
43
  .tag-explore .tag-filter-button {
@@ -153,8 +158,8 @@
153
158
  position: absolute;
154
159
  top: 0;
155
160
  left: 48px;
156
- height: 100vh;
157
- min-width: 300px;
161
+ height: 97dvh;
162
+ width: 400px;
158
163
  display: flex;
159
164
  flex-direction: column;
160
165
  background-color: #242629;
@@ -189,4 +194,6 @@
189
194
 
190
195
  .tag-explore .accordion-detail {
191
196
  background-color: #242629;
197
+ display: flex;
198
+ flex-direction: column;
192
199
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cu-mkp/editioncrafter",
3
3
  "type": "module",
4
- "version": "1.3.1-beta.1",
4
+ "version": "1.3.1-beta.11",
5
5
  "private": false,
6
6
  "description": "A simple digital critical edition publication tool",
7
7
  "license": "MIT",