next2d-development-mcp 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +344 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +211 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/prompts.test.d.ts +2 -0
- package/dist/prompts/prompts.test.d.ts.map +1 -0
- package/dist/prompts/prompts.test.js +23 -0
- package/dist/prompts/prompts.test.js.map +1 -0
- package/dist/references/develop-specs.md +1576 -0
- package/dist/references/framework-specs.md +1687 -0
- package/dist/references/player-specs.md +3292 -0
- package/dist/resources/index.d.ts +3 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +185 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/resources.test.d.ts +2 -0
- package/dist/resources/resources.test.d.ts.map +1 -0
- package/dist/resources/resources.test.js +32 -0
- package/dist/resources/resources.test.js.map +1 -0
- package/dist/templates/animation.d.ts +6 -0
- package/dist/templates/animation.d.ts.map +1 -0
- package/dist/templates/animation.js +65 -0
- package/dist/templates/animation.js.map +1 -0
- package/dist/templates/animation.test.d.ts +2 -0
- package/dist/templates/animation.test.d.ts.map +1 -0
- package/dist/templates/animation.test.js +30 -0
- package/dist/templates/animation.test.js.map +1 -0
- package/dist/templates/domainService.d.ts +9 -0
- package/dist/templates/domainService.d.ts.map +1 -0
- package/dist/templates/domainService.js +56 -0
- package/dist/templates/domainService.js.map +1 -0
- package/dist/templates/domainService.test.d.ts +2 -0
- package/dist/templates/domainService.test.d.ts.map +1 -0
- package/dist/templates/domainService.test.js +33 -0
- package/dist/templates/domainService.test.js.map +1 -0
- package/dist/templates/interfaceFile.d.ts +5 -0
- package/dist/templates/interfaceFile.d.ts.map +1 -0
- package/dist/templates/interfaceFile.js +16 -0
- package/dist/templates/interfaceFile.js.map +1 -0
- package/dist/templates/interfaceFile.test.d.ts +2 -0
- package/dist/templates/interfaceFile.test.d.ts.map +1 -0
- package/dist/templates/interfaceFile.test.js +30 -0
- package/dist/templates/interfaceFile.test.js.map +1 -0
- package/dist/templates/loading.d.ts +5 -0
- package/dist/templates/loading.d.ts.map +1 -0
- package/dist/templates/loading.js +62 -0
- package/dist/templates/loading.js.map +1 -0
- package/dist/templates/loading.test.d.ts +2 -0
- package/dist/templates/loading.test.d.ts.map +1 -0
- package/dist/templates/loading.test.js +31 -0
- package/dist/templates/loading.test.js.map +1 -0
- package/dist/templates/repository.d.ts +2 -0
- package/dist/templates/repository.d.ts.map +1 -0
- package/dist/templates/repository.js +42 -0
- package/dist/templates/repository.js.map +1 -0
- package/dist/templates/repository.test.d.ts +2 -0
- package/dist/templates/repository.test.d.ts.map +1 -0
- package/dist/templates/repository.test.js +44 -0
- package/dist/templates/repository.test.js.map +1 -0
- package/dist/templates/uiComponent.d.ts +4 -0
- package/dist/templates/uiComponent.d.ts.map +1 -0
- package/dist/templates/uiComponent.js +108 -0
- package/dist/templates/uiComponent.js.map +1 -0
- package/dist/templates/uiComponent.test.d.ts +2 -0
- package/dist/templates/uiComponent.test.d.ts.map +1 -0
- package/dist/templates/uiComponent.test.js +76 -0
- package/dist/templates/uiComponent.test.js.map +1 -0
- package/dist/templates/usecase.d.ts +2 -0
- package/dist/templates/usecase.d.ts.map +1 -0
- package/dist/templates/usecase.js +31 -0
- package/dist/templates/usecase.js.map +1 -0
- package/dist/templates/usecase.test.d.ts +2 -0
- package/dist/templates/usecase.test.d.ts.map +1 -0
- package/dist/templates/usecase.test.js +23 -0
- package/dist/templates/usecase.test.js.map +1 -0
- package/dist/templates/view.d.ts +3 -0
- package/dist/templates/view.d.ts.map +1 -0
- package/dist/templates/view.js +112 -0
- package/dist/templates/view.js.map +1 -0
- package/dist/templates/view.test.d.ts +2 -0
- package/dist/templates/view.test.d.ts.map +1 -0
- package/dist/templates/view.test.js +71 -0
- package/dist/templates/view.test.js.map +1 -0
- package/dist/tools/addRoute.d.ts +3 -0
- package/dist/tools/addRoute.d.ts.map +1 -0
- package/dist/tools/addRoute.js +101 -0
- package/dist/tools/addRoute.js.map +1 -0
- package/dist/tools/createAnimation.d.ts +3 -0
- package/dist/tools/createAnimation.d.ts.map +1 -0
- package/dist/tools/createAnimation.js +53 -0
- package/dist/tools/createAnimation.js.map +1 -0
- package/dist/tools/createDomainService.d.ts +3 -0
- package/dist/tools/createDomainService.d.ts.map +1 -0
- package/dist/tools/createDomainService.js +82 -0
- package/dist/tools/createDomainService.js.map +1 -0
- package/dist/tools/createInterface.d.ts +3 -0
- package/dist/tools/createInterface.d.ts.map +1 -0
- package/dist/tools/createInterface.js +59 -0
- package/dist/tools/createInterface.js.map +1 -0
- package/dist/tools/createLoading.d.ts +3 -0
- package/dist/tools/createLoading.d.ts.map +1 -0
- package/dist/tools/createLoading.js +52 -0
- package/dist/tools/createLoading.js.map +1 -0
- package/dist/tools/createRepository.d.ts +3 -0
- package/dist/tools/createRepository.d.ts.map +1 -0
- package/dist/tools/createRepository.js +55 -0
- package/dist/tools/createRepository.js.map +1 -0
- package/dist/tools/createUiComponent.d.ts +3 -0
- package/dist/tools/createUiComponent.d.ts.map +1 -0
- package/dist/tools/createUiComponent.js +80 -0
- package/dist/tools/createUiComponent.js.map +1 -0
- package/dist/tools/createUseCase.d.ts +3 -0
- package/dist/tools/createUseCase.d.ts.map +1 -0
- package/dist/tools/createUseCase.js +52 -0
- package/dist/tools/createUseCase.js.map +1 -0
- package/dist/tools/createView.d.ts +3 -0
- package/dist/tools/createView.d.ts.map +1 -0
- package/dist/tools/createView.js +59 -0
- package/dist/tools/createView.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +25 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/tools.test.d.ts +2 -0
- package/dist/tools/tools.test.d.ts.map +1 -0
- package/dist/tools/tools.test.js +58 -0
- package/dist/tools/tools.test.js.map +1 -0
- package/dist/tools/validateArchitecture.d.ts +3 -0
- package/dist/tools/validateArchitecture.d.ts.map +1 -0
- package/dist/tools/validateArchitecture.js +134 -0
- package/dist/tools/validateArchitecture.js.map +1 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +18 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +34 -0
- package/dist/utils.test.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1576 @@
|
|
|
1
|
+
# Next2D Framework TypeScript Template - Development Specs
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Overview](#next2d-framework-typescript-template---overview)
|
|
6
|
+
2. [CLI Commands Reference](#cli-commands-reference)
|
|
7
|
+
3. [Configuration Files](#configuration-files)
|
|
8
|
+
4. [Interface Definitions](#interface-definitions)
|
|
9
|
+
5. [Model Layer (Application / Domain / Infrastructure)](#model-layer-application--domain--infrastructure)
|
|
10
|
+
6. [UI Layer (Components / Animation / Content)](#ui-layer-components--animation--content)
|
|
11
|
+
7. [View / ViewModel (MVVM Pattern)](#view--viewmodel-mvvm-pattern)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Next2D Framework TypeScript Template - Overview
|
|
16
|
+
|
|
17
|
+
## Project Summary
|
|
18
|
+
|
|
19
|
+
Next2D Frameworkを使用したTypeScriptプロジェクトテンプレート。MVVM + Clean Architecture + Atomic Designを採用。
|
|
20
|
+
|
|
21
|
+
- **レンダリングエンジン**: Next2D Player
|
|
22
|
+
- **フレームワーク**: Next2D Framework
|
|
23
|
+
- **言語**: TypeScript
|
|
24
|
+
- **ビルドツール**: Vite
|
|
25
|
+
- **テスト**: Vitest
|
|
26
|
+
- **パッケージマネージャ**: npm
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- Node.js 22.x以上
|
|
31
|
+
- npm 10.x以上
|
|
32
|
+
- iOS: Xcode 14以上 (iOS/Androidビルド時のみ)
|
|
33
|
+
- Android: Android Studio, JDK 21以上 (iOS/Androidビルド時のみ)
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
**MVVM + Clean Architecture + Atomic Design** の5層構成:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
View Layer (view/, ui/)
|
|
41
|
+
└─ depends on ─→ Interface Layer (interface/)
|
|
42
|
+
↑
|
|
43
|
+
Application Layer (model/application/)
|
|
44
|
+
├─ depends on ─→ Interface Layer
|
|
45
|
+
├─ depends on ─→ Domain Layer (model/domain/)
|
|
46
|
+
└─ calls ──────→ Infrastructure Layer (model/infrastructure/)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Layer Dependencies (依存関係の方向)
|
|
50
|
+
|
|
51
|
+
- **View層** → Interface経由でApplication層を使用
|
|
52
|
+
- **Application層** → Interface経由でDomain層・Infrastructure層を使用
|
|
53
|
+
- **Domain層** → 何にも依存しない(最も安定、純粋なビジネスロジック)
|
|
54
|
+
- **Infrastructure層** → Interface層を実装
|
|
55
|
+
|
|
56
|
+
### Key Design Patterns
|
|
57
|
+
|
|
58
|
+
1. **MVVM**: View(表示) / ViewModel(橋渡し) / Model(ビジネスロジック+データアクセス)
|
|
59
|
+
2. **UseCase Pattern**: ユーザーアクションごとに専用のUseCaseクラスを作成
|
|
60
|
+
3. **Dependency Inversion**: 具象クラスではなくインターフェースに依存
|
|
61
|
+
4. **Repository Pattern**: データアクセスを抽象化
|
|
62
|
+
5. **Atomic Design**: Atom → Molecule → Organism → Template → Page
|
|
63
|
+
|
|
64
|
+
## Directory Structure
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/
|
|
68
|
+
├── config/ # 設定ファイル (stage.json, config.json, routing.json)
|
|
69
|
+
├── interface/ # TypeScriptインターフェース定義
|
|
70
|
+
├── model/
|
|
71
|
+
│ ├── application/ # UseCase (ビジネスロジック実装)
|
|
72
|
+
│ │ └── {screen}/usecase/
|
|
73
|
+
│ ├── domain/ # コアビジネスロジック
|
|
74
|
+
│ │ └── {feature}/service/
|
|
75
|
+
│ └── infrastructure/ # Repository (データアクセス)
|
|
76
|
+
│ └── repository/
|
|
77
|
+
├── ui/
|
|
78
|
+
│ ├── animation/ # アニメーション定義
|
|
79
|
+
│ │ └── {screen}/
|
|
80
|
+
│ ├── component/
|
|
81
|
+
│ │ ├── atom/ # 最小コンポーネント (Button, Text等)
|
|
82
|
+
│ │ ├── molecule/ # 複合コンポーネント
|
|
83
|
+
│ │ ├── organism/ # 複数Moleculeの組み合わせ (拡張用)
|
|
84
|
+
│ │ ├── page/ # ページコンポーネント
|
|
85
|
+
│ │ │ └── {screen}/
|
|
86
|
+
│ │ └── template/ # ページテンプレート (拡張用)
|
|
87
|
+
│ └── content/ # Animation Tool生成コンテンツ
|
|
88
|
+
├── view/ # View & ViewModel
|
|
89
|
+
│ └── {screen}/
|
|
90
|
+
│ ├── {Screen}View.ts
|
|
91
|
+
│ └── {Screen}ViewModel.ts
|
|
92
|
+
└── assets/ # 静的ファイル (画像, JSON)
|
|
93
|
+
|
|
94
|
+
@types/ # グローバル型定義 (.d.ts)
|
|
95
|
+
electron/ # Electron設定 (デスクトップビルド用)
|
|
96
|
+
file/ # Animation Tool n2dファイル
|
|
97
|
+
mock/ # 開発用モックデータ (API, Content, 画像)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Best Practices (全体共通)
|
|
101
|
+
|
|
102
|
+
1. **インターフェース優先**: 常に具象クラスではなくインターフェースに依存
|
|
103
|
+
2. **単一責任の原則**: 各クラスは1つの責務のみを持つ
|
|
104
|
+
3. **型安全性**: `any`型を避け、明示的な型定義を使用
|
|
105
|
+
4. **テスタブル**: 各層を独立してテスト可能にする
|
|
106
|
+
5. **JSDoc**: 処理内容を日英両方で明記
|
|
107
|
+
6. **executeメソッド**: UseCaseのエントリーポイントを統一
|
|
108
|
+
7. **エラーハンドリング**: Infrastructure層で適切に処理
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
# CLI Commands Reference
|
|
113
|
+
|
|
114
|
+
## Setup
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npm install # 依存パッケージのインストール
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm start # 開発サーバー起動 (http://localhost:5173)
|
|
124
|
+
npm run generate # routing.jsonからView/ViewModelクラスを自動生成
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npm test # 全テスト実行 (Vitest)
|
|
131
|
+
npm test -- --watch # ウォッチモード
|
|
132
|
+
npm test -- --coverage # カバレッジレポート
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Build
|
|
136
|
+
|
|
137
|
+
| Command | Platform | Output |
|
|
138
|
+
|---------|----------|--------|
|
|
139
|
+
| `npm run build:web -- --env prd` | Web (HTML) | `dist/web/prd/` |
|
|
140
|
+
| `npm run build:steam:windows -- --env prd` | Windows (Steam) | `dist/steam/windows/` |
|
|
141
|
+
| `npm run build:steam:macos -- --env prd` | macOS (Steam) | `dist/steam/macos/` |
|
|
142
|
+
| `npm run build:steam:linux -- --env prd` | Linux (Steam) | `dist/steam/linux/` |
|
|
143
|
+
| `npm run build:ios -- --env prd` | iOS | Xcode project |
|
|
144
|
+
| `npm run build:android -- --env prd` | Android | Android Studio project |
|
|
145
|
+
|
|
146
|
+
## Platform Emulators
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm run preview:windows -- --env prd # Windows
|
|
150
|
+
npm run preview:macos -- --env prd # macOS
|
|
151
|
+
npm run preview:linux -- --env prd # Linux
|
|
152
|
+
npm run preview:ios -- --env prd # iOS
|
|
153
|
+
npm run preview:android -- --env prd # Android
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`--env` オプション: `local`, `dev`, `stg`, `prd`
|
|
157
|
+
|
|
158
|
+
## Environment Configuration
|
|
159
|
+
|
|
160
|
+
環境ごとの設定は`src/config/config.json`で管理。`--env`で指定した環境名の設定値と`all`の設定値がマージされる。
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
# Configuration Files
|
|
165
|
+
|
|
166
|
+
設定ファイルは `src/config/` ディレクトリに配置。
|
|
167
|
+
|
|
168
|
+
## stage.json
|
|
169
|
+
|
|
170
|
+
表示領域(Stage)の設定。
|
|
171
|
+
|
|
172
|
+
| Property | Type | Default | Description |
|
|
173
|
+
|----------|------|---------|-------------|
|
|
174
|
+
| `width` | number | 240 | 表示領域の幅 |
|
|
175
|
+
| `height` | number | 240 | 表示領域の高さ |
|
|
176
|
+
| `fps` | number | 60 | 描画回数/秒 (1-60) |
|
|
177
|
+
| `options` | object | null | オプション設定 |
|
|
178
|
+
|
|
179
|
+
### Stage Options
|
|
180
|
+
|
|
181
|
+
| Property | Type | Default | Description |
|
|
182
|
+
|----------|------|---------|-------------|
|
|
183
|
+
| `options.fullScreen` | boolean | false | 画面全体に描画 |
|
|
184
|
+
| `options.tagId` | string | null | 描画先のエレメントID |
|
|
185
|
+
| `options.bgColor` | string | "transparent" | 背景色 (16進数) |
|
|
186
|
+
|
|
187
|
+
### Example
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"width": 240,
|
|
192
|
+
"height": 240,
|
|
193
|
+
"fps": 60,
|
|
194
|
+
"options": {
|
|
195
|
+
"fullScreen": true
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## config.json
|
|
203
|
+
|
|
204
|
+
環境別の設定ファイル。`local`, `dev`, `stg`, `prd`, `all` に分離。
|
|
205
|
+
|
|
206
|
+
### Structure
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"local": { "api": { "endPoint": "/" }, "content": { "endPoint": "/" } },
|
|
211
|
+
"dev": { "api": { "endPoint": "/" }, "content": { "endPoint": "/" } },
|
|
212
|
+
"stg": { "api": { "endPoint": "/" }, "content": { "endPoint": "/" } },
|
|
213
|
+
"prd": { "api": { "endPoint": "https://..." }, "content": { "endPoint": "https://..." } },
|
|
214
|
+
"all": { /* 全環境共通 */ }
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `all` Properties (全環境共通)
|
|
219
|
+
|
|
220
|
+
| Property | Type | Default | Description |
|
|
221
|
+
|----------|------|---------|-------------|
|
|
222
|
+
| `defaultTop` | string | "top" | ページトップのView名 |
|
|
223
|
+
| `spa` | boolean | true | SPA (URLでシーン制御) |
|
|
224
|
+
| `loading.callback` | string | "Loading" | ローディング画面のコールバッククラス。start/end関数が呼ばれる |
|
|
225
|
+
| `gotoView.callback` | string/array | ["callback.Background"] | 画面遷移完了後のコールバッククラス。execute関数がasync/awaitで呼ばれる |
|
|
226
|
+
|
|
227
|
+
### `platform` Property
|
|
228
|
+
|
|
229
|
+
ビルド時の`--platform`値がセットされる。値: `macos`, `windows`, `linux`, `ios`, `android`, `web`
|
|
230
|
+
|
|
231
|
+
### Config Access in Code
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { config } from "@/config/Config";
|
|
235
|
+
|
|
236
|
+
const endpoint = config.api.endPoint;
|
|
237
|
+
const stageWidth = config.stage.width;
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## routing.json
|
|
243
|
+
|
|
244
|
+
ルーティング設定。トッププロパティは英数字・スラッシュ。スラッシュをキーにCamelCaseでViewクラスにアクセス。
|
|
245
|
+
|
|
246
|
+
### Routing Example
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"quest/list": {
|
|
251
|
+
"requests": []
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
→ `https://example.com/quest/list` でアクセス可能。`QuestListView`クラスがセットされる。
|
|
257
|
+
|
|
258
|
+
### Cluster Pattern (共通リクエストの再利用)
|
|
259
|
+
|
|
260
|
+
`@`プレフィックスで共通リクエスト群を定義し、他のルートから参照:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"@sample": {
|
|
265
|
+
"requests": [
|
|
266
|
+
{
|
|
267
|
+
"type": "content",
|
|
268
|
+
"path": "{{ content.endPoint }}content/sample.json",
|
|
269
|
+
"name": "MainContent",
|
|
270
|
+
"cache": true
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
},
|
|
274
|
+
"top": {
|
|
275
|
+
"requests": [
|
|
276
|
+
{ "type": "cluster", "path": "@sample" },
|
|
277
|
+
{ "type": "json", "path": "{{ api.endPoint }}api/top.json", "name": "TopText" }
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Second Level Properties
|
|
284
|
+
|
|
285
|
+
| Property | Type | Default | Description |
|
|
286
|
+
|----------|------|---------|-------------|
|
|
287
|
+
| `private` | boolean | false | true時、URLアクセスするとTopViewが読み込まれる |
|
|
288
|
+
| `requests` | array | null | Viewバインド前に実行するリクエスト群 |
|
|
289
|
+
|
|
290
|
+
### Request Properties
|
|
291
|
+
|
|
292
|
+
| Property | Type | Default | Description |
|
|
293
|
+
|----------|------|---------|-------------|
|
|
294
|
+
| `type` | string | "content" | `json`, `content`, `custom`, `cluster` |
|
|
295
|
+
| `path` | string | "" | `{{***}}`でconfig変数を参照可能。`@`プレフィックスでcluster参照 |
|
|
296
|
+
| `name` | string | "" | responseのキー名。`app.getResponse().get("key")` |
|
|
297
|
+
| `cache` | boolean | false | キャッシュ有効。`app.getCache().get("key")` |
|
|
298
|
+
| `callback` | string/array | null | リクエスト完了後のコールバッククラス。execute関数が呼ばれる |
|
|
299
|
+
| `class` | string | "" | custom type時のリクエスト実行クラス |
|
|
300
|
+
| `access` | string | "public" | custom type時の関数アクセス (`public`/`static`) |
|
|
301
|
+
| `method` | string | "" | custom type時の関数名 |
|
|
302
|
+
|
|
303
|
+
### Request Types
|
|
304
|
+
|
|
305
|
+
- **`json`**: URLからJSONを取得
|
|
306
|
+
- **`content`**: Animation Toolコンテンツを取得
|
|
307
|
+
- **`custom`**: 指定クラスのメソッドを実行
|
|
308
|
+
- **`cluster`**: `@`プレフィックスの共通リクエスト群を参照
|
|
309
|
+
|
|
310
|
+
### Data Access
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// responseデータ (画面遷移で初期化される)
|
|
314
|
+
const data = app.getResponse().get("HomeText");
|
|
315
|
+
|
|
316
|
+
// cacheデータ (画面遷移しても保持される)
|
|
317
|
+
const cached = app.getCache().get("MainContent");
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Static Files
|
|
323
|
+
|
|
324
|
+
### mock/ Directory
|
|
325
|
+
|
|
326
|
+
ローカル開発用モックデータ。`http://localhost:5173/***`でアクセス可能。`routing.json`のパスと重複しないよう注意。
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
mock/
|
|
330
|
+
├── api/ # APIモック (JSON)
|
|
331
|
+
├── content/ # Animation Toolコンテンツモック
|
|
332
|
+
└── img/ # 画像モック
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### file/ Directory
|
|
336
|
+
|
|
337
|
+
Animation Toolで作成した`.n2d`ファイルを格納。バージョン管理可能。
|
|
338
|
+
|
|
339
|
+
### assets/ Directory
|
|
340
|
+
|
|
341
|
+
ビルド時にバンドルに含める静的アセット。
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// 画像インポート
|
|
345
|
+
import logoImage from "@/assets/logo.png?inline";
|
|
346
|
+
|
|
347
|
+
// JSONインポート
|
|
348
|
+
import animation from "@/assets/animation.json";
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
| 項目 | assets | mock |
|
|
352
|
+
|------|--------|------|
|
|
353
|
+
| 用途 | バンドルに含める | 開発サーバーで配信 |
|
|
354
|
+
| アクセス | importで取得 | URL経由でfetch |
|
|
355
|
+
| ビルド | バンドルに含まれる | 含まれない |
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## @types/ Directory
|
|
360
|
+
|
|
361
|
+
グローバルな型定義ファイル (.d.ts)。`Window`インターフェースの拡張等。アプリケーション固有のインターフェースは`src/interface/`に配置。
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
# Interface Definitions
|
|
366
|
+
|
|
367
|
+
TypeScriptインターフェース定義。Clean Architecture原則に従い、各層の依存関係を抽象化。
|
|
368
|
+
|
|
369
|
+
## Rules
|
|
370
|
+
|
|
371
|
+
- 命名規則: `I` プレフィックスを使用 (例: `IDraggable`, `ITextField`)
|
|
372
|
+
- 必要最小限のプロパティのみ定義
|
|
373
|
+
- `any`型を禁止、常に明示的な型を使用
|
|
374
|
+
- JSDocコメントを追加
|
|
375
|
+
|
|
376
|
+
## Interface Categories
|
|
377
|
+
|
|
378
|
+
### 1. UI関連 (コンポーネントの振る舞い)
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// IDraggable.ts - ドラッグ可能なオブジェクト
|
|
382
|
+
export interface IDraggable {
|
|
383
|
+
startDrag(): void;
|
|
384
|
+
stopDrag(): void;
|
|
385
|
+
}
|
|
386
|
+
// 使用: HomeBtnMolecule, HomeContent
|
|
387
|
+
|
|
388
|
+
// ITextField.ts - テキストフィールドの基本プロパティ
|
|
389
|
+
export interface ITextField {
|
|
390
|
+
width: number;
|
|
391
|
+
x: number;
|
|
392
|
+
}
|
|
393
|
+
// 使用: TextAtom, CenterTextFieldUseCase
|
|
394
|
+
|
|
395
|
+
// ITextFieldProps.ts - テキストフィールドの詳細プロパティ設定
|
|
396
|
+
// 使用: TextAtomのコンストラクタ
|
|
397
|
+
|
|
398
|
+
// ITextFieldType.ts - テキストフィールドタイプ
|
|
399
|
+
// ITextFieldAutoSize.ts - テキストフィールドオートサイズ
|
|
400
|
+
// ITextFormatAlign.ts - テキストフォーマットアライン
|
|
401
|
+
// ITextFormatObject.ts - テキストフォーマットスタイル設定
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### 2. データ転送オブジェクト (DTO)
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
// IHomeTextResponse.ts - APIレスポンス型
|
|
408
|
+
export interface IHomeTextResponse {
|
|
409
|
+
word: string;
|
|
410
|
+
}
|
|
411
|
+
// 使用: HomeTextRepository.get()の戻り値型
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 3. 画面遷移関連
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// IViewName.ts - 利用可能な画面名 (Union型)
|
|
418
|
+
export type ViewName = "top" | "home";
|
|
419
|
+
// 使用: NavigateToViewUseCase
|
|
420
|
+
// 新画面追加時はこの型にも追加が必要
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### 4. 設定関連
|
|
424
|
+
|
|
425
|
+
- `IConfig.ts` - アプリケーション全体設定
|
|
426
|
+
- `IStage.ts` - ステージ設定 (`stage.json`の型)
|
|
427
|
+
- `IRouting.ts` - ルーティング設定
|
|
428
|
+
- `IGotoView.ts` - 画面遷移オプション
|
|
429
|
+
- `IRequest.ts` / `IRequestType.ts` - HTTPリクエスト設定
|
|
430
|
+
- `IOptions.ts` - オプション設定
|
|
431
|
+
|
|
432
|
+
## Interface Template
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
/**
|
|
436
|
+
* @description [インターフェースの説明]
|
|
437
|
+
* [Interface description]
|
|
438
|
+
*
|
|
439
|
+
* @interface
|
|
440
|
+
*/
|
|
441
|
+
export interface IYourInterface
|
|
442
|
+
{
|
|
443
|
+
/**
|
|
444
|
+
* @description [プロパティの説明]
|
|
445
|
+
* [Property description]
|
|
446
|
+
*
|
|
447
|
+
* @type {type}
|
|
448
|
+
*/
|
|
449
|
+
propertyName: type;
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* @description [メソッドの説明]
|
|
453
|
+
* [Method description]
|
|
454
|
+
*
|
|
455
|
+
* @param {ParamType} paramName
|
|
456
|
+
* @return {ReturnType}
|
|
457
|
+
* @method
|
|
458
|
+
*/
|
|
459
|
+
methodName(paramName: ParamType): ReturnType;
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Best Practices
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
// OK: 必要最小限
|
|
467
|
+
export interface ITextField {
|
|
468
|
+
width: number;
|
|
469
|
+
x: number;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// NG: 不要なプロパティ
|
|
473
|
+
export interface ITextField {
|
|
474
|
+
width: number;
|
|
475
|
+
height: number; // 使用しない
|
|
476
|
+
x: number;
|
|
477
|
+
y: number; // 使用しない
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// OK: 型の再利用
|
|
481
|
+
export interface IPosition { x: number; y: number; }
|
|
482
|
+
export interface ITextField extends IPosition { width: number; }
|
|
483
|
+
|
|
484
|
+
// OK: 明示的型
|
|
485
|
+
export interface IHomeTextResponse { word: string; }
|
|
486
|
+
|
|
487
|
+
// NG: any型
|
|
488
|
+
export interface IHomeTextResponse { word: any; }
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Adding New Interface Steps
|
|
492
|
+
|
|
493
|
+
1. 目的を明確にする(どの層の依存を抽象化するか)
|
|
494
|
+
2. `I`プレフィックスの命名規則に従う
|
|
495
|
+
3. 必要最小限のプロパティ/メソッドのみ定義
|
|
496
|
+
4. JSDocコメントを追加
|
|
497
|
+
5. 使用箇所を明記
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
# Model Layer (Application / Domain / Infrastructure)
|
|
502
|
+
|
|
503
|
+
Model層はビジネスロジックとデータアクセスを担当。Clean Architectureに基づき3層で構成。
|
|
504
|
+
|
|
505
|
+
## Directory Structure
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
model/
|
|
509
|
+
├── application/ # UseCase (ビジネスロジック)
|
|
510
|
+
│ └── {screen}/
|
|
511
|
+
│ └── usecase/
|
|
512
|
+
│ └── {Action}UseCase.ts
|
|
513
|
+
├── domain/ # コアビジネスロジック
|
|
514
|
+
│ └── {feature}/
|
|
515
|
+
│ ├── {Feature}.ts
|
|
516
|
+
│ └── service/
|
|
517
|
+
│ └── {Feature}{Action}Service.ts
|
|
518
|
+
└── infrastructure/ # Repository (データアクセス)
|
|
519
|
+
└── repository/
|
|
520
|
+
└── {Resource}Repository.ts
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Layer Dependencies
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
Application → Domain (uses)
|
|
527
|
+
Application → Infrastructure (calls)
|
|
528
|
+
Domain → 依存なし (最も安定)
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Application Layer (UseCase)
|
|
534
|
+
|
|
535
|
+
### Rules
|
|
536
|
+
|
|
537
|
+
- 1つのユーザーアクションに対して1つのUseCaseクラスを作成
|
|
538
|
+
- エントリーポイントは `execute` メソッドに統一
|
|
539
|
+
- インターフェースに依存し、具象クラスに依存しない
|
|
540
|
+
- 画面ごとにディレクトリを作成: `application/{screen}/usecase/`
|
|
541
|
+
|
|
542
|
+
### UseCase Template
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
import type { IYourInterface } from "@/interface/IYourInterface";
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* @description [UseCaseの説明]
|
|
549
|
+
* [UseCase description]
|
|
550
|
+
*
|
|
551
|
+
* @class
|
|
552
|
+
*/
|
|
553
|
+
export class YourUseCase
|
|
554
|
+
{
|
|
555
|
+
/**
|
|
556
|
+
* @description [処理の説明]
|
|
557
|
+
* [Process description]
|
|
558
|
+
*
|
|
559
|
+
* @param {IYourInterface} param
|
|
560
|
+
* @return {void}
|
|
561
|
+
* @method
|
|
562
|
+
* @public
|
|
563
|
+
*/
|
|
564
|
+
execute (param: IYourInterface): void
|
|
565
|
+
{
|
|
566
|
+
// ビジネスロジックを実装
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### UseCase with Repository
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
import { YourRepository } from "@/model/infrastructure/repository/YourRepository";
|
|
575
|
+
import type { IYourResponse } from "@/interface/IYourResponse";
|
|
576
|
+
|
|
577
|
+
export class FetchDataUseCase
|
|
578
|
+
{
|
|
579
|
+
async execute (): Promise<IYourResponse>
|
|
580
|
+
{
|
|
581
|
+
try {
|
|
582
|
+
const data = await YourRepository.get();
|
|
583
|
+
// ビジネスロジック: データの加工・検証
|
|
584
|
+
return data;
|
|
585
|
+
} catch (error) {
|
|
586
|
+
console.error('Failed to fetch data:', error);
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### UseCase Composition (複数UseCaseの組み合わせ)
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
export class InitializeScreenUseCase
|
|
597
|
+
{
|
|
598
|
+
private readonly fetchUseCase: FetchDataUseCase;
|
|
599
|
+
private readonly centerUseCase: CenterTextFieldUseCase;
|
|
600
|
+
|
|
601
|
+
constructor ()
|
|
602
|
+
{
|
|
603
|
+
this.fetchUseCase = new FetchDataUseCase();
|
|
604
|
+
this.centerUseCase = new CenterTextFieldUseCase();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async execute (textField: ITextField): Promise<void>
|
|
608
|
+
{
|
|
609
|
+
const data = await this.fetchUseCase.execute();
|
|
610
|
+
this.centerUseCase.execute(textField, stageWidth);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### UseCase Anti-Patterns
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
// NG: 複数の責務
|
|
619
|
+
export class DragUseCase {
|
|
620
|
+
start(target: IDraggable): void { ... }
|
|
621
|
+
stop(target: IDraggable): void { ... }
|
|
622
|
+
validate(target: IDraggable): boolean { ... }
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// OK: 単一の責務
|
|
626
|
+
export class StartDragUseCase {
|
|
627
|
+
execute(target: IDraggable): void { target.startDrag(); }
|
|
628
|
+
}
|
|
629
|
+
export class StopDragUseCase {
|
|
630
|
+
execute(target: IDraggable): void { target.stopDrag(); }
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// NG: 具象クラスに依存
|
|
634
|
+
execute(target: HomeBtnMolecule): void { ... }
|
|
635
|
+
|
|
636
|
+
// OK: インターフェースに依存
|
|
637
|
+
execute(target: IDraggable): void { ... }
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### UseCase Test Template
|
|
641
|
+
|
|
642
|
+
```typescript
|
|
643
|
+
import { StartDragUseCase } from "./StartDragUseCase";
|
|
644
|
+
import type { IDraggable } from "@/interface/IDraggable";
|
|
645
|
+
|
|
646
|
+
describe('StartDragUseCase', () => {
|
|
647
|
+
test('should call startDrag on target', () => {
|
|
648
|
+
const mockDraggable: IDraggable = {
|
|
649
|
+
startDrag: vi.fn(),
|
|
650
|
+
stopDrag: vi.fn()
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
const useCase = new StartDragUseCase();
|
|
654
|
+
useCase.execute(mockDraggable);
|
|
655
|
+
|
|
656
|
+
expect(mockDraggable.startDrag).toHaveBeenCalled();
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## Domain Layer
|
|
664
|
+
|
|
665
|
+
### Rules
|
|
666
|
+
|
|
667
|
+
- アプリケーションのコアビジネスルールを実装
|
|
668
|
+
- 可能な限りフレームワーク非依存(※Next2D描画機能の使用は許容)
|
|
669
|
+
- 純粋関数を心がけ、副作用を最小化
|
|
670
|
+
- 可能な限り不変オブジェクトを使用
|
|
671
|
+
|
|
672
|
+
### Domain Service (Functional Style)
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
/**
|
|
676
|
+
* @description [サービスの説明]
|
|
677
|
+
* [Service description]
|
|
678
|
+
*
|
|
679
|
+
* @param {ParamType} param
|
|
680
|
+
* @return {ReturnType}
|
|
681
|
+
*/
|
|
682
|
+
export const execute = (param: ParamType): ReturnType =>
|
|
683
|
+
{
|
|
684
|
+
// ビジネスルールの実装
|
|
685
|
+
return result;
|
|
686
|
+
};
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Domain Class (Class-based Style)
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
import { Shape, stage } from "@next2d/display";
|
|
693
|
+
import { Event } from "@next2d/events";
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* @description [ドメインクラスの説明]
|
|
697
|
+
* [Domain class description]
|
|
698
|
+
*
|
|
699
|
+
* @class
|
|
700
|
+
*/
|
|
701
|
+
export class YourDomainClass
|
|
702
|
+
{
|
|
703
|
+
public readonly shape: Shape;
|
|
704
|
+
|
|
705
|
+
constructor ()
|
|
706
|
+
{
|
|
707
|
+
this.shape = new Shape();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
execute (): void
|
|
711
|
+
{
|
|
712
|
+
// コアビジネスロジック
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### Domain Callback Pattern
|
|
718
|
+
|
|
719
|
+
`config.json`の`gotoView.callback`で設定されたクラスは、画面遷移完了後に`execute()`が呼び出される。
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
// config.json: "gotoView": { "callback": ["domain.callback.Background"] }
|
|
723
|
+
// → model/domain/callback/Background.ts の execute() が呼ばれる
|
|
724
|
+
|
|
725
|
+
export class Background
|
|
726
|
+
{
|
|
727
|
+
execute (): void
|
|
728
|
+
{
|
|
729
|
+
const context = app.getContext();
|
|
730
|
+
const view = context.view;
|
|
731
|
+
if (!view) return;
|
|
732
|
+
view.addChildAt(this.shape, 0);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Domain Directory Extensions (将来の拡張)
|
|
738
|
+
|
|
739
|
+
```
|
|
740
|
+
domain/
|
|
741
|
+
├── callback/ # コールバック処理
|
|
742
|
+
├── service/ # ドメインサービス
|
|
743
|
+
├── entity/ # エンティティ (ID持ち)
|
|
744
|
+
└── value-object/ # 値オブジェクト
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## Infrastructure Layer (Repository)
|
|
750
|
+
|
|
751
|
+
### Rules
|
|
752
|
+
|
|
753
|
+
- 外部システムとの連携(API、DB等)を担当
|
|
754
|
+
- `any`型を避け、明示的な型定義を使用
|
|
755
|
+
- すべての外部アクセスでtry-catchを実装
|
|
756
|
+
- エンドポイントは`config`から取得(ハードコーディング禁止)
|
|
757
|
+
- シンプルな場合は静的メソッド、状態を持つ場合はインスタンスメソッド
|
|
758
|
+
|
|
759
|
+
### Repository Template
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
import type { IYourResponse } from "@/interface/IYourResponse";
|
|
763
|
+
import { config } from "@/config/Config";
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* @description [Repositoryの説明]
|
|
767
|
+
* [Repository description]
|
|
768
|
+
*
|
|
769
|
+
* @class
|
|
770
|
+
*/
|
|
771
|
+
export class YourRepository
|
|
772
|
+
{
|
|
773
|
+
/**
|
|
774
|
+
* @description [処理の説明]
|
|
775
|
+
* [Process description]
|
|
776
|
+
*
|
|
777
|
+
* @param {string} id
|
|
778
|
+
* @return {Promise<IYourResponse>}
|
|
779
|
+
* @static
|
|
780
|
+
* @throws {Error} [エラーの説明]
|
|
781
|
+
*/
|
|
782
|
+
static async get (id: string): Promise<IYourResponse>
|
|
783
|
+
{
|
|
784
|
+
try {
|
|
785
|
+
const response = await fetch(
|
|
786
|
+
`${config.api.endPoint}api/your-endpoint/${id}`
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
if (!response.ok) {
|
|
790
|
+
throw new Error(
|
|
791
|
+
`HTTP error! status: ${response.status}`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return await response.json() as IYourResponse;
|
|
796
|
+
|
|
797
|
+
} catch (error) {
|
|
798
|
+
console.error('Failed to fetch data:', error);
|
|
799
|
+
throw error;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### Repository with Cache
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
export class CachedRepository
|
|
809
|
+
{
|
|
810
|
+
private static cache: Map<string, { data: Data; timestamp: number }> = new Map();
|
|
811
|
+
private static readonly CACHE_TTL = 60000;
|
|
812
|
+
|
|
813
|
+
static async get (id: string): Promise<Data>
|
|
814
|
+
{
|
|
815
|
+
const cached = this.cache.get(id);
|
|
816
|
+
const now = Date.now();
|
|
817
|
+
|
|
818
|
+
if (cached && (now - cached.timestamp) < this.CACHE_TTL) {
|
|
819
|
+
return cached.data;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const response = await fetch(`${config.api.endPoint}api/${id}`);
|
|
823
|
+
const data = await response.json();
|
|
824
|
+
this.cache.set(id, { data, timestamp: now });
|
|
825
|
+
|
|
826
|
+
return data;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
### Repository Anti-Patterns
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
// NG: any型
|
|
835
|
+
static async get(): Promise<any> { ... }
|
|
836
|
+
|
|
837
|
+
// OK: 明示的型定義
|
|
838
|
+
static async get(): Promise<IHomeTextResponse> { ... }
|
|
839
|
+
|
|
840
|
+
// NG: エラーハンドリングなし
|
|
841
|
+
static async get(): Promise<Data> {
|
|
842
|
+
const response = await fetch(...);
|
|
843
|
+
return await response.json();
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// NG: ハードコーディング
|
|
847
|
+
const response = await fetch('https://example.com/api/data.json');
|
|
848
|
+
|
|
849
|
+
// OK: configから取得
|
|
850
|
+
const response = await fetch(`${config.api.endPoint}api/data.json`);
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
### routing.json Custom Request Pattern
|
|
854
|
+
|
|
855
|
+
`routing.json`でRepositoryを直接呼び出すことも可能:
|
|
856
|
+
|
|
857
|
+
```json
|
|
858
|
+
{
|
|
859
|
+
"home": {
|
|
860
|
+
"requests": [
|
|
861
|
+
{
|
|
862
|
+
"type": "custom",
|
|
863
|
+
"class": "infrastructure.repository.HomeTextRepository",
|
|
864
|
+
"access": "static",
|
|
865
|
+
"method": "get",
|
|
866
|
+
"name": "HomeText",
|
|
867
|
+
"cache": true
|
|
868
|
+
}
|
|
869
|
+
]
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
取得したデータは `app.getResponse().get("HomeText")` でアクセス可能。
|
|
875
|
+
|
|
876
|
+
---
|
|
877
|
+
|
|
878
|
+
# UI Layer (Components / Animation / Content)
|
|
879
|
+
|
|
880
|
+
## Directory Structure
|
|
881
|
+
|
|
882
|
+
```
|
|
883
|
+
ui/
|
|
884
|
+
├── animation/ # アニメーション定義
|
|
885
|
+
│ └── {screen}/
|
|
886
|
+
│ └── {Component}{Action}Animation.ts
|
|
887
|
+
├── component/
|
|
888
|
+
│ ├── atom/ # 最小単位 (Button, Text等)
|
|
889
|
+
│ ├── molecule/ # Atomの組み合わせ
|
|
890
|
+
│ ├── organism/ # 複数Moleculeの組み合わせ (拡張用)
|
|
891
|
+
│ ├── page/ # ページコンポーネント
|
|
892
|
+
│ │ └── {screen}/
|
|
893
|
+
│ └── template/ # ページテンプレート (拡張用)
|
|
894
|
+
└── content/ # Animation Tool生成コンテンツ
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
## Rules (共通)
|
|
898
|
+
|
|
899
|
+
- 各コンポーネントは単一の責務のみ
|
|
900
|
+
- ビジネスロジックやデータアクセスに直接依存しない
|
|
901
|
+
- データはViewModelから引数で受け取る
|
|
902
|
+
- インターフェースを実装して抽象化する
|
|
903
|
+
|
|
904
|
+
---
|
|
905
|
+
|
|
906
|
+
## DisplayObject 配置の基本方針(中心基準点パターン)
|
|
907
|
+
|
|
908
|
+
Next2D の座標系は画面左上 (0, 0) が基準点。Shape や Sprite をそのまま配置すると、スケールや回転アニメーション時に座標がずれる。
|
|
909
|
+
**基本方針として、子要素を親 Sprite に中心配置する。**
|
|
910
|
+
|
|
911
|
+
### パターン: Shape を Sprite に中心配置
|
|
912
|
+
|
|
913
|
+
```typescript
|
|
914
|
+
const sprite = new Sprite();
|
|
915
|
+
const shape = new Shape();
|
|
916
|
+
|
|
917
|
+
// 画像の場合、Retina対応でスケールを設定
|
|
918
|
+
shape.scaleX = shape.scaleY = 0.5;
|
|
919
|
+
|
|
920
|
+
// スケール設定後に中心配置
|
|
921
|
+
shape.x = -shape.width / 2;
|
|
922
|
+
shape.y = -shape.height / 2;
|
|
923
|
+
|
|
924
|
+
sprite.addChild(shape);
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
### パターン: Sprite を Sprite に中心配置
|
|
928
|
+
|
|
929
|
+
```typescript
|
|
930
|
+
const parent = new Sprite();
|
|
931
|
+
const child = new Sprite();
|
|
932
|
+
|
|
933
|
+
// 子要素のサイズ確定後に中心配置
|
|
934
|
+
child.x = -child.width / 2;
|
|
935
|
+
child.y = -child.height / 2;
|
|
936
|
+
|
|
937
|
+
parent.addChild(child);
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
### なぜ中心配置が必要か
|
|
941
|
+
|
|
942
|
+
- スケール・回転は DisplayObject の (0, 0) を基点に実行される
|
|
943
|
+
- 中心配置しないと、回転・拡縮時に意図しない位置ずれが発生する
|
|
944
|
+
- 中心配置により、親 Sprite の (x, y) がそのまま表示上の中心座標になる
|
|
945
|
+
|
|
946
|
+
**注意:** すべてのケースで必須ではないが、アニメーション対象の要素には基本的にこのパターンを適用する。
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
## Atomic Design Hierarchy
|
|
951
|
+
|
|
952
|
+
### Atom (原子) - 最小単位
|
|
953
|
+
|
|
954
|
+
最も基本的なUI要素。これ以上分割できない。
|
|
955
|
+
|
|
956
|
+
```typescript
|
|
957
|
+
// ButtonAtom: ボタンの基本機能 (enable/disable による連続押下防止機能付き)
|
|
958
|
+
import { Sprite } from "@next2d/display";
|
|
959
|
+
|
|
960
|
+
export class ButtonAtom extends Sprite
|
|
961
|
+
{
|
|
962
|
+
constructor ()
|
|
963
|
+
{
|
|
964
|
+
super();
|
|
965
|
+
this.buttonMode = true;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* @description ボタンを有効化する
|
|
970
|
+
* Enable button
|
|
971
|
+
*
|
|
972
|
+
* @return {void}
|
|
973
|
+
* @method
|
|
974
|
+
* @public
|
|
975
|
+
*/
|
|
976
|
+
enable (): void
|
|
977
|
+
{
|
|
978
|
+
this.mouseEnabled = true;
|
|
979
|
+
this.mouseChildren = true;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* @description ボタンを無効化する
|
|
984
|
+
* Disable button
|
|
985
|
+
*
|
|
986
|
+
* @return {void}
|
|
987
|
+
* @method
|
|
988
|
+
* @public
|
|
989
|
+
*/
|
|
990
|
+
disable (): void
|
|
991
|
+
{
|
|
992
|
+
this.mouseEnabled = false;
|
|
993
|
+
this.mouseChildren = false;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
```typescript
|
|
999
|
+
// TextAtom: テキスト表示の基本機能
|
|
1000
|
+
import { TextField } from "@next2d/text";
|
|
1001
|
+
import type { ITextField } from "@/interface/ITextField";
|
|
1002
|
+
import type { ITextFormatObject } from "@/interface/ITextFormatObject";
|
|
1003
|
+
|
|
1004
|
+
export class TextAtom extends TextField implements ITextField
|
|
1005
|
+
{
|
|
1006
|
+
constructor (
|
|
1007
|
+
text: string = "",
|
|
1008
|
+
props: any | null = null,
|
|
1009
|
+
format_object: ITextFormatObject | null = null
|
|
1010
|
+
) {
|
|
1011
|
+
super();
|
|
1012
|
+
// プロパティ設定、フォーマット設定
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
### ボタン連続押下防止パターン
|
|
1018
|
+
|
|
1019
|
+
ボタン押下後に処理が完了するまで連続押下を防止したい場合に使えるパターン。`ButtonAtom` の `disable()` / `enable()` を利用して `mouseEnabled` と `mouseChildren` を制御する。
|
|
1020
|
+
|
|
1021
|
+
連続押下を防止するかどうかはユースケースに応じて判断する。画面遷移やAPI通信など多重実行を避けたい処理では有効。
|
|
1022
|
+
|
|
1023
|
+
#### View でのイベント登録パターン
|
|
1024
|
+
|
|
1025
|
+
```typescript
|
|
1026
|
+
// View: ボタン押下時にViewModelのハンドラを呼び出す
|
|
1027
|
+
async initialize (): Promise<void>
|
|
1028
|
+
{
|
|
1029
|
+
const btn = new YourBtnMolecule();
|
|
1030
|
+
btn.addEventListener(PointerEvent.POINTER_DOWN, (event) => {
|
|
1031
|
+
this.vm.handleButtonTap(event);
|
|
1032
|
+
});
|
|
1033
|
+
this.addChild(btn);
|
|
1034
|
+
}
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
#### ViewModel での連続押下防止パターン
|
|
1038
|
+
|
|
1039
|
+
```typescript
|
|
1040
|
+
// ViewModel: disable → 処理 → enable で連続押下を防止
|
|
1041
|
+
handleButtonTap (event: PointerEvent): void
|
|
1042
|
+
{
|
|
1043
|
+
// ボタンを即座に無効化して連続押下を防止
|
|
1044
|
+
const button = event.currentTarget as unknown as ButtonAtom;
|
|
1045
|
+
button.disable();
|
|
1046
|
+
|
|
1047
|
+
// 処理実行 (画面遷移、API呼び出し、アニメーション等)
|
|
1048
|
+
this.someUseCase.execute();
|
|
1049
|
+
|
|
1050
|
+
// 処理完了後にボタンを再有効化 (画面遷移の場合は不要)
|
|
1051
|
+
button.enable();
|
|
1052
|
+
}
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
#### 非同期処理での連続押下防止パターン
|
|
1056
|
+
|
|
1057
|
+
```typescript
|
|
1058
|
+
// ViewModel: 非同期処理の完了を待ってから再有効化
|
|
1059
|
+
async handleButtonTap (event: PointerEvent): Promise<void>
|
|
1060
|
+
{
|
|
1061
|
+
const button = event.currentTarget as unknown as ButtonAtom;
|
|
1062
|
+
button.disable();
|
|
1063
|
+
|
|
1064
|
+
try {
|
|
1065
|
+
await this.fetchDataUseCase.execute();
|
|
1066
|
+
} finally {
|
|
1067
|
+
// エラー時も必ず再有効化
|
|
1068
|
+
button.enable();
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
#### アニメーション完了後に再有効化するパターン
|
|
1074
|
+
|
|
1075
|
+
```typescript
|
|
1076
|
+
// ViewModel: アニメーション完了コールバックで再有効化
|
|
1077
|
+
handleButtonTap (event: PointerEvent): void
|
|
1078
|
+
{
|
|
1079
|
+
const button = event.currentTarget as unknown as ButtonAtom;
|
|
1080
|
+
button.disable();
|
|
1081
|
+
|
|
1082
|
+
new SomeAnimation(button, () => {
|
|
1083
|
+
// アニメーション完了後に再有効化
|
|
1084
|
+
button.enable();
|
|
1085
|
+
}).start();
|
|
1086
|
+
}
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
#### 連続押下を許可するケース
|
|
1090
|
+
|
|
1091
|
+
以下のケースでは `disable()` / `enable()` を使わず、連続押下を許可する:
|
|
1092
|
+
|
|
1093
|
+
- **インクリメント/デクリメントボタン**: 数量の増減など、連打を前提とした操作
|
|
1094
|
+
- **連射系ゲーム操作**: 連続タップがゲームメカニクスの一部である場合
|
|
1095
|
+
- **トグルボタン**: ON/OFF を素早く切り替える必要がある場合
|
|
1096
|
+
|
|
1097
|
+
### Molecule (分子) - Atomの組み合わせ
|
|
1098
|
+
|
|
1099
|
+
複数のAtomを組み合わせた、特定の用途向けコンポーネント。
|
|
1100
|
+
|
|
1101
|
+
```typescript
|
|
1102
|
+
import { ButtonAtom } from "../atom/ButtonAtom";
|
|
1103
|
+
import { HomeContent } from "@/ui/content/HomeContent";
|
|
1104
|
+
import type { IDraggable } from "@/interface/IDraggable";
|
|
1105
|
+
|
|
1106
|
+
export class HomeBtnMolecule extends ButtonAtom implements IDraggable
|
|
1107
|
+
{
|
|
1108
|
+
private readonly homeContent: HomeContent;
|
|
1109
|
+
|
|
1110
|
+
constructor ()
|
|
1111
|
+
{
|
|
1112
|
+
super();
|
|
1113
|
+
this.homeContent = new HomeContent();
|
|
1114
|
+
this.addChild(this.homeContent);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// IDraggableメソッド(startDrag/stopDrag)はMovieClipContentの親クラスから継承
|
|
1118
|
+
}
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
```typescript
|
|
1122
|
+
import { ButtonAtom } from "../atom/ButtonAtom";
|
|
1123
|
+
import { TextAtom } from "../atom/TextAtom";
|
|
1124
|
+
|
|
1125
|
+
export class TopBtnMolecule extends ButtonAtom
|
|
1126
|
+
{
|
|
1127
|
+
constructor (text: string) // ViewModelからテキストを受け取る
|
|
1128
|
+
{
|
|
1129
|
+
super();
|
|
1130
|
+
const textField = new TextAtom(text, { autoSize: "center" });
|
|
1131
|
+
this.addChild(textField);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
playEntrance (callback: () => void): void
|
|
1135
|
+
{
|
|
1136
|
+
// アニメーション再生
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
### Organism (有機体) - 拡張用
|
|
1142
|
+
|
|
1143
|
+
複数のMoleculeを組み合わせた大きな機能単位。必要に応じて実装。
|
|
1144
|
+
|
|
1145
|
+
### Page (ページ)
|
|
1146
|
+
|
|
1147
|
+
画面全体を構成するコンポーネント。ViewからPageを配置し、PageがMolecule/Atomを組み合わせて画面構築。
|
|
1148
|
+
|
|
1149
|
+
### Template (テンプレート) - 拡張用
|
|
1150
|
+
|
|
1151
|
+
ページのレイアウト構造を定義。必要に応じて実装。
|
|
1152
|
+
|
|
1153
|
+
## Component Creation Templates
|
|
1154
|
+
|
|
1155
|
+
### New Atom
|
|
1156
|
+
|
|
1157
|
+
```typescript
|
|
1158
|
+
import { Sprite } from "@next2d/display";
|
|
1159
|
+
|
|
1160
|
+
export class YourAtom extends Sprite
|
|
1161
|
+
{
|
|
1162
|
+
constructor (props: any = null)
|
|
1163
|
+
{
|
|
1164
|
+
super();
|
|
1165
|
+
if (props) {
|
|
1166
|
+
Object.assign(this, props);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
### New Molecule
|
|
1173
|
+
|
|
1174
|
+
```typescript
|
|
1175
|
+
import { ButtonAtom } from "../atom/ButtonAtom";
|
|
1176
|
+
import { TextAtom } from "../atom/TextAtom";
|
|
1177
|
+
|
|
1178
|
+
export class YourMolecule extends ButtonAtom
|
|
1179
|
+
{
|
|
1180
|
+
constructor ()
|
|
1181
|
+
{
|
|
1182
|
+
super();
|
|
1183
|
+
const text = new TextAtom("Click me");
|
|
1184
|
+
this.addChild(text);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
## Anti-Patterns
|
|
1190
|
+
|
|
1191
|
+
```typescript
|
|
1192
|
+
// NG: コンポーネント内でデータ取得
|
|
1193
|
+
export class BadAtom extends TextField {
|
|
1194
|
+
async fetchDataFromAPI() { ... } // NG: データ取得は別層の責務
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// NG: 直接APIアクセス
|
|
1198
|
+
constructor() {
|
|
1199
|
+
const data = await Repository.get(); // NG
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// OK: ViewModelからデータを受け取る
|
|
1203
|
+
constructor(text: string) {
|
|
1204
|
+
this.textField = new TextAtom(text); // OK
|
|
1205
|
+
}
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
---
|
|
1209
|
+
|
|
1210
|
+
## Animation
|
|
1211
|
+
|
|
1212
|
+
アニメーションロジックをコンポーネントから分離し、再利用性と保守性を向上。
|
|
1213
|
+
|
|
1214
|
+
### Naming Convention
|
|
1215
|
+
|
|
1216
|
+
`{Component}{Action}Animation.ts` (例: `TopBtnShowAnimation.ts`)
|
|
1217
|
+
|
|
1218
|
+
### Animation Types
|
|
1219
|
+
|
|
1220
|
+
- **Show Animation**: 画面表示時のアニメーション
|
|
1221
|
+
- **Exit Animation**: 画面遷移時のアニメーション
|
|
1222
|
+
- **Interaction Animation**: ユーザー操作に対するアニメーション
|
|
1223
|
+
|
|
1224
|
+
### Animation Class Template
|
|
1225
|
+
|
|
1226
|
+
```typescript
|
|
1227
|
+
import type { Sprite } from "@next2d/display";
|
|
1228
|
+
import { Tween, Easing, type Job } from "@next2d/ui";
|
|
1229
|
+
import { Event } from "@next2d/events";
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* @description [アニメーションの説明]
|
|
1233
|
+
* [Animation description]
|
|
1234
|
+
*
|
|
1235
|
+
* @class
|
|
1236
|
+
* @public
|
|
1237
|
+
*/
|
|
1238
|
+
export class YourAnimation
|
|
1239
|
+
{
|
|
1240
|
+
private readonly _job: Job;
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* @param {Sprite} sprite - アニメーション対象
|
|
1244
|
+
* @param {() => void} callback - 完了時コールバック
|
|
1245
|
+
* @constructor
|
|
1246
|
+
* @public
|
|
1247
|
+
*/
|
|
1248
|
+
constructor (
|
|
1249
|
+
sprite: Sprite,
|
|
1250
|
+
callback?: () => void
|
|
1251
|
+
) {
|
|
1252
|
+
// 初期状態設定
|
|
1253
|
+
sprite.alpha = 0;
|
|
1254
|
+
|
|
1255
|
+
// Tween設定: (対象, 開始値, 終了値, 秒数, 遅延秒数, イージング)
|
|
1256
|
+
this._job = Tween.add(sprite,
|
|
1257
|
+
{ "alpha": 0 },
|
|
1258
|
+
{ "alpha": 1 },
|
|
1259
|
+
0.5, 1, Easing.outQuad
|
|
1260
|
+
);
|
|
1261
|
+
|
|
1262
|
+
if (callback) {
|
|
1263
|
+
this._job.addEventListener(Event.COMPLETE, callback);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* @description アニメーション開始
|
|
1269
|
+
* Start animation
|
|
1270
|
+
*
|
|
1271
|
+
* @method
|
|
1272
|
+
* @public
|
|
1273
|
+
*/
|
|
1274
|
+
start (): void
|
|
1275
|
+
{
|
|
1276
|
+
this._job.start();
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
### Component-Animation Coordination
|
|
1282
|
+
|
|
1283
|
+
```typescript
|
|
1284
|
+
// component/molecule/TopBtnMolecule.ts
|
|
1285
|
+
import { TopBtnShowAnimation } from "@/ui/animation/top/TopBtnShowAnimation";
|
|
1286
|
+
|
|
1287
|
+
export class TopBtnMolecule extends ButtonAtom {
|
|
1288
|
+
playShow(callback: () => void): void {
|
|
1289
|
+
new TopBtnShowAnimation(this, callback).start();
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
---
|
|
1295
|
+
|
|
1296
|
+
## Content (Animation Tool)
|
|
1297
|
+
|
|
1298
|
+
Animation Toolで作成されたコンテンツをTypeScriptクラスとしてラップ。
|
|
1299
|
+
|
|
1300
|
+
### Content Template
|
|
1301
|
+
|
|
1302
|
+
```typescript
|
|
1303
|
+
import { MovieClipContent } from "@next2d/framework";
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* @description [コンテンツの説明]
|
|
1307
|
+
* [Content description]
|
|
1308
|
+
*
|
|
1309
|
+
* @class
|
|
1310
|
+
* @extends {MovieClipContent}
|
|
1311
|
+
*/
|
|
1312
|
+
export class YourContent extends MovieClipContent
|
|
1313
|
+
{
|
|
1314
|
+
/**
|
|
1315
|
+
* @description Animation Toolのシンボル名を返す
|
|
1316
|
+
* Returns the Animation Tool symbol name
|
|
1317
|
+
*
|
|
1318
|
+
* @return {string}
|
|
1319
|
+
* @readonly
|
|
1320
|
+
*/
|
|
1321
|
+
get namespace (): string
|
|
1322
|
+
{
|
|
1323
|
+
return "YourSymbolName"; // Animation Toolで設定した名前と一致させる
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
### Content with Interface
|
|
1329
|
+
|
|
1330
|
+
```typescript
|
|
1331
|
+
import { MovieClipContent } from "@next2d/framework";
|
|
1332
|
+
import type { IDraggable } from "@/interface/IDraggable";
|
|
1333
|
+
|
|
1334
|
+
export class HomeContent extends MovieClipContent implements IDraggable
|
|
1335
|
+
{
|
|
1336
|
+
get namespace (): string
|
|
1337
|
+
{
|
|
1338
|
+
return "HomeContent";
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// IDraggableメソッド(startDrag/stopDrag)は
|
|
1342
|
+
// MovieClipContentの親クラス(MovieClip)から継承
|
|
1343
|
+
}
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
### Content Creation Steps
|
|
1347
|
+
|
|
1348
|
+
1. Animation Toolでシンボルを作成
|
|
1349
|
+
2. `.n2d`ファイルを`file/`ディレクトリに配置
|
|
1350
|
+
3. Contentクラスを作成 (`namespace`はシンボル名と一致させる)
|
|
1351
|
+
4. Molecule等のコンポーネントで使用
|
|
1352
|
+
|
|
1353
|
+
### Content Rules
|
|
1354
|
+
|
|
1355
|
+
- クラス名とシンボル名を一致させる
|
|
1356
|
+
- アニメーションの制御のみを担当
|
|
1357
|
+
- 必要な機能はインターフェースで定義
|
|
1358
|
+
|
|
1359
|
+
---
|
|
1360
|
+
|
|
1361
|
+
# View / ViewModel (MVVM Pattern)
|
|
1362
|
+
|
|
1363
|
+
## Rules
|
|
1364
|
+
|
|
1365
|
+
- 1画面にView + ViewModelをワンセット作成
|
|
1366
|
+
- ディレクトリ名はキャメルケースの最初のブロック (例: `questList` → `view/quest/`)
|
|
1367
|
+
- Viewは表示構造のみ担当、ビジネスロジックはViewModelに委譲
|
|
1368
|
+
- イベントは必ずViewModelに委譲(View内で完結させない)
|
|
1369
|
+
- ViewModelはインターフェースに依存し、具象クラスに依存しない
|
|
1370
|
+
|
|
1371
|
+
## Lifecycle (実行順序)
|
|
1372
|
+
|
|
1373
|
+
```
|
|
1374
|
+
1. ViewModel インスタンス生成
|
|
1375
|
+
2. ViewModel.initialize() ← ViewModelが先
|
|
1376
|
+
3. View インスタンス生成 (ViewModelを注入)
|
|
1377
|
+
4. View.initialize() ← UIコンポーネントの構築
|
|
1378
|
+
5. View.onEnter() ← 画面表示時の処理
|
|
1379
|
+
(ユーザー操作)
|
|
1380
|
+
6. View.onExit() ← 画面非表示時の処理
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
### View Lifecycle Methods
|
|
1384
|
+
|
|
1385
|
+
| Method | Timing | Purpose | Do | Don't |
|
|
1386
|
+
|--------|--------|---------|-----|-------|
|
|
1387
|
+
| `initialize()` | View生成直後、表示前 | UIコンポーネントの生成・配置・イベントリスナー登録 | addChild, addEventListener | API呼び出し、重い処理 |
|
|
1388
|
+
| `onEnter()` | initialize完了後、画面表示直前 | 入場アニメーション、データ取得、タイマー開始 | アニメーション再生、fetchInitialData | UIコンポーネント生成 |
|
|
1389
|
+
| `onExit()` | 別画面遷移前 | アニメーション停止、タイマークリア、リソース解放 | clearInterval, 状態リセット | 新リソース作成 |
|
|
1390
|
+
|
|
1391
|
+
### ViewModel Lifecycle Methods
|
|
1392
|
+
|
|
1393
|
+
| Method | Timing | Purpose | View参照 |
|
|
1394
|
+
|--------|--------|---------|---------|
|
|
1395
|
+
| `constructor()` | インスタンス生成時 | UseCaseの生成 | 不可 |
|
|
1396
|
+
| `initialize()` | Viewの`initialize()`より前 | 初期データ取得、状態初期化 | 不可 |
|
|
1397
|
+
| イベントハンドラ | ユーザー操作時 | ビジネスロジック実行 | 可能 |
|
|
1398
|
+
|
|
1399
|
+
## View Class Template
|
|
1400
|
+
|
|
1401
|
+
```typescript
|
|
1402
|
+
import type { {Screen}ViewModel } from "./{Screen}ViewModel";
|
|
1403
|
+
import { View } from "@next2d/framework";
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* @class
|
|
1407
|
+
* @extends {View}
|
|
1408
|
+
*/
|
|
1409
|
+
export class {Screen}View extends View
|
|
1410
|
+
{
|
|
1411
|
+
/**
|
|
1412
|
+
* @param {{Screen}ViewModel} vm
|
|
1413
|
+
* @constructor
|
|
1414
|
+
* @public
|
|
1415
|
+
*/
|
|
1416
|
+
constructor (
|
|
1417
|
+
private readonly vm: {Screen}ViewModel
|
|
1418
|
+
) {
|
|
1419
|
+
super();
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* @description 画面の初期化 - UIコンポーネントの構築
|
|
1424
|
+
* Initialize - Build UI components
|
|
1425
|
+
*
|
|
1426
|
+
* @return {Promise<void>}
|
|
1427
|
+
* @method
|
|
1428
|
+
* @override
|
|
1429
|
+
* @public
|
|
1430
|
+
*/
|
|
1431
|
+
async initialize (): Promise<void>
|
|
1432
|
+
{
|
|
1433
|
+
// UIコンポーネントの作成と配置
|
|
1434
|
+
// イベントリスナーの登録 (ViewModelのメソッドに接続)
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* @description 画面表示時の処理
|
|
1439
|
+
* On screen shown
|
|
1440
|
+
*
|
|
1441
|
+
* @return {Promise<void>}
|
|
1442
|
+
* @method
|
|
1443
|
+
* @override
|
|
1444
|
+
* @public
|
|
1445
|
+
*/
|
|
1446
|
+
async onEnter (): Promise<void>
|
|
1447
|
+
{
|
|
1448
|
+
// 入場アニメーション、データ取得
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* @description 画面非表示時の処理
|
|
1453
|
+
* On screen hidden
|
|
1454
|
+
*
|
|
1455
|
+
* @return {Promise<void>}
|
|
1456
|
+
* @method
|
|
1457
|
+
* @override
|
|
1458
|
+
* @public
|
|
1459
|
+
*/
|
|
1460
|
+
async onExit (): Promise<void>
|
|
1461
|
+
{
|
|
1462
|
+
// タイマークリア、リソース解放
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1467
|
+
## ViewModel Class Template
|
|
1468
|
+
|
|
1469
|
+
```typescript
|
|
1470
|
+
import { ViewModel } from "@next2d/framework";
|
|
1471
|
+
import { YourUseCase } from "@/model/application/{screen}/usecase/YourUseCase";
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* @class
|
|
1475
|
+
* @extends {ViewModel}
|
|
1476
|
+
*/
|
|
1477
|
+
export class {Screen}ViewModel extends ViewModel
|
|
1478
|
+
{
|
|
1479
|
+
private readonly yourUseCase: YourUseCase;
|
|
1480
|
+
|
|
1481
|
+
constructor ()
|
|
1482
|
+
{
|
|
1483
|
+
super();
|
|
1484
|
+
this.yourUseCase = new YourUseCase();
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/**
|
|
1488
|
+
* @description ViewModelの初期化 (Viewのinitialize()より前に呼ばれる)
|
|
1489
|
+
* Initialize ViewModel (called before View's initialize())
|
|
1490
|
+
*
|
|
1491
|
+
* @return {Promise<void>}
|
|
1492
|
+
* @method
|
|
1493
|
+
* @override
|
|
1494
|
+
* @public
|
|
1495
|
+
*/
|
|
1496
|
+
async initialize (): Promise<void>
|
|
1497
|
+
{
|
|
1498
|
+
// 初期データ取得、状態初期化
|
|
1499
|
+
// ※ この時点ではViewは未生成のためUI操作不可
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* @description イベントハンドラ
|
|
1504
|
+
* Event handler
|
|
1505
|
+
*
|
|
1506
|
+
* @param {PointerEvent} event
|
|
1507
|
+
* @return {void}
|
|
1508
|
+
* @method
|
|
1509
|
+
* @public
|
|
1510
|
+
*/
|
|
1511
|
+
yourEventHandler (event: PointerEvent): void
|
|
1512
|
+
{
|
|
1513
|
+
// インターフェースを通じてターゲットを取得
|
|
1514
|
+
const target = event.currentTarget as unknown as IYourInterface;
|
|
1515
|
+
this.yourUseCase.execute(target);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
## View-ViewModel Coordination Pattern
|
|
1521
|
+
|
|
1522
|
+
ViewModelの`initialize()`で事前取得したデータをViewで使用するパターン:
|
|
1523
|
+
|
|
1524
|
+
```typescript
|
|
1525
|
+
// ViewModel: 事前にデータ取得
|
|
1526
|
+
async initialize(): Promise<void> {
|
|
1527
|
+
const data = await HomeTextRepository.get();
|
|
1528
|
+
this.homeText = data.word;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
getHomeText(): string {
|
|
1532
|
+
return this.homeText;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// View: ViewModelから取得済みデータを使用
|
|
1536
|
+
async initialize(): Promise<void> {
|
|
1537
|
+
// vm.initialize()は既に完了している
|
|
1538
|
+
const text = this.vm.getHomeText();
|
|
1539
|
+
const textField = new TextAtom(text);
|
|
1540
|
+
this.addChild(textField);
|
|
1541
|
+
}
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
## Code Generation
|
|
1545
|
+
|
|
1546
|
+
```bash
|
|
1547
|
+
npm run generate
|
|
1548
|
+
```
|
|
1549
|
+
|
|
1550
|
+
`routing.json`のトッププロパティ値を分解し、`view`ディレクトリ直下に対象ディレクトリがなければ作成。View/ViewModelが存在しない場合のみ新規クラスを生成。
|
|
1551
|
+
|
|
1552
|
+
## Anti-Patterns
|
|
1553
|
+
|
|
1554
|
+
```typescript
|
|
1555
|
+
// NG: Viewでビジネスロジック
|
|
1556
|
+
class BadView extends View {
|
|
1557
|
+
async initialize() {
|
|
1558
|
+
btn.addEventListener(PointerEvent.POINTER_DOWN, async () => {
|
|
1559
|
+
const data = await Repository.get(); // NG
|
|
1560
|
+
this.processData(data); // NG
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// NG: ViewModelで具象クラスに依存
|
|
1566
|
+
homeContentPointerDownEvent(event: PointerEvent): void {
|
|
1567
|
+
const target = event.currentTarget as HomeBtnMolecule; // NG
|
|
1568
|
+
target.startDrag();
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// OK: ViewModelでインターフェースに依存
|
|
1572
|
+
homeContentPointerDownEvent(event: PointerEvent): void {
|
|
1573
|
+
const target = event.currentTarget as unknown as IDraggable; // OK
|
|
1574
|
+
this.startDragUseCase.execute(target);
|
|
1575
|
+
}
|
|
1576
|
+
```
|