## DIY Scene Editing Feature

> Applicable: Wi-Fi + BLE Panel

### 🖼️ Preview:
<Image src='/images/extended/lampSting/string-diy-scene-en.png' style={{ width: '120px', height: '224px' }} />

The DIY scene editing feature module allows users to create and edit custom lighting scenes, supporting two editing modes: dynamic scene editing (based on colors and motion parameters) and static scene editing (based on LED bead painting). This module provides complete scene creation, editing, preview, saving, and management functionality, making it a core tool for users to personalize lighting effects.

### Feature List

1. **Dynamic DIY Scene Editing**: Create dynamic scenes through color lists and motion parameters
2. **Static DIY Scene Editing**: Precisely control each LED bead color through painting
3. **Scene Preview**: Real-time preview of scene effects
4. **Scene Saving**: Save scenes to cloud storage
5. **Scene Loading**: Load existing scenes from cloud storage for editing
6. **Scene Deletion**: Delete unwanted scenes
7. **Scene List Management**: View and manage all DIY scenes


### Feature Implementation Details 

#### Dynamic DIY Scene Editing
Dynamic DIY scene editing uses `SceneData` data structure, containing color lists and motion parameters. The editing page provides color management and parameter adjustment functionality, and scene data is sent through the `rgbic_linerlight_scene` DP point.

##### Core Components

- `src/pages/diyEdit/index.tsx`: Dynamic DIY scene editing page
- `src/components/colors/index.tsx`: Color list component
- `src/components/motion-config/index.tsx`: Motion parameter configuration component
- `src/hooks/useSceneSet.ts`: Scene setting Hook

##### Data Flow

```mermaid
flowchart TD
    A[Enter editing page] --> B[Load scene data]
    B --> C{Is edit mode}
    C -->|Yes| D[Load from cloud storage]
    C -->|No| E[Use default data]
    D --> F[Display color list]
    E --> F
    F --> G[User adds/removes colors]
    G --> H[Select motion type]
    H --> I[Adjust motion parameters]
    I --> J[Preview scene]
    J --> K[Save scene]
    K --> L[Encode scene data]
    L --> M[Save to cloud storage]
    M --> N[Send DP to device]
```

##### Key Code Snippets

**Scene Save Implementation**:

```tsx
  // Reference: src/pages/diyEdit/index.tsx
  const handleSave = async (sceneName: string) => {
    const params = {
      ...DEFAULTOPTIONS,
      ...values,
      key: 100,
      name: sceneName || '',
      changeType: current,
      colors: getArray(diyEditColors).map(color => ({
        hue: color.h,
        saturation: Math.floor(color.s / 10),
        value: Math.floor(color.v / 10),
        brightness: 0,
        temperature: 0,
      })),
      dataType: 0,
    } as SceneData;

    if (isDataId(dataId)) {
      await storage.updateItem(dataId, encodeCloudSceneData(params));
      sceneApi.set(params);
      Dispatcher.instance.dispatch('diyCreated', dataId);
    } else {
      showLoading({
        title: '',
        mask: true,
      });
      const newDataId = await storage.addItem(encodeCloudSceneData(params), {
        dedup: (a, b) => {
          try {
            return JSON.parse(a).name === JSON.parse(b).name;
          } catch (error) {
            return false;
          }
        },
      });
      hideLoading();
      if (typeof newDataId === 'number') {
        sceneApi.set(params);
        Dispatcher.instance.dispatch('diyCreated', newDataId);
      } else {
        TipApi.show({
          show: true,
          content: (
            <View className={styles.tipWrap}>
              <Image src={res.icon_warn} className={styles.tipIcon} />
              <View className={styles.tipText}>{Strings.getLang('name_dedup')}</View>
            </View>
          ),
        });
        return -1;
      }
    }
  };
```

##### Data Structures

**Scene Data Format**:

```typescript
type SceneData = {
  dataType: number; // 0: Dynamic scene
  name: string; // Scene name
  key: number; // Scene key value
  changeType: number; // Motion type
  colors: Array<{
    // Color list
    hue: number;
    saturation: number;
    value: number;
    brightness: number;
    temperature: number;
  }>;
  // Other motion parameters...
};
```

#### Static DIY Scene Editing

Static DIY scene editing uses `DiySceneData` data structure, containing color information for each LED bead. Control each LED bead through painting, and scene data is sent through the `diy_scene` DP point.

##### Core Components

- `src/pages/staticDiyEdit/index.tsx`: Static DIY scene editing page
- `src/components/light-strip/index.tsx`: Light strip visualization component
- `src/hooks/useDrawToolDataList.ts`: Painting tool data management Hook
- `src/hooks/useCutDrawDiyScene.ts`: Painting scene setting Hook

##### Data Flow

```mermaid
sequenceDiagram
    participant User as User
    participant Page as Editing Page
    participant Strip as Light Strip Component
    participant Hook as Data Hook
    participant Protocol as Protocol Parser
    participant Device as Device

    User->>Page: Select color and painting mode
    User->>Strip: Select LED beads
    Strip->>Page: Update selection state
    Page->>Hook: Call update function
    Hook->>Hook: Update light strip data
    Hook->>Protocol: Encode scene data
    Protocol->>Device: Send DP
    Device-->>User: Display effect
    User->>Page: Save scene
    Page->>Hook: Save to cloud storage
```

##### Key Code Snippets

**Scene Save Implementation**:

```tsx
// Reference: src/pages/staticDiyEdit/index.tsx
  const handleSave = async (sceneName: string) => {
    const params: DiySceneData = {
      name: sceneName,
      ...(diy_scene || ({} as DiySceneData)),
      segments: hasChange ? diy_scene?.segments : getArray(getDefault().segments),
    };
    console.log('[handleSave]:', params);
    if (isDataId(dataId)) {
      await storage.updateItem(dataId, encodeCloudSceneData(params));
      cutDrawDiySceneSet(params);
      Dispatcher.instance.dispatch('diyCreated', dataId);
    } else {
      showLoading({ title: '', mask: true });

      const newDataId = await storage.addItem(encodeCloudSceneData(params));
      if (typeof newDataId === 'number') {
        cutDrawDiySceneSet(params);
        Dispatcher.instance.dispatch('diyCreated', newDataId);
        hideLoading();
      } else {
        hideLoading();
        TipApi.show({
          show: true,
          content: (
            <View className={styles.tipWrap}>
              <Image src={res.icon_warn} className={styles.tipIcon} />
              <View className={styles.tipText}>{Strings.getLang('name_dedup')}</View>
            </View>
          ),
        });
        return -1;
      }
    }
  };
```

**Painting Scene Setting**:

```tsx
// Reference: src/hooks/useCutDrawDiyScene.ts
export const useCutDrawDiySceneSet = () => {
  const ledNumber = useProps(p => p[dpCodes.led_number_set]);
  const structuredActions = useStructuredActions();

  return (data: DiySceneData, options?: PublishDpsOptions) => {
    const newData = { ...(data || ({} as DiySceneData)) };
    const segments = getArray(newData?.segments);
    if (ledNumber) {
      if (segments.length >= ledNumber) {
        newData.segments = segments.slice(0, ledNumber);
      } else {
        const defaultColor: DiySceneData['segments'][0] = {
          isWhite: false,
          hue: 0,
          saturation: 1000,
          value: 1000,
          brightness: 0,
          temperature: 0,
        };
        newData.segments = segments.concat(
          ...new Array(ledNumber - segments.length).fill(0).map(() => defaultColor)
        );
      }
    }
    structuredActions.diy_scene.set(newData, options);
  };
};
```

##### Data Structures

**DIY Scene Data Format**:

```typescript
type DiySceneData = {
  name?: string;
  version?: number;
  id?: number;
  daubType?: 'all' | 'single' | 'clear'; // Painting type
  effect?: number; // Effect type
  segments?: Array<{
    // LED bead data
    hue?: number;
    saturation?: number;
    value?: number;
    brightness?: number;
    temperature?: number;
    isWhite?: boolean;
  }>;
};
```

#### Scene Preview

Scene preview is implemented through the `preview` method of `useSceneSet` Hook, encoding scene data and sending to device without saving to cloud storage.

##### Core Components

- `src/hooks/useSceneSet.ts`: Scene setting Hook

##### Data Flow

```mermaid
flowchart TD
    A[User clicks preview] --> B[Build scene data]
    B --> C[useSceneSet.preview]
    C --> D[Encode scene data]
    D --> E[Send DP to device]
    E --> F[Device executes scene]
    F --> G[User observes effect]
    G --> H{Continue editing?}
    H -->|Yes| I[Return to editing]
    H -->|No| J[Save scene]
```

##### Key Code Snippets

**Preview Implementation**:

```tsx
// Reference: src/hooks/useSceneSet.ts
  const preview = async (scene: SceneData) => {
    if (getArray(scene?.colors).length > 0) {
      await set(scene);
    }
  };
```

#### Scene Saving

Scene saving includes three steps: data encoding, cloud storage saving, and DP sending. It supports both add and update modes, and triggers scene creation event after successful save.

##### Core Components

- `src/pages/diyEdit/index.tsx`: Dynamic scene save
- `src/pages/staticDiyEdit/index.tsx`: Static scene save
- `src/hooks/useCloudStorageCombinedList.ts`: Cloud storage list management
- `src/hooks/useCloudDrawToolList.ts`: Scene data encoding/decoding

##### Data Flow

Already detailed in Feature 1 and Feature 2.

#### Scene Loading

Scene loading reads scene data from cloud storage, decodes and fills into editing page. Load to corresponding editing page based on scene type (dynamic or static).

##### Core Components

- `src/pages/diyEdit/index.tsx`: Dynamic scene loading
- `src/pages/staticDiyEdit/index.tsx`: Static scene loading
- `src/hooks/useCloudDrawToolList.ts`: Scene data decoding

##### Data Flow

```mermaid
flowchart TD
    A[User selects edit] --> B[Get scene ID]
    B --> C[Read from cloud storage]
    C --> D[Decode scene data]
    D --> E{Scene type}
    E -->|Dynamic| F[Load to dynamic edit page]
    E -->|Static| G[Load to static edit page]
    F --> H[Fill color list]
    G --> I[Fill LED bead data]
    H --> J[Fill motion parameters]
    I --> K[Display light strip state]
```

#### Scene Deletion

Scene deletion removes scene data from cloud storage and updates scene list. User confirmation is required before deletion.

##### Core Components

- `src/pages/diyEdit/index.tsx`: Delete functionality
- `src/pages/staticDiyEdit/index.tsx`: Delete functionality
- `src/hooks/useCloudStorageCombinedList.ts`: Cloud storage deletion

##### Key Code Snippets

**Delete Implementation** (using dynamic scene as example):

```tsx
// Reference: src/pages/diyEdit/index.tsx
        onRightClick={() => {
          showConfirmDeleteModal({
            content: Strings.getLang('deleteSceneTip'),
            success(res) {
              if (res.confirm) {
                storage.delItem(dataId);
                router.back();
              }
            },
          });
        }}
```

#### Scene List Management


Scene list management is implemented through `useDiySceneInit` Hook, reading all scenes from cloud storage, categorizing by type, and supporting scene selection and switching.

##### Core Components

- `src/components/tab-diy/index.tsx`: DIY scene list component
- `src/hooks/useDiySceneInit.ts`: DIY scene initialization Hook

##### Data Flow

```mermaid
flowchart TD
    A[Enter DIY tab] --> B[useDiySceneInit initialization]
    B --> C[Read scene list from cloud storage]
    C --> D[Decode scene data]
    D --> E[Categorize by type]
    E --> F[Display scene cards]
    F --> G[User selects scene]
    G --> H{Scene type}
    H -->|Dynamic| I[Apply dynamic scene]
    H -->|Static| J[Apply static scene]
    I --> K[Send scene DP]
    J --> K
    K --> L[Update current scene ID]
```

##### Key Code Snippets

**Scene Initialization**:

```tsx
// Reference: src/hooks/useDiySceneInit.ts
  const initDiyScene = useCallback(() => {
    if (!inited) return;
    if (!switch_led) return;
    console.log('init diy', current);
    if (current) {
      if (+current !== -1) {
        const item = getArray(storage.list).find(item => +item.id === +current);
        if (item?.value) {
          const props = decodeCloudSceneData(item?.value);
          if (props.type === 'scene') {
            sceneApi.set(props.ret);
            actions.work_mode.set('scene');
            setCurrent(String(item?.id));
          }
          if (props.type === 'draw') {
            cutDrawDiySceneSet(props?.ret);
            setCurrent(String(item?.id));
          }
        }
      } else {
        const item = getArray(storage.list)[0];
        if (item?.value) {
          const props = decodeCloudSceneData(item?.value);
          if (props.type === 'scene') {
            sceneApi.set(props.ret);
            actions.work_mode.set('scene');
            setCurrent(String(item?.id));
          }
          if (props.type === 'draw') {
            cutDrawDiySceneSet(props?.ret);
            setCurrent(String(item?.id));
          }
        }
      }
    }
  }, [current, storage.list, switch_led, inited]);
```

**Scene List Generation**:

```tsx
// Reference: src/hooks/useDiySceneInit.ts
  const diySceneList = useMemo(
    () =>
      splitArray(
        getArray(storage.list)
          .sort((a, b) => b.id - a.id)
          .map(item => {
            const props = decodeCloudSceneData(item.value);
            return {
              dataId: item.id,
              data: props?.ret || {},
              cloudType: props.type,
            };
          }),
        2
      ),
    [storage.list]
  );
```

### Notes

####  Implementation Challenges

1. **Scene Data Encoding/Decoding**:

   - **Challenge**: Dynamic scenes and static scenes use different data formats and DP points
   - **Solution**: Use `encodeCloudSceneData` and `decodeCloudSceneData` for unified encoding/decoding, distinguish scene type through `type` field

2. **LED Bead Data Alignment**:

   - **Challenge**: Static scene LED bead data needs to align with device LED bead count
   - **Solution**: Check data length in `useCutDrawDiySceneSet`, pad with default colors when insufficient, truncate when exceeded

3. **Scene Type Differentiation**:

   - **Challenge**: Need to distinguish dynamic scenes (scene) and static scenes (draw)
   - **Solution**: Use `type` field in cloud storage data for identification, select corresponding processing logic based on type when loading

4. **Scene Name Deduplication**:
   - **Challenge**: Prevent duplicate scene names
   - **Solution**: Use `dedup` function to check names, prompt user to modify when saving

#### Common Issues

1. **Issue**: Data incorrect after scene loading

   - **Cause**: Data decoding failed or format mismatch
   - **Solution**: Check data format, add error handling, use default values as fallback

2. **Issue**: Preview has no effect

   - **Cause**: Device not connected or DP sending failed
   - **Solution**: Check device connection status, view DP sending logs

3. **Issue**: Static scene LED bead count mismatch
   - **Cause**: Device LED bead count changed or data corrupted
   - **Solution**: Automatically align LED bead count, pad or truncate data

#### Performance Optimization

1. **Scene List Caching**:

   - Use `useMemo` to cache scene list, avoid repeated calculations
   - Scene list sorted by ID in descending order, newest scenes first

2. **Data Encoding Optimization**:

   - Scene data encoding uses efficient string concatenation
   - Avoid unnecessary deep copies

3. **Lazy Loading**:

   - Scene list paginated display, avoid loading all scenes at once
   - Scene data decoded on demand

4. **Debounce Processing**:
   - Scene saving uses debounce, avoid repeated saves
   - Scene preview uses throttle, avoid frequent DP sending

#### Edge Cases

1. **Empty Scene List**:

   - Handling: Display empty state prompt, guide user to create scene
   - Provide default scene options

2. **Scene Data Corrupted**:

   - Handling: Use default data when decoding fails
   - Prompt user about scene data anomaly

3. **Device LED Bead Count Changes**:

   - Handling: Automatically align LED bead count
   - Truncate exceeded parts, pad insufficient parts with default colors

4. **Scene Name Too Long**:

   - Handling: Limit scene name length
   - Truncate exceeded parts or prompt user

5. **Editing Multiple Scenes Simultaneously**:
   - Handling: Only one scene can be edited at a time
   - Save current scene when switching edits



