@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.
@@ -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
- * - すべてのフォントが完全にダウンロード&キャッシュされてからisLoaded=trueになる
7
- * - 再生開始前に全フォントを一括ダウンロードし、再生中のフォントスワップを防止する
8
- * - preloadText: シナリオ内の全テキストを渡すと、使用される全ての文字でフォントをプリロード
6
+ * - すべてのフォントがプリロード&キャッシュされてからisLoaded=trueになる
9
7
  */
10
- export declare function useFontLoader(fonts: WorkFont[] | undefined, preloadText?: string): {
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
- * - すべてのフォントが完全にダウンロード&キャッシュされてからisLoaded=trueになる
83
- * - 再生開始前に全フォントを一括ダウンロードし、再生中のフォントスワップを防止する
84
- * - preloadText: シナリオ内の全テキストを渡すと、使用される全ての文字でフォントをプリロード
17
+ * - すべてのフォントがプリロード&キャッシュされてからisLoaded=trueになる
85
18
  */
86
- export function useFontLoader(fonts, preloadText) {
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
- const generation = ++generationRef.current;
98
- setIsLoaded(false);
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 textToPreload = preloadText || "あいうえおABCabc123";
103
- // すべてのGoogle FontsのCSS読み込みを並列で開始
104
- const googleFonts = fonts.filter((f) => f.type === "google" && !fontCache.has(f.fontFamily));
105
- const customFonts = fonts.filter((f) => f.type === "custom" && f.url && !fontCache.has(f.fontFamily));
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
- yield Promise.all(allFontPromises);
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, preloadText]);
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
  */
@@ -37,6 +37,6 @@ export function useUIVisibility(type) {
37
37
  // UI 状態の変更を監視
38
38
  const unsubscribe = pluginManager.subscribeUIVisibility(type, setIsVisible);
39
39
  return unsubscribe;
40
- }, [type]);
40
+ }, [pluginManager, type]);
41
41
  return isVisible;
42
42
  }
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, LunaPlugin, PlayerSettingsData, PluginAPI, ScenarioPlaybackData, TransitionSource, } from "./sdk";
13
- export { ComponentType, definePlugin, renderTextWithLineBreaks } from "./sdk";
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, definePlugin, renderTextWithLineBreaks } from "./sdk";
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 | undefined, config?: unknown, assets?: {
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, assets, plugin) {
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
- // ロード中の場合はそのPromiseを待って完了させる(二重ロード防止)
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, assets, plugin);
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, assets, localPlugin) {
174
+ doLoadPlugin(packageName, bundleUrl, config) {
190
175
  return __awaiter(this, void 0, void 0, function* () {
191
176
  try {
192
- // ローカルプラグインが渡された場合はスクリプトロードをスキップ
193
- const plugin = localPlugin
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
- // 既にキャッシュされたURLがあればAPIフェッチをスキップ
637
- let finalUrl = instance.assetUrls.get(filename);
638
- if (!finalUrl) {
639
- // APIエンドポイントURL
640
- const encodedPackageName = encodeURIComponent(packageName);
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
- /** リモートプラグインのバンドルURL(plugin未指定時に必須) */
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@luna-editor/engine",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "description": "Luna Editor scenario playback engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
- }
@@ -1,5 +0,0 @@
1
- import type { DisplayedCharacter } from "../types";
2
- export declare const useImagePreloader: (
3
- displayedCharacters: DisplayedCharacter[],
4
- singleImageUrl?: string
5
- ) => boolean;