# 画面設計書 9-メディアライブラリ画面

## 概要

本ドキュメントは、Decap CMS のメディアライブラリ画面（MediaLibrary）の画面設計書である。メディアファイルのアップロード・検索・選択・削除・ダウンロードを行うモーダルダイアログの仕様を定義する。

### 本画面の処理概要

**業務上の目的・背景**：コンテンツに画像やファイルを挿入するために、Gitリポジトリ内のメディアファイルを管理するモーダルダイアログである。ファイルのアップロード、検索、選択、削除、ダウンロードの機能を提供し、エディタのファイル/画像ウィジェットと連携してコンテンツにメディアファイルを挿入する。レスポンシブなカードグリッドレイアウトでメディアファイルを一覧表示する。

**画面へのアクセス方法**：ヘッダーの「Media」ボタンをクリック、またはエディタの画像/ファイルウィジェットからメディア選択操作を行うとモーダルとして表示される。URLの変更を伴わない。

**主要な操作・処理内容**：
1. メディアファイルをカードグリッド形式で一覧表示する
2. ファイルをアップロードする（ファイルサイズ制限チェック付き）
3. ファイルを検索する（インメモリ検索またはダイナミック検索）
4. ファイルを選択してエントリに挿入する
5. ファイルを削除する（確認ダイアログ付き）
6. ファイルをダウンロードする
7. ページネーション（Load More）でファイルを追加読み込みする
8. 画像フィールドからの呼び出し時は画像ファイルのみフィルター表示する

**画面遷移**：
- 遷移元：ヘッダーMediaボタン、エディタの画像/ファイルウィジェット
- 遷移先：前の画面に戻る（モーダルを閉じる）

**権限による表示制御**：`canInsert`プロップにより「Insert」ボタンの表示が制御される。外部メディアライブラリ（Cloudinary、Uploadcare等）使用時はこのコンポーネントは使用されない（`mediaLibrary.externalLibrary`で判定）。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 25 | メディアライブラリ | 主機能 | メディアファイルの一覧表示・検索・ページネーション処理 |
| 26 | メディアアップロード | 主機能 | ファイルアップロード処理。persistMediaアクションによるファイル保存 |
| 27 | メディア削除 | 補助機能 | 選択したメディアファイルの削除処理 |
| 28 | メディアインサート | 補助機能 | メディアファイルのエントリフィールドへの挿入処理 |
| 29 | アセット解決 | 補助機能 | メディアファイルの表示URL解決 |
| 30 | 外部メディアライブラリ連携 | 補助機能 | Cloudinary・Uploadcare等の外部メディアライブラリとの連携時の表示制御 |

## 画面種別

モーダルダイアログ（一覧 + アップロード）

## URL/ルーティング

URLの変更を伴わない。モーダルダイアログとして表示される。Redux stateの`mediaLibrary.isVisible`で開閉が制御される。

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 型 | 必須 | 説明 |
|--------|--------|-----|------|------|
| 検索クエリ | query | text | No | メディアファイルの検索入力 |
| ファイルアップロード | file input / drag & drop | file | No | アップロードするファイル |

## 表示項目

| 項目名 | 表示条件 | 説明 |
|--------|---------|------|
| タイトルバー | 常時 | モーダルヘッダー。閉じるボタン付き |
| 検索フィールド | 常時 | ファイル名検索入力 |
| アップロードボタン | 常時 | ファイルアップロードボタン |
| 削除ボタン | hasSelection | 選択ファイルの削除 |
| ダウンロードボタン | hasSelection | 選択ファイルのダウンロード |
| 挿入ボタン | canInsert && hasSelection | 選択ファイルのエントリへの挿入 |
| カードグリッド | hasMedia | メディアファイルのカードグリッド表示 |
| 空メッセージ | !hasMedia | ファイルなし/検索結果なし/読み込み中メッセージ |
| Load Moreボタン | hasNextPage | ページネーション追加読み込み |

## イベント仕様

### 1-ファイルアップロード

1. handlePersist（MediaLibrary.js 行184-214）が呼ばれる
2. event.dataTransferまたはevent.targetからファイルを取得
3. max_file_sizeチェック（設定値を超える場合はアラート表示）
4. `persistMedia(file, { privateUpload, field })`アクションをdispatch
5. isPersisted=trueに設定し、アップロード完了後に最新ファイルを選択状態にする
6. スクロールをトップに戻す

### 2-ファイル選択（クリック）

1. handleAssetClick（MediaLibrary.js 行176-179）が呼ばれる
2. 同じファイルをクリックした場合は選択解除、異なるファイルは選択
3. state.selectedFileを更新

### 3-ファイル挿入

1. handleInsert（MediaLibrary.js 行220-226）が呼ばれる
2. 選択ファイルのpathを取得
3. `insertMedia(path, field)`アクションをdispatch
4. モーダルを閉じる

### 4-ファイル削除

1. handleDelete（MediaLibrary.js 行231-241）が呼ばれる
2. `window.confirm`で削除確認ダイアログ表示
3. `deleteMedia(file, { privateUpload })`アクションをdispatch
4. 選択状態をクリア

### 5-ファイルダウンロード

1. handleDownload（MediaLibrary.js 行246-267）が呼ばれる
2. displayURLsまたはselectedFile.urlからURLを取得
3. `<a>` 要素を動的に作成してdownload属性付きでクリック
4. 選択状態をクリア

### 6-検索

1. handleSearchChange（MediaLibrary.js 行300-302）でクエリstateを更新
2. dynamicSearch=falseの場合：queryFilter（行307-320）でインメモリfuzzy検索
3. dynamicSearch=trueの場合：handleSearchKeyDown（行285-291）でEnterキー押下時にloadMediaを実行

### 7-ページネーション（Load More）

1. handleLoadMore（MediaLibrary.js 行273-276）が呼ばれる
2. `loadMedia({ query, page: page + 1, privateUpload })`アクションをdispatch

### 8-モーダルオープン時

1. isVisibleがfalse -> trueに変化時、stateをクリア（selectedFile, query）
2. privateUploadが変化した場合、`loadMedia({ privateUpload })`で再読み込み

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| アップロード | Gitリポジトリ / Redux State | INSERT | メディアファイルをGitリポジトリに追加 |
| 削除 | Gitリポジトリ / Redux State | DELETE | メディアファイルをGitリポジトリから削除 |
| 挿入 | Redux State (entryDraft) | UPDATE | エントリのフィールドにメディアパスを設定 |

### テーブル別更新項目詳細

#### Redux State: mediaLibrary

| 操作 | 項目（キー） | 更新値・取得条件 | 備考 |
|-----|-------------|-----------------|------|
| UPDATE | isVisible | true/false | モーダルの開閉 |
| UPDATE | files | ファイルオブジェクト配列 | loadMedia, persistMedia, deleteMedia後 |
| UPDATE | isLoading | boolean | 読み込み中状態 |
| UPDATE | isPersisting | boolean | アップロード中状態 |
| UPDATE | isDeleting | boolean | 削除中状態 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| mediaLibrary.mediaLibrary.onDelete | 確認 | 削除確認ダイアログ | 削除ボタン押下時 |
| mediaLibrary.mediaLibrary.fileTooLarge | エラー | ファイルサイズ超過アラート | max_file_sizeを超えるファイル |
| mediaLibrary.mediaLibraryModal.loading | 情報 | 読み込み中メッセージ | isLoading=true |
| mediaLibrary.mediaLibraryModal.noResults | 情報 | 検索結果なし | dynamicSearchActive && 結果0件 |
| mediaLibrary.mediaLibraryModal.noAssetsFound | 情報 | ファイルなし | files.length === 0 |
| mediaLibrary.mediaLibraryModal.noImagesFound | 情報 | 画像なし | forImage && 画像0件 |
| mediaLibrary.mediaLibraryCard.draft | 情報 | ドラフトバッジ | file.draft=true |

## 例外処理

| 例外条件 | 処理内容 | 画面表示 |
|---------|---------|---------|
| ファイルサイズ超過 | window.alertでエラー表示 | アラートダイアログ |
| アップロード失敗 | エラー通知 | トースト通知 |
| 削除失敗 | エラー通知 | トースト通知 |
| ダウンロードURL未取得 | 処理中断（return） | 何も起こらない |

## 備考

- 画像拡張子としてjpg, jpeg, webp, gif, png, bmp, tiff, svg, avifがサポートされる（MediaLibrary.js 行26-36）。
- インメモリ検索にはfuzzyライブラリが使用される（MediaLibrary.js 行8, 314）。
- カードの幅は280px、高さは240px、マージンは10px（MediaLibraryModal.js 行18-20）。
- レスポンシブレイアウト：画面幅に応じてカード列数が1〜6列に変化（MediaLibraryModal.js 行31-48）。
- 外部メディアライブラリ使用時（`mediaLibrary.externalLibrary`がtruthy）はこのコンポーネントは表示されない（App.js 行254）。
- privateUploadフラグが有効な場合、モーダルの背景色がダークモードになる（MediaLibraryModal.js 行32）。

---

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

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

### 推奨読解順序

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

メディアライブラリのRedux state構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MediaLibrary.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibrary.js` | mapStateToProps（行377-400）でのmediaLibrary stateの構造 |
| 1-2 | MediaLibraryModal.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js` | fileShape（行158-166）のファイルオブジェクト構造 |

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

MediaLibraryの初期化とモーダル表示制御を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | App.js | `packages/decap-cms-core/src/components/App/App.js` | 行254のuseMediaLibrary判定。MediaLibraryコンポーネントの描画 |
| 2-2 | MediaLibrary.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibrary.js` | componentDidMount（行80-85）のloadMedia呼び出し |

**主要処理フロー**:
1. **行84**: componentDidMountでloadMedia()を実行
2. **行87-104**: UNSAFE_componentWillReceivePropsでモーダルオープン時のstate初期化
3. **行106-119**: componentDidUpdateでprivateUpload変更時のloadMedia再実行

#### Step 3: ファイル操作を理解する

アップロード・削除・挿入・ダウンロードの処理フローを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | MediaLibrary.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibrary.js` | handlePersist（行184-214）、handleDelete（行231-241）、handleInsert（行220-226）、handleDownload（行246-267） |

#### Step 4: 検索・フィルタリングを理解する

画像フィルターとfuzzy検索の処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | MediaLibrary.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibrary.js` | filterImages（行129-134）、queryFilter（行307-320）、toTableData（行139-167） |

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

```
App.js
    |
    +-- MediaLibrary (connect + translate)
            |
            +-- componentDidMount -> loadMedia()
            |
            +-- MediaLibraryModal
                    |
                    +-- MediaLibraryTop
                    |       +-- 検索フィールド
                    |       +-- アップロードボタン -> handlePersist
                    |       +-- 削除ボタン -> handleDelete
                    |       +-- ダウンロードボタン -> handleDownload
                    |       +-- 挿入ボタン -> handleInsert
                    |       +-- 閉じるボタン -> handleClose
                    |
                    +-- MediaLibraryCardGrid
                    |       +-- カード -> handleAssetClick
                    |       +-- Load More -> handleLoadMore
                    |
                    +-- EmptyMessage (ファイルなし時)
```

### データフロー図

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

ファイルアップロード ──▶  persistMedia(file)                 ──▶  Gitリポジトリ
                         |                                       Redux State (files更新)

ファイル選択+挿入   ──▶  insertMedia(path, field)            ──▶  Redux State (entryDraft)
                         |                                       モーダルクローズ

検索クエリ入力      ──▶  queryFilter (fuzzy)                 ──▶  フィルタリングされた
                         or loadMedia({ query })                  ファイル一覧表示

画像フィルター      ──▶  filterImages                        ──▶  画像のみの一覧表示
(forImage=true)          (IMAGE_EXTENSIONS判定)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| MediaLibrary.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibrary.js` | ソース | メディアライブラリのメインコンテナ |
| MediaLibraryModal.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryModal.js` | ソース | モーダルダイアログのレイアウト |
| MediaLibraryTop.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryTop.js` | ソース | モーダル上部の操作バー |
| MediaLibraryCardGrid.js | `packages/decap-cms-core/src/components/MediaLibrary/MediaLibraryCardGrid.js` | ソース | ファイルカードグリッド |
| EmptyMessage.js | `packages/decap-cms-core/src/components/MediaLibrary/EmptyMessage.js` | ソース | 空状態メッセージ |
| mediaLibrary.ts | `packages/decap-cms-core/src/actions/mediaLibrary.ts` | ソース | メディア関連のReduxアクション |
| App.js | `packages/decap-cms-core/src/components/App/App.js` | ソース | MediaLibraryの描画条件 |
