@cu-mkp/editioncrafter 1.3.1-beta.1 → 1.3.1-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.
@@ -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,47 @@ 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])
57
90
 
58
91
  const navigate = useNavigate()
59
92
  const location = useLocation()
60
93
  const selection = useMemo(() => getSelection(location.pathname), [location])
94
+ const { clearTags } = useContext(TagFilterContext)
61
95
 
62
- const navigateToSelection = (nextSelection) => {
96
+ const navigateToSelection = useCallback((nextSelection) => {
63
97
  const folioID = nextSelection?.left ? `${nextSelection.left.localID}_${nextSelection.left.surfaceID}` : null
64
98
  const folioID2 = nextSelection?.right ? `${nextSelection.right.localID}_${nextSelection.right.surfaceID}` : null
65
99
  const navParams = `/ec/${folioID || '-1'}/${folioID ? 'f' : 'g'}/${folioID2 || '-1'}/${folioID2 ? 'f' : 'g'}`
66
100
  navigate(navParams + location.search)
67
- }
101
+ }, [location.search, navigate])
68
102
 
69
- const updatePageCount = (documentID, numPages) => {
103
+ const updatePageCount = useCallback((documentID, numPages) => {
70
104
  const newCount = pageCount
71
105
  newCount[documentID] = numPages
72
106
  setPageCount(newCount)
73
- }
107
+ }, [pageCount])
74
108
 
75
109
  useEffect(() => {
76
110
  let p = 0
@@ -80,7 +114,7 @@ function SurfaceBrowser(props) {
80
114
  setTotalPages(p)
81
115
  }, [pageCount, tags])
82
116
 
83
- const documentDetails = documents.map((doc) => {
117
+ const documentDetails = useMemo(() => filteredDocs.map((doc) => {
84
118
  return (
85
119
  <DocumentDetail
86
120
  key={`document-detail-${doc.id}`}
@@ -91,11 +125,11 @@ function SurfaceBrowser(props) {
91
125
  selection={selection}
92
126
  navigateToSelection={navigateToSelection}
93
127
  updatePageCount={count => updatePageCount(doc.id, count)}
94
- tags={tags}
128
+ tags={tags?.filter(tag => !docTags.includes(tag))}
95
129
  >
96
130
  </DocumentDetail>
97
131
  )
98
- })
132
+ }), [db, docTags, filteredDocs, navigateToSelection, selection, tags, updatePageCount])
99
133
 
100
134
  return (
101
135
  <Collapse in={open} horizontal>
@@ -108,34 +142,45 @@ function SurfaceBrowser(props) {
108
142
  <Divider></Divider>
109
143
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
110
144
  <Typography>Contents</Typography>
111
- <Button
112
- startIcon={<TuneIcon />}
113
- onClick={() => setShowFilters(current => (!current))}
114
- >
115
- Filter
145
+ <div>
116
146
  { tags && tags.length
117
147
  ? (
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
- }}
148
+ <Button
149
+ onClick={clearTags}
133
150
  >
134
- {tags.length}
135
- </div>
151
+ Clear All
152
+ </Button>
136
153
  )
137
154
  : null}
138
- </Button>
155
+ <Button
156
+ startIcon={<TuneIcon />}
157
+ onClick={() => setShowFilters(current => (!current))}
158
+ >
159
+ Filter
160
+ { tags && tags.length
161
+ ? (
162
+ <div style={{
163
+ fontSize: 'small',
164
+ backgroundColor: 'red',
165
+ borderRadius: '999px',
166
+ display: 'flex',
167
+ justifyContent: 'center',
168
+ alignItems: 'center',
169
+ padding: '3px',
170
+ color: 'white',
171
+ height: '16px',
172
+ width: '16px',
173
+ position: 'absolute',
174
+ top: '0',
175
+ left: '-12px',
176
+ }}
177
+ >
178
+ {tags.length}
179
+ </div>
180
+ )
181
+ : null}
182
+ </Button>
183
+ </div>
139
184
  </div>
140
185
  <Typography>
141
186
  {totalPages}
@@ -152,18 +197,22 @@ function SurfaceBrowser(props) {
152
197
  </IconButton>
153
198
  </ButtonGroup> */}
154
199
  { 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
- />
200
+ <div className="tag-filters">
201
+ <Input placeholder="Search for filters" value={query} onChange={(e) => { setQuery(e.target.value) }} className="tag-filters-search" />
202
+ <TagFilters
203
+ db={db}
204
+ filters={tags}
205
+ query={query}
206
+ onToggleSelected={(tagId) => {
207
+ if (tags.includes(tagId)) {
208
+ setTags(current => (current.filter(t => (t !== tagId))))
209
+ }
210
+ else {
211
+ setTags(current => ([...current, tagId]))
212
+ }
213
+ }}
214
+ />
215
+ </div>
167
216
  ) }
168
217
  <Box className="surface-browser-document-details">
169
218
  { documentDetails }
@@ -22,12 +22,6 @@ function getData(db) {
22
22
  tags
23
23
  LEFT JOIN taxonomies
24
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
25
  GROUP BY
32
26
  tags.xml_id`)
33
27
 
@@ -38,7 +32,7 @@ function getData(db) {
38
32
  }
39
33
 
40
34
  function TagFilters(props) {
41
- const { onToggleSelected, filters } = props
35
+ const { onToggleSelected, filters, query } = props
42
36
  const data = useMemo(() => getData(props.db), [props.db])
43
37
  const [expanded, setExpanded] = useState(data.taxonomies?.map(() => (false)))
44
38
  const [displayedTags, setDisplayedTags] = useState({})
@@ -47,59 +41,78 @@ function TagFilters(props) {
47
41
 
48
42
  useEffect(() => {
49
43
  const tags = {}
44
+ const filteredTags = data.tags.filter(tag => (!query || !query.length || tag.name.toLowerCase().includes(query.toLowerCase())))
50
45
  for (let i = 0; i < data.taxonomies.length; i++) {
51
46
  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
47
+ const tagList = expanded[i] ? filteredTags.filter(t => (t.taxonomy_id === tax.id)) : filteredTags.filter(t => (t.taxonomy_id === tax.id))?.slice(0, 5)
48
+ if (tagList?.length) {
49
+ tags[tax.id] = tagList
50
+ }
54
51
  }
55
52
  setDisplayedTags(tags)
56
- }, [expanded, data])
53
+ }, [expanded, data, query])
57
54
 
58
55
  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>
56
+ <>
57
+ {
58
+ data?.tags?.length
59
+ ? (
60
+ <div className="tag-list">
61
+ <FormGroup>
62
+ { data.taxonomies.map((tax, idx) => {
63
+ const tagList = displayedTags[tax.id]
64
+ return (
65
+ tagList?.length
66
+ ? (
67
+ <div key={tax.id}>
68
+ <Typography>{`${tax.name.slice(0, 1).toUpperCase()}${tax.name.slice(1)}`}</Typography>
69
+ <ul>
70
+ { tagList?.map(tag => (
71
+ <FormControlLabel
72
+ as="li"
73
+ control={(
74
+ <Checkbox
75
+ checked={filters.includes(tag.id)}
76
+ onChange={() => {
77
+ onToggleSelected(tag.id)
78
+ if (tax.is_surface) {
79
+ toggleTag(tag.xml_id, 'left')
80
+ toggleTag(tag.xml_id, 'right')
81
+ }
82
+ }}
83
+ />
84
+ )}
85
+ key={tag.id}
86
+ label={tag.name}
87
+ />
88
+ ))}
89
+ </ul>
90
+ { data.tags.filter(t => (t.taxonomy_id === tax.id))?.length && data.tags.filter(t => (t.taxonomy_id === tax.id)).length >= 6
91
+ ? (
92
+ <button
93
+ className="tag-filter-button"
94
+ type="button"
95
+ onClick={() => {
96
+ const newState = [...expanded]
97
+ newState[idx] = !expanded[idx]
98
+ setExpanded(newState)
99
+ }}
100
+ >
101
+ { expanded[idx] ? 'Show less' : 'Show more'}
102
+ </button>
103
+ )
104
+ : null }
105
+ </div>
106
+ )
107
+ : null
108
+ )
109
+ })}
110
+ </FormGroup>
97
111
  </div>
98
112
  )
99
- })}
100
- </FormGroup>
101
- </div>
102
- </div>
113
+ : null
114
+ }
115
+ </>
103
116
  )
104
117
  }
105
118
 
@@ -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'
@@ -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: 300px;
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;
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.3",
5
5
  "private": false,
6
6
  "description": "A simple digital critical edition publication tool",
7
7
  "license": "MIT",