# 機能設計書 25-メディアライブラリ

## 概要

本ドキュメントは、Decap CMSのメディアライブラリ機能について、メディアファイルの一覧表示・検索・アップロード・削除・選択挿入を行うモーダルダイアログの構造と処理を詳細に記述した機能設計書である。

### 本機能の処理概要

メディアファイルの管理を行うモーダルダイアログUIを提供する機能である。MediaLibraryコンポーネントがRedux Storeと連携してファイル一覧の表示、検索（インメモリfuzzy検索およびダイナミック検索）、ファイルのアップロード・削除・選択・挿入・ダウンロードの各操作を統括する。

**業務上の目的・背景**：CMSのコンテンツ編集において、画像や添付ファイルの管理は頻繁に行われる操作である。メディアライブラリはこれらのメディアファイルを一元管理するUIを提供し、エントリ編集中にファイルの選択・挿入を効率的に行えるようにする。

**機能の利用シーン**：
- ヘッダーの「メディア」ボタンからメディアライブラリを開くスタンドアロンモード
- エントリ編集中に画像/ファイルウィジェットからメディアライブラリを開く挿入モード
- ファイルのアップロード・削除・ダウンロード操作

**主要な処理内容**：
1. メディアライブラリモーダルの開閉制御（openMediaLibrary / closeMediaLibrary）
2. バックエンドからのメディアファイル一覧の読み込み（loadMedia）
3. 画像フィルタリング（forImageフラグに基づくIMAGE_EXTENSIONSフィルタ）
4. fuzzy検索によるインメモリファイル名検索
5. ダイナミック検索（アセットストアIntegration使用時）
6. ファイルアップロード（persistMedia呼び出し）
7. ファイル削除（deleteMedia呼び出し）
8. ファイル選択・挿入（insertMedia呼び出し）
9. ファイルダウンロード（displayURLを使用したブラウザダウンロード）
10. ページネーションによる追加読み込み

**関連システム・外部連携**：Git系バックエンド（GitHub/GitLab/Bitbucket等）、アセットストアIntegration（Algolia等）、外部メディアライブラリ（Cloudinary/Uploadcare）。

**権限による制御**：認証済みユーザーのみがメディアライブラリを使用可能。privateUploadモードでは制限付きアップロードが行われる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 9 | メディアライブラリ画面 | 主画面 | メディアファイルの一覧表示・検索・ページネーション処理。MediaLibraryModalコンポーネントによるモーダル描画 |

## 機能種別

CRUD操作（Read / 表示・検索・ページネーション）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| controlID | string | No | 呼び出し元ウィジェットのID | 挿入モード判定に使用 |
| forImage | boolean | No | 画像のみ表示モード | trueの場合IMAGE_EXTENSIONSでフィルタ |
| privateUpload | boolean | No | プライベートアップロードモード | アセットストアIntegration使用時のみ |
| value | string | No | 現在選択されている値 | ウィジェットの現在値 |
| allowMultiple | boolean | No | 複数選択許可フラグ | 外部メディアライブラリ用 |
| config | Map | No | メディアライブラリ設定 | コレクション/フィールドレベルの設定 |
| field | EntryField | No | 呼び出し元フィールド定義 | フィールド固有のメディアフォルダ判定 |

### 入力データソース

- openMediaLibraryアクションのpayload
- Redux Store: state.mediaLibrary（メディアライブラリ状態）
- Redux Store: state.entryDraft（現在編集中のエントリ）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| isVisible | boolean | モーダルの表示/非表示状態 |
| files | MediaFile[] | 表示するファイル一覧 |
| selectedFile | object | 現在選択されているファイル |
| query | string | 現在の検索クエリ |
| tableData | object[] | 表示用に変換されたファイルデータ |

### 出力先

- 画面表示（モーダルダイアログ）
- Redux Store: state.mediaLibrary（メディアライブラリ状態更新）

## 処理フロー

### 処理シーケンス

```
1. openMediaLibraryアクションでモーダルを開く
   └─ 外部メディアライブラリがある場合はshow()を呼び出し
   └─ MEDIA_LIBRARY_OPENアクションをディスパッチ
2. componentDidMount / componentDidUpdateでloadMedia()を実行
   └─ バックエンドからファイル一覧を取得
3. ファイル一覧の表示処理
   a. forImageフラグでIMAGE_EXTENSIONS(jpg,jpeg,webp,gif,png,bmp,tiff,svg,avif)でフィルタ
   b. queryがある場合fuzzy検索でフィルタ
   c. toTableDataでテーブル表示用データに変換
   d. MediaLibraryCardGridでカード形式で描画
4. ユーザー操作に応じた処理
   - handleAssetClick: ファイル選択トグル
   - handlePersist: ファイルアップロード
   - handleDelete: ファイル削除
   - handleInsert: ファイル挿入
   - handleDownload: ファイルダウンロード
   - handleSearchChange/handleSearchKeyDown: 検索
   - handleLoadMore: 追加読み込み
5. closeMediaLibraryでモーダルを閉じる
```

### フローチャート

```mermaid
flowchart TD
    A[openMediaLibrary呼び出し] --> B[MEDIA_LIBRARY_OPENディスパッチ]
    B --> C[loadMedia実行]
    C --> D[ファイル一覧取得]
    D --> E{forImage?}
    E -->|Yes| F[IMAGE_EXTENSIONSでフィルタ]
    E -->|No| G[全ファイル表示]
    F --> H{検索クエリあり?}
    G --> H
    H -->|Yes| I[fuzzy検索でフィルタ]
    H -->|No| J[フィルタなし]
    I --> K[toTableDataで変換]
    J --> K
    K --> L[MediaLibraryCardGrid描画]
    L --> M{ユーザー操作}
    M -->|選択| N[handleAssetClick]
    M -->|アップロード| O[handlePersist]
    M -->|削除| P[handleDelete]
    M -->|挿入| Q[handleInsert -> closeMediaLibrary]
    M -->|閉じる| R[closeMediaLibrary]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-25-01 | 画像拡張子フィルタ | forImage時にjpg,jpeg,webp,gif,png,bmp,tiff,svg,avifの拡張子でフィルタ | forImage === true |
| BR-25-02 | canInsert判定 | controlIDが設定されている場合のみ挿入ボタンが有効 | openMediaLibraryのcontrolIDパラメータ |
| BR-25-03 | ファイルサイズ制限 | config.max_file_sizeを超えるファイルはアップロード時にアラート表示 | max_file_sizeが設定されている場合 |
| BR-25-04 | 重複ファイル名検出 | 同名ファイルが存在する場合、上書き確認ダイアログを表示 | Git系バックエンド使用時（Integration非使用時） |
| BR-25-05 | ドラフトファイル表示 | 編集中のエントリのドラフトファイルは別扱いで表示 | editingDraftがtrueの場合 |
| BR-25-06 | ダイナミック検索 | アセットストアIntegration使用時はEnterキーでAPIリクエストを実行 | dynamicSearch === true |

### 計算ロジック

- ファイルサイズ表示: `Math.floor(maxFileSize / 1000)` KB単位
- fuzzy検索: `fuzzy.filter(strippedQuery, files, { extract: file => file.name })`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ファイル一覧取得 | Redux Store: state.mediaLibrary.files | SELECT | メディアファイル一覧を参照 |
| モーダル開閉 | Redux Store: state.mediaLibrary | UPDATE | isVisible, controlID, forImage等の状態更新 |
| ファイル選択 | コンポーネント内部state | UPDATE | selectedFile状態の更新 |

### テーブル別操作詳細

#### Redux Store: state.mediaLibrary

| 操作 | 項目（キー） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | isVisible | true / false | OPEN / CLOSE アクション |
| UPDATE | controlID | string | 挿入モード時の呼び出し元ID |
| UPDATE | canInsert | boolean | !!controlID |
| UPDATE | forImage | boolean | 画像のみモード |
| UPDATE | files | MediaFile[] | loadMedia成功時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | メディア読み込み失敗 | バックエンドAPI通信エラー | MEDIA_LOAD_FAILUREアクション、isLoadingをfalseに |
| 404 | メディアフォルダ未存在 | メディアフォルダがまだ作成されていない | 空配列で正常応答として処理 |
| - | ファイルサイズ超過 | max_file_sizeを超えるファイルのアップロード | window.alertでユーザーに通知 |

### リトライ仕様

自動リトライは実装されていない。モーダルの再表示で再読み込みが行われる。

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

メディアファイルの操作（アップロード・削除）は個別のAPI呼び出しで実行される。トランザクション管理は各バックエンド実装に依存する。

## パフォーマンス要件

- ページネーションによる段階的読み込み（hasNextPageフラグ）
- fuzzy検索はインメモリで実行されるため、大量ファイルではパフォーマンスに影響する可能性
- displayURLの遅延読み込み（loadMediaDisplayURL）

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

- 認証済みユーザーのみがメディアファイルにアクセス可能
- privateUploadモードでは制限付きアップロード

## 備考

- UNSAFE_componentWillReceivePropsが使用されている（React非推奨メソッド）
- 外部メディアライブラリ（Cloudinary/Uploadcare）使用時は、内蔵メディアライブラリの代わりに外部UIが表示される
- 検索状態とファイル選択状態はコンポーネント内部stateで管理される（Redux Store外）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | mediaLibrary.ts (reducer) | `packages/decap-cms-core/src/reducers/mediaLibrary.ts` | **37-56行目**: defaultState定義。isVisible, files, config等のメディアライブラリ状態構造 |
| 1-2 | mediaLibrary.ts (actions) | `packages/decap-cms-core/src/actions/mediaLibrary.ts` | **32-48行目**: アクション定数定義。MEDIA_LIBRARY_OPEN/CLOSE/CREATE等 |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | MediaLibrary.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibrary.js` | MediaLibraryクラスコンポーネント。**39-374行目**: 全体構造。**377-400行目**: mapStateToPropsでRedux Stateからpropsを生成 |

**主要処理フロー**:
1. **84行目**: componentDidMountでloadMedia()を実行
2. **87-104行目**: UNSAFE_componentWillReceivePropsでモーダル再オープン時の状態クリア
3. **129-134行目**: filterImages()でIMAGE_EXTENSIONSフィルタ
4. **139-167行目**: toTableData()で表示用データに変換
5. **176-179行目**: handleAssetClick()でファイル選択トグル
6. **184-214行目**: handlePersist()でアップロード（max_file_sizeチェック含む）
7. **220-226行目**: handleInsert()でファイル挿入とモーダル閉じ
8. **231-241行目**: handleDelete()でファイル削除（確認ダイアログ付き）
9. **307-321行目**: queryFilter()でfuzzy検索

#### Step 3: モーダルUIを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | MediaLibraryModal.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js` | **64-156行目**: MediaLibraryModal関数コンポーネント。ファイルフィルタリング・検索・表示の統合 |

#### Step 4: アクション詳細を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | mediaLibrary.ts (actions) | `packages/decap-cms-core/src/actions/mediaLibrary.ts` | **81-101行目**: openMediaLibrary関数。**103-112行目**: closeMediaLibrary関数。**136-186行目**: loadMedia関数 |

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

```
MediaLibrary.js (コンテナコンポーネント)
    |
    +-- componentDidMount -> loadMedia()
    |
    +-- MediaLibraryModal.js
        |
        +-- MediaLibraryTop.js (ヘッダー: 検索・ボタン)
        |   +-- MediaLibrarySearch.js
        |   +-- MediaLibraryButtons.js
        |   +-- MediaLibraryHeader.js
        |
        +-- EmptyMessage.js (ファイルなし時)
        |
        +-- MediaLibraryCardGrid.js (カードグリッド表示)
            +-- MediaLibraryCard.js (個別ファイルカード)
```

### データフロー図

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

openMediaLibrary       MEDIA_LIBRARY_OPEN              モーダル表示
(controlID, forImage)      |                           (MediaLibraryModal)
        |                  v
        +---------> loadMedia() -> backend.getMedia()   ファイル一覧
                         |                               (MediaLibraryCardGrid)
検索クエリ              fuzzy.filter() or
(query)                provider.retrieve()              フィルタ済み一覧
        |                  |
        +---------> filterImages() + toTableData()      表示用データ

ファイル選択            handleInsert()                   挿入パス
(selectedFile)             |                            (MEDIA_INSERT)
        |                  v
        +---------> insertMedia() -> closeMediaLibrary()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| MediaLibrary.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibrary.js` | ソース | メディアライブラリコンテナコンポーネント |
| MediaLibraryModal.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js` | ソース | メディアライブラリモーダルUI |
| MediaLibraryTop.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryTop.js` | ソース | モーダルヘッダー（検索・ボタン） |
| MediaLibraryCardGrid.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryCardGrid.js` | ソース | ファイルカードグリッド表示 |
| MediaLibraryCard.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryCard.js` | ソース | 個別ファイルカード |
| MediaLibrarySearch.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibrarySearch.js` | ソース | 検索フォーム |
| MediaLibraryButtons.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryButtons.js` | ソース | 操作ボタン群 |
| MediaLibraryHeader.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryHeader.js` | ソース | モーダルヘッダー |
| EmptyMessage.js | `packages/decap-cms-core/src/components/MediaLibrary/EmptyMessage.js` | ソース | 空メッセージ表示 |
| mediaLibrary.ts | `packages/decap-cms-core/src/actions/mediaLibrary.ts` | ソース | メディアライブラリアクション |
| mediaLibrary.ts | `packages/decap-cms-core/src/reducers/mediaLibrary.ts` | ソース | メディアライブラリリデューサー |
