@luna-editor/engine 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/dist/Player.d.ts +3 -0
- package/dist/Player.js +336 -0
- package/dist/atoms/screen-size.d.ts +22 -0
- package/dist/atoms/screen-size.js +8 -0
- package/dist/components/BacklogUI.d.ts +2 -0
- package/dist/components/BacklogUI.js +115 -0
- package/dist/components/ConversationLogUI.d.ts +2 -0
- package/dist/components/ConversationLogUI.js +115 -0
- package/dist/components/DebugControls.d.ts +12 -0
- package/dist/components/DebugControls.js +5 -0
- package/dist/components/DialogueBox.d.ts +2 -0
- package/dist/components/DialogueBox.js +28 -0
- package/dist/components/EndScreen.d.ts +8 -0
- package/dist/components/EndScreen.js +5 -0
- package/dist/components/GameScreen.d.ts +9 -0
- package/dist/components/GameScreen.js +111 -0
- package/dist/components/OverlayUI.d.ts +13 -0
- package/dist/components/OverlayUI.js +21 -0
- package/dist/components/PluginComponentProvider.d.ts +14 -0
- package/dist/components/PluginComponentProvider.js +24 -0
- package/dist/constants/screen-size.d.ts +3 -0
- package/dist/constants/screen-size.js +6 -0
- package/dist/contexts/DataContext.d.ts +24 -0
- package/dist/contexts/DataContext.js +101 -0
- package/dist/hooks/useBacklog.d.ts +14 -0
- package/dist/hooks/useBacklog.js +82 -0
- package/dist/hooks/useConversationLog.d.ts +14 -0
- package/dist/hooks/useConversationLog.js +82 -0
- package/dist/hooks/usePlayerLogic.d.ts +21 -0
- package/dist/hooks/usePlayerLogic.js +145 -0
- package/dist/hooks/usePluginAPI.d.ts +19 -0
- package/dist/hooks/usePluginAPI.js +42 -0
- package/dist/hooks/usePluginEvents.d.ts +14 -0
- package/dist/hooks/usePluginEvents.js +197 -0
- package/dist/hooks/usePreloadImages.d.ts +2 -0
- package/dist/hooks/usePreloadImages.js +56 -0
- package/dist/hooks/useScreenSize.d.ts +89 -0
- package/dist/hooks/useScreenSize.js +87 -0
- package/dist/hooks/useTypewriter.d.ts +11 -0
- package/dist/hooks/useTypewriter.js +56 -0
- package/dist/hooks/useUIVisibility.d.ts +9 -0
- package/dist/hooks/useUIVisibility.js +19 -0
- package/dist/hooks/useVoice.d.ts +4 -0
- package/dist/hooks/useVoice.js +21 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/plugin/PluginManager.d.ts +108 -0
- package/dist/plugin/PluginManager.js +851 -0
- package/dist/plugin/luna-react.d.ts +41 -0
- package/dist/plugin/luna-react.js +99 -0
- package/dist/sdk.d.ts +512 -0
- package/dist/sdk.js +64 -0
- package/dist/types.d.ts +186 -0
- package/dist/types.js +2 -0
- package/dist/utils/attributeNormalizer.d.ts +5 -0
- package/dist/utils/attributeNormalizer.js +53 -0
- package/dist/utils/facePositionCalculator.d.ts +29 -0
- package/dist/utils/facePositionCalculator.js +127 -0
- package/package.json +55 -0
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
11
|
+
import React from "react";
|
|
12
|
+
import { useScreenSizeAtom } from "../atoms/screen-size";
|
|
13
|
+
import { useDataAPI } from "../contexts/DataContext";
|
|
14
|
+
import { usePluginAPI, useUIVisibility } from "../hooks/usePluginAPI";
|
|
15
|
+
import { useScreenScale, useScreenSize, useToPixel, } from "../hooks/useScreenSize";
|
|
16
|
+
import { ComponentType, } from "../sdk";
|
|
17
|
+
export class PluginManager {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.plugins = new Map();
|
|
20
|
+
this.styleElements = new Map();
|
|
21
|
+
this.injectedStyles = new Set();
|
|
22
|
+
this.storage = new Map();
|
|
23
|
+
this.componentRegistry = new Map();
|
|
24
|
+
this.uiVisibilityState = new Map();
|
|
25
|
+
this.uiVisibilityListeners = new Map();
|
|
26
|
+
this.initializeReactRuntime();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* グローバルReactランタイムを初期化(クライアントサイドのみ)
|
|
30
|
+
*/
|
|
31
|
+
initializeReactRuntime() {
|
|
32
|
+
// サーバーサイドではスキップ
|
|
33
|
+
if (typeof window === "undefined") {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// JSXランタイム
|
|
37
|
+
window.__LUNA_JSX_RUNTIME__ = {
|
|
38
|
+
jsx: (type, props, key) => {
|
|
39
|
+
return React.createElement(type, key ? Object.assign(Object.assign({}, props), { key }) : props);
|
|
40
|
+
},
|
|
41
|
+
jsxs: (type, props, key) => {
|
|
42
|
+
return React.createElement(type, key ? Object.assign(Object.assign({}, props), { key }) : props);
|
|
43
|
+
},
|
|
44
|
+
Fragment: React.Fragment,
|
|
45
|
+
};
|
|
46
|
+
// Reactランタイム (useDataAPIフックと画面サイズフックを含む)
|
|
47
|
+
window.__LUNA_REACT_RUNTIME__ = Object.assign(Object.assign({}, React), { default: React, useDataAPI,
|
|
48
|
+
usePluginAPI,
|
|
49
|
+
useUIVisibility,
|
|
50
|
+
useToPixel,
|
|
51
|
+
useScreenSize,
|
|
52
|
+
useScreenScale,
|
|
53
|
+
useScreenSizeAtom,
|
|
54
|
+
ComponentType });
|
|
55
|
+
console.log("🔧 Luna React Runtime initialized for JSX support with hooks and screen size system");
|
|
56
|
+
}
|
|
57
|
+
loadPlugin(packageName, bundleUrl, config) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
try {
|
|
60
|
+
// スクリプトタグで動的にプラグインを読み込む
|
|
61
|
+
const plugin = yield this.loadPluginScript(bundleUrl, packageName);
|
|
62
|
+
const instance = {
|
|
63
|
+
actionNodes: new Map(),
|
|
64
|
+
hooks: {},
|
|
65
|
+
styles: new Map(),
|
|
66
|
+
effects: new Map(),
|
|
67
|
+
overlays: new Map(),
|
|
68
|
+
components: new Map(),
|
|
69
|
+
assetUrls: new Map(),
|
|
70
|
+
};
|
|
71
|
+
// プラグインAPIを作成
|
|
72
|
+
const api = this.createPluginAPI(packageName, instance);
|
|
73
|
+
// プラグインをセットアップ
|
|
74
|
+
plugin.setup(api);
|
|
75
|
+
console.log(`Plugin ${packageName} setup completed. Hooks:`, Object.keys(instance.hooks));
|
|
76
|
+
this.plugins.set(packageName, {
|
|
77
|
+
plugin,
|
|
78
|
+
instance,
|
|
79
|
+
enabled: true,
|
|
80
|
+
config: config || {},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error(`Failed to load plugin ${packageName}:`, error);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
loadPluginScript(url, packageName) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
// グローバル変数名を生成
|
|
91
|
+
const globalName = `__luna_plugin_${packageName.replace(/[@/-]/g, "_")}__`;
|
|
92
|
+
// グローバルコールバック関数を設定
|
|
93
|
+
// @ts-expect-error - 動的なグローバル変数の設定
|
|
94
|
+
window[globalName] = (plugin) => {
|
|
95
|
+
// プラグインを受け取ったらグローバル変数をクリーンアップ
|
|
96
|
+
// @ts-expect-error - 動的なグローバル変数の削除
|
|
97
|
+
delete window[globalName];
|
|
98
|
+
resolve(plugin);
|
|
99
|
+
};
|
|
100
|
+
const script = document.createElement("script");
|
|
101
|
+
script.type = "text/javascript";
|
|
102
|
+
script.dataset.plugin = packageName;
|
|
103
|
+
// エラーハンドリング
|
|
104
|
+
script.onerror = () => {
|
|
105
|
+
// @ts-expect-error - 動的なグローバル変数の削除
|
|
106
|
+
delete window[globalName];
|
|
107
|
+
reject(new Error(`Failed to load plugin script: ${url}`));
|
|
108
|
+
};
|
|
109
|
+
// プラグインバンドルをfetchで取得して実行
|
|
110
|
+
// 開発環境ではキャッシュを無効化してホットリロードを確実に動作させる
|
|
111
|
+
const isDevelopment = window.location.hostname === "localhost" ||
|
|
112
|
+
window.location.hostname === "127.0.0.1" ||
|
|
113
|
+
window.location.port === "3000";
|
|
114
|
+
const fetchOptions = isDevelopment
|
|
115
|
+
? {
|
|
116
|
+
cache: "no-cache",
|
|
117
|
+
headers: {
|
|
118
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
119
|
+
Pragma: "no-cache",
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
: {};
|
|
123
|
+
console.log(`🔄 Loading plugin ${packageName} (dev: ${isDevelopment}, cache: ${isDevelopment ? "disabled" : "enabled"})`);
|
|
124
|
+
fetch(url, fetchOptions)
|
|
125
|
+
.then((response) => {
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
128
|
+
}
|
|
129
|
+
return response.text();
|
|
130
|
+
})
|
|
131
|
+
.then((code) => {
|
|
132
|
+
const processedCode = code
|
|
133
|
+
// import文を削除
|
|
134
|
+
.replace(/^import\s+.*?from\s+['"].*?['"];?\s*$/gm, "")
|
|
135
|
+
.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__")');
|
|
142
|
+
// ES modules形式のプラグインコードを処理
|
|
143
|
+
const wrappedCode = `
|
|
144
|
+
(function() {
|
|
145
|
+
let pluginExport;
|
|
146
|
+
|
|
147
|
+
// definePlugin関数を提供
|
|
148
|
+
const definePlugin = function(plugin) {
|
|
149
|
+
pluginExport = plugin;
|
|
150
|
+
return plugin;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// CommonJS互換のexportsオブジェクト
|
|
154
|
+
const exports = {};
|
|
155
|
+
const module = { exports };
|
|
156
|
+
|
|
157
|
+
// Luna React Runtime を提供
|
|
158
|
+
const require = function(moduleName) {
|
|
159
|
+
if (moduleName === "__LUNA_REACT__" || moduleName === "luna-react" || moduleName === "react" || moduleName === "@luna-editor/engine") {
|
|
160
|
+
return window.__LUNA_REACT_RUNTIME__;
|
|
161
|
+
}
|
|
162
|
+
if (moduleName === "__LUNA_JSX_RUNTIME__" || moduleName === "luna-react/jsx-runtime" || moduleName === "react/jsx-runtime" || moduleName === "react/jsx-dev-runtime") {
|
|
163
|
+
return window.__LUNA_JSX_RUNTIME__;
|
|
164
|
+
}
|
|
165
|
+
throw new Error('Module not found: ' + moduleName);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// プラグインコードを実行
|
|
170
|
+
${processedCode}
|
|
171
|
+
|
|
172
|
+
// プラグインを取得
|
|
173
|
+
if (pluginExport) {
|
|
174
|
+
window['${globalName}'](pluginExport);
|
|
175
|
+
} else if (module.exports && module.exports.default) {
|
|
176
|
+
window['${globalName}'](module.exports.default);
|
|
177
|
+
} else if (exports.default) {
|
|
178
|
+
window['${globalName}'](exports.default);
|
|
179
|
+
} else {
|
|
180
|
+
throw new Error('Plugin export not found');
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('Plugin execution error:', error);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
187
|
+
`;
|
|
188
|
+
// スクリプトタグを作成
|
|
189
|
+
const scriptElement = document.createElement("script");
|
|
190
|
+
scriptElement.type = "text/javascript";
|
|
191
|
+
scriptElement.dataset.plugin = packageName;
|
|
192
|
+
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
|
+
// DOMに追加(この時点で同期的に実行される)
|
|
207
|
+
document.head.appendChild(scriptElement);
|
|
208
|
+
})
|
|
209
|
+
.catch((error) => {
|
|
210
|
+
// @ts-expect-error - 動的なグローバル変数の削除
|
|
211
|
+
delete window[globalName];
|
|
212
|
+
reject(error);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
createPluginAPI(packageName, instance) {
|
|
217
|
+
const createStyleElement = (elementName) => {
|
|
218
|
+
const classNames = new Set();
|
|
219
|
+
instance.styles.set(elementName, []);
|
|
220
|
+
return {
|
|
221
|
+
setClassName: (className) => {
|
|
222
|
+
classNames.clear();
|
|
223
|
+
className.split(" ").forEach((cn) => classNames.add(cn));
|
|
224
|
+
instance.styles.set(elementName, Array.from(classNames));
|
|
225
|
+
this.applyStyles(elementName, Array.from(classNames));
|
|
226
|
+
},
|
|
227
|
+
addClassName: (className) => {
|
|
228
|
+
className.split(" ").forEach((cn) => classNames.add(cn));
|
|
229
|
+
instance.styles.set(elementName, Array.from(classNames));
|
|
230
|
+
this.applyStyles(elementName, Array.from(classNames));
|
|
231
|
+
},
|
|
232
|
+
removeClassName: (className) => {
|
|
233
|
+
className.split(" ").forEach((cn) => classNames.delete(cn));
|
|
234
|
+
instance.styles.set(elementName, Array.from(classNames));
|
|
235
|
+
this.applyStyles(elementName, Array.from(classNames));
|
|
236
|
+
},
|
|
237
|
+
toggleClassName: (className) => {
|
|
238
|
+
className.split(" ").forEach((cn) => {
|
|
239
|
+
if (classNames.has(cn)) {
|
|
240
|
+
classNames.delete(cn);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
classNames.add(cn);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
instance.styles.set(elementName, Array.from(classNames));
|
|
247
|
+
this.applyStyles(elementName, Array.from(classNames));
|
|
248
|
+
},
|
|
249
|
+
setStyle: (style) => {
|
|
250
|
+
const element = this.styleElements.get(elementName);
|
|
251
|
+
if (element) {
|
|
252
|
+
Object.assign(element.style, style);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
when: (condition, className) => {
|
|
256
|
+
if (condition()) {
|
|
257
|
+
classNames.add(className);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
classNames.delete(className);
|
|
261
|
+
}
|
|
262
|
+
instance.styles.set(elementName, Array.from(classNames));
|
|
263
|
+
this.applyStyles(elementName, Array.from(classNames));
|
|
264
|
+
},
|
|
265
|
+
// オーバーレイ機能を追加
|
|
266
|
+
setOverlay: (options) => {
|
|
267
|
+
var _a, _b;
|
|
268
|
+
const overlayId = options.id || `${packageName}-overlay-${elementName}-${Date.now()}`;
|
|
269
|
+
const targetElement = this.styleElements.get(elementName);
|
|
270
|
+
if (!targetElement) {
|
|
271
|
+
console.warn(`❌ [Overlay] Target element not found for overlay: ${elementName}`);
|
|
272
|
+
return overlayId;
|
|
273
|
+
}
|
|
274
|
+
// 既存のオーバーレイを削除
|
|
275
|
+
if (options.id && instance.overlays.has(options.id)) {
|
|
276
|
+
this.removeOverlay(options.id, instance);
|
|
277
|
+
}
|
|
278
|
+
// オーバーレイ要素を作成
|
|
279
|
+
const overlay = document.createElement("div");
|
|
280
|
+
overlay.id = overlayId;
|
|
281
|
+
// ターゲット要素内の画像を探してマスクを適用
|
|
282
|
+
const targetImage = targetElement.querySelector("img");
|
|
283
|
+
let maskStyles = {};
|
|
284
|
+
if (targetImage && targetImage.src) {
|
|
285
|
+
// 画像のスタイルプロパティを取得
|
|
286
|
+
const computedImageStyle = getComputedStyle(targetImage);
|
|
287
|
+
const objectFit = computedImageStyle.objectFit;
|
|
288
|
+
const objectPosition = computedImageStyle.objectPosition;
|
|
289
|
+
// マスクは画像と同じobject-fitとobject-positionを使用
|
|
290
|
+
maskStyles = {
|
|
291
|
+
maskImage: `url(${targetImage.src})`,
|
|
292
|
+
maskRepeat: "no-repeat",
|
|
293
|
+
maskPosition: objectPosition || "center",
|
|
294
|
+
maskSize: objectFit === "contain"
|
|
295
|
+
? "contain"
|
|
296
|
+
: objectFit === "cover"
|
|
297
|
+
? "cover"
|
|
298
|
+
: "100% 100%",
|
|
299
|
+
WebkitMaskImage: `url(${targetImage.src})`,
|
|
300
|
+
WebkitMaskRepeat: "no-repeat",
|
|
301
|
+
WebkitMaskPosition: objectPosition || "center",
|
|
302
|
+
WebkitMaskSize: objectFit === "contain"
|
|
303
|
+
? "contain"
|
|
304
|
+
: objectFit === "cover"
|
|
305
|
+
? "cover"
|
|
306
|
+
: "100% 100%",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const overlayStyles = Object.assign({ position: "absolute", top: "0", left: "0", width: "100%", height: "100%", backgroundColor: options.color, opacity: String((_a = options.opacity) !== null && _a !== void 0 ? _a : 1), mixBlendMode: options.blendMode || "normal", zIndex: String((_b = options.zIndex) !== null && _b !== void 0 ? _b : 1000), pointerEvents: "none", transition: options.duration
|
|
310
|
+
? `opacity ${options.duration}ms ease`
|
|
311
|
+
: "none" }, maskStyles);
|
|
312
|
+
// スタイルを個別に適用
|
|
313
|
+
Object.entries(overlayStyles).forEach(([key, value]) => {
|
|
314
|
+
if (typeof value === "string") {
|
|
315
|
+
overlay.style[key] = value;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
overlay.dataset.plugin = packageName;
|
|
319
|
+
overlay.dataset.overlay = "true";
|
|
320
|
+
overlay.dataset.overlayElement = elementName;
|
|
321
|
+
// ターゲット要素にrelative positionを設定
|
|
322
|
+
const originalPosition = getComputedStyle(targetElement).position;
|
|
323
|
+
if (originalPosition === "static") {
|
|
324
|
+
targetElement.style.position = "relative";
|
|
325
|
+
}
|
|
326
|
+
targetElement.appendChild(overlay);
|
|
327
|
+
instance.overlays.set(overlayId, overlay);
|
|
328
|
+
return overlayId;
|
|
329
|
+
},
|
|
330
|
+
removeOverlay: (overlayId) => {
|
|
331
|
+
this.removeOverlay(overlayId, instance);
|
|
332
|
+
},
|
|
333
|
+
clearOverlays: () => {
|
|
334
|
+
const overlaysToRemove = Array.from(instance.overlays.keys()).filter((id) => id.includes(`-overlay-${elementName}-`));
|
|
335
|
+
overlaysToRemove.forEach((id) => this.removeOverlay(id, instance));
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
const styleAPI = {
|
|
340
|
+
elements: {
|
|
341
|
+
speakerName: createStyleElement("speakerName"),
|
|
342
|
+
scenarioBlockContent: createStyleElement("scenarioBlockContent"),
|
|
343
|
+
dialogueBox: createStyleElement("dialogueBox"),
|
|
344
|
+
gameScreen: createStyleElement("gameScreen"),
|
|
345
|
+
characterSprite: createStyleElement("characterSprite"),
|
|
346
|
+
background: createStyleElement("background"),
|
|
347
|
+
},
|
|
348
|
+
injectCSS: (css) => {
|
|
349
|
+
if (this.injectedStyles.has(css))
|
|
350
|
+
return;
|
|
351
|
+
const style = document.createElement("style");
|
|
352
|
+
style.textContent = css;
|
|
353
|
+
style.dataset.plugin = packageName;
|
|
354
|
+
document.head.appendChild(style);
|
|
355
|
+
this.injectedStyles.add(css);
|
|
356
|
+
},
|
|
357
|
+
enableTailwind: () => {
|
|
358
|
+
if (!document.querySelector('script[src*="tailwindcss"]')) {
|
|
359
|
+
const script = document.createElement("script");
|
|
360
|
+
script.src = "https://cdn.tailwindcss.com";
|
|
361
|
+
script.dataset.plugin = packageName;
|
|
362
|
+
document.head.appendChild(script);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
const effectAPI = {
|
|
367
|
+
show: (options) => {
|
|
368
|
+
const effectId = `${packageName}-effect-${Date.now()}-${Math.random()}`;
|
|
369
|
+
const container = document.createElement("div");
|
|
370
|
+
container.id = effectId;
|
|
371
|
+
container.className = options.className || "";
|
|
372
|
+
container.innerHTML = options.html;
|
|
373
|
+
// ターゲット要素を取得
|
|
374
|
+
let targetElement = null;
|
|
375
|
+
if (options.target === "speaker") {
|
|
376
|
+
targetElement = document.querySelector("[data-character-sprite]");
|
|
377
|
+
}
|
|
378
|
+
else if (options.target === "dialogue") {
|
|
379
|
+
targetElement = document.querySelector("[data-dialogue-element]");
|
|
380
|
+
}
|
|
381
|
+
else if (options.target === "screen") {
|
|
382
|
+
targetElement = document.body;
|
|
383
|
+
}
|
|
384
|
+
else if (options.target instanceof HTMLElement) {
|
|
385
|
+
targetElement = options.target;
|
|
386
|
+
}
|
|
387
|
+
if (targetElement) {
|
|
388
|
+
if (options.position === "precise") {
|
|
389
|
+
// Precise mode: minimal container setup, let plugin handle all positioning
|
|
390
|
+
container.style.position = "absolute";
|
|
391
|
+
container.style.top = "0";
|
|
392
|
+
container.style.left = "0";
|
|
393
|
+
container.style.zIndex = "1000"; // Higher z-index for better visibility
|
|
394
|
+
// Don't set pointerEvents to none - let plugin handle interactions
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
// Standard positioning mode
|
|
398
|
+
container.style.position = "relative";
|
|
399
|
+
container.style.zIndex = "50"; // Tailwindのz-50相当(z-index: 50)
|
|
400
|
+
// ターゲット要素の親要素にpositionを設定
|
|
401
|
+
const parentElement = targetElement.parentElement;
|
|
402
|
+
if (parentElement) {
|
|
403
|
+
// 相対位置設定のために親要素をrelativeにする
|
|
404
|
+
const originalPosition = parentElement.style.position;
|
|
405
|
+
if (!originalPosition || originalPosition === "static") {
|
|
406
|
+
parentElement.style.position = "relative";
|
|
407
|
+
}
|
|
408
|
+
// エフェクトコンテナのスタイル設定
|
|
409
|
+
container.style.position = "absolute";
|
|
410
|
+
switch (options.position) {
|
|
411
|
+
case "above":
|
|
412
|
+
container.style.bottom = "100%";
|
|
413
|
+
container.style.left = "50%";
|
|
414
|
+
container.style.transform = "translateX(-50%)";
|
|
415
|
+
break;
|
|
416
|
+
case "below":
|
|
417
|
+
container.style.top = "100%";
|
|
418
|
+
container.style.left = "50%";
|
|
419
|
+
container.style.transform = "translateX(-50%)";
|
|
420
|
+
break;
|
|
421
|
+
case "left":
|
|
422
|
+
container.style.right = "100%";
|
|
423
|
+
container.style.top = "50%";
|
|
424
|
+
container.style.transform = "translateY(-50%)";
|
|
425
|
+
break;
|
|
426
|
+
case "right":
|
|
427
|
+
container.style.left = "100%";
|
|
428
|
+
container.style.top = "50%";
|
|
429
|
+
container.style.transform = "translateY(-50%)";
|
|
430
|
+
break;
|
|
431
|
+
case "center":
|
|
432
|
+
container.style.top = "50%";
|
|
433
|
+
container.style.left = "50%";
|
|
434
|
+
container.style.transform = "translate(-50%, -50%)";
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
targetElement.appendChild(container);
|
|
440
|
+
instance.effects.set(effectId, container);
|
|
441
|
+
// アニメーション
|
|
442
|
+
if (options.animation && options.animation !== "none") {
|
|
443
|
+
container.style.animation = `${options.animation} 0.3s ease-out`;
|
|
444
|
+
}
|
|
445
|
+
// 自動削除
|
|
446
|
+
if (options.duration) {
|
|
447
|
+
setTimeout(() => {
|
|
448
|
+
this.hideEffect(effectId, instance);
|
|
449
|
+
}, options.duration);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return effectId;
|
|
453
|
+
},
|
|
454
|
+
hide: (effectId) => {
|
|
455
|
+
this.hideEffect(effectId, instance);
|
|
456
|
+
},
|
|
457
|
+
hideAll: () => {
|
|
458
|
+
instance.effects.forEach((_, effectId) => {
|
|
459
|
+
this.hideEffect(effectId, instance);
|
|
460
|
+
});
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
const storageAPI = {
|
|
464
|
+
get: (key) => {
|
|
465
|
+
const fullKey = `${packageName}:${key}`;
|
|
466
|
+
return this.storage.get(fullKey);
|
|
467
|
+
},
|
|
468
|
+
set: (key, value) => {
|
|
469
|
+
const fullKey = `${packageName}:${key}`;
|
|
470
|
+
this.storage.set(fullKey, value);
|
|
471
|
+
},
|
|
472
|
+
remove: (key) => {
|
|
473
|
+
const fullKey = `${packageName}:${key}`;
|
|
474
|
+
this.storage.delete(fullKey);
|
|
475
|
+
},
|
|
476
|
+
clear: () => {
|
|
477
|
+
// プラグイン固有のストレージのみクリア
|
|
478
|
+
const keysToDelete = [];
|
|
479
|
+
for (const [key] of this.storage) {
|
|
480
|
+
if (key.startsWith(`${packageName}:`)) {
|
|
481
|
+
keysToDelete.push(key);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
keysToDelete.forEach((key) => this.storage.delete(key));
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
const assetAPI = {
|
|
488
|
+
getUrl: (filename) => {
|
|
489
|
+
// プラグインアセットのAPIエンドポイントを返す(URLエンコーディング対応)
|
|
490
|
+
const encodedPackageName = encodeURIComponent(packageName);
|
|
491
|
+
const encodedFilename = encodeURIComponent(filename);
|
|
492
|
+
const url = `/api/plugin/${encodedPackageName}/assets/${encodedFilename}`;
|
|
493
|
+
// instance内にURLを保存(Context API経由でアクセス可能にするため)
|
|
494
|
+
instance.assetUrls.set(filename, url);
|
|
495
|
+
return url;
|
|
496
|
+
},
|
|
497
|
+
preload: (filenames) => __awaiter(this, void 0, void 0, function* () {
|
|
498
|
+
const promises = filenames.map((filename) => {
|
|
499
|
+
const url = assetAPI.getUrl(filename);
|
|
500
|
+
if (filename.match(/\.(png|jpg|jpeg|gif|webp)$/i)) {
|
|
501
|
+
return new Promise((resolve, reject) => {
|
|
502
|
+
const img = new Image();
|
|
503
|
+
img.onload = () => resolve();
|
|
504
|
+
img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
|
|
505
|
+
img.src = url;
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
if (filename.match(/\.(mp3|wav|ogg)$/i)) {
|
|
509
|
+
return new Promise((resolve, reject) => {
|
|
510
|
+
const audio = new Audio();
|
|
511
|
+
audio.oncanplaythrough = () => resolve();
|
|
512
|
+
audio.onerror = () => reject(new Error(`Failed to load audio: ${url}`));
|
|
513
|
+
audio.src = url;
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
return Promise.resolve();
|
|
517
|
+
});
|
|
518
|
+
yield Promise.all(promises);
|
|
519
|
+
}),
|
|
520
|
+
playSound: (filename, options) => {
|
|
521
|
+
console.log("playing sound", filename);
|
|
522
|
+
console.log("url:", assetAPI.getUrl(filename));
|
|
523
|
+
const audio = new Audio(assetAPI.getUrl(filename));
|
|
524
|
+
if ((options === null || options === void 0 ? void 0 : options.volume) !== undefined) {
|
|
525
|
+
audio.volume = options.volume;
|
|
526
|
+
}
|
|
527
|
+
if (options === null || options === void 0 ? void 0 : options.loop) {
|
|
528
|
+
audio.loop = true;
|
|
529
|
+
}
|
|
530
|
+
audio.play().catch((error) => {
|
|
531
|
+
console.error("Failed to play sound:", error);
|
|
532
|
+
});
|
|
533
|
+
},
|
|
534
|
+
stopSound: (_filename) => {
|
|
535
|
+
// eslint-disable-line @typescript-eslint/no-unused-vars
|
|
536
|
+
// TODO: 音声停止の実装
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
const componentAPI = {
|
|
540
|
+
registerComponent: (type, component) => {
|
|
541
|
+
// Check if component type is already registered by another plugin
|
|
542
|
+
if (this.componentRegistry.has(type)) {
|
|
543
|
+
const existingPlugin = this.findPluginByComponentType(type);
|
|
544
|
+
console.error(`Component type "${type}" is already registered by plugin "${existingPlugin}". Registration from "${packageName}" rejected.`);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
instance.components.set(type, component);
|
|
548
|
+
this.componentRegistry.set(type, component);
|
|
549
|
+
console.log(`Plugin ${packageName} registered component: ${type}`);
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
const uiAPI = {
|
|
553
|
+
show: (type) => {
|
|
554
|
+
this.setUIVisibility(type, true);
|
|
555
|
+
},
|
|
556
|
+
hide: (type) => {
|
|
557
|
+
this.setUIVisibility(type, false);
|
|
558
|
+
},
|
|
559
|
+
toggle: (type) => {
|
|
560
|
+
const currentState = this.uiVisibilityState.get(type) || false;
|
|
561
|
+
this.setUIVisibility(type, !currentState);
|
|
562
|
+
},
|
|
563
|
+
isVisible: (type) => {
|
|
564
|
+
return this.uiVisibilityState.get(type) || false;
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
return {
|
|
568
|
+
style: styleAPI,
|
|
569
|
+
components: componentAPI,
|
|
570
|
+
ui: uiAPI,
|
|
571
|
+
React: React,
|
|
572
|
+
defineActionNode: (definition) => {
|
|
573
|
+
instance.actionNodes.set(definition.type, definition);
|
|
574
|
+
},
|
|
575
|
+
setNextCharacterSpeakHandler: (callback) => {
|
|
576
|
+
instance.hooks.characterSpeakHandler = callback;
|
|
577
|
+
},
|
|
578
|
+
getConfig: () => {
|
|
579
|
+
const pluginEntry = this.plugins.get(packageName);
|
|
580
|
+
return (pluginEntry === null || pluginEntry === void 0 ? void 0 : pluginEntry.config) || {};
|
|
581
|
+
},
|
|
582
|
+
hooks: {
|
|
583
|
+
onInit: (callback) => {
|
|
584
|
+
instance.hooks.onInit = callback;
|
|
585
|
+
},
|
|
586
|
+
onScenarioReady: (callback) => {
|
|
587
|
+
instance.hooks.onScenarioReady = callback;
|
|
588
|
+
},
|
|
589
|
+
onBlockChange: (callback) => {
|
|
590
|
+
instance.hooks.onBlockChange = callback;
|
|
591
|
+
},
|
|
592
|
+
onCharacterEnter: (callback) => {
|
|
593
|
+
instance.hooks.onCharacterEnter = callback;
|
|
594
|
+
},
|
|
595
|
+
onCharacterExit: (callback) => {
|
|
596
|
+
instance.hooks.onCharacterExit = callback;
|
|
597
|
+
},
|
|
598
|
+
onDialogueShow: (callback) => {
|
|
599
|
+
instance.hooks.onDialogueShow = callback;
|
|
600
|
+
},
|
|
601
|
+
onActionExecute: (callback) => {
|
|
602
|
+
instance.hooks.onActionExecute = callback;
|
|
603
|
+
},
|
|
604
|
+
onScenarioStart: (callback) => {
|
|
605
|
+
instance.hooks.onScenarioStart = callback;
|
|
606
|
+
},
|
|
607
|
+
onScenarioEnd: (callback) => {
|
|
608
|
+
instance.hooks.onScenarioEnd = callback;
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
effects: effectAPI,
|
|
612
|
+
storage: storageAPI,
|
|
613
|
+
assets: assetAPI,
|
|
614
|
+
data: {
|
|
615
|
+
get: () => {
|
|
616
|
+
throw new Error("DataAPI.get() can only be called from within DataProvider context");
|
|
617
|
+
},
|
|
618
|
+
subscribe: () => {
|
|
619
|
+
throw new Error("DataAPI.subscribe() can only be called from within DataProvider context");
|
|
620
|
+
},
|
|
621
|
+
watch: () => {
|
|
622
|
+
throw new Error("DataAPI.watch() can only be called from within DataProvider context");
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
applyStyles(elementName, classNames) {
|
|
628
|
+
const element = this.styleElements.get(elementName);
|
|
629
|
+
if (element) {
|
|
630
|
+
element.className = classNames.join(" ");
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
hideEffect(effectId, instance) {
|
|
634
|
+
const element = instance.effects.get(effectId);
|
|
635
|
+
if (element) {
|
|
636
|
+
element.remove();
|
|
637
|
+
instance.effects.delete(effectId);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
removeOverlay(overlayId, instance) {
|
|
641
|
+
const overlay = instance.overlays.get(overlayId);
|
|
642
|
+
if (overlay) {
|
|
643
|
+
overlay.remove();
|
|
644
|
+
instance.overlays.delete(overlayId);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
registerStyleElement(name, element) {
|
|
648
|
+
// DOM要素にdata-style-element-name属性を追加
|
|
649
|
+
element.setAttribute("data-style-element-name", name);
|
|
650
|
+
this.styleElements.set(name, element);
|
|
651
|
+
}
|
|
652
|
+
// フック呼び出しメソッド
|
|
653
|
+
callHook(hookName, ...args) {
|
|
654
|
+
console.log(`Calling hook: ${hookName}`, args);
|
|
655
|
+
console.log(`Number of plugins: ${this.plugins.size}`);
|
|
656
|
+
this.plugins.forEach((loadedPlugin, packageName) => {
|
|
657
|
+
const { instance, enabled } = loadedPlugin;
|
|
658
|
+
console.log(`Plugin ${packageName}: enabled=${enabled}, has hook=${!!instance.hooks[hookName]}`);
|
|
659
|
+
if (enabled && instance.hooks[hookName]) {
|
|
660
|
+
try {
|
|
661
|
+
console.log(`Executing hook ${hookName} for plugin ${packageName}`);
|
|
662
|
+
const hook = instance.hooks[hookName];
|
|
663
|
+
if (hook) {
|
|
664
|
+
hook(...args);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
console.error(`Plugin hook error (${hookName}):`, error);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
// ActionNode実行
|
|
674
|
+
executeActionNode(nodeType, context) {
|
|
675
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
676
|
+
for (const [, loadedPlugin] of this.plugins) {
|
|
677
|
+
const { instance, enabled } = loadedPlugin;
|
|
678
|
+
if (enabled && instance.actionNodes.has(nodeType)) {
|
|
679
|
+
const definition = instance.actionNodes.get(nodeType);
|
|
680
|
+
if (!definition)
|
|
681
|
+
continue;
|
|
682
|
+
try {
|
|
683
|
+
// contextをActionExecutionContextとして扱う
|
|
684
|
+
yield definition.execute(context);
|
|
685
|
+
}
|
|
686
|
+
catch (error) {
|
|
687
|
+
console.error(`Action node execution error (${nodeType}):`, error);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
// プラグインの有効/無効切り替え
|
|
694
|
+
setPluginEnabled(packageName, enabled) {
|
|
695
|
+
const plugin = this.plugins.get(packageName);
|
|
696
|
+
if (plugin) {
|
|
697
|
+
plugin.enabled = enabled;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// クリーンアップ
|
|
701
|
+
cleanup() {
|
|
702
|
+
// 注入されたスタイルを削除
|
|
703
|
+
document
|
|
704
|
+
.querySelectorAll("style[data-plugin]")
|
|
705
|
+
.forEach((el) => el.remove());
|
|
706
|
+
document
|
|
707
|
+
.querySelectorAll("script[data-plugin]")
|
|
708
|
+
.forEach((el) => el.remove());
|
|
709
|
+
// エフェクトを削除
|
|
710
|
+
this.plugins.forEach(({ instance }) => {
|
|
711
|
+
instance.effects.forEach((effect) => effect.remove());
|
|
712
|
+
});
|
|
713
|
+
this.plugins.clear();
|
|
714
|
+
this.styleElements.clear();
|
|
715
|
+
this.injectedStyles.clear();
|
|
716
|
+
this.storage.clear();
|
|
717
|
+
this.componentRegistry.clear();
|
|
718
|
+
}
|
|
719
|
+
// Component registry helper methods
|
|
720
|
+
findPluginByComponentType(type) {
|
|
721
|
+
for (const [packageName, plugin] of this.plugins) {
|
|
722
|
+
if (plugin.instance.components.has(type)) {
|
|
723
|
+
return packageName;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
getComponent(type) {
|
|
729
|
+
return this.componentRegistry.get(type) || null;
|
|
730
|
+
}
|
|
731
|
+
hasComponent(type) {
|
|
732
|
+
return this.componentRegistry.has(type);
|
|
733
|
+
}
|
|
734
|
+
getRegisteredComponents() {
|
|
735
|
+
return Array.from(this.componentRegistry.keys());
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Get ActionNode definition by type
|
|
739
|
+
*/
|
|
740
|
+
getActionNodeDefinition(type) {
|
|
741
|
+
for (const [, { instance }] of this.plugins) {
|
|
742
|
+
// First try direct type match
|
|
743
|
+
const directMatch = instance.actionNodes.get(type);
|
|
744
|
+
if (directMatch) {
|
|
745
|
+
return directMatch;
|
|
746
|
+
}
|
|
747
|
+
// If no direct match, try to find by displayName (for backwards compatibility)
|
|
748
|
+
for (const [, definition] of instance.actionNodes) {
|
|
749
|
+
if (definition.displayName === type) {
|
|
750
|
+
return definition;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return undefined;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* プラグインのアセットURLを取得
|
|
758
|
+
* @param packageName - プラグインのパッケージ名
|
|
759
|
+
* @param filename - アセットファイル名
|
|
760
|
+
* @returns アセットのURL(見つからない場合は空文字列)
|
|
761
|
+
*/
|
|
762
|
+
getPluginAssetUrl(packageName, filename) {
|
|
763
|
+
const plugin = this.plugins.get(packageName);
|
|
764
|
+
if (!plugin) {
|
|
765
|
+
return "";
|
|
766
|
+
}
|
|
767
|
+
return plugin.instance.assetUrls.get(filename) || "";
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* UI コンポーネントの表示状態を設定
|
|
771
|
+
* @param type - コンポーネントタイプ
|
|
772
|
+
* @param visible - 表示状態
|
|
773
|
+
*/
|
|
774
|
+
setUIVisibility(type, visible) {
|
|
775
|
+
this.uiVisibilityState.set(type, visible);
|
|
776
|
+
// リスナーに通知
|
|
777
|
+
const listeners = this.uiVisibilityListeners.get(type);
|
|
778
|
+
if (listeners) {
|
|
779
|
+
listeners.forEach((listener) => listener(visible));
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* UI コンポーネントの表示状態を取得
|
|
784
|
+
* @param type - コンポーネントタイプ
|
|
785
|
+
* @returns 表示状態
|
|
786
|
+
*/
|
|
787
|
+
getUIVisibility(type) {
|
|
788
|
+
return this.uiVisibilityState.get(type) || false;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* UI コンポーネントの表示状態変更を監視
|
|
792
|
+
* @param type - コンポーネントタイプ
|
|
793
|
+
* @param listener - コールバック関数
|
|
794
|
+
* @returns リスナーを削除する関数
|
|
795
|
+
*/
|
|
796
|
+
subscribeUIVisibility(type, listener) {
|
|
797
|
+
if (!this.uiVisibilityListeners.has(type)) {
|
|
798
|
+
this.uiVisibilityListeners.set(type, new Set());
|
|
799
|
+
}
|
|
800
|
+
const listeners = this.uiVisibilityListeners.get(type);
|
|
801
|
+
listeners.add(listener);
|
|
802
|
+
// 現在の状態を即座に通知
|
|
803
|
+
listener(this.uiVisibilityState.get(type) || false);
|
|
804
|
+
// アンサブスクライブ関数を返す
|
|
805
|
+
return () => {
|
|
806
|
+
listeners.delete(listener);
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* UIAPIを取得する(プラグインコンポーネントで使用可能にするため)
|
|
811
|
+
*/
|
|
812
|
+
getUIAPI() {
|
|
813
|
+
return {
|
|
814
|
+
show: (type) => {
|
|
815
|
+
this.setUIVisibility(type, true);
|
|
816
|
+
},
|
|
817
|
+
hide: (type) => {
|
|
818
|
+
this.setUIVisibility(type, false);
|
|
819
|
+
},
|
|
820
|
+
toggle: (type) => {
|
|
821
|
+
const currentState = this.uiVisibilityState.get(type) || false;
|
|
822
|
+
this.setUIVisibility(type, !currentState);
|
|
823
|
+
},
|
|
824
|
+
isVisible: (type) => {
|
|
825
|
+
return this.uiVisibilityState.get(type) || false;
|
|
826
|
+
},
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* UI コンポーネントの表示/非表示を切り替え
|
|
831
|
+
* @param type - コンポーネントタイプ
|
|
832
|
+
*/
|
|
833
|
+
toggleUIVisibility(type) {
|
|
834
|
+
const currentState = this.uiVisibilityState.get(type) || false;
|
|
835
|
+
this.setUIVisibility(type, !currentState);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* UI コンポーネントを表示
|
|
839
|
+
* @param type - コンポーネントタイプ
|
|
840
|
+
*/
|
|
841
|
+
showUI(type) {
|
|
842
|
+
this.setUIVisibility(type, true);
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* UI コンポーネントを非表示
|
|
846
|
+
* @param type - コンポーネントタイプ
|
|
847
|
+
*/
|
|
848
|
+
hideUI(type) {
|
|
849
|
+
this.setUIVisibility(type, false);
|
|
850
|
+
}
|
|
851
|
+
}
|