mokkun 0.1.0
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 +283 -0
- package/bin/cli.mjs +155 -0
- package/bin/viewer.html +262 -0
- package/dist/mokkun.css +1 -0
- package/dist/mokkun.esm.js +12496 -0
- package/dist/mokkun.esm.js.map +1 -0
- package/dist/mokkun.js +703 -0
- package/dist/mokkun.js.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# mokkun
|
|
2
|
+
|
|
3
|
+
YAML形式のUI構造定義から画面モックアップを生成・表示するブラウザツール
|
|
4
|
+
|
|
5
|
+
## 概要
|
|
6
|
+
|
|
7
|
+
| 項目 | 内容 |
|
|
8
|
+
|------|------|
|
|
9
|
+
| 実行環境 | ブラウザのみ(サーバー不要) |
|
|
10
|
+
| YAML読込 | ファイル選択、URLパラメータ、ドラッグ&ドロップ |
|
|
11
|
+
| テーマ管理 | Light / Dark |
|
|
12
|
+
| 画面遷移 | リンククリックで遷移 |
|
|
13
|
+
|
|
14
|
+
## 技術スタック
|
|
15
|
+
|
|
16
|
+
| カテゴリ | 技術 |
|
|
17
|
+
|----------|------|
|
|
18
|
+
| 言語 | TypeScript |
|
|
19
|
+
| ビルド | Vite |
|
|
20
|
+
| テスト | Vitest |
|
|
21
|
+
| YAMLパーサー | js-yaml |
|
|
22
|
+
| スタイル | CSS Variables + テーマファイル |
|
|
23
|
+
|
|
24
|
+
## セットアップ
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 依存関係のインストール
|
|
28
|
+
pnpm install
|
|
29
|
+
|
|
30
|
+
# 開発サーバー起動
|
|
31
|
+
pnpm dev
|
|
32
|
+
|
|
33
|
+
# ビルド
|
|
34
|
+
pnpm build
|
|
35
|
+
|
|
36
|
+
# テスト実行
|
|
37
|
+
pnpm test
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## ディレクトリ構造
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
mokkun/
|
|
44
|
+
├── src/
|
|
45
|
+
│ ├── main.ts # エントリーポイント
|
|
46
|
+
│ ├── lib.ts # ライブラリエクスポート
|
|
47
|
+
│ ├── parser/ # YAMLパーサー
|
|
48
|
+
│ ├── renderer/ # レンダリングエンジン
|
|
49
|
+
│ │ ├── screen-renderer.ts
|
|
50
|
+
│ │ ├── action-handler.ts
|
|
51
|
+
│ │ ├── components/ # UIコンポーネント
|
|
52
|
+
│ │ └── utils/ # ユーティリティ
|
|
53
|
+
│ ├── theme/ # テーマ管理
|
|
54
|
+
│ ├── loader/ # ファイル読み込み
|
|
55
|
+
│ ├── types/ # 型定義
|
|
56
|
+
│ └── __tests__/ # テスト
|
|
57
|
+
├── themes/ # テーマ設定
|
|
58
|
+
├── schema.json # JSON Schema定義
|
|
59
|
+
└── index.html
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## サポートコンポーネント
|
|
63
|
+
|
|
64
|
+
### フォーム要素
|
|
65
|
+
|
|
66
|
+
| コンポーネント | 説明 |
|
|
67
|
+
|---------------|------|
|
|
68
|
+
| text | テキスト入力 |
|
|
69
|
+
| number | 数値入力 |
|
|
70
|
+
| textarea | テキストエリア |
|
|
71
|
+
| select | セレクトボックス |
|
|
72
|
+
| multi_select | 複数選択セレクト |
|
|
73
|
+
| combobox | 検索可能なセレクト |
|
|
74
|
+
| radio_group | ラジオボタングループ |
|
|
75
|
+
| checkbox | チェックボックス |
|
|
76
|
+
| checkbox_group | チェックボックスグループ |
|
|
77
|
+
| toggle | トグルスイッチ |
|
|
78
|
+
| date_picker | 日付選択 |
|
|
79
|
+
| time_picker | 時間選択 |
|
|
80
|
+
| duration_picker | 期間選択 |
|
|
81
|
+
| duration_input | 期間入力 |
|
|
82
|
+
| file_upload | ファイルアップロード |
|
|
83
|
+
| image_uploader | 画像アップローダー |
|
|
84
|
+
| calendar | カレンダー日付選択 |
|
|
85
|
+
| browser | 階層構造データの単一選択 |
|
|
86
|
+
|
|
87
|
+
### データ表示
|
|
88
|
+
|
|
89
|
+
| コンポーネント | 説明 |
|
|
90
|
+
|---------------|------|
|
|
91
|
+
| data_table | データテーブル |
|
|
92
|
+
| pagination | ページネーション |
|
|
93
|
+
| badge | バッジ |
|
|
94
|
+
| chip | チップ |
|
|
95
|
+
| status_label | ステータスラベル |
|
|
96
|
+
| timeline | タイムライン |
|
|
97
|
+
| definition_list | 定義リスト |
|
|
98
|
+
|
|
99
|
+
### レイアウト・ナビゲーション
|
|
100
|
+
|
|
101
|
+
| コンポーネント | 説明 |
|
|
102
|
+
|---------------|------|
|
|
103
|
+
| app_header | アプリケーションヘッダー(ロゴ、テナント切替、ユーザーメニュー) |
|
|
104
|
+
| app_navi | アプリナビゲーション(主要機能の切り替え) |
|
|
105
|
+
| heading | 見出し |
|
|
106
|
+
| tabs | タブ |
|
|
107
|
+
| accordion_panel | アコーディオン |
|
|
108
|
+
| disclosure | 開閉コンテンツ |
|
|
109
|
+
| wizard / stepper | ウィザード / ステッパー |
|
|
110
|
+
| section_nav | セクションナビゲーション |
|
|
111
|
+
| float_area | フローティング領域 |
|
|
112
|
+
| segmented_control | セグメントコントロール |
|
|
113
|
+
|
|
114
|
+
### フィードバック
|
|
115
|
+
|
|
116
|
+
| コンポーネント | 説明 |
|
|
117
|
+
|---------------|------|
|
|
118
|
+
| tooltip | ツールチップ |
|
|
119
|
+
| loader | ローダー |
|
|
120
|
+
| notification_bar | 通知バー |
|
|
121
|
+
| response_message | レスポンスメッセージ |
|
|
122
|
+
| information_panel | 情報パネル |
|
|
123
|
+
| line_clamp | 行数制限テキスト |
|
|
124
|
+
|
|
125
|
+
### その他
|
|
126
|
+
|
|
127
|
+
| コンポーネント | 説明 |
|
|
128
|
+
|---------------|------|
|
|
129
|
+
| dropdown | ドロップダウンメニュー |
|
|
130
|
+
| repeater | 動的フィールドグループ |
|
|
131
|
+
| google_map_embed | Googleマップ埋め込み |
|
|
132
|
+
| photo_manager | 写真管理 |
|
|
133
|
+
| delete_confirm_dialog | 削除確認ダイアログ |
|
|
134
|
+
|
|
135
|
+
## YAML構造
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
view:
|
|
139
|
+
screen_name:
|
|
140
|
+
title: "画面タイトル"
|
|
141
|
+
description: "画面の説明"
|
|
142
|
+
fields:
|
|
143
|
+
- id: "field_id"
|
|
144
|
+
type: "text"
|
|
145
|
+
label: "ラベル"
|
|
146
|
+
required: true
|
|
147
|
+
actions:
|
|
148
|
+
- id: "submit"
|
|
149
|
+
type: "submit"
|
|
150
|
+
label: "送信"
|
|
151
|
+
style: "primary"
|
|
152
|
+
|
|
153
|
+
common_components:
|
|
154
|
+
component_name:
|
|
155
|
+
type: "field_group"
|
|
156
|
+
fields: []
|
|
157
|
+
|
|
158
|
+
validations:
|
|
159
|
+
rule_name:
|
|
160
|
+
rules:
|
|
161
|
+
required: true
|
|
162
|
+
min: 1
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 使用方法
|
|
166
|
+
|
|
167
|
+
### ライブラリビルド
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# ライブラリとしてビルド
|
|
171
|
+
pnpm build:lib
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### HTMLでの使用例
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
<!DOCTYPE html>
|
|
178
|
+
<html>
|
|
179
|
+
<head>
|
|
180
|
+
<title>画面モックアップ</title>
|
|
181
|
+
<!-- CSSを読み込む -->
|
|
182
|
+
<link rel="stylesheet" href="./dist/mokkun.css">
|
|
183
|
+
</head>
|
|
184
|
+
<body>
|
|
185
|
+
<div id="mokkun-app"></div>
|
|
186
|
+
<script type="module">
|
|
187
|
+
import { Mokkun } from './dist/mokkun.esm.js';
|
|
188
|
+
|
|
189
|
+
// init()はPromiseを返す
|
|
190
|
+
Mokkun.init({
|
|
191
|
+
container: '#mokkun-app',
|
|
192
|
+
yamlUrl: './screens.yaml',
|
|
193
|
+
theme: 'light',
|
|
194
|
+
onReady: (instance) => {
|
|
195
|
+
console.log('Mokkun ready:', instance.getScreenNames());
|
|
196
|
+
},
|
|
197
|
+
onError: (error) => {
|
|
198
|
+
console.error('Mokkun error:', error);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
</script>
|
|
202
|
+
</body>
|
|
203
|
+
</html>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### UMD形式(グローバル変数として使用)
|
|
207
|
+
|
|
208
|
+
```html
|
|
209
|
+
<!DOCTYPE html>
|
|
210
|
+
<html>
|
|
211
|
+
<head>
|
|
212
|
+
<title>画面モックアップ</title>
|
|
213
|
+
<link rel="stylesheet" href="./dist/mokkun.css">
|
|
214
|
+
</head>
|
|
215
|
+
<body>
|
|
216
|
+
<div id="mokkun-app"></div>
|
|
217
|
+
<script src="./dist/mokkun.js"></script>
|
|
218
|
+
<script>
|
|
219
|
+
// グローバル変数Mokkunが利用可能
|
|
220
|
+
Mokkun.init({
|
|
221
|
+
container: '#mokkun-app',
|
|
222
|
+
yamlUrl: './screens.yaml',
|
|
223
|
+
theme: 'light'
|
|
224
|
+
});
|
|
225
|
+
</script>
|
|
226
|
+
</body>
|
|
227
|
+
</html>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### インラインYAMLの使用
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
const yamlContent = `
|
|
234
|
+
view:
|
|
235
|
+
login:
|
|
236
|
+
title: ログイン
|
|
237
|
+
fields:
|
|
238
|
+
- id: email
|
|
239
|
+
type: text
|
|
240
|
+
label: メールアドレス
|
|
241
|
+
required: true
|
|
242
|
+
- id: password
|
|
243
|
+
type: text
|
|
244
|
+
label: パスワード
|
|
245
|
+
required: true
|
|
246
|
+
actions:
|
|
247
|
+
- id: submit
|
|
248
|
+
type: submit
|
|
249
|
+
label: ログイン
|
|
250
|
+
style: primary
|
|
251
|
+
`;
|
|
252
|
+
|
|
253
|
+
const instance = await Mokkun.init({
|
|
254
|
+
container: '#mokkun-app',
|
|
255
|
+
yamlContent: yamlContent,
|
|
256
|
+
theme: 'dark'
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// 画面を切り替える
|
|
260
|
+
instance.showScreen('login');
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### サンプルファイル
|
|
265
|
+
|
|
266
|
+
`examples/`フォルダに動作するサンプルが用意されています:
|
|
267
|
+
|
|
268
|
+
| ファイル | 説明 |
|
|
269
|
+
|---------|------|
|
|
270
|
+
| `esm-example.html` | ESM形式でMokkunを読み込むサンプル |
|
|
271
|
+
| `umd-example.html` | UMD形式(グローバル変数)でMokkunを読み込むサンプル |
|
|
272
|
+
| `inline-yaml-example.html` | JavaScriptでYAMLを直接定義するサンプル |
|
|
273
|
+
| `screens.yaml` | サンプル画面定義(ログイン、新規登録、ダッシュボード) |
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# ローカルで確認する場合
|
|
277
|
+
python3 -m http.server 8080
|
|
278
|
+
# http://localhost:8080/examples/esm-example.html を開く
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## ライセンス
|
|
282
|
+
|
|
283
|
+
MIT
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createServer } from 'node:http'
|
|
4
|
+
import { readFileSync, existsSync, statSync } from 'node:fs'
|
|
5
|
+
import { resolve, dirname, extname, basename } from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { exec } from 'node:child_process'
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const distDir = resolve(__dirname, '../dist')
|
|
11
|
+
|
|
12
|
+
// Parse arguments
|
|
13
|
+
const args = process.argv.slice(2)
|
|
14
|
+
let yamlFile = null
|
|
15
|
+
let port = 3333
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < args.length; i++) {
|
|
18
|
+
if (args[i] === '--port' || args[i] === '-p') {
|
|
19
|
+
port = parseInt(args[i + 1], 10)
|
|
20
|
+
i++
|
|
21
|
+
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
22
|
+
printHelp()
|
|
23
|
+
process.exit(0)
|
|
24
|
+
} else if (!args[i].startsWith('-')) {
|
|
25
|
+
yamlFile = resolve(process.cwd(), args[i])
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function printHelp() {
|
|
30
|
+
console.log(`
|
|
31
|
+
mokkun - YAML-based form & mockup viewer
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
npx mokkun [options] [yaml-file]
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-p, --port <port> Port number (default: 3333)
|
|
38
|
+
-h, --help Show this help
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
npx mokkun # Start with built-in sample
|
|
42
|
+
npx mokkun ./my-form.yaml # Open specific YAML file
|
|
43
|
+
npx mokkun -p 8080 form.yaml # Custom port
|
|
44
|
+
`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Validate YAML file if specified
|
|
48
|
+
if (yamlFile) {
|
|
49
|
+
if (!existsSync(yamlFile)) {
|
|
50
|
+
console.error(`Error: File not found: ${yamlFile}`)
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
if (!yamlFile.endsWith('.yaml') && !yamlFile.endsWith('.yml')) {
|
|
54
|
+
console.error(`Error: File must be .yaml or .yml: ${yamlFile}`)
|
|
55
|
+
process.exit(1)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check dist directory exists
|
|
60
|
+
if (!existsSync(resolve(distDir, 'mokkun.js'))) {
|
|
61
|
+
console.error('Error: dist/mokkun.js not found. Run "pnpm build:lib" first.')
|
|
62
|
+
process.exit(1)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// MIME types
|
|
66
|
+
const MIME_TYPES = {
|
|
67
|
+
'.html': 'text/html; charset=utf-8',
|
|
68
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
69
|
+
'.mjs': 'application/javascript; charset=utf-8',
|
|
70
|
+
'.css': 'text/css; charset=utf-8',
|
|
71
|
+
'.yaml': 'text/yaml; charset=utf-8',
|
|
72
|
+
'.yml': 'text/yaml; charset=utf-8',
|
|
73
|
+
'.json': 'application/json; charset=utf-8',
|
|
74
|
+
'.map': 'application/json; charset=utf-8',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Generate viewer HTML
|
|
78
|
+
function generateViewerHtml() {
|
|
79
|
+
const yamlParam = yamlFile ? `yaml=${encodeURIComponent('/__yaml__/' + basename(yamlFile))}` : ''
|
|
80
|
+
const viewerHtmlPath = resolve(__dirname, 'viewer.html')
|
|
81
|
+
let html = readFileSync(viewerHtmlPath, 'utf-8')
|
|
82
|
+
if (yamlParam) {
|
|
83
|
+
html = html.replace('/* __YAML_URL__ */ null', JSON.stringify('/__yaml__/' + basename(yamlFile)))
|
|
84
|
+
}
|
|
85
|
+
return html
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Serve files
|
|
89
|
+
const server = createServer((req, res) => {
|
|
90
|
+
const url = new URL(req.url, `http://localhost:${port}`)
|
|
91
|
+
const pathname = url.pathname
|
|
92
|
+
|
|
93
|
+
// Root -> viewer HTML
|
|
94
|
+
if (pathname === '/' || pathname === '/index.html') {
|
|
95
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
96
|
+
res.end(generateViewerHtml())
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Serve user YAML file
|
|
101
|
+
if (pathname.startsWith('/__yaml__/') && yamlFile) {
|
|
102
|
+
try {
|
|
103
|
+
const content = readFileSync(yamlFile, 'utf-8')
|
|
104
|
+
res.writeHead(200, { 'Content-Type': 'text/yaml; charset=utf-8' })
|
|
105
|
+
res.end(content)
|
|
106
|
+
} catch {
|
|
107
|
+
res.writeHead(404)
|
|
108
|
+
res.end('Not found')
|
|
109
|
+
}
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Serve dist files (mokkun.js, mokkun.css, etc.)
|
|
114
|
+
const distPath = resolve(distDir, pathname.slice(1))
|
|
115
|
+
if (distPath.startsWith(distDir) && existsSync(distPath) && statSync(distPath).isFile()) {
|
|
116
|
+
const ext = extname(distPath)
|
|
117
|
+
const mime = MIME_TYPES[ext] || 'application/octet-stream'
|
|
118
|
+
try {
|
|
119
|
+
const content = readFileSync(distPath)
|
|
120
|
+
res.writeHead(200, { 'Content-Type': mime })
|
|
121
|
+
res.end(content)
|
|
122
|
+
} catch {
|
|
123
|
+
res.writeHead(500)
|
|
124
|
+
res.end('Internal error')
|
|
125
|
+
}
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
res.writeHead(404)
|
|
130
|
+
res.end('Not found')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
server.listen(port, () => {
|
|
134
|
+
const url = `http://localhost:${port}`
|
|
135
|
+
console.log(`Mokkun viewer running at ${url}`)
|
|
136
|
+
if (yamlFile) {
|
|
137
|
+
console.log(`Serving: ${yamlFile}`)
|
|
138
|
+
}
|
|
139
|
+
console.log('Press Ctrl+C to stop.\n')
|
|
140
|
+
|
|
141
|
+
// Open browser
|
|
142
|
+
openBrowser(url)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
function openBrowser(url) {
|
|
146
|
+
const platform = process.platform
|
|
147
|
+
const cmd =
|
|
148
|
+
platform === 'darwin' ? 'open' :
|
|
149
|
+
platform === 'win32' ? 'start' :
|
|
150
|
+
'xdg-open'
|
|
151
|
+
|
|
152
|
+
exec(`${cmd} ${url}`, () => {
|
|
153
|
+
// Ignore errors (e.g., no display server)
|
|
154
|
+
})
|
|
155
|
+
}
|
package/bin/viewer.html
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Mokkun</title>
|
|
7
|
+
<link rel="stylesheet" href="/mokkun.css">
|
|
8
|
+
<style>
|
|
9
|
+
* { box-sizing: border-box; }
|
|
10
|
+
|
|
11
|
+
body {
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Hiragino Sans', 'Noto Sans JP', sans-serif;
|
|
15
|
+
background-color: #f5f6f7;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.page-container {
|
|
19
|
+
min-height: 100vh;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.toolbar {
|
|
25
|
+
position: fixed;
|
|
26
|
+
top: 0;
|
|
27
|
+
left: 0;
|
|
28
|
+
right: 0;
|
|
29
|
+
background: #1a1a2e;
|
|
30
|
+
color: #fff;
|
|
31
|
+
padding: 12px 24px;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 16px;
|
|
35
|
+
z-index: 1000;
|
|
36
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.toolbar h1 {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-size: 16px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.toolbar-section {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 8px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.toolbar label {
|
|
52
|
+
font-size: 14px;
|
|
53
|
+
color: #a0a0a0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.toolbar select {
|
|
57
|
+
padding: 8px 12px;
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
border: 1px solid #3a3a5a;
|
|
60
|
+
background: #2a2a4a;
|
|
61
|
+
color: #fff;
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
min-width: 180px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.toolbar select:hover { border-color: #5a5a8a; }
|
|
68
|
+
.toolbar select:focus {
|
|
69
|
+
outline: none;
|
|
70
|
+
border-color: #6366f1;
|
|
71
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.toolbar-spacer { flex: 1; }
|
|
75
|
+
|
|
76
|
+
.theme-toggle {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: 8px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.theme-toggle button {
|
|
83
|
+
padding: 8px 16px;
|
|
84
|
+
border-radius: 6px;
|
|
85
|
+
border: 1px solid #3a3a5a;
|
|
86
|
+
background: transparent;
|
|
87
|
+
color: #fff;
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
transition: all 0.2s;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.theme-toggle button:hover { background: #3a3a5a; }
|
|
94
|
+
.theme-toggle button.active {
|
|
95
|
+
background: #6366f1;
|
|
96
|
+
border-color: #6366f1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#mokkun-app {
|
|
100
|
+
margin-top: 60px;
|
|
101
|
+
flex: 1;
|
|
102
|
+
padding: 24px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.loading {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
height: 50vh;
|
|
110
|
+
font-size: 18px;
|
|
111
|
+
color: #666;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.error-message {
|
|
115
|
+
background: #fee2e2;
|
|
116
|
+
border: 1px solid #ef4444;
|
|
117
|
+
border-radius: 8px;
|
|
118
|
+
padding: 16px 24px;
|
|
119
|
+
margin: 24px;
|
|
120
|
+
color: #b91c1c;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
body.dark-theme { background-color: #1a1a2e; }
|
|
124
|
+
body.dark-theme .toolbar { background: #0f0f1a; }
|
|
125
|
+
</style>
|
|
126
|
+
</head>
|
|
127
|
+
<body>
|
|
128
|
+
<div class="page-container">
|
|
129
|
+
<div class="toolbar">
|
|
130
|
+
<h1>Mokkun</h1>
|
|
131
|
+
|
|
132
|
+
<div class="toolbar-section">
|
|
133
|
+
<label for="screen-select">Screen:</label>
|
|
134
|
+
<select id="screen-select">
|
|
135
|
+
<option value="">Loading...</option>
|
|
136
|
+
</select>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="toolbar-spacer"></div>
|
|
140
|
+
|
|
141
|
+
<div class="theme-toggle">
|
|
142
|
+
<button id="light-theme" class="active">Light</button>
|
|
143
|
+
<button id="dark-theme">Dark</button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div id="mokkun-app">
|
|
148
|
+
<div class="loading">Loading...</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<script src="/mokkun.js"></script>
|
|
153
|
+
<script>
|
|
154
|
+
(function() {
|
|
155
|
+
var mokkunInstance = null;
|
|
156
|
+
var screenSelect = document.getElementById('screen-select');
|
|
157
|
+
var lightThemeBtn = document.getElementById('light-theme');
|
|
158
|
+
var darkThemeBtn = document.getElementById('dark-theme');
|
|
159
|
+
var currentTheme = 'light';
|
|
160
|
+
|
|
161
|
+
// YAML URL injected by CLI (or null for sample)
|
|
162
|
+
var yamlUrl = /* __YAML_URL__ */ null;
|
|
163
|
+
|
|
164
|
+
// Theme
|
|
165
|
+
function setTheme(theme) {
|
|
166
|
+
currentTheme = theme;
|
|
167
|
+
document.body.classList.toggle('dark-theme', theme === 'dark');
|
|
168
|
+
lightThemeBtn.classList.toggle('active', theme === 'light');
|
|
169
|
+
darkThemeBtn.classList.toggle('active', theme === 'dark');
|
|
170
|
+
if (mokkunInstance) mokkunInstance.setTheme(theme);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
lightThemeBtn.addEventListener('click', function() { setTheme('light'); });
|
|
174
|
+
darkThemeBtn.addEventListener('click', function() { setTheme('dark'); });
|
|
175
|
+
|
|
176
|
+
// Screen change
|
|
177
|
+
screenSelect.addEventListener('change', function() {
|
|
178
|
+
if (mokkunInstance && screenSelect.value) {
|
|
179
|
+
mokkunInstance.showScreen(screenSelect.value);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Sample YAML for when no file is specified
|
|
184
|
+
var sampleYaml = [
|
|
185
|
+
'view:',
|
|
186
|
+
' sample:',
|
|
187
|
+
' title: "Mokkun Sample"',
|
|
188
|
+
' description: "npx mokkun <yaml-file> to view your own YAML"',
|
|
189
|
+
' fields:',
|
|
190
|
+
' - id: name',
|
|
191
|
+
' type: text',
|
|
192
|
+
' label: "Name"',
|
|
193
|
+
' required: true',
|
|
194
|
+
' - id: email',
|
|
195
|
+
' type: text',
|
|
196
|
+
' label: "Email"',
|
|
197
|
+
' input_type: email',
|
|
198
|
+
' - id: category',
|
|
199
|
+
' type: select',
|
|
200
|
+
' label: "Category"',
|
|
201
|
+
' options:',
|
|
202
|
+
' - label: "Select..."',
|
|
203
|
+
' value: ""',
|
|
204
|
+
' - label: "Option A"',
|
|
205
|
+
' value: a',
|
|
206
|
+
' - label: "Option B"',
|
|
207
|
+
' value: b',
|
|
208
|
+
' - id: message',
|
|
209
|
+
' type: textarea',
|
|
210
|
+
' label: "Message"',
|
|
211
|
+
' rows: 4',
|
|
212
|
+
' actions:',
|
|
213
|
+
' - id: submit',
|
|
214
|
+
' type: submit',
|
|
215
|
+
' label: "Submit"',
|
|
216
|
+
' style: primary',
|
|
217
|
+
].join('\n');
|
|
218
|
+
|
|
219
|
+
// Initialize
|
|
220
|
+
async function init() {
|
|
221
|
+
var options = {
|
|
222
|
+
container: '#mokkun-app',
|
|
223
|
+
theme: currentTheme,
|
|
224
|
+
onReady: function(instance) {
|
|
225
|
+
var screens = instance.getScreenNames();
|
|
226
|
+
screenSelect.innerHTML = '';
|
|
227
|
+
screens.forEach(function(name) {
|
|
228
|
+
var opt = document.createElement('option');
|
|
229
|
+
opt.value = name;
|
|
230
|
+
opt.textContent = name;
|
|
231
|
+
screenSelect.appendChild(opt);
|
|
232
|
+
});
|
|
233
|
+
screenSelect.value = screens[0] || '';
|
|
234
|
+
},
|
|
235
|
+
onError: function(error) {
|
|
236
|
+
document.getElementById('mokkun-app').innerHTML =
|
|
237
|
+
'<div class="error-message"><strong>Error:</strong> ' + error.message + '</div>';
|
|
238
|
+
},
|
|
239
|
+
onSubmit: function(screenName, formData) {
|
|
240
|
+
console.log('Submit:', screenName, formData);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
if (yamlUrl) {
|
|
245
|
+
options.yamlUrl = yamlUrl;
|
|
246
|
+
} else {
|
|
247
|
+
options.yamlContent = sampleYaml;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
mokkunInstance = await Mokkun.init(options);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
document.getElementById('mokkun-app').innerHTML =
|
|
254
|
+
'<div class="error-message"><strong>Error:</strong> ' + e.message + '</div>';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
init();
|
|
259
|
+
})();
|
|
260
|
+
</script>
|
|
261
|
+
</body>
|
|
262
|
+
</html>
|