@luna-editor/engine 0.4.4 → 0.5.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.js +35 -113
- package/dist/components/BackgroundLayer.js +5 -7
- package/dist/components/GameScreen.d.ts +2 -8
- package/dist/components/GameScreen.js +7 -41
- package/dist/components/OverlayUI.d.ts +3 -2
- package/dist/components/OverlayUI.js +14 -3
- package/dist/components/PluginComponentProvider.js +1 -1
- package/dist/contexts/DataContext.d.ts +2 -0
- package/dist/contexts/DataContext.js +21 -26
- package/dist/data-api-types.d.ts +8 -6
- package/dist/hooks/useFontLoader.d.ts +2 -9
- package/dist/hooks/useFontLoader.js +87 -126
- package/dist/hooks/usePluginAPI.js +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- package/dist/plugin/PluginManager.d.ts +1 -4
- package/dist/plugin/PluginManager.js +22 -51
- package/dist/sdk.d.ts +1 -1
- package/dist/types.d.ts +2 -17
- package/package.json +1 -1
- package/dist/contexts/PlaybackTextContext.d.ts +0 -32
- package/dist/contexts/PlaybackTextContext.js +0 -29
- package/dist/hooks/useImagePreloader.d.ts +0 -5
- package/dist/hooks/useImagePreloader.js +0 -53
- package/dist/plugin/luna-react.d.ts +0 -41
- package/dist/plugin/luna-react.js +0 -99
package/dist/data-api-types.d.ts
CHANGED
|
@@ -51,10 +51,8 @@ export interface PlayerSettingsData {
|
|
|
51
51
|
voiceVolume: number;
|
|
52
52
|
/** スキップ設定 */
|
|
53
53
|
skipMode?: "all" | "unread" | "none";
|
|
54
|
-
/**
|
|
54
|
+
/** 選択されたフォントファミリー */
|
|
55
55
|
selectedFontFamily?: string;
|
|
56
|
-
/** 選択されたUIフォントファミリー(メニュー・ラベル等) */
|
|
57
|
-
selectedUIFontFamily?: string;
|
|
58
56
|
}
|
|
59
57
|
/**
|
|
60
58
|
* プラグインアセットデータ
|
|
@@ -74,10 +72,8 @@ export interface PluginAssetsData {
|
|
|
74
72
|
export interface FontsData {
|
|
75
73
|
/** 利用可能なフォント一覧 */
|
|
76
74
|
fonts: WorkFont[];
|
|
77
|
-
/**
|
|
75
|
+
/** 選択されているフォントファミリー */
|
|
78
76
|
selectedFontFamily: string | undefined;
|
|
79
|
-
/** 選択されているUIフォントファミリー(メニュー・ラベル等) */
|
|
80
|
-
selectedUIFontFamily: string | undefined;
|
|
81
77
|
/** フォントが読み込み完了しているかどうか */
|
|
82
78
|
isLoaded: boolean;
|
|
83
79
|
}
|
|
@@ -248,4 +244,10 @@ export interface DataAPI {
|
|
|
248
244
|
* @param state - 更新する感情エフェクトの状態
|
|
249
245
|
*/
|
|
250
246
|
updateEmotionEffect(state: EmotionEffectState | null): void;
|
|
247
|
+
/**
|
|
248
|
+
* シナリオを中断する
|
|
249
|
+
* プラグインのGameMenuなどから呼び出し、シナリオ再生を中断する。
|
|
250
|
+
* Player の onScenarioCancelled コールバックが発火される。
|
|
251
|
+
*/
|
|
252
|
+
cancelScenario(): void;
|
|
251
253
|
}
|
|
@@ -3,11 +3,9 @@ import type { WorkFont } from "../types";
|
|
|
3
3
|
* フォントを読み込むカスタムフック
|
|
4
4
|
* - Googleフォント: <link>タグで読み込み + document.fonts.load()でプリロード
|
|
5
5
|
* - カスタムフォント: FontFace APIで読み込み
|
|
6
|
-
* -
|
|
7
|
-
* - 再生開始前に全フォントを一括ダウンロードし、再生中のフォントスワップを防止する
|
|
8
|
-
* - preloadText: シナリオ内の全テキストを渡すと、使用される全ての文字でフォントをプリロード
|
|
6
|
+
* - すべてのフォントがプリロード&キャッシュされてからisLoaded=trueになる
|
|
9
7
|
*/
|
|
10
|
-
export declare function useFontLoader(fonts: WorkFont[] | undefined
|
|
8
|
+
export declare function useFontLoader(fonts: WorkFont[] | undefined): {
|
|
11
9
|
isLoaded: boolean;
|
|
12
10
|
loadedFonts: string[];
|
|
13
11
|
};
|
|
@@ -15,11 +13,6 @@ export declare function useFontLoader(fonts: WorkFont[] | undefined, preloadText
|
|
|
15
13
|
* フォントファミリーに基づいてCSSのfont-family値を生成
|
|
16
14
|
*/
|
|
17
15
|
export declare function getFontFamilyStyle(selectedFontFamily: string | undefined, fonts: WorkFont[] | undefined): string;
|
|
18
|
-
/**
|
|
19
|
-
* UIフォントファミリーに基づいてCSSのfont-family値を生成
|
|
20
|
-
* メニュー、ボタン、ラベルなどのUI要素に使用
|
|
21
|
-
*/
|
|
22
|
-
export declare function getUIFontFamilyStyle(selectedUIFontFamily: string | undefined, fonts: WorkFont[] | undefined): string;
|
|
23
16
|
/**
|
|
24
17
|
* フォントがキャッシュに存在するかチェック
|
|
25
18
|
*/
|
|
@@ -10,137 +10,115 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { useEffect, useRef, useState } from "react";
|
|
11
11
|
// グローバルなフォントキャッシュ(ページ内で共有)
|
|
12
12
|
const fontCache = new Set();
|
|
13
|
-
/**
|
|
14
|
-
* Googleフォントの<link>タグを挿入し、CSSの読み込み完了を待つ
|
|
15
|
-
*/
|
|
16
|
-
function loadGoogleFontCSS(fontFamily) {
|
|
17
|
-
const existingLink = document.querySelector(`link[data-font-family="${fontFamily}"]`);
|
|
18
|
-
if (existingLink) {
|
|
19
|
-
return Promise.resolve();
|
|
20
|
-
}
|
|
21
|
-
const link = document.createElement("link");
|
|
22
|
-
link.rel = "stylesheet";
|
|
23
|
-
// display=blockでフォント読み込みまでテキストを非表示にする(FOUTを防止)
|
|
24
|
-
link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontFamily)}:wght@400;700&display=block`;
|
|
25
|
-
link.setAttribute("data-font-family", fontFamily);
|
|
26
|
-
document.head.appendChild(link);
|
|
27
|
-
return new Promise((resolve) => {
|
|
28
|
-
link.onload = () => resolve();
|
|
29
|
-
link.onerror = () => {
|
|
30
|
-
console.warn(`Failed to load Google font CSS: ${fontFamily}`);
|
|
31
|
-
resolve();
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Googleフォントのフォントデータを事前にダウンロード
|
|
37
|
-
* 400/700両方のウェイトをプリロードする
|
|
38
|
-
*/
|
|
39
|
-
function preloadGoogleFont(fontFamily, textToPreload) {
|
|
40
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
-
try {
|
|
42
|
-
// 400と700両方のウェイトをプリロード
|
|
43
|
-
yield Promise.all([
|
|
44
|
-
document.fonts.load(`400 16px "${fontFamily}"`, textToPreload),
|
|
45
|
-
document.fonts.load(`700 16px "${fontFamily}"`, textToPreload),
|
|
46
|
-
]);
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
console.warn(`Failed to preload Google font: ${fontFamily}`, error);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* カスタムフォントをFontFace APIで読み込み
|
|
55
|
-
*/
|
|
56
|
-
function loadCustomFont(fontFamily, url, fontName) {
|
|
57
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
-
try {
|
|
59
|
-
// 既にdocument.fontsに登録されているかチェック
|
|
60
|
-
let fontExists = false;
|
|
61
|
-
for (const existingFont of document.fonts) {
|
|
62
|
-
if (existingFont.family === fontFamily) {
|
|
63
|
-
fontExists = true;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (!fontExists) {
|
|
68
|
-
const fontFace = new FontFace(fontFamily, `url(${url})`);
|
|
69
|
-
yield fontFace.load();
|
|
70
|
-
document.fonts.add(fontFace);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
console.warn(`Failed to load custom font: ${fontName}`, error);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
13
|
/**
|
|
79
14
|
* フォントを読み込むカスタムフック
|
|
80
15
|
* - Googleフォント: <link>タグで読み込み + document.fonts.load()でプリロード
|
|
81
16
|
* - カスタムフォント: FontFace APIで読み込み
|
|
82
|
-
* -
|
|
83
|
-
* - 再生開始前に全フォントを一括ダウンロードし、再生中のフォントスワップを防止する
|
|
84
|
-
* - preloadText: シナリオ内の全テキストを渡すと、使用される全ての文字でフォントをプリロード
|
|
17
|
+
* - すべてのフォントがプリロード&キャッシュされてからisLoaded=trueになる
|
|
85
18
|
*/
|
|
86
|
-
export function useFontLoader(fonts
|
|
19
|
+
export function useFontLoader(fonts) {
|
|
87
20
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
88
21
|
const [loadedFonts, setLoadedFonts] = useState([]);
|
|
89
|
-
|
|
90
|
-
const generationRef = useRef(0);
|
|
22
|
+
const loadingRef = useRef(false);
|
|
91
23
|
useEffect(() => {
|
|
92
24
|
if (!fonts || fonts.length === 0) {
|
|
93
25
|
setIsLoaded(true);
|
|
94
26
|
return;
|
|
95
27
|
}
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
28
|
+
// 既に読み込み中の場合はスキップ
|
|
29
|
+
if (loadingRef.current)
|
|
30
|
+
return;
|
|
31
|
+
loadingRef.current = true;
|
|
99
32
|
const loadFonts = () => __awaiter(this, void 0, void 0, function* () {
|
|
100
|
-
var _a;
|
|
101
33
|
const loadedFontFamilies = [];
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const cachedFonts = fonts.filter((f) => fontCache.has(f.fontFamily));
|
|
107
|
-
// キャッシュ済みフォントは即座に追加
|
|
108
|
-
for (const font of cachedFonts) {
|
|
109
|
-
loadedFontFamilies.push(font.fontFamily);
|
|
110
|
-
}
|
|
111
|
-
// Google FontsのCSS読み込みを並列実行
|
|
112
|
-
yield Promise.all(googleFonts.map((font) => loadGoogleFontCSS(font.fontFamily)));
|
|
113
|
-
// キャンセルチェック
|
|
114
|
-
if (generation !== generationRef.current)
|
|
115
|
-
return;
|
|
116
|
-
// Google Fontsのフォントデータプリロード + カスタムフォント読み込みを並列実行
|
|
117
|
-
const allFontPromises = [];
|
|
118
|
-
for (const font of googleFonts) {
|
|
119
|
-
allFontPromises.push(preloadGoogleFont(font.fontFamily, textToPreload).then(() => {
|
|
120
|
-
fontCache.add(font.fontFamily);
|
|
121
|
-
loadedFontFamilies.push(font.fontFamily);
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
for (const font of customFonts) {
|
|
125
|
-
allFontPromises.push(loadCustomFont(font.fontFamily, (_a = font.url) !== null && _a !== void 0 ? _a : "", font.name).then(() => {
|
|
126
|
-
fontCache.add(font.fontFamily);
|
|
34
|
+
const fontLoadPromises = [];
|
|
35
|
+
for (const font of fonts) {
|
|
36
|
+
// キャッシュにあればスキップ
|
|
37
|
+
if (fontCache.has(font.fontFamily)) {
|
|
127
38
|
loadedFontFamilies.push(font.fontFamily);
|
|
128
|
-
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
if (font.type === "google") {
|
|
43
|
+
// Googleフォントを<link>タグで読み込み
|
|
44
|
+
const existingLink = document.querySelector(`link[data-font-family="${font.fontFamily}"]`);
|
|
45
|
+
if (!existingLink) {
|
|
46
|
+
const link = document.createElement("link");
|
|
47
|
+
link.rel = "stylesheet";
|
|
48
|
+
link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(font.fontFamily)}:wght@400;700&display=swap`;
|
|
49
|
+
link.setAttribute("data-font-family", font.fontFamily);
|
|
50
|
+
document.head.appendChild(link);
|
|
51
|
+
// CSSファイルの読み込み完了を待つ
|
|
52
|
+
yield new Promise((resolve) => {
|
|
53
|
+
link.onload = () => resolve();
|
|
54
|
+
link.onerror = () => {
|
|
55
|
+
console.warn(`Failed to load Google font CSS: ${font.fontFamily}`);
|
|
56
|
+
resolve();
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// フォントの実際の読み込み(プリロード)を開始
|
|
61
|
+
// document.fonts.load()でフォントデータを事前にダウンロード
|
|
62
|
+
const preloadPromise = document.fonts
|
|
63
|
+
.load(`16px "${font.fontFamily}"`)
|
|
64
|
+
.then(() => {
|
|
65
|
+
fontCache.add(font.fontFamily);
|
|
66
|
+
loadedFontFamilies.push(font.fontFamily);
|
|
67
|
+
})
|
|
68
|
+
.catch((error) => {
|
|
69
|
+
console.warn(`Failed to preload Google font: ${font.fontFamily}`, error);
|
|
70
|
+
// エラーでもキャッシュに追加(再試行防止)
|
|
71
|
+
fontCache.add(font.fontFamily);
|
|
72
|
+
loadedFontFamilies.push(font.fontFamily);
|
|
73
|
+
});
|
|
74
|
+
fontLoadPromises.push(preloadPromise);
|
|
75
|
+
}
|
|
76
|
+
else if (font.type === "custom" && font.url) {
|
|
77
|
+
// カスタムフォントをFontFace APIで読み込み
|
|
78
|
+
const loadCustomFont = () => __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
try {
|
|
80
|
+
// 既にdocument.fontsに登録されているかチェック
|
|
81
|
+
let fontExists = false;
|
|
82
|
+
for (const existingFont of document.fonts) {
|
|
83
|
+
if (existingFont.family === font.fontFamily) {
|
|
84
|
+
fontExists = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!fontExists) {
|
|
89
|
+
const fontFace = new FontFace(font.fontFamily, `url(${font.url})`);
|
|
90
|
+
// フォントデータをダウンロード&パース
|
|
91
|
+
yield fontFace.load();
|
|
92
|
+
// document.fontsに追加(キャッシュとして機能)
|
|
93
|
+
document.fonts.add(fontFace);
|
|
94
|
+
}
|
|
95
|
+
fontCache.add(font.fontFamily);
|
|
96
|
+
loadedFontFamilies.push(font.fontFamily);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.warn(`Failed to load custom font: ${font.name}`, error);
|
|
100
|
+
// エラーでもキャッシュに追加(再試行防止)
|
|
101
|
+
fontCache.add(font.fontFamily);
|
|
102
|
+
loadedFontFamilies.push(font.fontFamily);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
fontLoadPromises.push(loadCustomFont());
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.warn(`Failed to load font: ${font.name}`, error);
|
|
110
|
+
}
|
|
129
111
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (generation !== generationRef.current)
|
|
133
|
-
return;
|
|
112
|
+
// すべてのフォント読み込みを並列で待つ
|
|
113
|
+
yield Promise.all(fontLoadPromises);
|
|
134
114
|
// document.fonts.readyで全フォントの準備完了を確認
|
|
135
115
|
yield document.fonts.ready;
|
|
136
|
-
// キャンセルチェック
|
|
137
|
-
if (generation !== generationRef.current)
|
|
138
|
-
return;
|
|
139
116
|
setLoadedFonts(loadedFontFamilies);
|
|
140
117
|
setIsLoaded(true);
|
|
118
|
+
loadingRef.current = false;
|
|
141
119
|
});
|
|
142
120
|
loadFonts();
|
|
143
|
-
}, [fonts
|
|
121
|
+
}, [fonts]);
|
|
144
122
|
return { isLoaded, loadedFonts };
|
|
145
123
|
}
|
|
146
124
|
/**
|
|
@@ -161,23 +139,6 @@ export function getFontFamilyStyle(selectedFontFamily, fonts) {
|
|
|
161
139
|
// 選択されていない、または見つからない場合はデフォルト(最初の)フォントを使用
|
|
162
140
|
return `"${fonts[0].fontFamily}", sans-serif`;
|
|
163
141
|
}
|
|
164
|
-
/**
|
|
165
|
-
* UIフォントファミリーに基づいてCSSのfont-family値を生成
|
|
166
|
-
* メニュー、ボタン、ラベルなどのUI要素に使用
|
|
167
|
-
*/
|
|
168
|
-
export function getUIFontFamilyStyle(selectedUIFontFamily, fonts) {
|
|
169
|
-
if (!fonts || fonts.length === 0) {
|
|
170
|
-
return "sans-serif";
|
|
171
|
-
}
|
|
172
|
-
if (selectedUIFontFamily) {
|
|
173
|
-
const selectedFont = fonts.find((f) => f.fontFamily === selectedUIFontFamily);
|
|
174
|
-
if (selectedFont) {
|
|
175
|
-
return `"${selectedUIFontFamily}", sans-serif`;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// 選択されていない、または見つからない場合はデフォルト(最初の)フォントを使用
|
|
179
|
-
return `"${fonts[0].fontFamily}", sans-serif`;
|
|
180
|
-
}
|
|
181
142
|
/**
|
|
182
143
|
* フォントがキャッシュに存在するかチェック
|
|
183
144
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,9 @@ export { aspectRatio, BasisHeight, BasisWidth } from "./constants/screen-size";
|
|
|
5
5
|
export type { AudioSettings } from "./contexts/AudioContext";
|
|
6
6
|
export { useAudioSettings } from "./contexts/AudioContext";
|
|
7
7
|
export { useDataAPI } from "./contexts/DataContext";
|
|
8
|
-
export { getFontFamilyStyle, getUIFontFamilyStyle, } from "./hooks/useFontLoader";
|
|
9
8
|
export { setGlobalUIAPI, usePluginAPI, useUIVisibility, } from "./hooks/usePluginAPI";
|
|
10
9
|
export { useScreenScale, useScreenSize, useToPixel, } from "./hooks/useScreenSize";
|
|
11
10
|
export { Player } from "./Player";
|
|
12
|
-
export type { BacklogData, BlockChangeContext, DataAPI, DataContext, FontsData,
|
|
13
|
-
export { ComponentType,
|
|
11
|
+
export type { BacklogData, BlockChangeContext, DataAPI, DataContext, FontsData, PlayerSettingsData, ScenarioPlaybackData, TransitionSource, } from "./sdk";
|
|
12
|
+
export { ComponentType, renderTextWithLineBreaks } from "./sdk";
|
|
14
13
|
export type { ActionNode, BacklogEntry, Character, DisplayedCharacter, EntityState, FontType, PlayerProps, PlayerSettings, PlayerState, PluginConfig, PublishedScenario, ScenarioBlock, ScenarioBlockAttributeValue, ScenarioBlockCharacter, ScenarioBlockEntityAttributeValue, ScenarioBlockType, WorkFont, WorkSound, } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,7 @@ export { OverlayUI } from "./components/OverlayUI";
|
|
|
5
5
|
export { aspectRatio, BasisHeight, BasisWidth } from "./constants/screen-size";
|
|
6
6
|
export { useAudioSettings } from "./contexts/AudioContext";
|
|
7
7
|
export { useDataAPI } from "./contexts/DataContext";
|
|
8
|
-
export { getFontFamilyStyle, getUIFontFamilyStyle, } from "./hooks/useFontLoader";
|
|
9
8
|
export { setGlobalUIAPI, usePluginAPI, useUIVisibility, } from "./hooks/usePluginAPI";
|
|
10
9
|
export { useScreenScale, useScreenSize, useToPixel, } from "./hooks/useScreenSize";
|
|
11
10
|
export { Player } from "./Player";
|
|
12
|
-
export { ComponentType,
|
|
11
|
+
export { ComponentType, renderTextWithLineBreaks } from "./sdk";
|
|
@@ -62,10 +62,7 @@ export declare class PluginManager {
|
|
|
62
62
|
* 既に初期化済みの場合はスキップ
|
|
63
63
|
*/
|
|
64
64
|
private initializeReactRuntime;
|
|
65
|
-
loadPlugin(packageName: string, bundleUrl: string
|
|
66
|
-
filename: string;
|
|
67
|
-
url: string;
|
|
68
|
-
}[], plugin?: LunaPlugin): Promise<void>;
|
|
65
|
+
loadPlugin(packageName: string, bundleUrl: string, config?: unknown): Promise<void>;
|
|
69
66
|
private doLoadPlugin;
|
|
70
67
|
private loadPluginScript;
|
|
71
68
|
private createPluginAPI;
|
|
@@ -21,7 +21,6 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
21
21
|
import React from "react";
|
|
22
22
|
import { useScreenSizeAtom } from "../atoms/screen-size";
|
|
23
23
|
import { useDataAPI } from "../contexts/DataContext";
|
|
24
|
-
import { getFontFamilyStyle, getUIFontFamilyStyle, } from "../hooks/useFontLoader";
|
|
25
24
|
import { usePluginAPI, useUIVisibility } from "../hooks/usePluginAPI";
|
|
26
25
|
import { useScreenScale, useScreenSize, useToPixel, } from "../hooks/useScreenSize";
|
|
27
26
|
import { ComponentType, definePlugin, } from "../sdk";
|
|
@@ -108,32 +107,18 @@ export class PluginManager {
|
|
|
108
107
|
useScreenScale,
|
|
109
108
|
useScreenSizeAtom,
|
|
110
109
|
ComponentType,
|
|
111
|
-
definePlugin
|
|
112
|
-
getFontFamilyStyle,
|
|
113
|
-
getUIFontFamilyStyle });
|
|
110
|
+
definePlugin });
|
|
114
111
|
}
|
|
115
|
-
loadPlugin(packageName, bundleUrl, config
|
|
112
|
+
loadPlugin(packageName, bundleUrl, config) {
|
|
116
113
|
return __awaiter(this, void 0, void 0, function* () {
|
|
117
114
|
// 開発環境ではキャッシュをバイパスして常に最新コードをロード
|
|
118
115
|
const isDevelopment = this.isDevelopment();
|
|
119
116
|
if (isDevelopment) {
|
|
120
|
-
//
|
|
121
|
-
const existingLoadPromise = globalLoadingPlugins.get(packageName);
|
|
122
|
-
if (existingLoadPromise) {
|
|
123
|
-
yield existingLoadPromise;
|
|
124
|
-
// ロード完了後、結果をローカルにコピー
|
|
125
|
-
const loaded = globalLoadedPlugins.get(packageName);
|
|
126
|
-
if (loaded) {
|
|
127
|
-
this.plugins.set(packageName, Object.assign(Object.assign({}, loaded), { config: config !== null && config !== void 0 ? config : loaded.config }));
|
|
128
|
-
for (const [type, component] of loaded.instance.components) {
|
|
129
|
-
this.componentRegistry.set(type, component);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
// ロード中でなければキャッシュをクリアして再ロード
|
|
117
|
+
// 開発環境では強制リロード(キャッシュをクリア)
|
|
135
118
|
globalLoadedPlugins.delete(packageName);
|
|
136
119
|
this.plugins.delete(packageName);
|
|
120
|
+
// ロード中のPromiseもクリア(再ロードを確実にするため)
|
|
121
|
+
globalLoadingPlugins.delete(packageName);
|
|
137
122
|
}
|
|
138
123
|
else {
|
|
139
124
|
// 本番環境のみキャッシュを使用
|
|
@@ -175,7 +160,7 @@ export class PluginManager {
|
|
|
175
160
|
}
|
|
176
161
|
}
|
|
177
162
|
// ロード処理をPromiseとして保存
|
|
178
|
-
const loadPromise = this.doLoadPlugin(packageName, bundleUrl, config
|
|
163
|
+
const loadPromise = this.doLoadPlugin(packageName, bundleUrl, config);
|
|
179
164
|
globalLoadingPlugins.set(packageName, loadPromise);
|
|
180
165
|
try {
|
|
181
166
|
yield loadPromise;
|
|
@@ -186,13 +171,11 @@ export class PluginManager {
|
|
|
186
171
|
}
|
|
187
172
|
});
|
|
188
173
|
}
|
|
189
|
-
doLoadPlugin(packageName, bundleUrl, config
|
|
174
|
+
doLoadPlugin(packageName, bundleUrl, config) {
|
|
190
175
|
return __awaiter(this, void 0, void 0, function* () {
|
|
191
176
|
try {
|
|
192
|
-
//
|
|
193
|
-
const plugin =
|
|
194
|
-
? localPlugin
|
|
195
|
-
: yield this.loadPluginScript(bundleUrl, packageName);
|
|
177
|
+
// スクリプトタグで動的にプラグインを読み込む
|
|
178
|
+
const plugin = yield this.loadPluginScript(bundleUrl, packageName);
|
|
196
179
|
const instance = {
|
|
197
180
|
actionNodes: new Map(),
|
|
198
181
|
hooks: {},
|
|
@@ -202,12 +185,6 @@ export class PluginManager {
|
|
|
202
185
|
components: new Map(),
|
|
203
186
|
assetUrls: new Map(),
|
|
204
187
|
};
|
|
205
|
-
// 外部アプリから渡されたアセットURLを事前にキャッシュ
|
|
206
|
-
if (assets) {
|
|
207
|
-
for (const asset of assets) {
|
|
208
|
-
instance.assetUrls.set(asset.filename, asset.url);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
188
|
// プラグインAPIを作成
|
|
212
189
|
const api = this.createPluginAPI(packageName, instance);
|
|
213
190
|
// プラグインをセットアップ
|
|
@@ -633,26 +610,16 @@ export class PluginManager {
|
|
|
633
610
|
preload: (filenames) => {
|
|
634
611
|
const preloadPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
635
612
|
const promises = filenames.map((filename) => __awaiter(this, void 0, void 0, function* () {
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const encodedFilename = encodeURIComponent(filename);
|
|
642
|
-
const apiUrl = `/api/plugin/${encodedPackageName}/assets/${encodedFilename}`;
|
|
643
|
-
// fetchでリダイレクト後の最終URLを取得
|
|
644
|
-
try {
|
|
645
|
-
const response = yield fetch(apiUrl, { method: "HEAD" });
|
|
646
|
-
finalUrl = response.url; // リダイレクト後の最終URL
|
|
647
|
-
// 最終URLをキャッシュに保存
|
|
648
|
-
instance.assetUrls.set(filename, finalUrl);
|
|
649
|
-
}
|
|
650
|
-
catch (error) {
|
|
651
|
-
console.error(`Failed to preload asset: ${filename}`, error);
|
|
652
|
-
return Promise.resolve();
|
|
653
|
-
}
|
|
654
|
-
}
|
|
613
|
+
// APIエンドポイントURL
|
|
614
|
+
const encodedPackageName = encodeURIComponent(packageName);
|
|
615
|
+
const encodedFilename = encodeURIComponent(filename);
|
|
616
|
+
const apiUrl = `/api/plugin/${encodedPackageName}/assets/${encodedFilename}`;
|
|
617
|
+
// fetchでリダイレクト後の最終URLを取得
|
|
655
618
|
try {
|
|
619
|
+
const response = yield fetch(apiUrl, { method: "HEAD" });
|
|
620
|
+
const finalUrl = response.url; // リダイレクト後の最終URL
|
|
621
|
+
// 最終URLをキャッシュに保存
|
|
622
|
+
instance.assetUrls.set(filename, finalUrl);
|
|
656
623
|
// 画像をプリロード
|
|
657
624
|
if (filename.match(/\.(png|jpg|jpeg|gif|webp)$/i)) {
|
|
658
625
|
return new Promise((resolve, reject) => {
|
|
@@ -675,6 +642,7 @@ export class PluginManager {
|
|
|
675
642
|
catch (error) {
|
|
676
643
|
console.error(`Failed to preload asset: ${filename}`, error);
|
|
677
644
|
}
|
|
645
|
+
return Promise.resolve();
|
|
678
646
|
}));
|
|
679
647
|
yield Promise.all(promises);
|
|
680
648
|
}))();
|
|
@@ -870,6 +838,9 @@ export class PluginManager {
|
|
|
870
838
|
console.warn("updateEmotionEffect callback is not registered. Make sure to call setEmotionEffectUpdaterCallback.");
|
|
871
839
|
}
|
|
872
840
|
},
|
|
841
|
+
cancelScenario: () => {
|
|
842
|
+
throw new Error("DataAPI.cancelScenario() can only be called from within DataProvider context");
|
|
843
|
+
},
|
|
873
844
|
},
|
|
874
845
|
};
|
|
875
846
|
}
|
package/dist/sdk.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useDataAPI } from "./contexts/DataContext";
|
|
3
2
|
import type { DataAPI, DataContext, DataSubscriber } from "./data-api-types";
|
|
4
3
|
import type { FacePosition } from "./emotion-effect-types";
|
|
5
4
|
import type { Character, ConversationBranchState, EntityState, ScenarioBlock, ScenarioBlockType } from "./types";
|
|
5
|
+
import { useDataAPI } from "./contexts/DataContext";
|
|
6
6
|
/**
|
|
7
7
|
* ブロックオプションの定義
|
|
8
8
|
* プラグインがシナリオブロックに追加できるカスタムオプションを定義
|
package/dist/types.d.ts
CHANGED
|
@@ -16,7 +16,6 @@ export interface ScenarioBlock {
|
|
|
16
16
|
updatedAt: Date;
|
|
17
17
|
/** Plugin-defined block options (key-value pairs) */
|
|
18
18
|
options?: BlockOptions | null;
|
|
19
|
-
characterLayoutMode?: "detailed" | "simple";
|
|
20
19
|
speaker?: {
|
|
21
20
|
id: string;
|
|
22
21
|
name: string;
|
|
@@ -312,27 +311,13 @@ export interface PlayerSettings {
|
|
|
312
311
|
defaultBackgroundFadeDuration?: number;
|
|
313
312
|
/** 全ての音声をミュートする (デフォルト: false) */
|
|
314
313
|
muteAudio?: boolean;
|
|
315
|
-
/**
|
|
314
|
+
/** 選択されたフォントファミリー */
|
|
316
315
|
selectedFontFamily?: string;
|
|
317
|
-
/** 選択されたUIフォントファミリー(メニュー・ラベル等) */
|
|
318
|
-
selectedUIFontFamily?: string;
|
|
319
|
-
/** 非アクティブキャラクターの明るさ (0-1, デフォルト: 0.8) */
|
|
320
|
-
inactiveCharacterBrightness?: number;
|
|
321
|
-
/** キャラクター間隔(簡易モード用、デフォルト: 0.1) */
|
|
322
|
-
characterSpacing?: number;
|
|
323
316
|
}
|
|
324
317
|
export interface PluginConfig {
|
|
325
318
|
packageName: string;
|
|
326
|
-
|
|
327
|
-
bundleUrl?: string;
|
|
328
|
-
/** ローカルプラグイン: LunaPluginオブジェクトを直接渡す(bundleUrl不要) */
|
|
329
|
-
plugin?: import("./sdk").LunaPlugin;
|
|
319
|
+
bundleUrl: string;
|
|
330
320
|
config?: unknown;
|
|
331
|
-
/** アセットのファイル名→絶対URLマッピング(外部アプリから利用時に必要) */
|
|
332
|
-
assets?: {
|
|
333
|
-
filename: string;
|
|
334
|
-
url: string;
|
|
335
|
-
}[];
|
|
336
321
|
}
|
|
337
322
|
/** 作品のサウンドデータ(プラグインからアクセス可能) */
|
|
338
323
|
export interface WorkSound {
|
package/package.json
CHANGED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
/**
|
|
3
|
-
* 文字送り用の軽量コンテキスト
|
|
4
|
-
* displayText と isTyping は頻繁に更新されるため、
|
|
5
|
-
* DataContext から分離して不要な再レンダリングを防ぐ
|
|
6
|
-
*/
|
|
7
|
-
interface PlaybackTextContextValue {
|
|
8
|
-
/** 現在表示中のテキスト(文字送り中は部分テキスト) */
|
|
9
|
-
displayText: string | React.ReactNode;
|
|
10
|
-
/** 文字送り中かどうか */
|
|
11
|
-
isTyping: boolean;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* PlaybackTextProvider
|
|
15
|
-
* 文字送り状態を提供するProvider
|
|
16
|
-
* DataProviderとは別に、displayTextとisTypingのみを管理
|
|
17
|
-
*/
|
|
18
|
-
export declare const PlaybackTextProvider: React.FC<{
|
|
19
|
-
displayText: string | React.ReactNode;
|
|
20
|
-
isTyping: boolean;
|
|
21
|
-
children: ReactNode;
|
|
22
|
-
}>;
|
|
23
|
-
/**
|
|
24
|
-
* 文字送り状態を取得するフック
|
|
25
|
-
*/
|
|
26
|
-
export declare function usePlaybackText(): PlaybackTextContextValue;
|
|
27
|
-
/**
|
|
28
|
-
* 文字送り状態を取得するフック(オプショナル版)
|
|
29
|
-
* PlaybackTextProviderの外でも使用可能(その場合はnullを返す)
|
|
30
|
-
*/
|
|
31
|
-
export declare function usePlaybackTextOptional(): PlaybackTextContextValue | null;
|
|
32
|
-
export {};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useMemo } from "react";
|
|
3
|
-
const PlaybackTextContextInstance = createContext(null);
|
|
4
|
-
/**
|
|
5
|
-
* PlaybackTextProvider
|
|
6
|
-
* 文字送り状態を提供するProvider
|
|
7
|
-
* DataProviderとは別に、displayTextとisTypingのみを管理
|
|
8
|
-
*/
|
|
9
|
-
export const PlaybackTextProvider = ({ displayText, isTyping, children }) => {
|
|
10
|
-
const value = useMemo(() => ({ displayText, isTyping }), [displayText, isTyping]);
|
|
11
|
-
return (_jsx(PlaybackTextContextInstance.Provider, { value: value, children: children }));
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* 文字送り状態を取得するフック
|
|
15
|
-
*/
|
|
16
|
-
export function usePlaybackText() {
|
|
17
|
-
const context = useContext(PlaybackTextContextInstance);
|
|
18
|
-
if (!context) {
|
|
19
|
-
throw new Error("usePlaybackText must be used within PlaybackTextProvider");
|
|
20
|
-
}
|
|
21
|
-
return context;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* 文字送り状態を取得するフック(オプショナル版)
|
|
25
|
-
* PlaybackTextProviderの外でも使用可能(その場合はnullを返す)
|
|
26
|
-
*/
|
|
27
|
-
export function usePlaybackTextOptional() {
|
|
28
|
-
return useContext(PlaybackTextContextInstance);
|
|
29
|
-
}
|