@cu-mkp/editioncrafter 1.3.0-beta.1 → 1.3.0-beta.3

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.
Files changed (44) hide show
  1. package/README.md +7 -0
  2. package/dist/editioncrafter.js +34747 -31554
  3. package/dist/es/src/EditionCrafter/component/DiploMatic.js +19 -17
  4. package/dist/es/src/EditionCrafter/component/DocumentView.js +13 -9
  5. package/dist/es/src/EditionCrafter/component/EmptyPaneView.js +22 -0
  6. package/dist/es/src/EditionCrafter/component/ImageView.js +61 -40
  7. package/dist/es/src/EditionCrafter/component/Navigation.js +1 -0
  8. package/dist/es/src/EditionCrafter/component/SplitPaneView.js +10 -9
  9. package/dist/es/src/EditionCrafter/component/TagToolbar.jsx +14 -4
  10. package/dist/es/src/EditionCrafter/context/TagFilter.jsx +22 -11
  11. package/dist/es/src/EditionCrafter/context/TagFilterContext.js +2 -1
  12. package/dist/es/src/EditionCrafter/index.jsx +31 -0
  13. package/dist/es/src/EditionCrafter/model/Folio.js +27 -1
  14. package/dist/es/src/EditionCrafter/saga/RouteListenerSaga.js +22 -7
  15. package/dist/es/src/EditionCrafter/scss/_imageView.scss +1 -1
  16. package/dist/es/src/RecordList/component/PhraseListTable.jsx +108 -0
  17. package/dist/es/src/RecordList/component/Record.jsx +38 -2
  18. package/dist/es/src/RecordList/component/RecordListView.jsx +1 -1
  19. package/dist/es/src/RecordList/component/Sidebar.jsx +1 -1
  20. package/dist/es/src/RecordList/component/SidebarTagList.jsx +1 -1
  21. package/dist/es/src/RecordList/index.jsx +10 -5
  22. package/dist/es/src/RecordList/styles/base.css +2 -17
  23. package/dist/es/src/RecordList/styles/record.css +92 -4
  24. package/dist/es/src/TagExplore/assets/InsertLeft.jsx +18 -0
  25. package/dist/es/src/TagExplore/assets/InsertRight.jsx +18 -0
  26. package/dist/es/src/TagExplore/assets/Left.jsx +17 -0
  27. package/dist/es/src/TagExplore/assets/Right.jsx +17 -0
  28. package/dist/es/src/TagExplore/assets/insert_left.svg +12 -0
  29. package/dist/es/src/TagExplore/assets/insert_right.svg +12 -0
  30. package/dist/es/src/TagExplore/assets/left.svg +11 -0
  31. package/dist/es/src/TagExplore/assets/right.svg +11 -0
  32. package/dist/es/src/TagExplore/components/DocumentDetail.jsx +224 -0
  33. package/dist/es/src/TagExplore/components/NarrowSidebar.jsx +35 -0
  34. package/dist/es/src/TagExplore/components/SurfaceBrowser.jsx +176 -0
  35. package/dist/es/src/TagExplore/components/TagExploreSidebar.jsx +55 -0
  36. package/dist/es/src/TagExplore/components/TagFilters.jsx +106 -0
  37. package/dist/es/src/TagExplore/index.jsx +109 -0
  38. package/dist/es/src/TagExplore/styles/base.css +192 -0
  39. package/dist/es/src/{RecordList/component → common/components}/Pill.jsx +2 -0
  40. package/dist/es/src/common/components/Pill.style.css +16 -0
  41. package/dist/es/src/index.jsx +3 -27
  42. package/package.json +2 -28
  43. /package/dist/es/src/{RecordList/component → common/components}/Loading.jsx +0 -0
  44. /package/dist/es/src/{RecordList → common}/lib/sql.js +0 -0
@@ -0,0 +1,224 @@
1
+ import { Accordion, AccordionDetails, AccordionSummary, Grid, Typography } from '@material-ui/core'
2
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
3
+ import { useEffect, useMemo } from 'react'
4
+ import { getObjs } from '../../common/lib/sql'
5
+ import InsertLeft from '../assets/InsertLeft'
6
+ import InsertRight from '../assets/InsertRight'
7
+ import Left from '../assets/Left'
8
+ import Right from '../assets/Right'
9
+
10
+ const MAX_THUMBNAIL_DIMENSION = 100
11
+
12
+ function getData(db, docID, tags = []) {
13
+ const surfaceStmt = db.prepare(`
14
+ SELECT
15
+ surfaces.id AS id,
16
+ surfaces.name AS name,
17
+ surfaces.xml_id AS xml_id,
18
+ surfaces.width AS width,
19
+ surfaces.height AS height,
20
+ surfaces.image_type AS image_type,
21
+ surfaces.image_url AS image_url,
22
+ surfaces.position AS position,
23
+ taggings.tag_id
24
+ FROM
25
+ surfaces
26
+ LEFT JOIN
27
+ elements
28
+ ON
29
+ surfaces.id = elements.surface_id
30
+ LEFT JOIN
31
+ taggings
32
+ ON
33
+ elements.id = taggings.element_id
34
+ WHERE
35
+ document_id=${docID}${tags.length ? ` AND taggings.tag_id IN (${tags.join(',')})` : ''}
36
+ ORDER BY
37
+ surfaces.id, position
38
+ `)
39
+
40
+ // add thumbnail URLs
41
+ const surfaces = getObjs(surfaceStmt)
42
+ for (const surface of surfaces) {
43
+ surface.thumbnail_url = getThumbnailURL(surface)
44
+ }
45
+
46
+ return surfaces
47
+ }
48
+
49
+ function getThumbnailURL(surface) {
50
+ const { image_url, width, height, image_type } = surface
51
+
52
+ const ratio = height !== 0 ? width / height : 0
53
+
54
+ let thumbnailDimensions = []
55
+ if (ratio > 1) {
56
+ thumbnailDimensions = [MAX_THUMBNAIL_DIMENSION, Math.round(MAX_THUMBNAIL_DIMENSION / ratio)]
57
+ }
58
+ else {
59
+ thumbnailDimensions = [Math.round(MAX_THUMBNAIL_DIMENSION * ratio), MAX_THUMBNAIL_DIMENSION]
60
+ }
61
+
62
+ const thumbnailURL = image_type === 'iiif'
63
+ ? `${image_url}/full/${thumbnailDimensions.join(',')}/0/default.jpg`
64
+ : image_url
65
+
66
+ return thumbnailURL
67
+ }
68
+
69
+ function Thumbnail(props) {
70
+ const { surfaceID, name, imageURL, thumbnailURL, onClick, selection, info } = props
71
+
72
+ const isLeft = useMemo(() => (selection && selection?.left?.localID === info?.localID && selection?.left?.surfaceID === info?.surfaceID), [selection, info])
73
+ const isRight = useMemo(() => (selection && selection?.right?.localID === info?.localID && selection?.right?.surfaceID === info?.surfaceID), [selection, info])
74
+
75
+ const onError = (currentTarget) => {
76
+ currentTarget.onerror = null
77
+ const fullImageURL = `${imageURL.slice(0, -9)}full/full/0/default.jpg`
78
+ if (currentTarget.src !== fullImageURL) {
79
+ currentTarget.src = fullImageURL
80
+ }
81
+ }
82
+
83
+ return (
84
+ <Grid className="surface-thumbnail">
85
+ <figure className="surface-thumbnail-figure">
86
+ {isLeft
87
+ ? (
88
+ <div className="surface-thumbnail-overlay-selected left">
89
+ <div>
90
+ <Left />
91
+ <Typography>Left</Typography>
92
+ </div>
93
+ </div>
94
+ )
95
+ : isRight
96
+ ? (
97
+ <div className="surface-thumbnail-overlay-selected right">
98
+ <div>
99
+ <Right />
100
+ <Typography>Right</Typography>
101
+ </div>
102
+ </div>
103
+ )
104
+ : (
105
+ <div className="surface-thumbnail-overlay">
106
+ <div className="surface-thumbnail-overlay-content">
107
+ <a onClick={onClick.left} title="Insert left"><InsertLeft /></a>
108
+ <a onClick={onClick.right} title="Insert right"><InsertRight /></a>
109
+ </div>
110
+ </div>
111
+ )}
112
+ <div id={surfaceID}>
113
+ <img
114
+ src={thumbnailURL}
115
+ alt={name}
116
+ style={{ maxWidth: `${MAX_THUMBNAIL_DIMENSION}px`, maxHeight: `${MAX_THUMBNAIL_DIMENSION}px` }}
117
+ onError={onError}
118
+ />
119
+ </div>
120
+ </figure>
121
+ <figcaption className="surface-thumbnail-caption">
122
+ {name}
123
+ </figcaption>
124
+ </Grid>
125
+ )
126
+ }
127
+
128
+ function ThumbnailGrid(props) {
129
+ const { surfaces, selection, navigateToSelection, documentLocalID } = props
130
+
131
+ return (
132
+ <Grid className="thumbnail-grid" container>
133
+ {
134
+ surfaces.map(surface => (
135
+ <Thumbnail
136
+ key={`doc-detail-thumb-${surface.id}`}
137
+ surfaceID={surface.id}
138
+ name={surface.name}
139
+ imageURL={surface.image_url}
140
+ thumbnailURL={surface.thumbnail_url}
141
+ selection={selection}
142
+ info={{
143
+ localID: documentLocalID,
144
+ surfaceID: surface.xml_id,
145
+ }}
146
+ onClick={{
147
+ left: (e) => {
148
+ e.preventDefault()
149
+ navigateToSelection({
150
+ left: {
151
+ localID: documentLocalID,
152
+ surfaceID: surface.xml_id,
153
+ },
154
+ right: selection.right,
155
+ })
156
+ },
157
+ right: (e) => {
158
+ e.preventDefault()
159
+ navigateToSelection({
160
+ left: selection.left,
161
+ right: {
162
+ localID: documentLocalID,
163
+ surfaceID: surface.xml_id,
164
+ },
165
+ })
166
+ },
167
+ }}
168
+ >
169
+ </Thumbnail>
170
+ ),
171
+ )
172
+ }
173
+ </Grid>
174
+ )
175
+ }
176
+
177
+ function DocumentDetail(props) {
178
+ const { db, documentName, documentID, documentLocalID, navigateToSelection, updatePageCount, selection, tags } = props
179
+ const surfaces = useMemo(() => {
180
+ const taggedSurfaces = getData(db, documentID, tags)
181
+ const data = []
182
+ for (const entry of taggedSurfaces) {
183
+ if (!data.find(d => (d.id === entry.id))) {
184
+ const tags = taggedSurfaces.filter(s => (s.id === entry.id)).map(s => (s.tag_id))
185
+ data.push({
186
+ ...entry,
187
+ tag_id: [...new Set(tags)],
188
+ })
189
+ }
190
+ }
191
+ return data.filter(s => (tags.every(t => s.tag_id.includes(t))))
192
+ }, [db, documentID, tags])
193
+
194
+ useEffect(() => {
195
+ updatePageCount(surfaces?.length)
196
+ }, [surfaces, updatePageCount, tags])
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>
221
+ )
222
+ }
223
+
224
+ export default DocumentDetail
@@ -0,0 +1,35 @@
1
+ import {
2
+ FaQuestionCircle
3
+ } from 'react-icons/fa'
4
+
5
+ import { BsFillGrid3X3GapFill } from 'react-icons/bs'
6
+ import { List, ListItem, ListItemIcon, Paper } from '@material-ui/core'
7
+
8
+ function NarrowSidebar(props) {
9
+ const { helpRef, toggleDrawer, toggleHelp } = props
10
+ return (
11
+ <Paper className="narrow-sidebar">
12
+ <List>
13
+ <ListItem
14
+ onClick={toggleDrawer}
15
+ button
16
+ >
17
+ <ListItemIcon>
18
+ <BsFillGrid3X3GapFill />
19
+ </ListItemIcon>
20
+ </ListItem>
21
+ <ListItem
22
+ onClick={toggleHelp}
23
+ ref={helpRef}
24
+ button
25
+ >
26
+ <ListItemIcon>
27
+ <FaQuestionCircle />
28
+ </ListItemIcon>
29
+ </ListItem>
30
+ </List>
31
+ </Paper>
32
+ )
33
+ }
34
+
35
+ export default NarrowSidebar
@@ -0,0 +1,176 @@
1
+ import { Box, Button, ButtonGroup, Collapse, Divider, IconButton, Typography } from '@material-ui/core'
2
+ import { red } from '@material-ui/core/colors'
3
+ import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
4
+ import GridOnIcon from '@material-ui/icons/GridOn'
5
+ import ListIcon from '@material-ui/icons/List'
6
+ import TuneIcon from '@material-ui/icons/Tune'
7
+ import { useEffect, useMemo, useState } from 'react'
8
+
9
+ import { useLocation, useNavigate } from 'react-router-dom'
10
+ import { getObjs } from '../../common/lib/sql'
11
+ import DocumentDetail from './DocumentDetail'
12
+ import TagFilters from './TagFilters'
13
+
14
+ function getData(db) {
15
+ const docStmt = db.prepare(`
16
+ SELECT
17
+ documents.id AS id,
18
+ documents.name AS name,
19
+ documents.local_id AS local_id
20
+ FROM
21
+ documents
22
+ `)
23
+
24
+ return getObjs(docStmt)
25
+ }
26
+
27
+ function parseFolioID(folioID) {
28
+ if (!folioID) {
29
+ return null
30
+ }
31
+ const parts = folioID.split('_')
32
+ const localID = parts.slice(0, parts.length - 1).join('_')
33
+ const surfaceID = parts[parts.length - 1]
34
+
35
+ return {
36
+ localID,
37
+ surfaceID,
38
+ }
39
+ }
40
+
41
+ function getSelection(path) {
42
+ const parts = path.split('/')
43
+ const folioID = parts[2]
44
+ const folioID2 = parts[4]
45
+ const left = parseFolioID(folioID)
46
+ const right = parseFolioID(folioID2)
47
+ return { left, right }
48
+ }
49
+
50
+ function SurfaceBrowser(props) {
51
+ const { db, open, toggleOpen } = props
52
+ const documents = useMemo(() => getData(db), [db])
53
+ const [pageCount, setPageCount] = useState({})
54
+ const [totalPages, setTotalPages] = useState(0)
55
+ const [tags, setTags] = useState([])
56
+ const [showFilters, setShowFilters] = useState(false)
57
+
58
+ const navigate = useNavigate()
59
+ const location = useLocation()
60
+ const selection = useMemo(() => getSelection(location.pathname), [location])
61
+
62
+ const navigateToSelection = (nextSelection) => {
63
+ const folioID = nextSelection?.left ? `${nextSelection.left.localID}_${nextSelection.left.surfaceID}` : null
64
+ const folioID2 = nextSelection?.right ? `${nextSelection.right.localID}_${nextSelection.right.surfaceID}` : null
65
+ const navParams = `/ec/${folioID || '-1'}/${folioID ? 'f' : 'g'}/${folioID2 || '-1'}/${folioID2 ? 'f' : 'g'}`
66
+ navigate(navParams + location.search)
67
+ }
68
+
69
+ const updatePageCount = (documentID, numPages) => {
70
+ const newCount = pageCount
71
+ newCount[documentID] = numPages
72
+ setPageCount(newCount)
73
+ }
74
+
75
+ useEffect(() => {
76
+ let p = 0
77
+ for (const key of Object.keys(pageCount)) {
78
+ p += pageCount[key]
79
+ }
80
+ setTotalPages(p)
81
+ }, [pageCount, tags])
82
+
83
+ const documentDetails = documents.map((doc) => {
84
+ return (
85
+ <DocumentDetail
86
+ key={`document-detail-${doc.id}`}
87
+ db={db}
88
+ documentName={doc.name}
89
+ documentLocalID={doc.local_id}
90
+ documentID={doc.id}
91
+ selection={selection}
92
+ navigateToSelection={navigateToSelection}
93
+ updatePageCount={count => updatePageCount(doc.id, count)}
94
+ tags={tags}
95
+ >
96
+ </DocumentDetail>
97
+ )
98
+ })
99
+
100
+ return (
101
+ <Collapse in={open} horizontal>
102
+ <Box
103
+ className="surface-browser"
104
+ >
105
+ <IconButton onClick={toggleOpen} className="surface-browser-close">
106
+ <ChevronLeftIcon />
107
+ </IconButton>
108
+ <Divider></Divider>
109
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
110
+ <Typography>Contents</Typography>
111
+ <Button
112
+ startIcon={<TuneIcon />}
113
+ onClick={() => setShowFilters(current => (!current))}
114
+ >
115
+ Filter
116
+ { tags && tags.length
117
+ ? (
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
+ }}
133
+ >
134
+ {tags.length}
135
+ </div>
136
+ )
137
+ : null}
138
+ </Button>
139
+ </div>
140
+ <Typography>
141
+ {totalPages}
142
+ {' '}
143
+ Page
144
+ { totalPages !== 1 ? 's' : ''}
145
+ </Typography>
146
+ {/* <ButtonGroup color="primary" aria-label="outlined primary button group">
147
+ <IconButton aria-label="grid">
148
+ <GridOnIcon></GridOnIcon>
149
+ </IconButton>
150
+ <IconButton aria-label="list">
151
+ <ListIcon></ListIcon>
152
+ </IconButton>
153
+ </ButtonGroup> */}
154
+ { 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
+ />
167
+ ) }
168
+ <Box className="surface-browser-document-details">
169
+ { documentDetails }
170
+ </Box>
171
+ </Box>
172
+ </Collapse>
173
+ )
174
+ }
175
+
176
+ export default SurfaceBrowser
@@ -0,0 +1,55 @@
1
+ import { createTheme, ThemeProvider } from '@material-ui/core/styles'
2
+ import React, { useRef, useState } from 'react'
3
+
4
+ import HelpPopper from '../../EditionCrafter/component/HelpPopper'
5
+ import NarrowSidebar from './NarrowSidebar'
6
+ import SurfaceBrowser from './SurfaceBrowser'
7
+
8
+ function TagExploreSidebar(props) {
9
+ const { db } = props
10
+ const [openHelp, setOpenHelp] = useState(false)
11
+ const [openDrawer, setOpenDrawer] = useState(false)
12
+
13
+ const toggleHelp = () => {
14
+ setOpenHelp(!openHelp)
15
+ }
16
+
17
+ const toggleDrawer = () => {
18
+ setOpenDrawer(!openDrawer)
19
+ }
20
+
21
+ const helpRef = useRef(null)
22
+ const helpMarginStyle = { marginLeft: '60px', backgroundColor: '#242629', zIndex: '9999' }
23
+ const theme = createTheme({
24
+ palette: {
25
+ type: 'dark',
26
+ },
27
+ })
28
+
29
+ return (
30
+ <div className="tag-explore-sidebar">
31
+ <ThemeProvider theme={theme}>
32
+ <NarrowSidebar
33
+ toggleDrawer={toggleDrawer}
34
+ toggleHelp={toggleHelp}
35
+ helpRef={helpRef}
36
+ >
37
+ </NarrowSidebar>
38
+ <SurfaceBrowser
39
+ db={db}
40
+ open={openDrawer}
41
+ toggleOpen={toggleDrawer}
42
+ >
43
+ </SurfaceBrowser>
44
+ <HelpPopper
45
+ marginStyle={helpMarginStyle}
46
+ anchorEl={helpRef.current}
47
+ open={openHelp}
48
+ onClose={toggleHelp}
49
+ />
50
+ </ThemeProvider>
51
+ </div>
52
+ )
53
+ }
54
+
55
+ export default TagExploreSidebar
@@ -0,0 +1,106 @@
1
+ import { Checkbox, FormControlLabel, FormGroup, Typography } from '@material-ui/core'
2
+ import { useContext, useEffect, useMemo, useState } from 'react'
3
+ import { getObjs } from '../../common/lib/sql'
4
+ import TagFilterContext from '../../EditionCrafter/context/TagFilterContext'
5
+
6
+ function getData(db) {
7
+ const taxonomiesStmt = db.prepare(`
8
+ SELECT
9
+ *
10
+ FROM
11
+ taxonomies;
12
+ `)
13
+
14
+ const tagsStmt = db.prepare(`
15
+ SELECT
16
+ tags.id AS id,
17
+ tags.name AS name,
18
+ tags.xml_id AS xml_id,
19
+ taxonomies.name as taxonomy,
20
+ taxonomies.id as taxonomy_id
21
+ FROM
22
+ tags
23
+ LEFT JOIN taxonomies
24
+ 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
+ GROUP BY
32
+ tags.xml_id`)
33
+
34
+ return {
35
+ tags: getObjs(tagsStmt),
36
+ taxonomies: getObjs(taxonomiesStmt),
37
+ }
38
+ }
39
+
40
+ function TagFilters(props) {
41
+ const { onToggleSelected, filters } = props
42
+ const data = useMemo(() => getData(props.db), [props.db])
43
+ const [expanded, setExpanded] = useState(data.taxonomies?.map(() => (false)))
44
+ const [displayedTags, setDisplayedTags] = useState({})
45
+
46
+ const { toggleTag } = useContext(TagFilterContext)
47
+
48
+ useEffect(() => {
49
+ const tags = {}
50
+ for (let i = 0; i < data.taxonomies.length; i++) {
51
+ 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
54
+ }
55
+ setDisplayedTags(tags)
56
+ }, [expanded, data])
57
+
58
+ 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>
97
+ </div>
98
+ )
99
+ })}
100
+ </FormGroup>
101
+ </div>
102
+ </div>
103
+ )
104
+ }
105
+
106
+ export default TagFilters
@@ -0,0 +1,109 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { HashRouter } from 'react-router-dom'
3
+ import initSqlJs from 'sql.js'
4
+ import sqlJsInfo from 'sql.js/package.json'
5
+ import Loading from '../common/components/Loading'
6
+ import { getObjs } from '../common/lib/sql'
7
+ import EditionCrafter from '../EditionCrafter'
8
+ import TagFilterProvider from '../EditionCrafter/context/TagFilter'
9
+ import TagExploreSidebar from './components/TagExploreSidebar'
10
+ import './styles/base.css'
11
+
12
+ const initialFilters = {
13
+ categories: [],
14
+ tags: [],
15
+ }
16
+
17
+ async function initDb(url) {
18
+ const file = await fetch(url)
19
+
20
+ if (!file.ok) {
21
+ throw new Error('Failed fetching SQLite file.')
22
+ }
23
+
24
+ const buf = await file.arrayBuffer()
25
+ const arr = new Uint8Array(buf)
26
+
27
+ const db = await initSqlJs({
28
+ locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/${sqlJsInfo.version}/${file}`,
29
+ }).then((SQL) => {
30
+ const db = new SQL.Database(arr)
31
+ return db
32
+ })
33
+
34
+ return db
35
+ }
36
+
37
+ function getData(db) {
38
+ const documentStmt = db.prepare(`
39
+ SELECT
40
+ documents.name AS name,
41
+ documents.local_id AS local_id
42
+ FROM
43
+ documents
44
+ `)
45
+
46
+ return getObjs(documentStmt)
47
+ }
48
+
49
+ function generateECProps(props, db) {
50
+ const documents = getData(db)
51
+ const { documentName, baseURL, transcriptionTypes } = props
52
+ const documentInfo = {}
53
+
54
+ for (const document of documents) {
55
+ documentInfo[document.local_id] = {
56
+ documentName: document.name,
57
+ transcriptionTypes,
58
+ iiifManifest: `${baseURL}/${document.local_id}/iiif/manifest.json`,
59
+ }
60
+ }
61
+
62
+ return {
63
+ documentName,
64
+ documentInfo,
65
+ tagExplorerMode: true,
66
+ }
67
+ }
68
+
69
+ function TagExplore(props) {
70
+ const [db, setDb] = useState(null)
71
+ const [ecProps, setECProps] = useState(null)
72
+ const [filters, setFilters] = useState(initialFilters)
73
+
74
+ useEffect(() => {
75
+ const loadDb = async () => {
76
+ const db = await initDb(props.dbUrl)
77
+ const ecProps = generateECProps(props, db)
78
+ setDb(db)
79
+ setECProps(ecProps)
80
+ }
81
+
82
+ if (!db) {
83
+ loadDb()
84
+ }
85
+
86
+ return () => {
87
+ if (db) {
88
+ db.close()
89
+ }
90
+ }
91
+ }, [props.dbUrl, db])
92
+
93
+ if (!db || !ecProps) {
94
+ return <Loading />
95
+ }
96
+
97
+ return (
98
+ <div className="tag-explore">
99
+ <HashRouter>
100
+ <TagFilterProvider>
101
+ <TagExploreSidebar db={db} />
102
+ <EditionCrafter {...ecProps} />
103
+ </TagFilterProvider>
104
+ </HashRouter>
105
+ </div>
106
+ )
107
+ }
108
+
109
+ export default TagExplore