@cu-mkp/editioncrafter 1.3.0-beta.2 → 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.
@@ -0,0 +1,108 @@
1
+ import { useContext, useMemo, useState } from 'react'
2
+ import Pill from '../../common/components/Pill'
3
+ import FilterContext from '../context/FilterContext'
4
+
5
+ const MAX_PHRASE_LENGTH = 20
6
+
7
+ function PhraseList(props) {
8
+ const phraseStr = useMemo(() => {
9
+ const arr = []
10
+
11
+ props.phrases.forEach((phrase) => {
12
+ if (phrase.layer !== props.layer) {
13
+ return
14
+ }
15
+
16
+ if (arr.includes(phrase.name)) {
17
+ return
18
+ }
19
+
20
+ if (phrase.name.length > MAX_PHRASE_LENGTH) {
21
+ arr.push(`${phrase.name.slice(0, MAX_PHRASE_LENGTH)}...`)
22
+ }
23
+ else {
24
+ arr.push(phrase.name)
25
+ }
26
+ })
27
+
28
+ return arr.join('; ')
29
+ }, [props.layer, props.phrases])
30
+
31
+ return <p>{phraseStr}</p>
32
+ }
33
+
34
+ function PhraseListTable(props) {
35
+ const ctx = useContext(FilterContext)
36
+
37
+ const [layer, setLayer] = useState(Object.keys(ctx.layers)[0])
38
+
39
+ const tagsToShow = useMemo(
40
+ () => Object.keys(props.tagCounts).filter(tagName => props.tagCounts[tagName].phrases.some(phrase => phrase.layer === layer)),
41
+ [layer, props.tagCounts],
42
+ )
43
+
44
+ return (
45
+ <div
46
+ className="phrase-list-table-container"
47
+ style={{ display: props.visible ? 'initial' : 'none' }}
48
+ >
49
+ <div className="layer-select-container">
50
+ <span>Layer</span>
51
+ <select
52
+ className="layer-select"
53
+ onChange={e => setLayer(e.target.value)}
54
+ >
55
+ {Object.keys(ctx.layers).map(xmlId => (
56
+ <option
57
+ key={xmlId}
58
+ value={xmlId}
59
+ >
60
+ {ctx.layers[xmlId]}
61
+ </option>
62
+ ))}
63
+ </select>
64
+ </div>
65
+ <table className="phrase-list-table">
66
+ <thead>
67
+ <tr>
68
+ <th>Tags for phrases</th>
69
+ <th>
70
+ <span>References in this layer</span>
71
+ </th>
72
+ </tr>
73
+ </thead>
74
+ <tbody>
75
+ {tagsToShow.length === 0 && (
76
+ <tr>
77
+ <td colSpan={2}>
78
+ <div className="empty-table-container">
79
+ No tags in this layer.
80
+ </div>
81
+ </td>
82
+ </tr>
83
+ )}
84
+ {tagsToShow.map(tag => (
85
+ <tr key={tag}>
86
+ <td>
87
+ <Pill
88
+ label={tag}
89
+ isActive={ctx.tags.includes(props.tagCounts[tag].id)}
90
+ >
91
+ <span className="tag-count">{props.tagCounts[tag].count}</span>
92
+ </Pill>
93
+ </td>
94
+ <td>
95
+ <PhraseList
96
+ layer={layer}
97
+ phrases={props.tagCounts[tag].phrases}
98
+ />
99
+ </td>
100
+ </tr>
101
+ ))}
102
+ </tbody>
103
+ </table>
104
+ </div>
105
+ )
106
+ }
107
+
108
+ export default PhraseListTable
@@ -1,6 +1,8 @@
1
- import { useContext, useMemo } from 'react'
1
+ import { useContext, useMemo, useState } from 'react'
2
+ import { IoChevronDownOutline, IoChevronUpOutline } from 'react-icons/io5'
2
3
  import Pill from '../../common/components/Pill'
3
4
  import FilterContext from '../context/FilterContext'
5
+ import PhraseList from './PhraseListTable'
4
6
 
5
7
  function getRecordName(div) {
6
8
  if (div.element_name) {
@@ -22,7 +24,24 @@ function getSurfaceLink(baseUrl, div, cats, tags) {
22
24
  )
23
25
  }
24
26
 
27
+ function PhraseToggle(props) {
28
+ return (
29
+ <button
30
+ aria-label="Toggle phrases"
31
+ className="phrase-toggle"
32
+ onClick={props.onClick}
33
+ type="button"
34
+ >
35
+ {props.toggled
36
+ ? <IoChevronUpOutline />
37
+ : <IoChevronDownOutline />}
38
+ </button>
39
+ )
40
+ }
41
+
25
42
  function Record(props) {
43
+ const [showPhrases, setShowPhrases] = useState(false)
44
+
26
45
  const ctx = useContext(FilterContext)
27
46
 
28
47
  const categories = useMemo(
@@ -38,11 +57,19 @@ function Record(props) {
38
57
  tags.forEach((tag) => {
39
58
  if (result[tag.name]) {
40
59
  result[tag.name].count++
60
+ result[tag.name].phrases.push({
61
+ name: el.element_name,
62
+ layer: el.layer_xml_id,
63
+ })
41
64
  }
42
65
  else {
43
66
  result[tag.name] = {
44
67
  id: tag.xml_id,
45
68
  count: 1,
69
+ phrases: [{
70
+ name: el.element_name,
71
+ layer: el.layer_xml_id,
72
+ }],
46
73
  }
47
74
  }
48
75
  })
@@ -74,7 +101,16 @@ function Record(props) {
74
101
  <span className="tag-count">{tagCounts[tagName].count}</span>
75
102
  </Pill>
76
103
  ))}
104
+ <PhraseToggle
105
+ toggled={showPhrases}
106
+ onClick={() => setShowPhrases(!showPhrases)}
107
+ />
77
108
  </div>
109
+ <PhraseList
110
+ tagCounts={tagCounts}
111
+ elements={props.childElements}
112
+ visible={showPhrases}
113
+ />
78
114
  </div>
79
115
  )
80
116
  }
@@ -1,9 +1,9 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'react'
2
2
  import initSqlJs from 'sql.js'
3
+ import sqlJsInfo from 'sql.js/package.json'
3
4
  import Loading from '../common/components/Loading'
4
5
  import RecordListView from './component/RecordListView'
5
6
  import Sidebar from './component/Sidebar'
6
-
7
7
  import FilterContext from './context/FilterContext'
8
8
 
9
9
  import './styles/base.css'
@@ -26,7 +26,7 @@ async function initDb(url) {
26
26
  const arr = new Uint8Array(buf)
27
27
 
28
28
  const SQL = await initSqlJs({
29
- locateFile: file => `https://sql.js.org/dist/${file}`,
29
+ locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/${sqlJsInfo.version}/${file}`,
30
30
  })
31
31
 
32
32
  const db = new SQL.Database(arr)
@@ -56,7 +56,8 @@ function RecordList(props) {
56
56
  ...filters,
57
57
  toggleCategoryFilter,
58
58
  toggleTagFilter,
59
- }), [filters, toggleCategoryFilter, toggleTagFilter])
59
+ layers: props.layers,
60
+ }), [filters, toggleCategoryFilter, toggleTagFilter, props.layers])
60
61
 
61
62
  useEffect(() => {
62
63
  const loadDb = async () => {
@@ -83,7 +84,11 @@ function RecordList(props) {
83
84
  <FilterContext.Provider value={initialContext}>
84
85
  <div className="editioncrafter-record-list">
85
86
  <Sidebar db={db} />
86
- <RecordListView db={db} recordLabel={props.recordLabel} viewerUrl={props.viewerUrl} />
87
+ <RecordListView
88
+ db={db}
89
+ recordLabel={props.recordLabel}
90
+ viewerUrl={props.viewerUrl}
91
+ />
87
92
  </div>
88
93
  </FilterContext.Provider>
89
94
  )
@@ -3,7 +3,8 @@
3
3
  .editioncrafter-record-list {
4
4
  width: 100%;
5
5
  height: 100%;
6
- display: flex;
6
+ display: grid;
7
+ grid-template-columns: auto 1fr;
7
8
  gap: 20px;
8
9
  font-family: 'Inter', sans-serif;
9
10
  margin: 0;
@@ -36,13 +36,34 @@
36
36
  align-items: center;
37
37
  font-size: 14px;
38
38
  font-weight: 500;
39
+ position: relative;
39
40
  }
40
41
 
41
42
  .editioncrafter-record-list .record-list-view .record-box .tag-list .tag-list-label {
42
43
  font-weight: 600;
43
44
  }
44
45
 
45
- .editioncrafter-record-list .record-list-view .record-box .tag-list .pill {
46
+ .editioncrafter-record-list .record-list-view .record-box .phrase-toggle {
47
+ position: absolute;
48
+ right: 0;
49
+ bottom: 2px;
50
+ background: #07529A;
51
+ border: none;
52
+ border-radius: 5px;
53
+ color: white;
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ padding: 6px;
58
+ }
59
+
60
+ .editioncrafter-record-list .record-list-view .record-box .phrase-toggle svg {
61
+ height: 18px;
62
+ width: 18px;
63
+ }
64
+
65
+ .editioncrafter-record-list .record-list-view .record-box .tag-list .pill,
66
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table .pill {
46
67
  background: #ffffff;
47
68
  display: flex;
48
69
  height: 32px;
@@ -59,7 +80,7 @@
59
80
  color: #ffffff
60
81
  }
61
82
 
62
- .editioncrafter-record-list .record-list-view .record-box .tag-list .pill .tag-count {
83
+ .editioncrafter-record-list .record-list-view .record-box .pill .tag-count {
63
84
  display: inline-flex;
64
85
  height: 24px;
65
86
  min-width: 24px;
@@ -73,12 +94,79 @@
73
94
  background: #B5C8DC99;
74
95
  }
75
96
 
76
- .editioncrafter-record-list .record-list-view .record-box .tag-list .pill.active {
97
+ .editioncrafter-record-list .record-list-view .record-box .pill.active {
77
98
  border: 1px solid #07529A;
78
99
  color: #07529A;
79
100
  }
80
101
 
81
- .editioncrafter-record-list .record-list-view .record-box .tag-list .pill.active .tag-count {
102
+ .editioncrafter-record-list .record-list-view .record-box .pill.active .tag-count {
82
103
  background: #07529A;
83
104
  color: #ffffff;
84
105
  }
106
+
107
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table-container {
108
+ position: relative;
109
+ }
110
+
111
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table-container .layer-select-container {
112
+ position: absolute;
113
+ right: 10px;
114
+ top: 0;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ gap: 12px;
119
+ font-size: 13px;
120
+ font-style: normal;
121
+ font-weight: 400;
122
+ line-height: 140%;
123
+ }
124
+
125
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table-container button:hover {
126
+ cursor: default;
127
+ }
128
+
129
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table-container .layer-select {
130
+ display: flex;
131
+ height: 32px;
132
+ padding: 0px 12px;
133
+ justify-content: space-between;
134
+ align-items: center;
135
+ font-size: 14px;
136
+ border-radius: 4px;
137
+ border: 1px solid#E1E3E6;
138
+ background:#F3F4F7;
139
+ }
140
+
141
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table-container .layer-select:hover {
142
+ cursor: pointer;
143
+ }
144
+
145
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table th {
146
+ font-weight: 600;
147
+ }
148
+
149
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table {
150
+ text-align: left;
151
+ border-collapse: collapse;
152
+ font-size: 14px;
153
+ width: 100%;
154
+ }
155
+
156
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table tr {
157
+ border-bottom: 1px solid #E1E3E6;
158
+ }
159
+
160
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table td,
161
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table th {
162
+ padding: 12px;
163
+
164
+ }
165
+
166
+ .editioncrafter-record-list .record-list-view .record-box .phrase-list-table .empty-table-container {
167
+ height: 80px;
168
+ width: 100%;
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ }
@@ -1,6 +1,7 @@
1
1
  import { useEffect, useState } from 'react'
2
2
  import { HashRouter } from 'react-router-dom'
3
3
  import initSqlJs from 'sql.js'
4
+ import sqlJsInfo from 'sql.js/package.json'
4
5
  import Loading from '../common/components/Loading'
5
6
  import { getObjs } from '../common/lib/sql'
6
7
  import EditionCrafter from '../EditionCrafter'
@@ -24,7 +25,7 @@ async function initDb(url) {
24
25
  const arr = new Uint8Array(buf)
25
26
 
26
27
  const db = await initSqlJs({
27
- locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.12.0/${file}`,
28
+ locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/${sqlJsInfo.version}/${file}`,
28
29
  }).then((SQL) => {
29
30
  const db = new SQL.Database(arr)
30
31
  return db
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cu-mkp/editioncrafter",
3
3
  "type": "module",
4
- "version": "1.3.0-beta.2",
4
+ "version": "1.3.0-beta.3",
5
5
  "private": false,
6
6
  "description": "A simple digital critical edition publication tool",
7
7
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  "redux": "^4.2.1",
48
48
  "redux-saga": "^1.2.2",
49
49
  "remark-gfm": "^3.0.1",
50
- "sql.js": "^1.12.0"
50
+ "sql.js": "^1.13.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@antfu/eslint-config": "^3.8.0",