@things-factory/dataset 9.1.18 → 9.1.19
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.
- package/EXPLORATION_INDEX.md +252 -0
- package/FRONTEND_ARCHITECTURE.md +732 -0
- package/QUICK_REFERENCE.md +365 -0
- package/dist-client/pages/data-set/data-item-list.js +15 -1
- package/dist-client/pages/data-set/data-item-list.js.map +1 -1
- package/dist-client/pages/data-set/data-set-list-page.js +0 -1
- package/dist-client/pages/data-set/data-set-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/create-data-sample.js +57 -5
- package/dist-server/controllers/create-data-sample.js.map +1 -1
- package/dist-server/service/data-set/data-item-type.d.ts +259 -0
- package/dist-server/service/data-set/data-item-type.js +259 -0
- package/dist-server/service/data-set/data-item-type.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/index.d.ts +1 -0
- package/dist-server/utils/index.js +1 -0
- package/dist-server/utils/index.js.map +1 -1
- package/dist-server/utils/media-validation.d.ts +51 -0
- package/dist-server/utils/media-validation.js +148 -0
- package/dist-server/utils/media-validation.js.map +1 -0
- package/package.json +12 -12
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
# Things-Factory Dataset Client-Side Architecture Analysis
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
The Things-Factory dataset client uses a **component-based architecture** with Lit Web Components to render data entry forms. The frontend leverages the `@operato/dataset` package which provides core components like `OxDataEntryForm`, while `@operato/input` provides specialized input components.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Input Component Architecture
|
|
9
|
+
|
|
10
|
+
### 1.1 Current Input Component Pattern
|
|
11
|
+
|
|
12
|
+
**Base Class: OxFormField**
|
|
13
|
+
- Location: `@operato/input/dist/src/ox-form-field.d.ts`
|
|
14
|
+
- Extends: `LitElement`
|
|
15
|
+
- Properties:
|
|
16
|
+
- `name?: string` - Form field name
|
|
17
|
+
- `disabled?: boolean` - Disabled state
|
|
18
|
+
- `value?: T` - Generic value type
|
|
19
|
+
- `protected _form: HTMLFormElement | null` - Parent form reference
|
|
20
|
+
- Methods:
|
|
21
|
+
- `connectedCallback()` - Register with parent form
|
|
22
|
+
- `disconnectedCallback()` - Unregister from parent form
|
|
23
|
+
- `appendFormData()` - Append to FormDataEvent
|
|
24
|
+
|
|
25
|
+
### 1.2 Existing Input Components
|
|
26
|
+
|
|
27
|
+
**Examples from @operato/input package:**
|
|
28
|
+
|
|
29
|
+
1. **OxInputFile** - File upload component
|
|
30
|
+
- Path: `@operato/input/dist/src/ox-input-file.js`
|
|
31
|
+
- Features:
|
|
32
|
+
- Drag-and-drop support
|
|
33
|
+
- Multiple file selection
|
|
34
|
+
- File list display with delete action
|
|
35
|
+
- Icon and description customization
|
|
36
|
+
- Hidden file list option
|
|
37
|
+
- Key Properties:
|
|
38
|
+
- `multiple?: boolean` - Allow multiple files
|
|
39
|
+
- `accept?: string` - MIME type filter
|
|
40
|
+
- `icon?: string` - Custom icon (default: 'upload')
|
|
41
|
+
- `label?: string` - Button label
|
|
42
|
+
- `description?: string` - Placeholder text
|
|
43
|
+
- `hideFileList: boolean` - Toggle file list display
|
|
44
|
+
- `attachFileList: boolean` - Attach list to component
|
|
45
|
+
- Implementation Details:
|
|
46
|
+
- Uses `FileDropHelper` from @operato/utils
|
|
47
|
+
- Extends OxFormField
|
|
48
|
+
- Styled with Material Design tokens
|
|
49
|
+
- Handles FormDataEvent for form submission
|
|
50
|
+
|
|
51
|
+
2. **OxInputImage** - Image upload component
|
|
52
|
+
- Path: `@operato/input/dist/src/ox-input-image.js`
|
|
53
|
+
- Similar structure to OxInputFile
|
|
54
|
+
- Single file selection (typically)
|
|
55
|
+
|
|
56
|
+
3. **Other Specialized Components:**
|
|
57
|
+
- ox-input-signature (digital signature)
|
|
58
|
+
- ox-input-color
|
|
59
|
+
- ox-input-barcode
|
|
60
|
+
- ox-input-code
|
|
61
|
+
- And 50+ more...
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 2. Data Entry Form System
|
|
66
|
+
|
|
67
|
+
### 2.1 OxDataEntryForm Component
|
|
68
|
+
|
|
69
|
+
**Location:** `@operato/dataset/dist/src/ox-data-entry-form.js`
|
|
70
|
+
|
|
71
|
+
**Purpose:** Renders a form based on a DataSet definition
|
|
72
|
+
|
|
73
|
+
**Key Methods:**
|
|
74
|
+
|
|
75
|
+
1. **render()** - Main rendering method
|
|
76
|
+
- Creates a `<form>` element with dataset name and description
|
|
77
|
+
- Calls `buildInputs()` to generate form fields
|
|
78
|
+
|
|
79
|
+
2. **buildInputs()** - Builds input elements for all active data items
|
|
80
|
+
- Filters active items: `dataItems.filter(item => item.active)`
|
|
81
|
+
- Groups items by "group" property
|
|
82
|
+
- Returns array of Lit TemplateResults
|
|
83
|
+
|
|
84
|
+
3. **buildInputsForSubgroup()** - Handles grouped data items
|
|
85
|
+
- Renders `<ox-data-entry-subgroup-form>` for groups
|
|
86
|
+
- Passes dataItems and values to subgroup component
|
|
87
|
+
- Listens to 'change' events
|
|
88
|
+
|
|
89
|
+
4. **buildInputsForNonGrouped()** - Handles non-grouped items
|
|
90
|
+
- Maps each dataItem to input element
|
|
91
|
+
- Uses `OxDataInputFactory.createInputElement()` for rendering
|
|
92
|
+
- Supports quota (multiple inputs per field)
|
|
93
|
+
- Structure:
|
|
94
|
+
```html
|
|
95
|
+
<div label>
|
|
96
|
+
<div name>${name}${unit ? ` (${unit})` : ''}</div>
|
|
97
|
+
<div description><md-icon>info</md-icon> ${description}</div>
|
|
98
|
+
<div elements>
|
|
99
|
+
<!-- Input elements -->
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
5. **buildValue()** - Extracts and returns form values
|
|
105
|
+
- Combines non-grouped values and subgroup values
|
|
106
|
+
- Returns object: `{ [tag: string]: any }`
|
|
107
|
+
|
|
108
|
+
6. **extractValuesFromInputs()** - Gets values from DOM inputs
|
|
109
|
+
- Queries inputs by name attribute: `[name=${tag}]`
|
|
110
|
+
- Handles boolean (checked) and other types (value)
|
|
111
|
+
- Returns array of values for each field
|
|
112
|
+
|
|
113
|
+
### 2.2 OxDataInputFactory - Type-to-Component Mapping
|
|
114
|
+
|
|
115
|
+
**Location:** `@operato/dataset/dist/src/ox-data-input-factory.js`
|
|
116
|
+
|
|
117
|
+
**Core Method: createInputElement()**
|
|
118
|
+
|
|
119
|
+
This is the **KEY MAPPING FUNCTION** that determines which component to render for each data type:
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
static createInputElement(type, tag, value, options = {}, idx = 0) {
|
|
123
|
+
switch (type) {
|
|
124
|
+
case 'select':
|
|
125
|
+
return this.createSelect(tag, value, options);
|
|
126
|
+
case 'radio':
|
|
127
|
+
return this.createRadio(tag, value, options, idx);
|
|
128
|
+
case 'boolean':
|
|
129
|
+
return html`<input type="checkbox" name=${tag} .checked=${value} />`;
|
|
130
|
+
case 'number':
|
|
131
|
+
return html`<input type="number" name=${tag} .value=${value} />`;
|
|
132
|
+
case 'date':
|
|
133
|
+
return html`<input type="date" name=${tag} .value=${value} />`;
|
|
134
|
+
case 'datetime':
|
|
135
|
+
return html`<input type="datetime-local" name=${tag} .value=${value} />`;
|
|
136
|
+
case 'file':
|
|
137
|
+
return html`<ox-input-file name=${tag} multiple .value=${value}></ox-input-file>`;
|
|
138
|
+
case 'signature':
|
|
139
|
+
return html`<ox-input-signature name=${tag} .value=${value}></ox-input-signature>`;
|
|
140
|
+
case 'employee':
|
|
141
|
+
return this.createEmployee(tag, value, options);
|
|
142
|
+
case 'string':
|
|
143
|
+
default:
|
|
144
|
+
return html`<input type="text" name=${tag} .value=${value || ''} />`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**IMPORTANT: Image, video, and audio types are NOT currently handled!**
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 3. DataItemType Definition
|
|
154
|
+
|
|
155
|
+
**Location:** `packages/dataset/server/service/data-set/data-item-type.ts`
|
|
156
|
+
|
|
157
|
+
**Enum Values:**
|
|
158
|
+
```typescript
|
|
159
|
+
enum DataItemType {
|
|
160
|
+
number = 'number',
|
|
161
|
+
text = 'text',
|
|
162
|
+
boolean = 'boolean',
|
|
163
|
+
select = 'select',
|
|
164
|
+
radio = 'radio',
|
|
165
|
+
date = 'date',
|
|
166
|
+
datetime = 'datetime',
|
|
167
|
+
file = 'file',
|
|
168
|
+
image = 'image', // DEFINED BUT NOT IMPLEMENTED IN FACTORY
|
|
169
|
+
video = 'video', // DEFINED BUT NOT IMPLEMENTED IN FACTORY
|
|
170
|
+
audio = 'audio', // DEFINED BUT NOT IMPLEMENTED IN FACTORY
|
|
171
|
+
signature = 'signature'
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**DataItem Object Structure:**
|
|
176
|
+
```typescript
|
|
177
|
+
class DataItem {
|
|
178
|
+
name: string // Display name
|
|
179
|
+
description?: string // Field help text
|
|
180
|
+
tag?: string // Unique identifier for data
|
|
181
|
+
group?: string // Group identifier
|
|
182
|
+
subgroup?: string // Subgroup identifier
|
|
183
|
+
active?: boolean // Active/inactive toggle
|
|
184
|
+
hidden?: boolean // Visibility toggle
|
|
185
|
+
type?: DataItemType // Field type
|
|
186
|
+
options?: { [option: string]: any } // Type-specific options
|
|
187
|
+
stat?: DataItemStatType // Statistical aggregation
|
|
188
|
+
agg?: DataItemStatType // Aggregation function
|
|
189
|
+
unit?: string // Unit of measurement
|
|
190
|
+
quota?: number // Max values allowed (default: 1)
|
|
191
|
+
spec?: { [key: string]: any } // Validation specs
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 4. Data Entry Components Architecture
|
|
198
|
+
|
|
199
|
+
### 4.1 Component Hierarchy
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
data-entry-form (LitElement)
|
|
203
|
+
├── ox-data-entry-form (@operato/dataset)
|
|
204
|
+
│ ├── ox-data-entry-subgroup-form (for grouped items)
|
|
205
|
+
│ │ └── ox-grist (data grid, for subgroups)
|
|
206
|
+
│ │ └── OxDataInputFactory.mapGristType()
|
|
207
|
+
│ └── OxDataInputFactory.createInputElement() (for non-grouped items)
|
|
208
|
+
│ ├── ox-input-file
|
|
209
|
+
│ ├── ox-input-signature
|
|
210
|
+
│ ├── ox-input-text
|
|
211
|
+
│ ├── ox-input-checkbox
|
|
212
|
+
│ └── ... (native HTML inputs)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 4.2 Client-Side Data Entry Forms
|
|
216
|
+
|
|
217
|
+
**Location:** `packages/dataset/client/components/`
|
|
218
|
+
|
|
219
|
+
#### data-entry-form.ts
|
|
220
|
+
- **Purpose:** Wrapper component for standard data entry
|
|
221
|
+
- **Key Properties:**
|
|
222
|
+
- `dataSet: DataSet & { id: string; entryType?: string }`
|
|
223
|
+
- `dataSample?: { id: string; collectedAt: Date }`
|
|
224
|
+
- **Methods:**
|
|
225
|
+
- `updateDataItems()` - GraphQL mutation to save data
|
|
226
|
+
- Uses `@query('ox-data-entry-form')` decorator to access ox-data-entry-form
|
|
227
|
+
- Calls `buildValue()` to extract form data
|
|
228
|
+
- GraphQL Context: `hasUpload: true` - enables file upload support
|
|
229
|
+
|
|
230
|
+
#### checklist-entry-form.ts
|
|
231
|
+
- **Purpose:** Wrapper for checklist-style data entry
|
|
232
|
+
- Similar structure to data-entry-form
|
|
233
|
+
- Uses `ox-checklist-entry-form` component
|
|
234
|
+
|
|
235
|
+
### 4.3 Activity Integration
|
|
236
|
+
|
|
237
|
+
**Location:** `packages/dataset/client/activities/activity-data-collect-edit.ts`
|
|
238
|
+
|
|
239
|
+
- **Purpose:** Workflow activity for data collection
|
|
240
|
+
- **Entry Types Supported:**
|
|
241
|
+
- 'generated' → OxDataEntryForm
|
|
242
|
+
- 'checklist' → OxChecklistEntryForm
|
|
243
|
+
- 'board' → (not implemented)
|
|
244
|
+
- 'page' → (not implemented)
|
|
245
|
+
- **Properties:**
|
|
246
|
+
- `input: { dataSetId: string }` - Input from workflow
|
|
247
|
+
- `output: { [tag: string]: any }` - Form values
|
|
248
|
+
- Dispatches 'change' events on form update
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 5. File Upload Handling
|
|
253
|
+
|
|
254
|
+
### 5.1 GraphQL Mutation Context
|
|
255
|
+
|
|
256
|
+
**in data-entry-form.ts (lines 93-94):**
|
|
257
|
+
```typescript
|
|
258
|
+
context: {
|
|
259
|
+
hasUpload: true // Enables multipart/form-data
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
This flag tells Apollo Client to use form-data encoding for file uploads.
|
|
264
|
+
|
|
265
|
+
### 5.2 Backend File Processing
|
|
266
|
+
|
|
267
|
+
**Location:** `packages/dataset/server/controllers/create-data-sample.ts`
|
|
268
|
+
|
|
269
|
+
**File Upload Flow (lines 109-189):**
|
|
270
|
+
|
|
271
|
+
1. **Iteration through dataItems:**
|
|
272
|
+
- Checks if type is file or media: `isFileOrMediaType(dataItem.type)`
|
|
273
|
+
- Gets the files array: `data[tag]`
|
|
274
|
+
|
|
275
|
+
2. **For Each File (if media type):**
|
|
276
|
+
- **Validation** (lines 136-152):
|
|
277
|
+
```typescript
|
|
278
|
+
if (isMediaType(dataItem.type)) {
|
|
279
|
+
const validation = validateMediaFile(
|
|
280
|
+
{
|
|
281
|
+
mimetype: file.file.mimetype,
|
|
282
|
+
size: file.file.size,
|
|
283
|
+
name: file.file.filename
|
|
284
|
+
},
|
|
285
|
+
dataItem.type,
|
|
286
|
+
dataItem.options
|
|
287
|
+
)
|
|
288
|
+
if (!validation.valid) {
|
|
289
|
+
throw new Error(`Media validation failed...`)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
3. **Attachment Creation** (lines 154-176):
|
|
295
|
+
- Creates attachment record
|
|
296
|
+
- Returns attachment with: `id, mimetype, name, fullpath`
|
|
297
|
+
|
|
298
|
+
4. **Store in Data Sample:**
|
|
299
|
+
- Replaces file objects with path info
|
|
300
|
+
- `data[tag] = pathsArray` - Array of path objects
|
|
301
|
+
|
|
302
|
+
### 5.3 Media Validation Utilities
|
|
303
|
+
|
|
304
|
+
**Location:** `packages/dataset/server/utils/media-validation.ts`
|
|
305
|
+
|
|
306
|
+
**Key Functions:**
|
|
307
|
+
|
|
308
|
+
1. **validateMediaFile()** - Main validation function
|
|
309
|
+
- Checks MIME type against allowed types
|
|
310
|
+
- Validates file extension (if accept option specified)
|
|
311
|
+
- Validates file size (if maxSize option specified)
|
|
312
|
+
- Returns: `{ valid: boolean, errors: string[] }`
|
|
313
|
+
|
|
314
|
+
2. **MIME Type Mappings:**
|
|
315
|
+
```typescript
|
|
316
|
+
const MEDIA_MIME_TYPES = {
|
|
317
|
+
image: ['image/jpeg', 'image/png', 'image/gif', ...],
|
|
318
|
+
video: ['video/mp4', 'video/webm', ...],
|
|
319
|
+
audio: ['audio/mpeg', 'audio/wav', ...]
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
3. **Helper Functions:**
|
|
324
|
+
- `isValidMimeType()` - Check MIME type validity
|
|
325
|
+
- `isAcceptedFormat()` - Check file extension
|
|
326
|
+
- `isValidFileSize()` - Check file size constraint
|
|
327
|
+
- `isMediaType()` - Check if type is media
|
|
328
|
+
- `isFileOrMediaType()` - Check if file or media type
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## 6. Component Registration & Bootstrap
|
|
333
|
+
|
|
334
|
+
### 6.1 Bootstrap File
|
|
335
|
+
|
|
336
|
+
**Location:** `packages/dataset/client/bootstrap.ts`
|
|
337
|
+
|
|
338
|
+
**Registers:**
|
|
339
|
+
- Custom Grist editors and renderers
|
|
340
|
+
- Data item spec editor
|
|
341
|
+
- Signature editor/renderer
|
|
342
|
+
- Partition keys editor/renderer
|
|
343
|
+
|
|
344
|
+
**Note:** OxDataEntryForm is auto-registered through imports in wrapper components.
|
|
345
|
+
|
|
346
|
+
### 6.2 Component Import Pattern
|
|
347
|
+
|
|
348
|
+
Components are registered via ES6 imports:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import '@operato/dataset/ox-data-entry-form.js'
|
|
352
|
+
import '@operato/dataset/ox-checklist-entry-form.js'
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
When imported, the `@customElement('ox-data-entry-form')` decorator registers them globally.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 7. Subgroup Form System
|
|
360
|
+
|
|
361
|
+
### 7.1 OxDataEntrySubgroupForm
|
|
362
|
+
|
|
363
|
+
**Location:** `@operato/dataset/dist/src/ox-data-entry-subgroup-form.js`
|
|
364
|
+
|
|
365
|
+
**Purpose:** Render grouped data items in a Grist data grid (table)
|
|
366
|
+
|
|
367
|
+
**Key Methods:**
|
|
368
|
+
|
|
369
|
+
1. **buildGristConfiguration()** - Creates grid config
|
|
370
|
+
- Maps DataItemType to Grist column types using `OxDataInputFactory.mapGristType()`
|
|
371
|
+
- Returns columns with editing properties
|
|
372
|
+
- Uses `OxDataInputFactory.getGristRecordOptions()` for column-specific options
|
|
373
|
+
|
|
374
|
+
2. **mapGristType()** - Maps form types to Grist types
|
|
375
|
+
```javascript
|
|
376
|
+
case 'radio': return 'select';
|
|
377
|
+
case 'select': return 'select';
|
|
378
|
+
case 'boolean': return 'boolean';
|
|
379
|
+
case 'number': return 'number';
|
|
380
|
+
case 'date': return 'date';
|
|
381
|
+
case 'datetime': return 'datetime';
|
|
382
|
+
case 'file': return 'file';
|
|
383
|
+
case 'signature': return 'signature';
|
|
384
|
+
case 'string': return 'text';
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
3. **getGristRecordOptions()** - Column configuration
|
|
388
|
+
```javascript
|
|
389
|
+
case 'select': return { options: [...] };
|
|
390
|
+
case 'boolean': return { align: 'center' };
|
|
391
|
+
case 'number': return { align: 'right' };
|
|
392
|
+
case 'file': return { multiple: true };
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 8. File/Media Options Schema
|
|
398
|
+
|
|
399
|
+
**Defined in:** `data-item-type.ts` (lines 174-429)
|
|
400
|
+
|
|
401
|
+
### 8.1 Image Type Options Example
|
|
402
|
+
```typescript
|
|
403
|
+
{
|
|
404
|
+
type: 'image',
|
|
405
|
+
options: {
|
|
406
|
+
accept: ['jpeg', 'png', 'webp'], // Allowed formats
|
|
407
|
+
maxSize: 10485760, // 10MB
|
|
408
|
+
maxWidth: 4096, // pixels
|
|
409
|
+
maxHeight: 4096, // pixels
|
|
410
|
+
minWidth: 224, // pixels
|
|
411
|
+
minHeight: 224, // pixels
|
|
412
|
+
multiple: true, // Allow multiple
|
|
413
|
+
maxFiles: 10, // Max number
|
|
414
|
+
thumbnail: {
|
|
415
|
+
enabled: true,
|
|
416
|
+
width: 200,
|
|
417
|
+
height: 200,
|
|
418
|
+
format: 'jpeg',
|
|
419
|
+
quality: 85
|
|
420
|
+
},
|
|
421
|
+
preview: {
|
|
422
|
+
enabled: true,
|
|
423
|
+
maxWidth: 1024,
|
|
424
|
+
maxHeight: 1024
|
|
425
|
+
},
|
|
426
|
+
metadata: {
|
|
427
|
+
extractExif: true,
|
|
428
|
+
extractDimensions: true,
|
|
429
|
+
extractFormat: true,
|
|
430
|
+
extractColorProfile: true
|
|
431
|
+
},
|
|
432
|
+
compression: {
|
|
433
|
+
enabled: true,
|
|
434
|
+
format: 'webp',
|
|
435
|
+
quality: 85,
|
|
436
|
+
maxDimension: 2048
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### 8.2 Video Type Options Example
|
|
443
|
+
```typescript
|
|
444
|
+
{
|
|
445
|
+
type: 'video',
|
|
446
|
+
options: {
|
|
447
|
+
accept: ['mp4', 'webm'],
|
|
448
|
+
maxSize: 104857600, // 100MB
|
|
449
|
+
maxDuration: 300, // 5 minutes
|
|
450
|
+
maxWidth: 1920,
|
|
451
|
+
maxHeight: 1080,
|
|
452
|
+
multiple: false,
|
|
453
|
+
maxFiles: 1,
|
|
454
|
+
thumbnail: {
|
|
455
|
+
enabled: true,
|
|
456
|
+
width: 320,
|
|
457
|
+
height: 180,
|
|
458
|
+
format: 'jpeg',
|
|
459
|
+
quality: 85,
|
|
460
|
+
captureTime: 1 // At 1 second
|
|
461
|
+
},
|
|
462
|
+
preview: {
|
|
463
|
+
enabled: true,
|
|
464
|
+
maxWidth: 640,
|
|
465
|
+
maxHeight: 480,
|
|
466
|
+
controls: true,
|
|
467
|
+
autoplay: false,
|
|
468
|
+
muted: true
|
|
469
|
+
},
|
|
470
|
+
metadata: {
|
|
471
|
+
extractDuration: true,
|
|
472
|
+
extractResolution: true,
|
|
473
|
+
extractFormat: true,
|
|
474
|
+
extractCodec: true,
|
|
475
|
+
extractFrameRate: true,
|
|
476
|
+
extractBitrate: true
|
|
477
|
+
},
|
|
478
|
+
transcoding: {
|
|
479
|
+
enabled: true,
|
|
480
|
+
format: 'mp4',
|
|
481
|
+
codec: 'h264',
|
|
482
|
+
quality: 'medium',
|
|
483
|
+
maxDimension: 1280
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### 8.3 Audio Type Options Example
|
|
490
|
+
```typescript
|
|
491
|
+
{
|
|
492
|
+
type: 'audio',
|
|
493
|
+
options: {
|
|
494
|
+
accept: ['mp3', 'wav', 'ogg'],
|
|
495
|
+
maxSize: 52428800, // 50MB
|
|
496
|
+
maxDuration: 600, // 10 minutes
|
|
497
|
+
multiple: false,
|
|
498
|
+
maxFiles: 1,
|
|
499
|
+
waveform: {
|
|
500
|
+
enabled: true,
|
|
501
|
+
width: 800,
|
|
502
|
+
height: 100,
|
|
503
|
+
color: '#3498db'
|
|
504
|
+
},
|
|
505
|
+
preview: {
|
|
506
|
+
enabled: true,
|
|
507
|
+
controls: true,
|
|
508
|
+
autoplay: false,
|
|
509
|
+
visualizer: true
|
|
510
|
+
},
|
|
511
|
+
metadata: {
|
|
512
|
+
extractDuration: true,
|
|
513
|
+
extractFormat: true,
|
|
514
|
+
extractCodec: true,
|
|
515
|
+
extractBitrate: true,
|
|
516
|
+
extractSampleRate: true,
|
|
517
|
+
extractChannels: true,
|
|
518
|
+
extractID3Tags: true
|
|
519
|
+
},
|
|
520
|
+
transcoding: {
|
|
521
|
+
enabled: true,
|
|
522
|
+
format: 'mp3',
|
|
523
|
+
codec: 'mp3',
|
|
524
|
+
bitrate: 128,
|
|
525
|
+
sampleRate: 44100,
|
|
526
|
+
channels: 2
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## 9. Implementation Guide: Adding Media Components
|
|
535
|
+
|
|
536
|
+
### 9.1 Where to Add New Components
|
|
537
|
+
|
|
538
|
+
**For OxInputImage, OxInputVideo, OxInputAudio:**
|
|
539
|
+
- These should be added to **@operato/input** package (not dataset)
|
|
540
|
+
- But you can create them in **Things-Factory dataset** package first as extensions
|
|
541
|
+
|
|
542
|
+
**Suggested Location:**
|
|
543
|
+
```
|
|
544
|
+
packages/dataset/client/
|
|
545
|
+
└── components/
|
|
546
|
+
├── ox-input-image.ts (NEW)
|
|
547
|
+
├── ox-input-video.ts (NEW)
|
|
548
|
+
├── ox-input-audio.ts (NEW)
|
|
549
|
+
├── data-entry-form.ts (existing)
|
|
550
|
+
└── checklist-entry-form.ts (existing)
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### 9.2 Component Pattern (Example: OxInputImage)
|
|
554
|
+
|
|
555
|
+
Based on OxInputFile implementation:
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
import { css, html } from 'lit'
|
|
559
|
+
import { customElement, property, query } from 'lit/decorators.js'
|
|
560
|
+
import { OxFormField } from '@operato/input'
|
|
561
|
+
|
|
562
|
+
@customElement('ox-input-image')
|
|
563
|
+
export class OxInputImage extends OxFormField {
|
|
564
|
+
static styles = [
|
|
565
|
+
// CSS styles
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
@property({ type: Boolean }) multiple? = false
|
|
569
|
+
@property({ type: String }) accept? = 'image/*'
|
|
570
|
+
@property({ type: String }) icon? = 'image'
|
|
571
|
+
@property({ type: String }) label? = 'Select images'
|
|
572
|
+
@property({ type: String }) description? = 'drop images here!'
|
|
573
|
+
|
|
574
|
+
@query('#input-file') fileInput?: HTMLInputElement
|
|
575
|
+
|
|
576
|
+
render() {
|
|
577
|
+
// Similar to OxInputFile but with image-specific styling
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Implement required methods: updated, firstUpdated, _onChangeValue, _notifyChange
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### 9.3 Update OxDataInputFactory
|
|
585
|
+
|
|
586
|
+
**File:** `@operato/dataset` source (need to find source repo)
|
|
587
|
+
|
|
588
|
+
**Add to createInputElement() switch:**
|
|
589
|
+
```typescript
|
|
590
|
+
case 'image':
|
|
591
|
+
return html`<ox-input-image name=${tag} .value=${value} .options=${options}></ox-input-image>`;
|
|
592
|
+
case 'video':
|
|
593
|
+
return html`<ox-input-video name=${tag} .value=${value} .options=${options}></ox-input-video>`;
|
|
594
|
+
case 'audio':
|
|
595
|
+
return html`<ox-input-audio name=${tag} .value=${value} .options=${options}></ox-input-audio>`;
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Add to mapGristType():**
|
|
599
|
+
```typescript
|
|
600
|
+
case 'image': return 'image';
|
|
601
|
+
case 'video': return 'video';
|
|
602
|
+
case 'audio': return 'audio';
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Add to getGristRecordOptions():**
|
|
606
|
+
```typescript
|
|
607
|
+
case 'image':
|
|
608
|
+
case 'video':
|
|
609
|
+
case 'audio':
|
|
610
|
+
return { multiple: options.multiple || false };
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### 9.4 Update Component Imports
|
|
614
|
+
|
|
615
|
+
**In wrapper components that import from @operato/dataset:**
|
|
616
|
+
```typescript
|
|
617
|
+
import '@operato/dataset/ox-input-image.js'
|
|
618
|
+
import '@operato/dataset/ox-input-video.js'
|
|
619
|
+
import '@operato/dataset/ox-input-audio.js'
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Or add to ox-data-entry-form.js imports:
|
|
623
|
+
```typescript
|
|
624
|
+
import './ox-input-image.js'
|
|
625
|
+
import './ox-input-video.js'
|
|
626
|
+
import './ox-input-audio.js'
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## 10. Current Usage Flow
|
|
632
|
+
|
|
633
|
+
### 10.1 Complete Data Collection Flow
|
|
634
|
+
|
|
635
|
+
1. **Workflow Activity Triggered**
|
|
636
|
+
- `activity-data-collect-edit` receives dataSetId
|
|
637
|
+
|
|
638
|
+
2. **DataSet Fetched**
|
|
639
|
+
- GraphQL query fetches DataSet with dataItems
|
|
640
|
+
|
|
641
|
+
3. **Form Rendered**
|
|
642
|
+
- `OxDataEntryForm` renders based on DataSet
|
|
643
|
+
- `OxDataInputFactory.createInputElement()` creates inputs
|
|
644
|
+
- For subgroups: `OxDataEntrySubgroupForm` renders Grist table
|
|
645
|
+
|
|
646
|
+
4. **User Enters Data**
|
|
647
|
+
- Files selected via native file inputs or ox-input-file
|
|
648
|
+
- Form tracks changes via @change events
|
|
649
|
+
|
|
650
|
+
5. **Data Submitted**
|
|
651
|
+
- `buildValue()` extracts form values
|
|
652
|
+
- GraphQL mutation sends to server with `hasUpload: true`
|
|
653
|
+
|
|
654
|
+
6. **Server Processing**
|
|
655
|
+
- `create-data-sample` controller processes files
|
|
656
|
+
- Media validation occurs for image/video/audio
|
|
657
|
+
- Attachments created and linked to DataSample
|
|
658
|
+
- File paths stored in data
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## 11. Key Integration Points for Media Components
|
|
663
|
+
|
|
664
|
+
### 11.1 Client-Side
|
|
665
|
+
1. **OxDataInputFactory** - Add case statements for 'image', 'video', 'audio'
|
|
666
|
+
2. **OxInputImage, OxInputVideo, OxInputAudio** - Create new components
|
|
667
|
+
3. **Package.json** - Ensure proper dependencies
|
|
668
|
+
|
|
669
|
+
### 11.2 Server-Side (Already Implemented)
|
|
670
|
+
1. ✓ DataItemType enum includes image, video, audio
|
|
671
|
+
2. ✓ Media validation utilities ready
|
|
672
|
+
3. ✓ File/media options schema documented
|
|
673
|
+
4. ✓ create-data-sample handles media validation and attachment
|
|
674
|
+
|
|
675
|
+
### 11.3 Bootstrap
|
|
676
|
+
- Import new components in bootstrap.ts or individual wrapper components
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## 12. Directory Structure Summary
|
|
681
|
+
|
|
682
|
+
```
|
|
683
|
+
things-factory/packages/
|
|
684
|
+
├── dataset/
|
|
685
|
+
│ ├── client/
|
|
686
|
+
│ │ ├── components/
|
|
687
|
+
│ │ │ ├── data-entry-form.ts (Form wrapper)
|
|
688
|
+
│ │ │ ├── checklist-entry-form.ts (Checklist wrapper)
|
|
689
|
+
│ │ │ ├── ox-input-image.ts (NEW - to create)
|
|
690
|
+
│ │ │ ├── ox-input-video.ts (NEW - to create)
|
|
691
|
+
│ │ │ └── ox-input-audio.ts (NEW - to create)
|
|
692
|
+
│ │ ├── activities/
|
|
693
|
+
│ │ │ └── activity-data-collect-edit.ts (Entry point)
|
|
694
|
+
│ │ ├── bootstrap.ts (Component registration)
|
|
695
|
+
│ │ └── index.ts
|
|
696
|
+
│ │
|
|
697
|
+
│ └── server/
|
|
698
|
+
│ ├── service/data-set/
|
|
699
|
+
│ │ └── data-item-type.ts (DataItemType enum & schema)
|
|
700
|
+
│ ├── utils/
|
|
701
|
+
│ │ └── media-validation.ts (Media validation utils)
|
|
702
|
+
│ └── controllers/
|
|
703
|
+
│ └── create-data-sample.ts (File upload handling)
|
|
704
|
+
│
|
|
705
|
+
└── operato-dataset/ (Node modules dependency)
|
|
706
|
+
└── node_modules/@operato/dataset/
|
|
707
|
+
└── dist/src/
|
|
708
|
+
├── ox-data-entry-form.js (Main form component)
|
|
709
|
+
├── ox-data-entry-subgroup-form.js (Grouped items)
|
|
710
|
+
└── ox-data-input-factory.js (Type mapping factory)
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## Summary
|
|
716
|
+
|
|
717
|
+
**Current State:**
|
|
718
|
+
- File type has OxInputFile component for general file upload
|
|
719
|
+
- Signature type has OxInputSignature
|
|
720
|
+
- Image, video, audio types are DEFINED but NOT RENDERED
|
|
721
|
+
|
|
722
|
+
**What Needs Implementation:**
|
|
723
|
+
1. Create OxInputImage, OxInputVideo, OxInputAudio components
|
|
724
|
+
2. Update OxDataInputFactory to handle these types
|
|
725
|
+
3. Import components in appropriate locations
|
|
726
|
+
4. Backend is already ready for media handling
|
|
727
|
+
|
|
728
|
+
**Key Architecture Pattern:**
|
|
729
|
+
- All components extend OxFormField
|
|
730
|
+
- Input factory uses switch statement for type routing
|
|
731
|
+
- Form values extracted via name attribute selectors
|
|
732
|
+
- File upload via GraphQL multipart/form-data (hasUpload: true context)
|