@luna-editor/engine 0.2.0 → 0.3.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/dist/Player.d.ts +1 -1
- package/dist/Player.js +504 -77
- package/dist/api/conversationBranch.d.ts +4 -0
- package/dist/api/conversationBranch.js +83 -0
- package/dist/components/BackgroundLayer.d.ts +19 -0
- package/dist/components/BackgroundLayer.js +218 -0
- package/dist/components/ClickWaitIndicator.d.ts +10 -0
- package/dist/components/ClickWaitIndicator.js +31 -0
- package/dist/components/ConversationBranchBox.d.ts +2 -0
- package/dist/components/ConversationBranchBox.js +29 -0
- package/dist/components/DialogueBox.js +16 -1
- package/dist/components/FontSettingsPanel.d.ts +10 -0
- package/dist/components/FontSettingsPanel.js +30 -0
- package/dist/components/FullscreenTextBox.d.ts +6 -0
- package/dist/components/FullscreenTextBox.js +70 -0
- package/dist/components/GameScreen.d.ts +1 -0
- package/dist/components/GameScreen.js +363 -81
- package/dist/components/PluginComponentProvider.d.ts +2 -2
- package/dist/components/PluginComponentProvider.js +3 -3
- package/dist/components/TimeWaitIndicator.d.ts +15 -0
- package/dist/components/TimeWaitIndicator.js +17 -0
- package/dist/contexts/AudioContext.d.ts +1 -0
- package/dist/contexts/AudioContext.js +1 -0
- package/dist/contexts/DataContext.js +69 -11
- package/dist/hooks/useBacklog.js +3 -0
- package/dist/hooks/useConversationBranch.d.ts +16 -0
- package/dist/hooks/useConversationBranch.js +125 -0
- package/dist/hooks/useFontLoader.d.ts +23 -0
- package/dist/hooks/useFontLoader.js +153 -0
- package/dist/hooks/useFullscreenText.d.ts +17 -0
- package/dist/hooks/useFullscreenText.js +120 -0
- package/dist/hooks/usePlayerLogic.d.ts +10 -3
- package/dist/hooks/usePlayerLogic.js +115 -18
- package/dist/hooks/usePluginEvents.d.ts +4 -1
- package/dist/hooks/usePluginEvents.js +16 -11
- package/dist/hooks/usePreloadImages.js +27 -7
- package/dist/hooks/useSoundPlayer.d.ts +15 -0
- package/dist/hooks/useSoundPlayer.js +209 -0
- package/dist/hooks/useTypewriter.d.ts +6 -2
- package/dist/hooks/useTypewriter.js +42 -6
- package/dist/hooks/useVoice.js +4 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/plugin/PluginManager.d.ts +66 -2
- package/dist/plugin/PluginManager.js +349 -79
- package/dist/sdk.d.ts +178 -21
- package/dist/sdk.js +27 -2
- package/dist/types.d.ts +288 -4
- package/dist/utils/branchBlockConverter.d.ts +2 -0
- package/dist/utils/branchBlockConverter.js +21 -0
- package/dist/utils/branchNavigator.d.ts +14 -0
- package/dist/utils/branchNavigator.js +55 -0
- package/dist/utils/facePositionCalculator.js +0 -1
- package/dist/utils/variableManager.d.ts +18 -0
- package/dist/utils/variableManager.js +159 -0
- package/package.json +1 -1
- package/dist/components/ConversationLogUI.d.ts +0 -2
- package/dist/components/ConversationLogUI.js +0 -115
- package/dist/hooks/useConversationLog.d.ts +0 -14
- package/dist/hooks/useConversationLog.js +0 -82
- package/dist/hooks/useUIVisibility.d.ts +0 -9
- package/dist/hooks/useUIVisibility.js +0 -19
- package/dist/plugin/luna-react.d.ts +0 -41
- package/dist/plugin/luna-react.js +0 -99
|
@@ -7,13 +7,26 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
|
|
10
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
11
21
|
import React from "react";
|
|
12
22
|
import { useScreenSizeAtom } from "../atoms/screen-size";
|
|
13
23
|
import { useDataAPI } from "../contexts/DataContext";
|
|
14
24
|
import { usePluginAPI, useUIVisibility } from "../hooks/usePluginAPI";
|
|
15
25
|
import { useScreenScale, useScreenSize, useToPixel, } from "../hooks/useScreenSize";
|
|
16
|
-
import { ComponentType, } from "../sdk";
|
|
26
|
+
import { ComponentType, definePlugin, } from "../sdk";
|
|
27
|
+
// グローバルなロード状態管理(複数のPluginManagerインスタンス間で共有)
|
|
28
|
+
const globalLoadingPlugins = new Map();
|
|
29
|
+
const globalLoadedPlugins = new Map();
|
|
17
30
|
export class PluginManager {
|
|
18
31
|
constructor() {
|
|
19
32
|
this.plugins = new Map();
|
|
@@ -23,38 +36,117 @@ export class PluginManager {
|
|
|
23
36
|
this.componentRegistry = new Map();
|
|
24
37
|
this.uiVisibilityState = new Map();
|
|
25
38
|
this.uiVisibilityListeners = new Map();
|
|
39
|
+
this.selectChoiceCallback = null;
|
|
40
|
+
this.getBranchStateCallback = null;
|
|
41
|
+
this.preloadPromises = [];
|
|
42
|
+
this.muteAudio = false;
|
|
43
|
+
// 作品のサウンドデータ(ID → URL)
|
|
44
|
+
this.workSounds = new Map();
|
|
45
|
+
// ビルトインのクリック音設定
|
|
46
|
+
this.clickSoundUrl = null;
|
|
47
|
+
this.clickSoundVolume = 0.5;
|
|
48
|
+
// プラグインによるクリック音の上書きフラグ
|
|
49
|
+
this.clickSoundOverridden = false;
|
|
26
50
|
this.initializeReactRuntime();
|
|
27
51
|
}
|
|
28
52
|
/**
|
|
29
53
|
* グローバルReactランタイムを初期化(クライアントサイドのみ)
|
|
54
|
+
* 既に初期化済みの場合はスキップ
|
|
30
55
|
*/
|
|
31
56
|
initializeReactRuntime() {
|
|
32
57
|
// サーバーサイドではスキップ
|
|
33
58
|
if (typeof window === "undefined") {
|
|
34
59
|
return;
|
|
35
60
|
}
|
|
61
|
+
const globalWindow = window;
|
|
62
|
+
// 既に初期化済みの場合はスキップ
|
|
63
|
+
if (globalWindow.__LUNA_REACT_RUNTIME_INITIALIZED__) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
globalWindow.__LUNA_REACT_RUNTIME_INITIALIZED__ = true;
|
|
36
67
|
// JSXランタイム
|
|
37
|
-
|
|
68
|
+
globalWindow.__LUNA_JSX_RUNTIME__ = {
|
|
38
69
|
jsx: (type, props, key) => {
|
|
39
|
-
|
|
70
|
+
const { children } = props, restProps = __rest(props, ["children"]);
|
|
71
|
+
const propsWithKey = key ? Object.assign(Object.assign({}, restProps), { key }) : restProps;
|
|
72
|
+
return children !== undefined
|
|
73
|
+
? React.createElement(type, propsWithKey, children)
|
|
74
|
+
: React.createElement(type, propsWithKey);
|
|
40
75
|
},
|
|
41
76
|
jsxs: (type, props, key) => {
|
|
42
|
-
|
|
77
|
+
const { children } = props, restProps = __rest(props, ["children"]);
|
|
78
|
+
const propsWithKey = key ? Object.assign(Object.assign({}, restProps), { key }) : restProps;
|
|
79
|
+
// jsxsはchildren配列をスプレッドして渡す
|
|
80
|
+
return Array.isArray(children)
|
|
81
|
+
? React.createElement(type, propsWithKey, ...children)
|
|
82
|
+
: React.createElement(type, propsWithKey, children);
|
|
43
83
|
},
|
|
44
84
|
Fragment: React.Fragment,
|
|
45
85
|
};
|
|
46
86
|
// Reactランタイム (useDataAPIフックと画面サイズフックを含む)
|
|
47
|
-
|
|
87
|
+
globalWindow.__LUNA_REACT_RUNTIME__ = Object.assign(Object.assign({}, React), { default: React, useDataAPI,
|
|
48
88
|
usePluginAPI,
|
|
49
89
|
useUIVisibility,
|
|
50
90
|
useToPixel,
|
|
51
91
|
useScreenSize,
|
|
52
92
|
useScreenScale,
|
|
53
93
|
useScreenSizeAtom,
|
|
54
|
-
ComponentType
|
|
94
|
+
ComponentType,
|
|
95
|
+
definePlugin });
|
|
55
96
|
console.log("🔧 Luna React Runtime initialized for JSX support with hooks and screen size system");
|
|
56
97
|
}
|
|
57
98
|
loadPlugin(packageName, bundleUrl, config) {
|
|
99
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
// グローバルに既にロード済みの場合はローカルにコピーしてスキップ
|
|
101
|
+
const globalLoaded = globalLoadedPlugins.get(packageName);
|
|
102
|
+
if (globalLoaded) {
|
|
103
|
+
console.log(`Plugin ${packageName} is already loaded globally, reusing.`);
|
|
104
|
+
// 新しいconfigで更新したコピーを作成
|
|
105
|
+
const updatedPlugin = Object.assign(Object.assign({}, globalLoaded), { config: config !== null && config !== void 0 ? config : globalLoaded.config });
|
|
106
|
+
this.plugins.set(packageName, updatedPlugin);
|
|
107
|
+
// コンポーネントレジストリにも登録
|
|
108
|
+
for (const [type, component] of globalLoaded.instance.components) {
|
|
109
|
+
this.componentRegistry.set(type, component);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// ローカルに既にロード済みの場合はconfigだけ更新してスキップ
|
|
114
|
+
const localLoaded = this.plugins.get(packageName);
|
|
115
|
+
if (localLoaded) {
|
|
116
|
+
console.log(`Plugin ${packageName} is already loaded, updating config.`);
|
|
117
|
+
// configを更新
|
|
118
|
+
localLoaded.config = config !== null && config !== void 0 ? config : localLoaded.config;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// グローバルでロード中の場合はそのPromiseを待つ
|
|
122
|
+
const existingLoadPromise = globalLoadingPlugins.get(packageName);
|
|
123
|
+
if (existingLoadPromise) {
|
|
124
|
+
console.log(`Plugin ${packageName} is currently loading, waiting...`);
|
|
125
|
+
yield existingLoadPromise;
|
|
126
|
+
// ロード完了後にグローバルからコピー(新しいconfigで更新)
|
|
127
|
+
const loaded = globalLoadedPlugins.get(packageName);
|
|
128
|
+
if (loaded) {
|
|
129
|
+
const updatedPlugin = Object.assign(Object.assign({}, loaded), { config: config !== null && config !== void 0 ? config : loaded.config });
|
|
130
|
+
this.plugins.set(packageName, updatedPlugin);
|
|
131
|
+
for (const [type, component] of loaded.instance.components) {
|
|
132
|
+
this.componentRegistry.set(type, component);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// ロード処理をPromiseとして保存
|
|
138
|
+
const loadPromise = this.doLoadPlugin(packageName, bundleUrl, config);
|
|
139
|
+
globalLoadingPlugins.set(packageName, loadPromise);
|
|
140
|
+
try {
|
|
141
|
+
yield loadPromise;
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
// ロード完了後にマップから削除
|
|
145
|
+
globalLoadingPlugins.delete(packageName);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
doLoadPlugin(packageName, bundleUrl, config) {
|
|
58
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
59
151
|
try {
|
|
60
152
|
// スクリプトタグで動的にプラグインを読み込む
|
|
@@ -73,12 +165,15 @@ export class PluginManager {
|
|
|
73
165
|
// プラグインをセットアップ
|
|
74
166
|
plugin.setup(api);
|
|
75
167
|
console.log(`Plugin ${packageName} setup completed. Hooks:`, Object.keys(instance.hooks));
|
|
76
|
-
|
|
168
|
+
const loadedPlugin = {
|
|
77
169
|
plugin,
|
|
78
170
|
instance,
|
|
79
171
|
enabled: true,
|
|
80
172
|
config: config || {},
|
|
81
|
-
}
|
|
173
|
+
};
|
|
174
|
+
// ローカルとグローバル両方に保存
|
|
175
|
+
this.plugins.set(packageName, loadedPlugin);
|
|
176
|
+
globalLoadedPlugins.set(packageName, loadedPlugin);
|
|
82
177
|
}
|
|
83
178
|
catch (error) {
|
|
84
179
|
console.error(`Failed to load plugin ${packageName}:`, error);
|
|
@@ -133,12 +228,7 @@ export class PluginManager {
|
|
|
133
228
|
// import文を削除
|
|
134
229
|
.replace(/^import\s+.*?from\s+['"].*?['"];?\s*$/gm, "")
|
|
135
230
|
.replace(/export\s*{[^}]*}\s*;?\s*$/g, "")
|
|
136
|
-
.replace(/export\s+default\s+/g, "")
|
|
137
|
-
// luna-react/jsxランタイムへの参照を置換
|
|
138
|
-
.replace(/from\s+['"]luna-react\/jsx-runtime['"]/, 'from "__LUNA_JSX_RUNTIME__"')
|
|
139
|
-
.replace(/from\s+['"]luna-react['"]/, 'from "__LUNA_REACT__"')
|
|
140
|
-
.replace(/require\(['"]luna-react\/jsx-runtime['"]\)/, 'require("__LUNA_JSX_RUNTIME__")')
|
|
141
|
-
.replace(/require\(['"]luna-react['"]\)/, 'require("__LUNA_REACT__")');
|
|
231
|
+
.replace(/export\s+default\s+/g, "");
|
|
142
232
|
// ES modules形式のプラグインコードを処理
|
|
143
233
|
const wrappedCode = `
|
|
144
234
|
(function() {
|
|
@@ -156,10 +246,10 @@ export class PluginManager {
|
|
|
156
246
|
|
|
157
247
|
// Luna React Runtime を提供
|
|
158
248
|
const require = function(moduleName) {
|
|
159
|
-
if (moduleName === "
|
|
249
|
+
if (moduleName === "react" || moduleName === "luna-react" || moduleName === "@luna-editor/engine" || moduleName === "@luna-editor/engine/sdk" || moduleName === "@luna-editor/plugin-sdk") {
|
|
160
250
|
return window.__LUNA_REACT_RUNTIME__;
|
|
161
251
|
}
|
|
162
|
-
if (moduleName === "
|
|
252
|
+
if (moduleName === "react/jsx-runtime" || moduleName === "react/jsx-dev-runtime" || moduleName === "luna-react/jsx-runtime" || moduleName === "luna-react/jsx-dev-runtime") {
|
|
163
253
|
return window.__LUNA_JSX_RUNTIME__;
|
|
164
254
|
}
|
|
165
255
|
throw new Error('Module not found: ' + moduleName);
|
|
@@ -190,19 +280,6 @@ export class PluginManager {
|
|
|
190
280
|
scriptElement.type = "text/javascript";
|
|
191
281
|
scriptElement.dataset.plugin = packageName;
|
|
192
282
|
scriptElement.textContent = wrappedCode;
|
|
193
|
-
// タイムアウトを設定(グローバルコールバックが5秒以内に呼ばれない場合はエラー)
|
|
194
|
-
const timeoutId = setTimeout(() => {
|
|
195
|
-
if (window[globalName]) {
|
|
196
|
-
delete window[globalName];
|
|
197
|
-
reject(new Error(`Plugin ${packageName} did not initialize within timeout`));
|
|
198
|
-
}
|
|
199
|
-
}, 5000);
|
|
200
|
-
// グローバルコールバック関数にタイムアウトクリアを追加
|
|
201
|
-
const originalCallback = window[globalName];
|
|
202
|
-
window[globalName] = (plugin) => {
|
|
203
|
-
clearTimeout(timeoutId);
|
|
204
|
-
originalCallback(plugin);
|
|
205
|
-
};
|
|
206
283
|
// DOMに追加(この時点で同期的に実行される)
|
|
207
284
|
document.head.appendChild(scriptElement);
|
|
208
285
|
})
|
|
@@ -220,17 +297,23 @@ export class PluginManager {
|
|
|
220
297
|
return {
|
|
221
298
|
setClassName: (className) => {
|
|
222
299
|
classNames.clear();
|
|
223
|
-
className.split(" ")
|
|
300
|
+
for (const cn of className.split(" ")) {
|
|
301
|
+
classNames.add(cn);
|
|
302
|
+
}
|
|
224
303
|
instance.styles.set(elementName, Array.from(classNames));
|
|
225
304
|
this.applyStyles(elementName, Array.from(classNames));
|
|
226
305
|
},
|
|
227
306
|
addClassName: (className) => {
|
|
228
|
-
className.split(" ")
|
|
307
|
+
for (const cn of className.split(" ")) {
|
|
308
|
+
classNames.add(cn);
|
|
309
|
+
}
|
|
229
310
|
instance.styles.set(elementName, Array.from(classNames));
|
|
230
311
|
this.applyStyles(elementName, Array.from(classNames));
|
|
231
312
|
},
|
|
232
313
|
removeClassName: (className) => {
|
|
233
|
-
className.split(" ")
|
|
314
|
+
for (const cn of className.split(" ")) {
|
|
315
|
+
classNames.delete(cn);
|
|
316
|
+
}
|
|
234
317
|
instance.styles.set(elementName, Array.from(classNames));
|
|
235
318
|
this.applyStyles(elementName, Array.from(classNames));
|
|
236
319
|
},
|
|
@@ -281,7 +364,7 @@ export class PluginManager {
|
|
|
281
364
|
// ターゲット要素内の画像を探してマスクを適用
|
|
282
365
|
const targetImage = targetElement.querySelector("img");
|
|
283
366
|
let maskStyles = {};
|
|
284
|
-
if (targetImage
|
|
367
|
+
if (targetImage === null || targetImage === void 0 ? void 0 : targetImage.src) {
|
|
285
368
|
// 画像のスタイルプロパティを取得
|
|
286
369
|
const computedImageStyle = getComputedStyle(targetImage);
|
|
287
370
|
const objectFit = computedImageStyle.objectFit;
|
|
@@ -312,7 +395,7 @@ export class PluginManager {
|
|
|
312
395
|
// スタイルを個別に適用
|
|
313
396
|
Object.entries(overlayStyles).forEach(([key, value]) => {
|
|
314
397
|
if (typeof value === "string") {
|
|
315
|
-
overlay.style[
|
|
398
|
+
overlay.style.setProperty(key.replace(/([A-Z])/g, "-$1").toLowerCase(), value);
|
|
316
399
|
}
|
|
317
400
|
});
|
|
318
401
|
overlay.dataset.plugin = packageName;
|
|
@@ -332,7 +415,9 @@ export class PluginManager {
|
|
|
332
415
|
},
|
|
333
416
|
clearOverlays: () => {
|
|
334
417
|
const overlaysToRemove = Array.from(instance.overlays.keys()).filter((id) => id.includes(`-overlay-${elementName}-`));
|
|
335
|
-
|
|
418
|
+
for (const id of overlaysToRemove) {
|
|
419
|
+
this.removeOverlay(id, instance);
|
|
420
|
+
}
|
|
336
421
|
},
|
|
337
422
|
};
|
|
338
423
|
};
|
|
@@ -344,6 +429,8 @@ export class PluginManager {
|
|
|
344
429
|
gameScreen: createStyleElement("gameScreen"),
|
|
345
430
|
characterSprite: createStyleElement("characterSprite"),
|
|
346
431
|
background: createStyleElement("background"),
|
|
432
|
+
fullscreenTextOverlay: createStyleElement("fullscreenTextOverlay"),
|
|
433
|
+
fullscreenTextContainer: createStyleElement("fullscreenTextContainer"),
|
|
347
434
|
},
|
|
348
435
|
injectCSS: (css) => {
|
|
349
436
|
if (this.injectedStyles.has(css))
|
|
@@ -481,45 +568,71 @@ export class PluginManager {
|
|
|
481
568
|
keysToDelete.push(key);
|
|
482
569
|
}
|
|
483
570
|
}
|
|
484
|
-
|
|
571
|
+
for (const key of keysToDelete) {
|
|
572
|
+
this.storage.delete(key);
|
|
573
|
+
}
|
|
485
574
|
},
|
|
486
575
|
};
|
|
487
576
|
const assetAPI = {
|
|
488
577
|
getUrl: (filename) => {
|
|
489
|
-
//
|
|
578
|
+
// キャッシュに最終URL(リダイレクト後)があればそれを返す
|
|
579
|
+
const cachedUrl = instance.assetUrls.get(filename);
|
|
580
|
+
if (cachedUrl) {
|
|
581
|
+
return cachedUrl;
|
|
582
|
+
}
|
|
583
|
+
// なければAPIエンドポイントを返す(URLエンコーディング対応)
|
|
490
584
|
const encodedPackageName = encodeURIComponent(packageName);
|
|
491
585
|
const encodedFilename = encodeURIComponent(filename);
|
|
492
586
|
const url = `/api/plugin/${encodedPackageName}/assets/${encodedFilename}`;
|
|
493
|
-
// instance内にURLを保存(Context API経由でアクセス可能にするため)
|
|
494
|
-
instance.assetUrls.set(filename, url);
|
|
495
587
|
return url;
|
|
496
588
|
},
|
|
497
|
-
preload: (filenames) =>
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
589
|
+
preload: (filenames) => {
|
|
590
|
+
const preloadPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
591
|
+
const promises = filenames.map((filename) => __awaiter(this, void 0, void 0, function* () {
|
|
592
|
+
// APIエンドポイントURL
|
|
593
|
+
const encodedPackageName = encodeURIComponent(packageName);
|
|
594
|
+
const encodedFilename = encodeURIComponent(filename);
|
|
595
|
+
const apiUrl = `/api/plugin/${encodedPackageName}/assets/${encodedFilename}`;
|
|
596
|
+
// fetchでリダイレクト後の最終URLを取得
|
|
597
|
+
try {
|
|
598
|
+
const response = yield fetch(apiUrl, { method: "HEAD" });
|
|
599
|
+
const finalUrl = response.url; // リダイレクト後の最終URL
|
|
600
|
+
// 最終URLをキャッシュに保存
|
|
601
|
+
instance.assetUrls.set(filename, finalUrl);
|
|
602
|
+
// 画像をプリロード
|
|
603
|
+
if (filename.match(/\.(png|jpg|jpeg|gif|webp)$/i)) {
|
|
604
|
+
return new Promise((resolve, reject) => {
|
|
605
|
+
const img = new Image();
|
|
606
|
+
img.onload = () => resolve();
|
|
607
|
+
img.onerror = () => reject(new Error(`Failed to load image: ${finalUrl}`));
|
|
608
|
+
img.src = finalUrl;
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
// 音声をプリロード
|
|
612
|
+
if (filename.match(/\.(mp3|wav|ogg)$/i)) {
|
|
613
|
+
return new Promise((resolve, reject) => {
|
|
614
|
+
const audio = new Audio();
|
|
615
|
+
audio.oncanplaythrough = () => resolve();
|
|
616
|
+
audio.onerror = () => reject(new Error(`Failed to load audio: ${finalUrl}`));
|
|
617
|
+
audio.src = finalUrl;
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
console.error(`Failed to preload asset: ${filename}`, error);
|
|
623
|
+
}
|
|
624
|
+
return Promise.resolve();
|
|
625
|
+
}));
|
|
626
|
+
yield Promise.all(promises);
|
|
627
|
+
}))();
|
|
628
|
+
// プリロードPromiseを追跡
|
|
629
|
+
this.preloadPromises.push(preloadPromise);
|
|
630
|
+
return preloadPromise;
|
|
631
|
+
},
|
|
520
632
|
playSound: (filename, options) => {
|
|
521
|
-
|
|
522
|
-
|
|
633
|
+
// ミュート中は再生しない
|
|
634
|
+
if (this.muteAudio)
|
|
635
|
+
return;
|
|
523
636
|
const audio = new Audio(assetAPI.getUrl(filename));
|
|
524
637
|
if ((options === null || options === void 0 ? void 0 : options.volume) !== undefined) {
|
|
525
638
|
audio.volume = options.volume;
|
|
@@ -531,10 +644,30 @@ export class PluginManager {
|
|
|
531
644
|
console.error("Failed to play sound:", error);
|
|
532
645
|
});
|
|
533
646
|
},
|
|
647
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
534
648
|
stopSound: (_filename) => {
|
|
535
|
-
// eslint-disable-line @typescript-eslint/no-unused-vars
|
|
536
649
|
// TODO: 音声停止の実装
|
|
537
650
|
},
|
|
651
|
+
playWorkSound: (soundId, options) => {
|
|
652
|
+
// ミュート中は再生しない
|
|
653
|
+
if (this.muteAudio)
|
|
654
|
+
return;
|
|
655
|
+
const soundUrl = this.getWorkSoundUrl(soundId);
|
|
656
|
+
if (!soundUrl) {
|
|
657
|
+
console.warn(`Work sound not found: ${soundId}`);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const audio = new Audio(soundUrl);
|
|
661
|
+
if ((options === null || options === void 0 ? void 0 : options.volume) !== undefined) {
|
|
662
|
+
audio.volume = options.volume;
|
|
663
|
+
}
|
|
664
|
+
if (options === null || options === void 0 ? void 0 : options.loop) {
|
|
665
|
+
audio.loop = true;
|
|
666
|
+
}
|
|
667
|
+
audio.play().catch((error) => {
|
|
668
|
+
console.error("Failed to play work sound:", error);
|
|
669
|
+
});
|
|
670
|
+
},
|
|
538
671
|
};
|
|
539
672
|
const componentAPI = {
|
|
540
673
|
registerComponent: (type, component) => {
|
|
@@ -579,6 +712,23 @@ export class PluginManager {
|
|
|
579
712
|
const pluginEntry = this.plugins.get(packageName);
|
|
580
713
|
return (pluginEntry === null || pluginEntry === void 0 ? void 0 : pluginEntry.config) || {};
|
|
581
714
|
},
|
|
715
|
+
selectChoice: (choiceId) => {
|
|
716
|
+
if (this.selectChoiceCallback) {
|
|
717
|
+
this.selectChoiceCallback(choiceId);
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
console.warn("selectChoice callback is not registered. Make sure to call setSelectChoiceCallback.");
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
getBranchState: () => {
|
|
724
|
+
if (this.getBranchStateCallback) {
|
|
725
|
+
return this.getBranchStateCallback();
|
|
726
|
+
}
|
|
727
|
+
return null;
|
|
728
|
+
},
|
|
729
|
+
overrideClickSound: () => {
|
|
730
|
+
this.overrideClickSound();
|
|
731
|
+
},
|
|
582
732
|
hooks: {
|
|
583
733
|
onInit: (callback) => {
|
|
584
734
|
instance.hooks.onInit = callback;
|
|
@@ -592,9 +742,6 @@ export class PluginManager {
|
|
|
592
742
|
onCharacterEnter: (callback) => {
|
|
593
743
|
instance.hooks.onCharacterEnter = callback;
|
|
594
744
|
},
|
|
595
|
-
onCharacterExit: (callback) => {
|
|
596
|
-
instance.hooks.onCharacterExit = callback;
|
|
597
|
-
},
|
|
598
745
|
onDialogueShow: (callback) => {
|
|
599
746
|
instance.hooks.onDialogueShow = callback;
|
|
600
747
|
},
|
|
@@ -607,10 +754,28 @@ export class PluginManager {
|
|
|
607
754
|
onScenarioEnd: (callback) => {
|
|
608
755
|
instance.hooks.onScenarioEnd = callback;
|
|
609
756
|
},
|
|
757
|
+
onBranchStart: (callback) => {
|
|
758
|
+
instance.hooks.onBranchStart = callback;
|
|
759
|
+
},
|
|
760
|
+
onChoiceSelected: (callback) => {
|
|
761
|
+
instance.hooks.onChoiceSelected = callback;
|
|
762
|
+
},
|
|
763
|
+
onBranchEnd: (callback) => {
|
|
764
|
+
instance.hooks.onBranchEnd = callback;
|
|
765
|
+
},
|
|
766
|
+
onBranchBlockChange: (callback) => {
|
|
767
|
+
instance.hooks.onBranchBlockChange = callback;
|
|
768
|
+
},
|
|
610
769
|
},
|
|
611
770
|
effects: effectAPI,
|
|
612
771
|
storage: storageAPI,
|
|
613
772
|
assets: assetAPI,
|
|
773
|
+
blockOptions: {
|
|
774
|
+
register: () => {
|
|
775
|
+
// ランタイムでは何もしない(サーバー側での抽出時のみ使用)
|
|
776
|
+
// プラグインのsetup()が呼ばれた時点でBlockOptionsはすでにDBに保存済み
|
|
777
|
+
},
|
|
778
|
+
},
|
|
614
779
|
data: {
|
|
615
780
|
get: () => {
|
|
616
781
|
throw new Error("DataAPI.get() can only be called from within DataProvider context");
|
|
@@ -624,6 +789,21 @@ export class PluginManager {
|
|
|
624
789
|
updateSettings: () => {
|
|
625
790
|
throw new Error("DataAPI.updateSettings() can only be called from within DataProvider context");
|
|
626
791
|
},
|
|
792
|
+
getBlockOption: () => {
|
|
793
|
+
throw new Error("DataAPI.getBlockOption() can only be called from within DataProvider context");
|
|
794
|
+
},
|
|
795
|
+
setBgmVolume: () => {
|
|
796
|
+
throw new Error("DataAPI.setBgmVolume() can only be called from within DataProvider context");
|
|
797
|
+
},
|
|
798
|
+
setSeVolume: () => {
|
|
799
|
+
throw new Error("DataAPI.setSeVolume() can only be called from within DataProvider context");
|
|
800
|
+
},
|
|
801
|
+
setVoiceVolume: () => {
|
|
802
|
+
throw new Error("DataAPI.setVoiceVolume() can only be called from within DataProvider context");
|
|
803
|
+
},
|
|
804
|
+
setVolumes: () => {
|
|
805
|
+
throw new Error("DataAPI.setVolumes() can only be called from within DataProvider context");
|
|
806
|
+
},
|
|
627
807
|
},
|
|
628
808
|
};
|
|
629
809
|
}
|
|
@@ -703,16 +883,18 @@ export class PluginManager {
|
|
|
703
883
|
// クリーンアップ
|
|
704
884
|
cleanup() {
|
|
705
885
|
// 注入されたスタイルを削除
|
|
706
|
-
document
|
|
707
|
-
.
|
|
708
|
-
|
|
709
|
-
document
|
|
710
|
-
.
|
|
711
|
-
|
|
886
|
+
for (const el of document.querySelectorAll("style[data-plugin]")) {
|
|
887
|
+
el.remove();
|
|
888
|
+
}
|
|
889
|
+
for (const el of document.querySelectorAll("script[data-plugin]")) {
|
|
890
|
+
el.remove();
|
|
891
|
+
}
|
|
712
892
|
// エフェクトを削除
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
893
|
+
for (const { instance } of this.plugins.values()) {
|
|
894
|
+
for (const effect of instance.effects.values()) {
|
|
895
|
+
effect.remove();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
716
898
|
this.plugins.clear();
|
|
717
899
|
this.styleElements.clear();
|
|
718
900
|
this.injectedStyles.clear();
|
|
@@ -779,7 +961,9 @@ export class PluginManager {
|
|
|
779
961
|
// リスナーに通知
|
|
780
962
|
const listeners = this.uiVisibilityListeners.get(type);
|
|
781
963
|
if (listeners) {
|
|
782
|
-
|
|
964
|
+
for (const listener of listeners) {
|
|
965
|
+
listener(visible);
|
|
966
|
+
}
|
|
783
967
|
}
|
|
784
968
|
}
|
|
785
969
|
/**
|
|
@@ -801,6 +985,8 @@ export class PluginManager {
|
|
|
801
985
|
this.uiVisibilityListeners.set(type, new Set());
|
|
802
986
|
}
|
|
803
987
|
const listeners = this.uiVisibilityListeners.get(type);
|
|
988
|
+
if (!listeners)
|
|
989
|
+
return () => { };
|
|
804
990
|
listeners.add(listener);
|
|
805
991
|
// 現在の状態を即座に通知
|
|
806
992
|
listener(this.uiVisibilityState.get(type) || false);
|
|
@@ -851,4 +1037,88 @@ export class PluginManager {
|
|
|
851
1037
|
hideUI(type) {
|
|
852
1038
|
this.setUIVisibility(type, false);
|
|
853
1039
|
}
|
|
1040
|
+
setSelectChoiceCallback(callback) {
|
|
1041
|
+
this.selectChoiceCallback = callback;
|
|
1042
|
+
}
|
|
1043
|
+
setGetBranchStateCallback(callback) {
|
|
1044
|
+
this.getBranchStateCallback = callback;
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* 全てのアセットプリロードが完了するまで待機
|
|
1048
|
+
* @returns プリロード完了時にresolveするPromise
|
|
1049
|
+
*/
|
|
1050
|
+
waitForPreloads() {
|
|
1051
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1052
|
+
yield Promise.all(this.preloadPromises);
|
|
1053
|
+
this.preloadPromises = [];
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* 音声のミュート状態を設定
|
|
1058
|
+
* @param mute - ミュートするかどうか
|
|
1059
|
+
*/
|
|
1060
|
+
setMuteAudio(mute) {
|
|
1061
|
+
this.muteAudio = mute;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* 作品のサウンドデータを設定
|
|
1065
|
+
* @param sounds - サウンドIDをキーとしたURL/名前のマップ
|
|
1066
|
+
*/
|
|
1067
|
+
setWorkSounds(sounds) {
|
|
1068
|
+
this.workSounds.clear();
|
|
1069
|
+
for (const sound of sounds) {
|
|
1070
|
+
this.workSounds.set(sound.id, { url: sound.url, name: sound.name });
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* 作品のサウンドURLを取得
|
|
1075
|
+
* @param soundId - サウンドID
|
|
1076
|
+
*/
|
|
1077
|
+
getWorkSoundUrl(soundId) {
|
|
1078
|
+
var _a, _b;
|
|
1079
|
+
return (_b = (_a = this.workSounds.get(soundId)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : null;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* ビルトインのクリック音を設定
|
|
1083
|
+
* @param url - サウンドファイルのURL(nullで無効化)
|
|
1084
|
+
* @param volume - 音量(0.0 - 1.0)
|
|
1085
|
+
*/
|
|
1086
|
+
setClickSound(url, volume = 0.5) {
|
|
1087
|
+
this.clickSoundUrl = url;
|
|
1088
|
+
this.clickSoundVolume = volume;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* プラグインがクリック音を上書きする
|
|
1092
|
+
* これを呼び出すと、ビルトインのクリック音は再生されなくなる
|
|
1093
|
+
*/
|
|
1094
|
+
overrideClickSound() {
|
|
1095
|
+
this.clickSoundOverridden = true;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* プラグインのクリック音上書きをリセット
|
|
1099
|
+
* 各ブロック遷移の前に呼び出す
|
|
1100
|
+
*/
|
|
1101
|
+
resetClickSoundOverride() {
|
|
1102
|
+
this.clickSoundOverridden = false;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* クリック音が上書きされているかを取得
|
|
1106
|
+
*/
|
|
1107
|
+
isClickSoundOverridden() {
|
|
1108
|
+
return this.clickSoundOverridden;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* ビルトインのクリック音を再生
|
|
1112
|
+
* プラグインで上書きされている場合やミュート中は再生しない
|
|
1113
|
+
*/
|
|
1114
|
+
playClickSound() {
|
|
1115
|
+
if (this.muteAudio || this.clickSoundOverridden || !this.clickSoundUrl) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const audio = new Audio(this.clickSoundUrl);
|
|
1119
|
+
audio.volume = this.clickSoundVolume;
|
|
1120
|
+
audio.play().catch((error) => {
|
|
1121
|
+
console.error("Failed to play click sound:", error);
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
854
1124
|
}
|