# Things-Factory Dataset Client-Side Architecture Analysis

## Overview
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.

---

## 1. Input Component Architecture

### 1.1 Current Input Component Pattern

**Base Class: OxFormField**
- Location: `@operato/input/dist/src/ox-form-field.d.ts`
- Extends: `LitElement`
- Properties:
  - `name?: string` - Form field name
  - `disabled?: boolean` - Disabled state
  - `value?: T` - Generic value type
  - `protected _form: HTMLFormElement | null` - Parent form reference
- Methods:
  - `connectedCallback()` - Register with parent form
  - `disconnectedCallback()` - Unregister from parent form
  - `appendFormData()` - Append to FormDataEvent

### 1.2 Existing Input Components

**Examples from @operato/input package:**

1. **OxInputFile** - File upload component
   - Path: `@operato/input/dist/src/ox-input-file.js`
   - Features:
     - Drag-and-drop support
     - Multiple file selection
     - File list display with delete action
     - Icon and description customization
     - Hidden file list option
   - Key Properties:
     - `multiple?: boolean` - Allow multiple files
     - `accept?: string` - MIME type filter
     - `icon?: string` - Custom icon (default: 'upload')
     - `label?: string` - Button label
     - `description?: string` - Placeholder text
     - `hideFileList: boolean` - Toggle file list display
     - `attachFileList: boolean` - Attach list to component
   - Implementation Details:
     - Uses `FileDropHelper` from @operato/utils
     - Extends OxFormField
     - Styled with Material Design tokens
     - Handles FormDataEvent for form submission

2. **OxInputImage** - Image upload component
   - Path: `@operato/input/dist/src/ox-input-image.js`
   - Similar structure to OxInputFile
   - Single file selection (typically)

3. **Other Specialized Components:**
   - ox-input-signature (digital signature)
   - ox-input-color
   - ox-input-barcode
   - ox-input-code
   - And 50+ more...

---

## 2. Data Entry Form System

### 2.1 OxDataEntryForm Component

**Location:** `@operato/dataset/dist/src/ox-data-entry-form.js`

**Purpose:** Renders a form based on a DataSet definition

**Key Methods:**

1. **render()** - Main rendering method
   - Creates a `<form>` element with dataset name and description
   - Calls `buildInputs()` to generate form fields

2. **buildInputs()** - Builds input elements for all active data items
   - Filters active items: `dataItems.filter(item => item.active)`
   - Groups items by "group" property
   - Returns array of Lit TemplateResults

3. **buildInputsForSubgroup()** - Handles grouped data items
   - Renders `<ox-data-entry-subgroup-form>` for groups
   - Passes dataItems and values to subgroup component
   - Listens to 'change' events

4. **buildInputsForNonGrouped()** - Handles non-grouped items
   - Maps each dataItem to input element
   - Uses `OxDataInputFactory.createInputElement()` for rendering
   - Supports quota (multiple inputs per field)
   - Structure:
     ```html
     <div label>
       <div name>${name}${unit ? ` (${unit})` : ''}</div>
       <div description><md-icon>info</md-icon> ${description}</div>
       <div elements>
         <!-- Input elements -->
       </div>
     </div>
     ```

5. **buildValue()** - Extracts and returns form values
   - Combines non-grouped values and subgroup values
   - Returns object: `{ [tag: string]: any }`

6. **extractValuesFromInputs()** - Gets values from DOM inputs
   - Queries inputs by name attribute: `[name=${tag}]`
   - Handles boolean (checked) and other types (value)
   - Returns array of values for each field

### 2.2 OxDataInputFactory - Type-to-Component Mapping

**Location:** `@operato/dataset/dist/src/ox-data-input-factory.js`

**Core Method: createInputElement()**

This is the **KEY MAPPING FUNCTION** that determines which component to render for each data type:

```javascript
static createInputElement(type, tag, value, options = {}, idx = 0) {
  switch (type) {
    case 'select':
      return this.createSelect(tag, value, options);
    case 'radio':
      return this.createRadio(tag, value, options, idx);
    case 'boolean':
      return html`<input type="checkbox" name=${tag} .checked=${value} />`;
    case 'number':
      return html`<input type="number" name=${tag} .value=${value} />`;
    case 'date':
      return html`<input type="date" name=${tag} .value=${value} />`;
    case 'datetime':
      return html`<input type="datetime-local" name=${tag} .value=${value} />`;
    case 'file':
      return html`<ox-input-file name=${tag} multiple .value=${value}></ox-input-file>`;
    case 'signature':
      return html`<ox-input-signature name=${tag} .value=${value}></ox-input-signature>`;
    case 'employee':
      return this.createEmployee(tag, value, options);
    case 'string':
    default:
      return html`<input type="text" name=${tag} .value=${value || ''} />`;
  }
}
```

**IMPORTANT: Image, video, and audio types are NOT currently handled!**

---

## 3. DataItemType Definition

**Location:** `packages/dataset/server/service/data-set/data-item-type.ts`

**Enum Values:**
```typescript
enum DataItemType {
  number = 'number',
  text = 'text',
  boolean = 'boolean',
  select = 'select',
  radio = 'radio',
  date = 'date',
  datetime = 'datetime',
  file = 'file',
  image = 'image',      // DEFINED BUT NOT IMPLEMENTED IN FACTORY
  video = 'video',      // DEFINED BUT NOT IMPLEMENTED IN FACTORY
  audio = 'audio',      // DEFINED BUT NOT IMPLEMENTED IN FACTORY
  signature = 'signature'
}
```

**DataItem Object Structure:**
```typescript
class DataItem {
  name: string                      // Display name
  description?: string              // Field help text
  tag?: string                      // Unique identifier for data
  group?: string                    // Group identifier
  subgroup?: string                 // Subgroup identifier
  active?: boolean                  // Active/inactive toggle
  hidden?: boolean                  // Visibility toggle
  type?: DataItemType               // Field type
  options?: { [option: string]: any } // Type-specific options
  stat?: DataItemStatType           // Statistical aggregation
  agg?: DataItemStatType            // Aggregation function
  unit?: string                     // Unit of measurement
  quota?: number                    // Max values allowed (default: 1)
  spec?: { [key: string]: any }     // Validation specs
}
```

---

## 4. Data Entry Components Architecture

### 4.1 Component Hierarchy

```
data-entry-form (LitElement)
├── ox-data-entry-form (@operato/dataset)
│   ├── ox-data-entry-subgroup-form (for grouped items)
│   │   └── ox-grist (data grid, for subgroups)
│   │       └── OxDataInputFactory.mapGristType()
│   └── OxDataInputFactory.createInputElement() (for non-grouped items)
│       ├── ox-input-file
│       ├── ox-input-signature
│       ├── ox-input-text
│       ├── ox-input-checkbox
│       └── ... (native HTML inputs)
```

### 4.2 Client-Side Data Entry Forms

**Location:** `packages/dataset/client/components/`

#### data-entry-form.ts
- **Purpose:** Wrapper component for standard data entry
- **Key Properties:**
  - `dataSet: DataSet & { id: string; entryType?: string }`
  - `dataSample?: { id: string; collectedAt: Date }`
- **Methods:**
  - `updateDataItems()` - GraphQL mutation to save data
  - Uses `@query('ox-data-entry-form')` decorator to access ox-data-entry-form
  - Calls `buildValue()` to extract form data
  - GraphQL Context: `hasUpload: true` - enables file upload support

#### checklist-entry-form.ts
- **Purpose:** Wrapper for checklist-style data entry
- Similar structure to data-entry-form
- Uses `ox-checklist-entry-form` component

### 4.3 Activity Integration

**Location:** `packages/dataset/client/activities/activity-data-collect-edit.ts`

- **Purpose:** Workflow activity for data collection
- **Entry Types Supported:**
  - 'generated' → OxDataEntryForm
  - 'checklist' → OxChecklistEntryForm
  - 'board' → (not implemented)
  - 'page' → (not implemented)
- **Properties:**
  - `input: { dataSetId: string }` - Input from workflow
  - `output: { [tag: string]: any }` - Form values
  - Dispatches 'change' events on form update

---

## 5. File Upload Handling

### 5.1 GraphQL Mutation Context

**in data-entry-form.ts (lines 93-94):**
```typescript
context: {
  hasUpload: true  // Enables multipart/form-data
}
```

This flag tells Apollo Client to use form-data encoding for file uploads.

### 5.2 Backend File Processing

**Location:** `packages/dataset/server/controllers/create-data-sample.ts`

**File Upload Flow (lines 109-189):**

1. **Iteration through dataItems:**
   - Checks if type is file or media: `isFileOrMediaType(dataItem.type)`
   - Gets the files array: `data[tag]`

2. **For Each File (if media type):**
   - **Validation** (lines 136-152):
     ```typescript
     if (isMediaType(dataItem.type)) {
       const validation = validateMediaFile(
         {
           mimetype: file.file.mimetype,
           size: file.file.size,
           name: file.file.filename
         },
         dataItem.type,
         dataItem.options
       )
       if (!validation.valid) {
         throw new Error(`Media validation failed...`)
       }
     }
     ```

3. **Attachment Creation** (lines 154-176):
   - Creates attachment record
   - Returns attachment with: `id, mimetype, name, fullpath`

4. **Store in Data Sample:**
   - Replaces file objects with path info
   - `data[tag] = pathsArray` - Array of path objects

### 5.3 Media Validation Utilities

**Location:** `packages/dataset/server/utils/media-validation.ts`

**Key Functions:**

1. **validateMediaFile()** - Main validation function
   - Checks MIME type against allowed types
   - Validates file extension (if accept option specified)
   - Validates file size (if maxSize option specified)
   - Returns: `{ valid: boolean, errors: string[] }`

2. **MIME Type Mappings:**
   ```typescript
   const MEDIA_MIME_TYPES = {
     image: ['image/jpeg', 'image/png', 'image/gif', ...],
     video: ['video/mp4', 'video/webm', ...],
     audio: ['audio/mpeg', 'audio/wav', ...]
   }
   ```

3. **Helper Functions:**
   - `isValidMimeType()` - Check MIME type validity
   - `isAcceptedFormat()` - Check file extension
   - `isValidFileSize()` - Check file size constraint
   - `isMediaType()` - Check if type is media
   - `isFileOrMediaType()` - Check if file or media type

---

## 6. Component Registration & Bootstrap

### 6.1 Bootstrap File

**Location:** `packages/dataset/client/bootstrap.ts`

**Registers:**
- Custom Grist editors and renderers
- Data item spec editor
- Signature editor/renderer
- Partition keys editor/renderer

**Note:** OxDataEntryForm is auto-registered through imports in wrapper components.

### 6.2 Component Import Pattern

Components are registered via ES6 imports:

```typescript
import '@operato/dataset/ox-data-entry-form.js'
import '@operato/dataset/ox-checklist-entry-form.js'
```

When imported, the `@customElement('ox-data-entry-form')` decorator registers them globally.

---

## 7. Subgroup Form System

### 7.1 OxDataEntrySubgroupForm

**Location:** `@operato/dataset/dist/src/ox-data-entry-subgroup-form.js`

**Purpose:** Render grouped data items in a Grist data grid (table)

**Key Methods:**

1. **buildGristConfiguration()** - Creates grid config
   - Maps DataItemType to Grist column types using `OxDataInputFactory.mapGristType()`
   - Returns columns with editing properties
   - Uses `OxDataInputFactory.getGristRecordOptions()` for column-specific options

2. **mapGristType()** - Maps form types to Grist types
   ```javascript
   case 'radio': return 'select';
   case 'select': return 'select';
   case 'boolean': return 'boolean';
   case 'number': return 'number';
   case 'date': return 'date';
   case 'datetime': return 'datetime';
   case 'file': return 'file';
   case 'signature': return 'signature';
   case 'string': return 'text';
   ```

3. **getGristRecordOptions()** - Column configuration
   ```javascript
   case 'select': return { options: [...] };
   case 'boolean': return { align: 'center' };
   case 'number': return { align: 'right' };
   case 'file': return { multiple: true };
   ```

---

## 8. File/Media Options Schema

**Defined in:** `data-item-type.ts` (lines 174-429)

### 8.1 Image Type Options Example
```typescript
{
  type: 'image',
  options: {
    accept: ['jpeg', 'png', 'webp'],       // Allowed formats
    maxSize: 10485760,                     // 10MB
    maxWidth: 4096,                        // pixels
    maxHeight: 4096,                       // pixels
    minWidth: 224,                         // pixels
    minHeight: 224,                        // pixels
    multiple: true,                        // Allow multiple
    maxFiles: 10,                          // Max number
    thumbnail: {
      enabled: true,
      width: 200,
      height: 200,
      format: 'jpeg',
      quality: 85
    },
    preview: {
      enabled: true,
      maxWidth: 1024,
      maxHeight: 1024
    },
    metadata: {
      extractExif: true,
      extractDimensions: true,
      extractFormat: true,
      extractColorProfile: true
    },
    compression: {
      enabled: true,
      format: 'webp',
      quality: 85,
      maxDimension: 2048
    }
  }
}
```

### 8.2 Video Type Options Example
```typescript
{
  type: 'video',
  options: {
    accept: ['mp4', 'webm'],
    maxSize: 104857600,                    // 100MB
    maxDuration: 300,                      // 5 minutes
    maxWidth: 1920,
    maxHeight: 1080,
    multiple: false,
    maxFiles: 1,
    thumbnail: {
      enabled: true,
      width: 320,
      height: 180,
      format: 'jpeg',
      quality: 85,
      captureTime: 1                       // At 1 second
    },
    preview: {
      enabled: true,
      maxWidth: 640,
      maxHeight: 480,
      controls: true,
      autoplay: false,
      muted: true
    },
    metadata: {
      extractDuration: true,
      extractResolution: true,
      extractFormat: true,
      extractCodec: true,
      extractFrameRate: true,
      extractBitrate: true
    },
    transcoding: {
      enabled: true,
      format: 'mp4',
      codec: 'h264',
      quality: 'medium',
      maxDimension: 1280
    }
  }
}
```

### 8.3 Audio Type Options Example
```typescript
{
  type: 'audio',
  options: {
    accept: ['mp3', 'wav', 'ogg'],
    maxSize: 52428800,                     // 50MB
    maxDuration: 600,                      // 10 minutes
    multiple: false,
    maxFiles: 1,
    waveform: {
      enabled: true,
      width: 800,
      height: 100,
      color: '#3498db'
    },
    preview: {
      enabled: true,
      controls: true,
      autoplay: false,
      visualizer: true
    },
    metadata: {
      extractDuration: true,
      extractFormat: true,
      extractCodec: true,
      extractBitrate: true,
      extractSampleRate: true,
      extractChannels: true,
      extractID3Tags: true
    },
    transcoding: {
      enabled: true,
      format: 'mp3',
      codec: 'mp3',
      bitrate: 128,
      sampleRate: 44100,
      channels: 2
    }
  }
}
```

---

## 9. Implementation Guide: Adding Media Components

### 9.1 Where to Add New Components

**For OxInputImage, OxInputVideo, OxInputAudio:**
- These should be added to **@operato/input** package (not dataset)
- But you can create them in **Things-Factory dataset** package first as extensions

**Suggested Location:**
```
packages/dataset/client/
└── components/
    ├── ox-input-image.ts      (NEW)
    ├── ox-input-video.ts      (NEW)
    ├── ox-input-audio.ts      (NEW)
    ├── data-entry-form.ts     (existing)
    └── checklist-entry-form.ts (existing)
```

### 9.2 Component Pattern (Example: OxInputImage)

Based on OxInputFile implementation:

```typescript
import { css, html } from 'lit'
import { customElement, property, query } from 'lit/decorators.js'
import { OxFormField } from '@operato/input'

@customElement('ox-input-image')
export class OxInputImage extends OxFormField {
  static styles = [
    // CSS styles
  ]

  @property({ type: Boolean }) multiple? = false
  @property({ type: String }) accept? = 'image/*'
  @property({ type: String }) icon? = 'image'
  @property({ type: String }) label? = 'Select images'
  @property({ type: String }) description? = 'drop images here!'

  @query('#input-file') fileInput?: HTMLInputElement

  render() {
    // Similar to OxInputFile but with image-specific styling
  }

  // Implement required methods: updated, firstUpdated, _onChangeValue, _notifyChange
}
```

### 9.3 Update OxDataInputFactory

**File:** `@operato/dataset` source (need to find source repo)

**Add to createInputElement() switch:**
```typescript
case 'image':
  return html`<ox-input-image name=${tag} .value=${value} .options=${options}></ox-input-image>`;
case 'video':
  return html`<ox-input-video name=${tag} .value=${value} .options=${options}></ox-input-video>`;
case 'audio':
  return html`<ox-input-audio name=${tag} .value=${value} .options=${options}></ox-input-audio>`;
```

**Add to mapGristType():**
```typescript
case 'image': return 'image';
case 'video': return 'video';
case 'audio': return 'audio';
```

**Add to getGristRecordOptions():**
```typescript
case 'image':
case 'video':
case 'audio':
  return { multiple: options.multiple || false };
```

### 9.4 Update Component Imports

**In wrapper components that import from @operato/dataset:**
```typescript
import '@operato/dataset/ox-input-image.js'
import '@operato/dataset/ox-input-video.js'
import '@operato/dataset/ox-input-audio.js'
```

Or add to ox-data-entry-form.js imports:
```typescript
import './ox-input-image.js'
import './ox-input-video.js'
import './ox-input-audio.js'
```

---

## 10. Current Usage Flow

### 10.1 Complete Data Collection Flow

1. **Workflow Activity Triggered**
   - `activity-data-collect-edit` receives dataSetId

2. **DataSet Fetched**
   - GraphQL query fetches DataSet with dataItems

3. **Form Rendered**
   - `OxDataEntryForm` renders based on DataSet
   - `OxDataInputFactory.createInputElement()` creates inputs
   - For subgroups: `OxDataEntrySubgroupForm` renders Grist table

4. **User Enters Data**
   - Files selected via native file inputs or ox-input-file
   - Form tracks changes via @change events

5. **Data Submitted**
   - `buildValue()` extracts form values
   - GraphQL mutation sends to server with `hasUpload: true`

6. **Server Processing**
   - `create-data-sample` controller processes files
   - Media validation occurs for image/video/audio
   - Attachments created and linked to DataSample
   - File paths stored in data

---

## 11. Key Integration Points for Media Components

### 11.1 Client-Side
1. **OxDataInputFactory** - Add case statements for 'image', 'video', 'audio'
2. **OxInputImage, OxInputVideo, OxInputAudio** - Create new components
3. **Package.json** - Ensure proper dependencies

### 11.2 Server-Side (Already Implemented)
1. ✓ DataItemType enum includes image, video, audio
2. ✓ Media validation utilities ready
3. ✓ File/media options schema documented
4. ✓ create-data-sample handles media validation and attachment

### 11.3 Bootstrap
- Import new components in bootstrap.ts or individual wrapper components

---

## 12. Directory Structure Summary

```
things-factory/packages/
├── dataset/
│   ├── client/
│   │   ├── components/
│   │   │   ├── data-entry-form.ts          (Form wrapper)
│   │   │   ├── checklist-entry-form.ts     (Checklist wrapper)
│   │   │   ├── ox-input-image.ts           (NEW - to create)
│   │   │   ├── ox-input-video.ts           (NEW - to create)
│   │   │   └── ox-input-audio.ts           (NEW - to create)
│   │   ├── activities/
│   │   │   └── activity-data-collect-edit.ts (Entry point)
│   │   ├── bootstrap.ts                    (Component registration)
│   │   └── index.ts
│   │
│   └── server/
│       ├── service/data-set/
│       │   └── data-item-type.ts           (DataItemType enum & schema)
│       ├── utils/
│       │   └── media-validation.ts         (Media validation utils)
│       └── controllers/
│           └── create-data-sample.ts       (File upload handling)
│
└── operato-dataset/ (Node modules dependency)
    └── node_modules/@operato/dataset/
        └── dist/src/
            ├── ox-data-entry-form.js       (Main form component)
            ├── ox-data-entry-subgroup-form.js (Grouped items)
            └── ox-data-input-factory.js    (Type mapping factory)
```

---

## Summary

**Current State:**
- File type has OxInputFile component for general file upload
- Signature type has OxInputSignature
- Image, video, audio types are DEFINED but NOT RENDERED

**What Needs Implementation:**
1. Create OxInputImage, OxInputVideo, OxInputAudio components
2. Update OxDataInputFactory to handle these types
3. Import components in appropriate locations
4. Backend is already ready for media handling

**Key Architecture Pattern:**
- All components extend OxFormField
- Input factory uses switch statement for type routing
- Form values extracted via name attribute selectors
- File upload via GraphQL multipart/form-data (hasUpload: true context)
