@things-factory/dataset 9.1.17 → 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.
@@ -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)