# 機能設計書 61-画像ウィジェット

## 概要

本ドキュメントは、Decap CMSの画像ウィジェット（Image Widget）の機能設計を定義する。画像ウィジェットは、エントリ編集画面において画像ファイルのアップロード・選択・プレビュー表示を提供するUIコンポーネントである。

### 本機能の処理概要

画像ウィジェットは、CMSのエントリ編集時に画像ファイルをアップロード・選択・プレビューするためのウィジェットである。ファイルウィジェット（decap-cms-widget-file）を基盤とし、画像専用の機能（プレビュー表示、複数画像対応、ソート対応）を付加している。

**業務上の目的・背景**：CMSでコンテンツを管理する際、画像はテキストと並んで最も頻繁に使用されるメディア種別である。コンテンツ編集者が直感的に画像を選択・挿入・プレビューできる仕組みを提供することで、コンテンツ作成の効率化とミスの削減を図る。

**機能の利用シーン**：エントリ編集画面でフィールドタイプが`image`に設定されているフィールドに対して利用される。ブログ記事のアイキャッチ画像、プロフィール写真、ギャラリー画像など、あらゆる画像入力に使用される。

**主要な処理内容**：
1. メディアライブラリ連携による画像ファイルの選択・アップロード
2. 選択された画像のプレビュー表示（サムネイル）
3. 複数画像の選択と並べ替え（ドラッグ＆ドロップ）
4. URL直接入力による画像指定
5. 画像の差し替え・削除
6. AssetProxy経由の画像URL解決

**関連システム・外部連携**：メディアライブラリ（内蔵・Cloudinary・Uploadcare）と連携し、画像のアップロード・選択を行う。

**権限による制御**：`private`フィールド設定により、プライベートアップロードの制御が可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | 既存エントリの画像フィールド編集 |
| 7 | エントリ新規作成画面 | 主画面 | 新規エントリの画像フィールド入力 |
| 9 | メディアライブラリ画面 | 参照画面 | 画像選択時にメディアライブラリモーダルを表示 |

## 機能種別

CRUD操作（画像ファイルの選択・更新・削除）/ UI入力コンポーネント

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| field | ImmutableMap | Yes | フィールド定義（name, widget, allow_multiple等） | widgetが'image'であること |
| value | string / string[] / List | No | 現在の画像パス（単一または複数） | - |
| onChange | function | Yes | 値変更コールバック | - |
| getAsset | function | Yes | アセットURL解決関数 | - |
| onOpenMediaLibrary | function | Yes | メディアライブラリ表示関数 | - |
| forID | string | Yes | フィールド一意識別子 | - |

### 入力データソース

- 画面入力: メディアライブラリからの画像選択、URL直接入力
- Redux Store: mediaPaths（メディアライブラリ選択結果）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| value | string / string[] | 画像パス（相対パスまたはURL） |

### 出力先

- Redux Store: エントリのフィールド値として保存
- プレビューペイン: ImagePreviewコンポーネントによる画像プレビュー表示

## 処理フロー

### 処理シーケンス

```
1. ウィジェット初期化
   └─ FileControlクラスをforImage=trueで生成（withFileControl({ forImage: true })）
2. 画像未選択時
   └─ 「Choose an image」ボタンと「Insert from URL」ボタンを表示
3. 画像選択操作
   └─ メディアライブラリモーダルを開き、画像を選択
4. 画像選択後
   └─ mediaPaths経由で選択パスを受け取り、onChangeで値を更新
5. 画像選択済み時
   └─ サムネイルプレビュー表示、差し替え・削除ボタンを表示
6. 複数画像時
   └─ ドラッグ＆ドロップによる並べ替え対応
```

### フローチャート

```mermaid
flowchart TD
    A[ウィジェット表示] --> B{画像選択済み?}
    B -->|No| C[選択ボタン表示]
    B -->|Yes| D[プレビュー表示]
    C --> E{ボタン押下}
    E -->|Choose| F[メディアライブラリ表示]
    E -->|URL入力| G[URLプロンプト表示]
    F --> H[画像選択]
    G --> H
    H --> I[onChange実行]
    I --> D
    D --> J{操作}
    J -->|差し替え| F
    J -->|削除| K[onChange空値]
    J -->|並び替え| L[arrayMoveで順序変更]
    K --> C
    L --> D
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-61-01 | 画像専用制御 | controlComponentはwithFileControl({ forImage: true })で生成され、画像プレビューと画像専用UIが有効化される | 常時 |
| BR-61-02 | 複数画像許可 | allow_multipleスキーマプロパティによりフィールドレベルで複数画像選択の可否を制御 | フィールド設定にallow_multipleが定義されている場合 |
| BR-61-03 | URL直接入力 | choose_urlオプション（デフォルトtrue）によりURL直接入力の可否を制御 | フィールド設定にchoose_urlが定義されている場合 |
| BR-61-04 | プライベートアップロード | field.privateがtrueの場合、プライベートアップロードとして処理 | field.privateがtrue |

### 計算ロジック

特になし。

## データベース操作仕様

### 操作別データベース影響一覧

該当なし（クライアントサイドのみの処理）。画像データはRedux Storeを経由してバックエンドに保存される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | アセット解決エラー | getAsset関数で画像URLが解決できない場合 | 空のsrcで画像タグを表示 |
| - | メディアライブラリエラー | メディアライブラリモーダルの操作でエラーが発生した場合 | 通知システムでエラーメッセージを表示 |

### リトライ仕様

リトライ処理はウィジェット層では行わない。

## トランザクション仕様

該当なし（Redux Storeへの値更新はアトミック操作）。

## パフォーマンス要件

- 画像プレビューはmax-width: 100%、height: autoでレスポンシブ表示
- 複数画像のドラッグ＆ドロップはdnd-kitライブラリによる高速な並べ替え

## セキュリティ考慮事項

- URL直接入力時の外部URLは検証なしで受け入れるため、XSSリスクに注意（imgタグのsrc属性として使用）
- File APIによるローカルファイル参照時はURL.createObjectURL()を使用

## 備考

- 画像ウィジェットはファイルウィジェット（decap-cms-widget-file）のwithFileControl HOCを`forImage: true`で呼び出すことで実装されている
- プレビューコンポーネントのみ独自実装（ImagePreview.js）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

画像ウィジェットの値はstring（単一画像パス）またはstring[]（複数画像パス）で管理される。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `packages/decap-cms-widget-image/src/schema.js` | allow_multipleプロパティのスキーマ定義を確認 |

**読解のコツ**: スキーマはウィジェット設定のバリデーションに使われる。propertiesオブジェクトで許可される設定項目が定義される。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `packages/decap-cms-widget-image/src/index.js` | ウィジェットの登録構造。controlComponentがwithFileControl({ forImage: true })で生成される点が重要 |

**主要処理フロー**:
1. **1行目**: DecapCmsWidgetFileからwithFileControlをインポート
2. **6行目**: `forImage: true`オプションで制御コンポーネントを生成
3. **8-16行目**: Widgetファクトリ関数でname='image'として登録

#### Step 3: 制御コンポーネント（FileControl）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | withFileControl.js | `packages/decap-cms-widget-file/src/withFileControl.js` | 画像・ファイル共通の制御ロジック。forImageフラグによる分岐を確認 |

**主要処理フロー**:
- **230行目**: withFileControl HOC関数の定義。forImageパラメータで画像モード切替
- **299-313行目**: handleChange - メディアライブラリを開いて画像選択
- **417-442行目**: renderImages - 画像プレビュー表示（単一・複数対応）
- **444-470行目**: renderSelection - 選択済み状態のUI表示
- **472-486行目**: renderNoSelection - 未選択状態のUI表示

#### Step 4: プレビューコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ImagePreview.js | `packages/decap-cms-widget-image/src/ImagePreview.js` | プレビュー表示ロジック。File APIとgetAssetの使い分けに注目 |

**主要処理フロー**:
- **13-21行目**: StyledImageAsset - FileオブジェクトならURL.createObjectURL、それ以外はgetAssetでURL解決
- **23-31行目**: ImagePreviewContent - 配列/List値なら複数画像を表示、そうでなければ単一画像を表示

### プログラム呼び出し階層図

```
DecapCmsWidgetImage (index.js)
    │
    ├─ withFileControl({ forImage: true }) → FileControl (withFileControl.js)
    │      ├─ handleChange() → onOpenMediaLibrary()
    │      ├─ handleUrl() → window.prompt → onChange()
    │      ├─ handleRemove() → onClearMediaControl() → onChange('')
    │      ├─ renderImages() → SortableMultiImageWrapper / ImageWrapper
    │      ├─ onSortEnd() → arrayMove → onChange()
    │      └─ componentDidUpdate() → mediaPaths監視 → onChange()
    │
    └─ ImagePreview (ImagePreview.js)
           ├─ StyledImageAsset → getAsset() / URL.createObjectURL()
           └─ ImagePreviewContent → 配列/単一分岐
```

### データフロー図

```
[入力]                    [処理]                         [出力]

メディアライブラリ ──▶ FileControl.handleChange() ──▶ Redux Store (value)
  (画像選択)              │                                │
                         ▼                                ▼
URLプロンプト ───────▶ onChange(path)              ──▶ ImagePreview
  (URL入力)                                               │
                                                          ▼
                                                    getAsset(value) ──▶ <img> 表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `packages/decap-cms-widget-image/src/index.js` | ソース | ウィジェット登録エントリーポイント |
| ImagePreview.js | `packages/decap-cms-widget-image/src/ImagePreview.js` | ソース | 画像プレビューコンポーネント |
| schema.js | `packages/decap-cms-widget-image/src/schema.js` | ソース | ウィジェットスキーマ定義 |
| withFileControl.js | `packages/decap-cms-widget-file/src/withFileControl.js` | ソース | ファイル/画像制御HOC（共通基盤） |
| index.js | `packages/decap-cms-widget-file/src/index.js` | ソース | ファイルウィジェットエントリーポイント |
