@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
@@ -36,25 +36,27 @@ function DiploMatic(props) {
36
36
  const { fixedFrameMode } = props.diplomatic
37
37
  const fixedFrameModeClass = fixedFrameMode ? 'editioncrafter' : 'editioncrafter sticky'
38
38
 
39
+ const mainBody = (
40
+ <div id="diplomatic" className={fixedFrameModeClass} ref={containerRef} style={{ height: containerHeight }}>
41
+ <RouteListener />
42
+ <div id="content" style={{ height: '100%' }}>
43
+ <Routes>
44
+ <Route path="/ec/:folioID/:transcriptionType/:folioID2/:transcriptionType2/:folioID3/:transcriptionType3" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
45
+ <Route path="/ec/:folioID/:transcriptionType/:folioID2/:transcriptionType2" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
46
+ <Route path="/ec/:folioID/:transcriptionType" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
47
+ <Route path="/ec/:folioID" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
48
+ <Route path="/ec" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
49
+ <Route path="/" element={<Navigate to="/ec" />} exact />
50
+ </Routes>
51
+ </div>
52
+ </div>
53
+ )
54
+
55
+ const topLevel = !(props.tagExplorerMode === true) ? <HashRouter><TagFilterProvider>{mainBody}</TagFilterProvider></HashRouter> : mainBody
56
+
39
57
  return (
40
58
  <Provider store={props.store}>
41
- <HashRouter>
42
- <TagFilterProvider>
43
- <div id="diplomatic" className={fixedFrameModeClass} ref={containerRef} style={{ height: containerHeight }}>
44
- <RouteListener />
45
- <div id="content" style={{ height: '100%' }}>
46
- <Routes>
47
- <Route path="/ec/:folioID/:transcriptionType/:folioID2/:transcriptionType2/:folioID3/:transcriptionType3" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
48
- <Route path="/ec/:folioID/:transcriptionType/:folioID2/:transcriptionType2" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
49
- <Route path="/ec/:folioID/:transcriptionType" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
50
- <Route path="/ec/:folioID" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
51
- <Route path="/ec" element={<DocumentView {...props} containerWidth={containerWidth} />} exact />
52
- <Route path="/" element={<Navigate to="/ec" />} exact />
53
- </Routes>
54
- </div>
55
- </div>
56
- </TagFilterProvider>
57
- </HashRouter>
59
+ { topLevel }
58
60
  </Provider>
59
61
  )
60
62
  }
@@ -7,10 +7,11 @@ import {
7
7
  useParams,
8
8
  } from 'react-router-dom'
9
9
  import { dispatchAction } from '../model/ReduxStore'
10
+ import EmptyPaneView from './EmptyPaneView'
10
11
  import GlossaryView from './GlossaryView'
11
- import NotesView from './NotesView'
12
12
  import ImageGridView from './ImageGridView'
13
13
  import ImageView from './ImageView'
14
+ import NotesView from './NotesView'
14
15
  import SinglePaneView from './SinglePaneView'
15
16
  import SplitPaneView from './SplitPaneView'
16
17
  import TranscriptionView from './TranscriptionView'
@@ -462,14 +463,17 @@ function DocumentView(props) {
462
463
  }
463
464
 
464
465
  if (viewType === 'ImageGridView') {
465
- return (
466
- <ImageGridView
467
- key={key}
468
- documentView={docView}
469
- documentViewActions={documentViewActions}
470
- side={side}
471
- selectedDoc={document || props.document.variorum && Object.keys(props.document.derivativeNames)[side === 'left' ? 0 : side === 'right' ? 1 : Object.keys(props.document.derivativeNames).length > 2 ? 2 : 1]}
472
- />
466
+ return (props.tagExplorerMode
467
+ ? <EmptyPaneView side={side} documentView={docView} />
468
+ : (
469
+ <ImageGridView
470
+ key={key}
471
+ documentView={docView}
472
+ documentViewActions={documentViewActions}
473
+ side={side}
474
+ selectedDoc={document || props.document.variorum && Object.keys(props.document.derivativeNames)[side === 'left' ? 0 : side === 'right' ? 1 : Object.keys(props.document.derivativeNames).length > 2 ? 2 : 1]}
475
+ />
476
+ )
473
477
  )
474
478
  }
475
479
 
@@ -0,0 +1,22 @@
1
+ import { Typography } from '@material-ui/core'
2
+ import Left from '../../TagExplore/assets/Left'
3
+ import Right from '../../TagExplore/assets/Right'
4
+
5
+ function EmptyPaneView(props) {
6
+ return (
7
+ <div style={{ backgroundColor: 'black', display: 'flex', justifyContent: 'center', alignItems: 'center', color: 'white' }}>
8
+ <div style={{ display: 'flex', flexDirection: 'row', gap: '12px', alignItems: 'center' }}>
9
+ { props.side === 'left' ? <Left /> : <Right /> }
10
+ <Typography>
11
+ Select
12
+ {' '}
13
+ {props.side}
14
+ {' '}
15
+ page
16
+ </Typography>
17
+ </div>
18
+ </div>
19
+ )
20
+ }
21
+
22
+ export default EmptyPaneView
@@ -1,6 +1,6 @@
1
1
  import Annotorious from '@recogito/annotorious-openseadragon'
2
2
  import OpenSeadragon from 'openseadragon'
3
- import React, { useEffect, useState } from 'react'
3
+ import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
4
4
  import { connect } from 'react-redux'
5
5
 
6
6
  import {
@@ -9,10 +9,11 @@ import {
9
9
  useNavigate,
10
10
  useSearchParams,
11
11
  } from 'react-router-dom'
12
+ import TagFilterContext from '../context/TagFilterContext'
12
13
  import ImageZoomControl from './ImageZoomControl'
13
14
  import Navigation from './Navigation'
14
- import { BigRingSpinner } from './RingSpinner'
15
15
 
16
+ import { BigRingSpinner } from './RingSpinner'
16
17
  import SeaDragonComponent from './SeaDragonComponent'
17
18
  import '@recogito/annotorious-openseadragon/dist/annotorious.min.css'
18
19
 
@@ -20,41 +21,68 @@ function ImageView(props) {
20
21
  const [viewer, setViewer] = useState(null)
21
22
  const [anno, setAnno] = useState(null)
22
23
 
24
+ const { tagsLeft, tagsRight } = useContext(TagFilterContext)
25
+
26
+ const tags = useMemo(() => (props.side === 'right' ? tagsRight : tagsLeft), [props.side, tagsLeft, tagsRight])
27
+
23
28
  const location = useLocation()
24
29
  const navigate = useNavigate()
25
30
 
26
31
  const [searchParams] = useSearchParams()
27
- const [loading, setLoading] = useState(false)
28
32
 
29
- useEffect(() => {
30
- if (anno && searchParams.get('zone')) {
31
- // TODO: Figure out why annotations are an empty list
32
- // unless I wait for > 20 ms.
33
- setTimeout(() => anno.selectAnnotation(searchParams.get('zone')), 50)
33
+ const imageViewRef = useRef(null)
34
+
35
+ const folio = props.document.folioIndex[props.folioID]
36
+
37
+ const updateHighlightedZones = useCallback(() => {
38
+ if (folio.zoneTagIndex && imageViewRef.current) {
39
+ const annotationEls = imageViewRef.current.querySelectorAll('.a9s-annotation')
40
+ const zonesToHighlight = Object.keys(folio.zoneTagIndex)
41
+ .filter(zoneId => folio.zoneTagIndex[zoneId].some(tag => tags.includes(tag)))
42
+
43
+ const manualSelection = searchParams.get('zone')
44
+
45
+ if (manualSelection && !zonesToHighlight.includes(manualSelection)) {
46
+ zonesToHighlight.push(manualSelection)
47
+ }
48
+
49
+ annotationEls.forEach((annoEl) => {
50
+ const annoId = annoEl.getAttribute('data-id')
51
+ if (zonesToHighlight.includes(annoId)) {
52
+ annoEl.classList.add('selected')
53
+ }
54
+ else {
55
+ annoEl.classList.remove('selected')
56
+ }
57
+ })
34
58
  }
35
- }, [anno])
59
+ }, [folio, tags, imageViewRef, searchParams])
60
+
61
+ useEffect(() => {
62
+ setTimeout(() => updateHighlightedZones(), 50)
63
+ }, [updateHighlightedZones])
36
64
 
37
- const onZoomGrid = (e) => {
65
+ const onZoomGrid = () => {
38
66
  props.documentViewActions.changeTranscriptionType(props.side, 'g')
39
67
  }
40
68
 
41
- const onZoomFixed_1 = (e) => {
69
+ const onZoomFixed_1 = () => {
42
70
  viewer.viewport.zoomTo(viewer.viewport.getMaxZoom())
43
71
  }
44
72
 
45
- const onZoomFixed_2 = (e) => {
73
+ const onZoomFixed_2 = () => {
46
74
  viewer.viewport.zoomTo((viewer.viewport.getMaxZoom() / 2))
47
75
  }
48
76
 
49
- const onZoomFixed_3 = (e) => {
77
+ const onZoomFixed_3 = () => {
50
78
  viewer.viewport.fitVertically()
51
79
  }
52
80
 
53
- const onZoomIn = (e) => {
81
+ const onZoomIn = () => {
54
82
  viewer.viewport.zoomBy(2)
55
83
  }
56
84
 
57
- const onZoomOut = (e) => {
85
+ const onZoomOut = () => {
58
86
  viewer.viewport.zoomBy(0.5)
59
87
  }
60
88
 
@@ -74,14 +102,25 @@ function ImageView(props) {
74
102
  // every time it changes!
75
103
  anno.on('selectAnnotation', (annotation) => {
76
104
  searchParams.set('zone', annotation.id)
105
+ updateHighlightedZones()
77
106
  navigate(`${location.pathname}?${createSearchParams(searchParams.toString())}`)
78
107
  })
79
108
 
80
109
  anno.on('cancelSelected', () => {
81
- navigate(location.pathname)
110
+ searchParams.delete('zone')
111
+ navigate(`${location.pathname}?${createSearchParams(searchParams.toString())}`)
82
112
  })
83
113
  }
84
- }, [location.pathname, anno])
114
+ }, [searchParams, anno, updateHighlightedZones, folio, navigate, location.pathname])
115
+
116
+ useEffect(() => {
117
+ if (folio.tileSource && viewer) {
118
+ viewer.open(folio.tileSource)
119
+ if (folio.annotations && anno) {
120
+ anno.setAnnotations(folio.annotations)
121
+ }
122
+ }
123
+ }, [anno, viewer, folio, props.document.folioIndex])
85
124
 
86
125
  const initViewer = async (el, tileSource) => {
87
126
  if (!el) {
@@ -117,29 +156,11 @@ function ImageView(props) {
117
156
  if (viewer) {
118
157
  viewer.destroy()
119
158
  }
120
- }, [])
121
-
122
- const { tileSource } = props.document.folioIndex[props.folioID]
123
-
124
- useEffect(() => {
125
- const folio = props.document.folioIndex[props.folioID]
126
- if (folio.loading) {
127
- setLoading(true)
128
- }
129
- if (folio.tileSource && viewer) {
130
- viewer.open(folio.tileSource)
131
- if (folio.annotations && anno) {
132
- anno.setAnnotations(folio.annotations)
133
- }
134
- }
135
- if (!folio.loading) {
136
- setLoading(false)
137
- }
138
- }, [anno, viewer, props.folioID, props.document.folioIndex])
159
+ }, [viewer])
139
160
 
140
161
  return (
141
- <div>
142
- { tileSource
162
+ <div ref={imageViewRef}>
163
+ { folio.tileSource
143
164
  ? (
144
165
  <div className={`image-view imageViewComponent ${props.side}`} style={{ position: 'relative' }}>
145
166
  <Navigation
@@ -162,9 +183,9 @@ function ImageView(props) {
162
183
  <SeaDragonComponent
163
184
  key={props.folioID}
164
185
  side={props.side}
165
- tileSource={tileSource}
186
+ tileSource={folio.tileSource}
166
187
  initViewer={initViewer}
167
- loading={loading}
188
+ loading={folio.loading}
168
189
  />
169
190
  </div>
170
191
  )
@@ -367,6 +367,7 @@ function Navigation(props) {
367
367
  document={props.document}
368
368
  folio={folio}
369
369
  toggleTags={toggleTags}
370
+ side={side}
370
371
  />
371
372
  )}
372
373
  <div className="navigationComponentNarrow">
@@ -12,7 +12,8 @@ class SplitPaneView extends Component {
12
12
  this.splitFraction = props.threePanel ? 0.49 : 0.5
13
13
  this.splitFractionRight = props.threePanel ? 0.01 : 0
14
14
  this.dividerWidth = 16
15
- const whole = window.innerWidth
15
+ this.ecComponent = document.querySelector('#diplomatic')
16
+ const whole = this.ecComponent.clientWidth
16
17
  const leftW = whole / 3
17
18
 
18
19
  const split_left = (leftW / whole)
@@ -37,12 +38,12 @@ class SplitPaneView extends Component {
37
38
  // On drag, update the UI continuously
38
39
  onDrag = (e) => {
39
40
  if (this.dragging) {
40
- const whole = window.innerWidth - 2 * this.dividerWidth
41
+ const whole = this.ecComponent.clientWidth - 2 * this.dividerWidth
41
42
  let left_viewWidth
42
43
  let right_viewWidth
43
44
  let third_viewWidth
44
45
  if (this.activeDivider === 1) {
45
- left_viewWidth = e.clientX - this.dividerWidth / 2
46
+ left_viewWidth = (e.clientX - this.ecComponent.offsetLeft) - this.dividerWidth / 2
46
47
  third_viewWidth = whole * this.splitFractionRight
47
48
  right_viewWidth = whole - left_viewWidth - third_viewWidth
48
49
  }
@@ -98,9 +99,9 @@ class SplitPaneView extends Component {
98
99
  // Update the sizes of the panes
99
100
  updatePaneSize() {
100
101
  // Record state change
101
- const left_px = Math.floor(Math.abs(window.innerWidth * this.splitFraction))
102
- const third_px = Math.floor(Math.abs(window.innerWidth * this.splitFractionRight))
103
- const right_px = Math.floor(window.innerWidth * (1.0 - this.splitFraction - this.splitFractionRight))
102
+ const left_px = Math.floor(Math.abs(this.ecComponent.clientWidth * this.splitFraction))
103
+ const third_px = Math.floor(Math.abs(this.ecComponent.clientWidth * this.splitFractionRight))
104
+ const right_px = Math.floor(this.ecComponent.clientWidth * (1.0 - this.splitFraction - this.splitFractionRight))
104
105
  if (this.props.onWidth && left_px >= this.leftPaneMinWidth && right_px >= this.rightPaneMinWidth && third_px >= this.thirdPaneMinWidth) {
105
106
  this.props.onWidth(left_px, right_px, third_px)
106
107
  }
@@ -113,9 +114,9 @@ class SplitPaneView extends Component {
113
114
  window.addEventListener('resize', this.onResize)
114
115
  // Set the default width on mount
115
116
  if (this.props.onWidth) {
116
- const left_px = Math.floor(Math.abs(window.innerWidth * this.splitFraction))
117
- const right_px = Math.floor(window.innerWidth * (1.0 - this.splitFraction))
118
- const third_px = Math.floor(window.innerWidth * (1.0 - this.splitFraction))
117
+ const left_px = Math.floor(Math.abs(this.ecComponent.clientWidth * this.splitFraction))
118
+ const right_px = Math.floor(this.ecComponent.clientWidth * (1.0 - this.splitFraction))
119
+ const third_px = Math.floor(this.ecComponent.clientWidth * (1.0 - this.splitFraction))
119
120
  this.props.onWidth(left_px, right_px, third_px)
120
121
  }
121
122
  }
@@ -1,4 +1,4 @@
1
- import { useContext } from 'react'
1
+ import { useContext, useMemo } from 'react'
2
2
  import { BsCheck, BsX } from 'react-icons/bs'
3
3
  import { GoTag } from 'react-icons/go'
4
4
  import TagFilterContext from '../context/TagFilterContext'
@@ -17,7 +17,8 @@ function TagPill(props) {
17
17
  }
18
18
 
19
19
  function TagToolbar(props) {
20
- const { tags, toggleTag } = useContext(TagFilterContext)
20
+ const { tagsLeft, tagsRight, toggleTag } = useContext(TagFilterContext)
21
+ const tags = useMemo(() => (props.side === 'right' ? tagsRight : tagsLeft), [props.side, tagsRight, tagsLeft])
21
22
 
22
23
  return (
23
24
  <div className="tag-bar">
@@ -27,7 +28,16 @@ function TagToolbar(props) {
27
28
  Tags
28
29
  </span>
29
30
  {props.folio.tagIds.map((xmlId) => {
30
- const name = props.document.tags[xmlId]
31
+ let name
32
+
33
+ if (props.document.variorum) {
34
+ name = props.document.tags[props.folio.doc_id]
35
+ ? props.document.tags[props.folio.doc_id][xmlId]
36
+ : undefined
37
+ }
38
+ else {
39
+ name = props.document.tags[xmlId]
40
+ }
31
41
 
32
42
  if (name) {
33
43
  return (
@@ -35,7 +45,7 @@ function TagToolbar(props) {
35
45
  isActive={tags.includes(xmlId)}
36
46
  key={xmlId}
37
47
  name={name}
38
- onClick={() => toggleTag(xmlId)}
48
+ onClick={() => toggleTag(xmlId, props.side)}
39
49
  />
40
50
  )
41
51
  }
@@ -8,7 +8,7 @@ import {
8
8
  import TagFilterContext from './TagFilterContext'
9
9
 
10
10
  function getTagsFromParams(val) {
11
- if (val) {
11
+ if (val && val !== 'null') {
12
12
  return val.split(',')
13
13
  }
14
14
 
@@ -20,36 +20,47 @@ function TagFilterProvider(props) {
20
20
  const location = useLocation()
21
21
  const navigate = useNavigate()
22
22
 
23
- const [tags, setTags] = useState(getTagsFromParams(searchParams.get('tags')))
23
+ const [tagsLeft, setTagsLeft] = useState(getTagsFromParams(searchParams.get('tagsLeft')))
24
+ const [tagsRight, setTagsRight] = useState(getTagsFromParams(searchParams.get('tagsRight')))
24
25
 
25
- const tagParams = searchParams.get('tags')
26
+ const tagParamsLeft = searchParams.get('tagsLeft')
27
+ const tagParamsRight = searchParams.get('tagsRight')
26
28
 
27
29
  useEffect(() => {
28
- const newTags = getTagsFromParams(tagParams)
29
- setTags(newTags)
30
- }, [tagParams])
30
+ const newTags = getTagsFromParams(tagParamsLeft)
31
+ setTagsLeft(newTags)
32
+ }, [tagParamsLeft])
33
+
34
+ useEffect(() => {
35
+ const newTags = getTagsFromParams(tagParamsRight)
36
+ setTagsRight(newTags)
37
+ }, [tagParamsRight])
31
38
 
32
39
  const ctxValue = useMemo(() => {
33
- const toggleTag = (xmlId) => {
40
+ const toggleTag = (xmlId, side) => {
41
+ const tags = side === 'right' ? tagsRight : tagsLeft
42
+ const tagParams = side === 'right' ? tagParamsRight : tagParamsLeft
43
+ const setTags = side === 'right' ? setTagsRight : setTagsLeft
34
44
  if (tags.includes(xmlId)) {
35
45
  const oldTags = getTagsFromParams(tagParams)
36
46
  const newTags = oldTags.filter(t => t !== xmlId)
37
- searchParams.set('tags', newTags.join(','))
47
+ searchParams.set(`tags${side === 'right' ? 'Right' : 'Left'}`, newTags.join(','))
38
48
  setTags(newTags)
39
49
  navigate(`${location.pathname}?${createSearchParams(searchParams.toString())}`)
40
50
  }
41
51
  else {
42
- searchParams.set('tags', `${tagParams},${xmlId}`)
52
+ searchParams.set(`tags${side === 'right' ? 'Right' : 'Left'}`, `${tagParams},${xmlId}`)
43
53
  setTags([...tags, xmlId])
44
54
  navigate(`${location.pathname}?${createSearchParams(searchParams.toString())}`)
45
55
  }
46
56
  }
47
57
 
48
58
  return {
49
- tags,
59
+ tagsLeft,
60
+ tagsRight,
50
61
  toggleTag,
51
62
  }
52
- }, [location.pathname, navigate, searchParams, tagParams, tags])
63
+ }, [location.pathname, navigate, searchParams, tagParamsLeft, tagParamsRight, tagsLeft, tagsRight])
53
64
 
54
65
  return (
55
66
  <TagFilterContext.Provider value={ctxValue}>
@@ -1,7 +1,8 @@
1
1
  import { createContext } from 'react'
2
2
 
3
3
  const TagFilterContext = createContext({
4
- tags: [],
4
+ tagsLeft: [],
5
+ tagsRight: [],
5
6
  toggleTag: () => null,
6
7
  })
7
8
 
@@ -0,0 +1,31 @@
1
+ import { createTheme, ThemeProvider } from '@material-ui/core/styles'
2
+ import React from 'react'
3
+ import DiploMatic from './component/DiploMatic'
4
+ import { createReduxStore } from './model/ReduxStore'
5
+ import './scss/editioncrafter.scss'
6
+
7
+ /**
8
+ * Default instantiation
9
+ */
10
+ function EditionCrafter(props) {
11
+ const theme = createTheme({
12
+ palette: {
13
+ primary: {
14
+ main: '#792421',
15
+ },
16
+ secondary: {
17
+ main: '#EBE3DD',
18
+ },
19
+ },
20
+ })
21
+
22
+ const tagExplorerMode = props.tagExplorerMode === true
23
+
24
+ return (
25
+ <ThemeProvider theme={theme}>
26
+ <DiploMatic config={props} store={createReduxStore(props)} tagExplorerMode={tagExplorerMode} />
27
+ </ThemeProvider>
28
+ )
29
+ }
30
+
31
+ export default EditionCrafter
@@ -24,6 +24,27 @@ function getTagIds(html) {
24
24
  return tagIds
25
25
  }
26
26
 
27
+ function getZoneTagData(annotations) {
28
+ const tagIds = new Set()
29
+ const zoneTagIndex = {}
30
+
31
+ annotations.forEach((anno) => {
32
+ zoneTagIndex[anno.id] = []
33
+ anno.body.forEach((item) => {
34
+ if (item.purpose === 'classifying') {
35
+ const value = item.value.slice(1)
36
+ tagIds.add(value)
37
+ zoneTagIndex[anno.id].push(value)
38
+ }
39
+ })
40
+ })
41
+
42
+ return {
43
+ tagIds: Array.from(tagIds),
44
+ zoneTagIndex,
45
+ }
46
+ }
47
+
27
48
  export async function loadFolio(folioData) {
28
49
  if (folioData.loading) {
29
50
  return folioData
@@ -49,6 +70,11 @@ export async function loadFolio(folioData) {
49
70
  })
50
71
  }
51
72
 
73
+ const { tagIds, zoneTagIndex } = getZoneTagData(folio.annotations)
74
+
75
+ folio.tagIds = [...tagIds]
76
+ folio.zoneTagIndex = { ...zoneTagIndex }
77
+
52
78
  if (transcriptionTypes.length > 0) {
53
79
  for await (const transcriptionType of transcriptionTypes) {
54
80
  const { htmlURL, xmlURL } = folio.annotationURLs[transcriptionType]
@@ -68,7 +94,7 @@ export async function loadFolio(folioData) {
68
94
  }
69
95
  else {
70
96
  folio.transcription[transcriptionType] = transcription
71
- folio.tagIds = tagIds
97
+ folio.tagIds = [...folio.tagIds, ...tagIds]
72
98
  folio.loading = false
73
99
  transcriptionTypeTracker[transcriptionType] = true
74
100
  }
@@ -7,13 +7,10 @@ const justDocument = state => state.document
7
7
  const justGlossary = state => state.glossary
8
8
  const justNotes = state => state.notes
9
9
 
10
- function* parseTags(headerUrl) {
11
- const tags = {}
12
-
13
- const res = yield fetch(headerUrl)
10
+ function* parseTagUrl(url) {
11
+ const res = yield fetch(url)
14
12
 
15
13
  if (!res.ok) {
16
- yield putResolveAction('DocumentActions.loadTags', tags)
17
14
  return null
18
15
  }
19
16
 
@@ -23,6 +20,8 @@ function* parseTags(headerUrl) {
23
20
 
24
21
  const categoryEls = headerDoc.querySelectorAll('tei-category')
25
22
 
23
+ const documentTags = {}
24
+
26
25
  for (const categoryEl of categoryEls) {
27
26
  const xmlId = categoryEl.getAttribute('id')
28
27
 
@@ -30,12 +29,28 @@ function* parseTags(headerUrl) {
30
29
  const desc = categoryEl.querySelector('tei-catdesc')
31
30
  if (desc) {
32
31
  const name = desc.textContent
33
- tags[xmlId] = name
32
+ documentTags[xmlId] = name
34
33
  }
35
34
  }
36
35
  }
37
36
 
38
- yield putResolveAction('DocumentActions.loadTags', tags)
37
+ return documentTags
38
+ }
39
+
40
+ function* parseTags(headerUrl) {
41
+ if (typeof headerUrl === 'string') {
42
+ const result = yield parseTagUrl(headerUrl)
43
+ yield putResolveAction('DocumentActions.loadTags', result)
44
+ }
45
+ else {
46
+ const tags = {}
47
+
48
+ for (const docId of Object.keys(headerUrl)) {
49
+ tags[docId] = yield parseTagUrl(headerUrl[docId])
50
+ }
51
+
52
+ yield putResolveAction('DocumentActions.loadTags', tags)
53
+ }
39
54
  }
40
55
 
41
56
  function* userNavigation(action) {
@@ -31,7 +31,7 @@
31
31
  stroke-linejoin: round;
32
32
  fill: rgba(67, 133, 246, 0.1) !important;
33
33
  }
34
-
34
+
35
35
  .a9s-annotation.a9s-annotation.hover > rect,
36
36
  .a9s-annotation.a9s-annotation.hover > polygon
37
37
  {