@operato/dataset 7.1.4 → 7.1.6

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 (43) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/src/ox-data-entry-form.js +3 -7
  3. package/dist/src/ox-data-entry-form.js.map +1 -1
  4. package/dist/src/ox-data-entry-subgroup-form.d.ts +1 -0
  5. package/dist/src/ox-data-entry-subgroup-form.js +5 -0
  6. package/dist/src/ox-data-entry-subgroup-form.js.map +1 -1
  7. package/dist/src/ox-data-entry-subgroup-view.d.ts +15 -0
  8. package/dist/src/ox-data-entry-subgroup-view.js +198 -0
  9. package/dist/src/ox-data-entry-subgroup-view.js.map +1 -0
  10. package/dist/src/ox-data-entry-view.d.ts +4 -3
  11. package/dist/src/ox-data-entry-view.js +61 -79
  12. package/dist/src/ox-data-entry-view.js.map +1 -1
  13. package/dist/src/ox-data-sample-subgroup-view.js +1 -1
  14. package/dist/src/ox-data-sample-subgroup-view.js.map +1 -1
  15. package/dist/src/ox-data-sample-view.js +3 -1
  16. package/dist/src/ox-data-sample-view.js.map +1 -1
  17. package/dist/src/ox-data-summary-view.js +1 -1
  18. package/dist/src/ox-data-summary-view.js.map +1 -1
  19. package/dist/src/usecase/ccp/ox-input-ccp-limits.d.ts +2 -1
  20. package/dist/src/usecase/ccp/ox-input-ccp-limits.js +1 -0
  21. package/dist/src/usecase/ccp/ox-input-ccp-limits.js.map +1 -1
  22. package/dist/src/usecase/qc/ox-input-qc-limits.d.ts +2 -1
  23. package/dist/src/usecase/qc/ox-input-qc-limits.js +1 -0
  24. package/dist/src/usecase/qc/ox-input-qc-limits.js.map +1 -1
  25. package/dist/src/usecase/spc/ox-input-spc-limits.d.ts +2 -1
  26. package/dist/src/usecase/spc/ox-input-spc-limits.js +1 -0
  27. package/dist/src/usecase/spc/ox-input-spc-limits.js.map +1 -1
  28. package/dist/stories/ox-grist-editor-data-item-spec.stories.js +1 -1
  29. package/dist/stories/ox-grist-editor-data-item-spec.stories.js.map +1 -1
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +9 -9
  32. package/src/ox-data-entry-form.ts +3 -7
  33. package/src/ox-data-entry-subgroup-form.ts +6 -0
  34. package/src/ox-data-entry-subgroup-view.ts +221 -0
  35. package/src/ox-data-entry-view.ts +70 -93
  36. package/src/ox-data-sample-subgroup-view.ts +1 -1
  37. package/src/ox-data-sample-view.ts +3 -3
  38. package/src/ox-data-summary-view.ts +1 -1
  39. package/src/usecase/ccp/ox-input-ccp-limits.ts +2 -1
  40. package/src/usecase/qc/ox-input-qc-limits.ts +2 -1
  41. package/src/usecase/spc/ox-input-spc-limits.ts +2 -1
  42. package/stories/ox-grist-editor-data-item-spec.stories.ts +1 -1
  43. package/themes/form-theme.css +0 -1
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@operato/dataset",
3
3
  "description": "WebApplication dataset supporting components following open-wc recommendations",
4
4
  "author": "heartyoh",
5
- "version": "7.1.4",
5
+ "version": "7.1.6",
6
6
  "main": "dist/src/index.js",
7
7
  "module": "dist/src/index.js",
8
8
  "exports": {
@@ -108,15 +108,15 @@
108
108
  },
109
109
  "dependencies": {
110
110
  "@material/web": "^2.0.0",
111
- "@operato/data-grist": "^7.1.4",
111
+ "@operato/data-grist": "^7.1.6",
112
112
  "@operato/graphql": "^7.1.1",
113
- "@operato/grist-editor": "^7.1.4",
113
+ "@operato/grist-editor": "^7.1.6",
114
114
  "@operato/i18n": "^7.1.1",
115
- "@operato/input": "^7.1.4",
116
- "@operato/popup": "^7.1.1",
117
- "@operato/property-editor": "^7.1.4",
118
- "@operato/shell": "^7.1.1",
119
- "@operato/styles": "^7.1.1",
115
+ "@operato/input": "^7.1.6",
116
+ "@operato/popup": "^7.1.6",
117
+ "@operato/property-editor": "^7.1.6",
118
+ "@operato/shell": "^7.1.6",
119
+ "@operato/styles": "^7.1.6",
120
120
  "@operato/utils": "^7.1.1",
121
121
  "lit": "^3.1.2"
122
122
  },
@@ -152,5 +152,5 @@
152
152
  "prettier --write"
153
153
  ]
154
154
  },
155
- "gitHead": "1abfd7c0d36d4083e38f88bd83cffa92198bb520"
155
+ "gitHead": "6f754b46ee6e01c930e71073611de781da4e7bf5"
156
156
  }
@@ -172,6 +172,7 @@ export class OxDataEntryForm extends LitElement {
172
172
  .subgroup=${subgroup}
173
173
  .dataItems=${dataItems}
174
174
  .value=${value}
175
+ @change=${(e: Event) => this.onChange(e)}
175
176
  ></ox-data-entry-subgroup-form>
176
177
  </div>
177
178
  </div>
@@ -234,15 +235,10 @@ export class OxDataEntryForm extends LitElement {
234
235
  return html` <input type="datetime-local" name=${tag} value=${v} />`
235
236
 
236
237
  case 'file':
237
- return html`<ox-input-file name=${tag}></ox-input-file>`
238
+ return html`<ox-input-file name=${tag} multiple .value=${v}></ox-input-file>`
238
239
 
239
240
  case 'signature':
240
- return html`<ox-input-signature
241
- name=${tag}
242
- label="Attach Files"
243
- accept="*/*"
244
- multiple="true"
245
- ></ox-input-signature>`
241
+ return html`<ox-input-signature name=${tag} value=${v}></ox-input-signature>`
246
242
 
247
243
  case 'string':
248
244
  default:
@@ -38,6 +38,7 @@ export class OxDataEntrySubgroupForm extends LitElement {
38
38
  .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
39
39
  .config=${this.buildGristConfiguration()}
40
40
  .fetchHandler=${this.fetchHandler.bind(this)}
41
+ @field-change=${this.onFieldChange.bind(this)}
41
42
  >
42
43
  </ox-grist>
43
44
  `
@@ -91,6 +92,7 @@ export class OxDataEntrySubgroupForm extends LitElement {
91
92
  return columnConfig
92
93
 
93
94
  case 'file':
95
+ columnConfig.record.multiple = true
94
96
  return columnConfig
95
97
 
96
98
  case 'signature':
@@ -148,4 +150,8 @@ export class OxDataEntrySubgroupForm extends LitElement {
148
150
  return partial
149
151
  }, {} as any)
150
152
  }
153
+
154
+ onFieldChange(e: CustomEvent) {
155
+ this.dispatchEvent(new CustomEvent('change', { detail: this.grist.dirtyData }))
156
+ }
151
157
  }
@@ -0,0 +1,221 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@operato/input/ox-input-file.js'
3
+ import '@operato/input/ox-input-signature.js'
4
+
5
+ import { css, html, LitElement, nothing } from 'lit'
6
+ import { customElement, property } from 'lit/decorators.js'
7
+
8
+ import { i18next } from '@operato/i18n'
9
+
10
+ import { DataSample, DataSet, DataSpecLimitSet } from './types.js'
11
+ import { OxDataUseCase } from './usecase/ox-data-use-case.js'
12
+
13
+ @customElement('ox-data-entry-subgroup-view')
14
+ export class OxDataEntrySubgroupView extends LitElement {
15
+ static styles = css`
16
+ :host {
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ h3 {
22
+ margin: var(--title-margin);
23
+ font: var(--title-font);
24
+ font-size: 20px;
25
+ color: var(--title-text-color);
26
+ text-transform: capitalize;
27
+ }
28
+
29
+ table {
30
+ border-collapse: collapse;
31
+ margin-bottom: var(--spacing-medium);
32
+ }
33
+
34
+ th {
35
+ padding: var(--th-padding);
36
+ border-top: var(--th-border-top);
37
+ border-left: var(--td-border-line, 1px solid rgba(0, 0, 0, 0.05));
38
+ border-bottom: var(--td-border-bottom);
39
+ text-transform: var(--th-text-transform);
40
+ font: var(--th-font);
41
+ font-weight: bold;
42
+ color: var(--th-color);
43
+ text-align: center;
44
+ white-space: nowrap;
45
+ }
46
+
47
+ th.label,
48
+ td.label {
49
+ background-color: var(--label-cell-background-color, #f6f6f6);
50
+ width: 120px;
51
+ text-transform: var(--th-text-transform);
52
+ }
53
+
54
+ tr {
55
+ background-color: var(--tr-background-color);
56
+ }
57
+
58
+ tr:hover {
59
+ background-color: var(--tr-background-hover-color);
60
+ }
61
+
62
+ td {
63
+ border-left: var(--td-border-line, 1px solid rgba(0, 0, 0, 0.05));
64
+ border-bottom: var(--td-border-bottom);
65
+ padding: var(--td-padding);
66
+ font: var(--td-font);
67
+ color: var(--td-color);
68
+ text-align: center;
69
+ }
70
+ tr th:first-child,
71
+ tr td:first-child {
72
+ border-left: none;
73
+ }
74
+
75
+ td md-icon {
76
+ color: var(--md-sys-color-error);
77
+ }
78
+
79
+ pre {
80
+ tab-size: 2;
81
+ text-align: left;
82
+ }
83
+
84
+ a[file] {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: var(--spacing-small);
88
+ }
89
+
90
+ a[file] md-icon {
91
+ --md-icon-size: 16px;
92
+ color: var(--md-sys-color-primary);
93
+ }
94
+ `
95
+
96
+ @property({ type: Object }) dataSet?: DataSet
97
+ @property({ type: Object }) data?: any
98
+ @property({ type: String }) subgroup?: string
99
+
100
+ render() {
101
+ if (!this.data || !this.dataSet) {
102
+ return html``
103
+ }
104
+
105
+ const { useCase, dataItems = [] } = this.dataSet
106
+
107
+ const { data } = this
108
+ const useCaseNames = useCase?.split(',').filter(useCase => useCase.trim()) || []
109
+ const subgroupDataItems = dataItems.filter(dataItem => dataItem.group == this.subgroup && !dataItem.hidden)
110
+ const records = subgroupDataItems.reduce((max, dataItem) => {
111
+ const value = data[dataItem.tag]
112
+ return Math.max(max, Array.isArray(value) ? value.length : 1)
113
+ }, 0)
114
+ const judgment = subgroupDataItems.reduce(
115
+ (judgment, dataItem) => {
116
+ const tag = dataItem.tag
117
+ judgment[tag] = OxDataUseCase.evaluateTag(this.dataSet!, dataItems, data, tag) || {}
118
+ return judgment
119
+ },
120
+ {} as { [tag: string]: { ooc: boolean; oos: boolean } }
121
+ )
122
+
123
+ return html` <h3>${this.subgroup}</h3>
124
+ <table>
125
+ <tr>
126
+ <th class="label">${i18next.t('field.name')}</th>
127
+ ${subgroupDataItems.map(
128
+ dataItem => html` <th>${dataItem.name} ${dataItem.unit ? `(${dataItem.unit})` : ''}</th> `
129
+ )}
130
+ </tr>
131
+ <tr>
132
+ <td class="label">${i18next.t('field.description')}</td>
133
+ ${subgroupDataItems.map(dataItem => html` <td>${dataItem.description}</td> `)}
134
+ </tr>
135
+ ${Array.from({ length: records }, (_, index) => index).map(
136
+ index => html`
137
+ <tr>
138
+ <td class="label">${records > 1 ? index + 1 : i18next.t('field.value')}</td>
139
+ ${subgroupDataItems.map(dataItem => {
140
+ const { tag = '', type } = dataItem
141
+ const valueArray = data[tag]
142
+ const value = Array.isArray(valueArray) ? valueArray[index] : index == 0 ? valueArray : undefined
143
+
144
+ return html` <td>${this.buildValue(type, value)}</td> `
145
+ })}
146
+ </tr>
147
+ `
148
+ )}
149
+ <tr>
150
+ <td class="label">${i18next.t('field.spec')}</td>
151
+ ${subgroupDataItems.map(
152
+ dataItem => html` <td><pre>${this.buildSpec(useCaseNames, dataItem.spec)}</pre></td> `
153
+ )}
154
+ </tr>
155
+ <tr>
156
+ <td class="label">${i18next.t('field.ooc')}</td>
157
+ ${subgroupDataItems.map(
158
+ dataItem => html` <td>${judgment?.[dataItem.tag]?.ooc ? html`<md-icon>done</md-icon>` : nothing}</td> `
159
+ )}
160
+ </tr>
161
+ <tr>
162
+ <td class="label">${i18next.t('field.oos')}</td>
163
+ ${subgroupDataItems.map(
164
+ dataItem => html` <td>${judgment?.[dataItem.tag]?.oos ? html`<md-icon>done</md-icon>` : nothing}</td> `
165
+ )}
166
+ </tr>
167
+ </table>`
168
+ }
169
+
170
+ private buildSpec(useCaseNames: string[], spec: DataSpecLimitSet): string {
171
+ return OxDataUseCase.elaborateDataItemSpec(useCaseNames, spec)
172
+ }
173
+
174
+ private download(file: { mimetype: string; name: string; fullpath: string }) {
175
+ const element = document.createElement('a')
176
+ element.setAttribute('href', file.fullpath)
177
+ element.setAttribute('download', file.name!)
178
+ document.body.appendChild(element)
179
+ element.click()
180
+ }
181
+
182
+ private buildValue(type: string, value: any | any[]) {
183
+ if (value === undefined) {
184
+ return ''
185
+ }
186
+ const values = value instanceof Array ? value : [value]
187
+
188
+ if (type == 'file') {
189
+ const files = values.flat() as { mimetype: string; name: string; fullpath: string }[]
190
+
191
+ return files.filter(Boolean).map(
192
+ (file, idx) => html`
193
+ <a @click=${() => this.download(file)} file><md-icon>description</md-icon>${file.name}</a>
194
+ ${files.length - 1 == idx ? html`</br>` : nothing}
195
+ `
196
+ )
197
+ }
198
+
199
+ if (type == 'signature') {
200
+ return html` <ox-input-signature .value=${value} disabled></ox-input-signature>`
201
+ }
202
+
203
+ const elements = values.map((v: any, idx) => {
204
+ switch (typeof v) {
205
+ case 'boolean':
206
+ return html` <input type="checkbox" .checked=${v} disabled />`
207
+ break
208
+
209
+ default:
210
+ if (type == 'date') {
211
+ return v ? new Date(v).toLocaleDateString() : ''
212
+ } else if (type == 'datetime') {
213
+ return v ? new Date(v).toLocaleString() : ''
214
+ }
215
+ return v ?? ''
216
+ }
217
+ })
218
+
219
+ return typeof values[0] === 'boolean' ? elements : elements.join(', ')
220
+ }
221
+ }
@@ -1,12 +1,14 @@
1
+ import '@material/web/icon/icon.js'
1
2
  import '@operato/input/ox-input-file.js'
2
3
  import '@operato/input/ox-input-signature.js'
4
+ import './ox-data-entry-subgroup-view.js'
3
5
 
4
6
  import { css, html, LitElement, nothing } from 'lit'
5
7
  import { customElement, property } from 'lit/decorators.js'
6
8
 
7
9
  import { i18next } from '@operato/i18n'
8
10
 
9
- import { DataSet, DataSpecLimitSet } from './types.js'
11
+ import { DataItem, DataSet, DataSpecLimitSet } from './types.js'
10
12
  import { OxDataUseCase } from './usecase/ox-data-use-case.js'
11
13
 
12
14
  @customElement('ox-data-entry-view')
@@ -15,6 +17,9 @@ export class OxDataEntryView extends LitElement {
15
17
  :host {
16
18
  display: flex;
17
19
  flex-direction: column;
20
+
21
+ --signature-min-width: 100px;
22
+ --signature-min-height: 60px;
18
23
  }
19
24
 
20
25
  form {
@@ -106,68 +111,6 @@ export class OxDataEntryView extends LitElement {
106
111
  @property({ type: Object }) dataSet?: DataSet
107
112
  @property({ type: Object }) value?: { [tag: string]: any }
108
113
 
109
- __render() {
110
- return html`<form>
111
- <h2>${this.dataSet?.name || ''}</h2>
112
- <h3>${this.dataSet?.description || ''}</h3>
113
- ${this.buildEntryViews()}
114
- </form> `
115
- }
116
-
117
- private buildEntryViews() {
118
- const dataItems = this.dataSet?.dataItems.filter(item => item.active)
119
-
120
- return (dataItems || []).map(dataItem => {
121
- const { name, description, tag, type, quota = 1, options = {}, unit } = dataItem
122
-
123
- const samples = new Array(quota).fill(0)
124
- const value = this.value && this.value[tag]
125
-
126
- const elements = samples.map((_, idx) => {
127
- const v = value instanceof Array ? value[idx] : idx === 0 ? value : undefined
128
-
129
- switch (type) {
130
- case 'select':
131
- return html` <select .name=${tag} disabled>
132
- <option value=""></option>
133
- ${(options.options || []).map(
134
- option => html`<option value=${option.value} ?selected=${option.value === v}>${option.text}</option>`
135
- )}
136
- </select>`
137
- break
138
-
139
- case 'boolean':
140
- return html` <input type="checkbox" name=${tag} .checked=${v} disabled />`
141
- break
142
-
143
- case 'number':
144
- return html` <input type="number" name=${tag} value=${v} disabled />`
145
- break
146
-
147
- case 'file':
148
- return html`<ox-input-file
149
- name=${tag}
150
- label="Attach Files"
151
- accept="*/*"
152
- multiple="true"
153
- hide-filelist
154
- disabled
155
- ></ox-input-file>`
156
-
157
- case 'string':
158
- default:
159
- return html` <input type="text" name=${tag} value=${v} disabled />`
160
- }
161
- })
162
-
163
- return html` <label .title=${description}>
164
- <div name>${name}${unit ? `(${unit})` : ''}</div>
165
- <div description><md-icon>info</md-icon> ${description}</div>
166
- <div elements>${elements}</div>
167
- </label>`
168
- })
169
- }
170
-
171
114
  render() {
172
115
  if (!this.dataSet) {
173
116
  return nothing
@@ -176,45 +119,79 @@ export class OxDataEntryView extends LitElement {
176
119
  const { name, description, useCase, dataItems = [] } = this.dataSet!
177
120
 
178
121
  const data = this.value || {}
122
+
179
123
  const useCaseNames = useCase?.split(',').filter(useCase => useCase.trim()) || []
124
+ const nonGroupDataItems = dataItems.filter(dataItem => !dataItem.group && !dataItem.hidden)
125
+ const dataItemSubgroups = this.groupDataItemsByGroup(dataItems)
180
126
 
181
127
  return html` <h2>${name}</h2>
182
128
  <p page-description><md-icon>info</md-icon> ${description}<br /></p>
183
129
 
184
130
  <form>
185
- <table>
186
- <tr>
187
- <th item>${i18next.t('field.item')}</th>
188
- <th>${i18next.t('field.description')}</th>
189
- <th>${i18next.t('field.finalizing-function')}</th>
190
- <th>${i18next.t('field.unit')}</th>
191
- <th value>${i18next.t('field.value')}</th>
192
- <th>${i18next.t('field.spec')}</th>
193
- <th>${i18next.t('field.ooc')}</th>
194
- <th>${i18next.t('field.oos')}</th>
195
- </tr>
196
- ${dataItems.map(dataItem => {
197
- const { name = '', tag = '', description = '', stat, unit = '', spec = {}, type } = dataItem
198
- const value = data[tag]
199
- const { ooc, oos } = OxDataUseCase.evaluateTag(this.dataSet!, dataItems, data, tag) || {}
200
-
201
- return html`
202
- <tr ?ooc=${ooc} ?oos=${oos}>
203
- <td name>${name}</td>
204
- <td>${description}</td>
205
- <td>${stat}</td>
206
- <td>${unit}</td>
207
- <td>${this.buildValue(type, value)}</td>
208
- <td><pre>${this.buildSpec(useCaseNames, spec)}</pre></td>
209
- <td>${ooc ? html`<md-icon>done</md-icon>` : ''}</td>
210
- <td>${oos ? html`<md-icon>done</md-icon>` : ''}</td>
211
- </tr>
131
+ ${nonGroupDataItems.length > 0
132
+ ? html`
133
+ <table>
134
+ <tr>
135
+ <th item>${i18next.t('field.item')}</th>
136
+ <th>${i18next.t('field.description')}</th>
137
+ <th>${i18next.t('field.finalizing-function')}</th>
138
+ <th>${i18next.t('field.unit')}</th>
139
+ <th value>${i18next.t('field.value')}</th>
140
+ <th>${i18next.t('field.spec')}</th>
141
+ <th>${i18next.t('field.ooc')}</th>
142
+ <th>${i18next.t('field.oos')}</th>
143
+ </tr>
144
+ ${nonGroupDataItems.map(dataItem => {
145
+ const { name = '', tag = '', description = '', stat, unit = '', spec = {}, type } = dataItem
146
+ const value = data[tag]
147
+ const { ooc, oos } = OxDataUseCase.evaluateTag(this.dataSet!, dataItems, data, tag) || {}
148
+
149
+ return html`
150
+ <tr ?ooc=${ooc} ?oos=${oos}>
151
+ <td name>${name}</td>
152
+ <td>${description}</td>
153
+ <td>${stat}</td>
154
+ <td>${unit}</td>
155
+ <td>${this.buildValue(type, value)}</td>
156
+ <td><pre>${this.buildSpec(useCaseNames, spec)}</pre></td>
157
+ <td>${ooc ? html`<md-icon>done</md-icon>` : ''}</td>
158
+ <td>${oos ? html`<md-icon>done</md-icon>` : ''}</td>
159
+ </tr>
160
+ `
161
+ })}
162
+ </table>
212
163
  `
213
- })}
214
- </table>
164
+ : nothing}
165
+ ${Object.keys(dataItemSubgroups).map(subgroup => {
166
+ return html`
167
+ <ox-data-entry-subgroup-view
168
+ .subgroup=${subgroup}
169
+ .dataSet=${this.dataSet}
170
+ .data=${this.value}
171
+ ></ox-data-entry-subgroup-view>
172
+ `
173
+ })}
215
174
  </form>`
216
175
  }
217
176
 
177
+ private groupDataItemsByGroup(dataItems: DataItem[]): { [group: string]: DataItem[] } {
178
+ const groupedDataItems: { [group: string]: DataItem[] } = {}
179
+
180
+ for (const dataItem of dataItems) {
181
+ const { group, hidden } = dataItem
182
+
183
+ if (group && !hidden) {
184
+ if (!groupedDataItems[group]) {
185
+ groupedDataItems[group] = []
186
+ }
187
+
188
+ groupedDataItems[group].push(dataItem)
189
+ }
190
+ }
191
+
192
+ return groupedDataItems
193
+ }
194
+
218
195
  private buildSpec(useCaseNames: string[], spec: DataSpecLimitSet): string {
219
196
  return OxDataUseCase.elaborateDataItemSpec(useCaseNames, spec)
220
197
  }
@@ -236,7 +213,7 @@ export class OxDataEntryView extends LitElement {
236
213
  if (type == 'file') {
237
214
  const files = values.flat() as { mimetype: string; name: string; fullpath: string }[]
238
215
 
239
- return files.map(file => html`<a @click=${() => this.download(file)}>${file.name}</a></br>`)
216
+ return files.filter(Boolean).map(file => html`<a @click=${() => this.download(file)}>${file.name}</a></br>`)
240
217
  }
241
218
 
242
219
  if (type == 'signature') {
@@ -177,7 +177,7 @@ export class OxDataSampleSubgroupView extends LitElement {
177
177
  if (type == 'file') {
178
178
  const files = values.flat() as { mimetype: string; name: string; fullpath: string }[]
179
179
 
180
- return files.map(
180
+ return files.filter(Boolean).map(
181
181
  (file, idx) => html`
182
182
  <a @click=${() => this.download(file)} file><md-icon>description</md-icon>${file.name}</a>
183
183
  ${files.length - 1 == idx ? html`</br>` : nothing}
@@ -241,9 +241,9 @@ export class OxDataSampleView extends LitElement {
241
241
  if (type == 'file') {
242
242
  const files = values.flat() as { mimetype: string; name: string; fullpath: string }[]
243
243
 
244
- return files.map(
245
- file => html`<a @click=${() => this.download(file)} file><md-icon>description</md-icon>${file.name}</a>`
246
- )
244
+ return files
245
+ .filter(Boolean)
246
+ .map(file => html`<a @click=${() => this.download(file)} file><md-icon>description</md-icon>${file.name}</a>`)
247
247
  }
248
248
 
249
249
  if (type == 'signature') {
@@ -170,7 +170,7 @@ export class OxDataSummaryView extends LitElement {
170
170
  if (type == 'file') {
171
171
  const files = values.flat() as { mimetype: string; name: string; fullpath: string }[]
172
172
 
173
- return files.map(file => html`<a @click=${() => this.download(file)}>${file.name}</a></br>`)
173
+ return files.filter(Boolean).map(file => html`<a @click=${() => this.download(file)}>${file.name}</a></br>`)
174
174
  }
175
175
 
176
176
  if (type == 'signature') {
@@ -12,7 +12,8 @@ export enum DataItemType {
12
12
  text = 'text',
13
13
  boolean = 'boolean',
14
14
  select = 'select',
15
- file = 'file'
15
+ file = 'file',
16
+ signature = 'signature'
16
17
  }
17
18
 
18
19
  export type CcpLimitValue = {
@@ -12,7 +12,8 @@ export enum DataItemType {
12
12
  text = 'text',
13
13
  boolean = 'boolean',
14
14
  select = 'select',
15
- file = 'file'
15
+ file = 'file',
16
+ signature = 'signature'
16
17
  }
17
18
 
18
19
  export type QcLimitValue = {
@@ -12,7 +12,8 @@ export enum DataItemType {
12
12
  text = 'text',
13
13
  boolean = 'boolean',
14
14
  select = 'select',
15
- file = 'file'
15
+ file = 'file',
16
+ signature = 'signature'
16
17
  }
17
18
 
18
19
  export type SpcLimitValue = {
@@ -191,7 +191,7 @@ class GristDemo extends LitElement {
191
191
  name: 'type',
192
192
  header: i18next.t('field.type'),
193
193
  record: {
194
- options: ['', 'number', 'text', 'select', 'boolean', 'file'],
194
+ options: ['', 'number', 'text', 'select', 'boolean', 'file', 'signature'],
195
195
  editable: true
196
196
  },
197
197
  width: 120
@@ -39,7 +39,6 @@ body {
39
39
  --file-uploader-label-color: #fff;
40
40
  --file-uploader-li-padding: 2px 5px 0px 5px;
41
41
  --file-uploader-li-border-bottom: 1px dotted rgba(0, 0, 0, 0.1);
42
- --file-uploader-li-icon-margin: 2px 0 2px 5px;
43
42
  --file-uploader-li-icon-font: normal 15px var(--md-icon-font, 'Material Symbols Outlined');
44
43
  --file-uploader-li-icon-focus-color: var(--md-sys-color-primary);
45
44
  }