@luna-editor/engine 0.5.5 → 0.5.7

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 CHANGED
@@ -794,12 +794,52 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
794
794
  const skipToNext = useCallback(() => {
795
795
  handleNext("click");
796
796
  }, [handleNext]);
797
+ // プラグインからシナリオを最後までスキップして正常終了するためのコールバック
798
+ const skipScenario = useCallback(() => {
799
+ if (!hasStarted || state.isEnded)
800
+ return;
801
+ setState((prev) => (Object.assign(Object.assign({}, prev), { isEnded: true, isPlaying: false })));
802
+ pluginManager.callHook("onScenarioEnd", {
803
+ scenarioId: scenario.id,
804
+ scenarioName: scenario.name || "scenario",
805
+ });
806
+ onScenarioEnd === null || onScenarioEnd === void 0 ? void 0 : onScenarioEnd();
807
+ onEnd === null || onEnd === void 0 ? void 0 : onEnd();
808
+ }, [hasStarted, state.isEnded, pluginManager, scenario.id, scenario.name, onScenarioEnd, onEnd]);
797
809
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
798
810
  const _handlePrevious = useCallback(() => {
799
811
  if (state.currentBlockIndex > 0) {
800
812
  handlePreviousInternal();
801
813
  }
802
814
  }, [state.currentBlockIndex, handlePreviousInternal]);
815
+ // AUTO再生: テキスト表示完了後にautoPlaySpeed秒待って自動で次へ進む
816
+ useEffect(() => {
817
+ if (!state.isPlaying ||
818
+ state.isEnded ||
819
+ isTyping ||
820
+ !isFirstRenderComplete ||
821
+ !pluginsLoaded ||
822
+ !currentBlock) {
823
+ return;
824
+ }
825
+ // 会話分岐中はAUTO進行しない
826
+ if (currentBlock.blockType === "conversation_branch") {
827
+ return;
828
+ }
829
+ const timer = setTimeout(() => {
830
+ handleNext("auto");
831
+ }, mergedSettings.autoPlaySpeed * 1000);
832
+ return () => clearTimeout(timer);
833
+ }, [
834
+ state.isPlaying,
835
+ state.isEnded,
836
+ isTyping,
837
+ isFirstRenderComplete,
838
+ pluginsLoaded,
839
+ currentBlock,
840
+ handleNext,
841
+ mergedSettings.autoPlaySpeed,
842
+ ]);
803
843
  // 現在の背景を計算
804
844
  const currentBackground = useMemo(() => calculateCurrentBackground(actualBlockIndex), [calculateCurrentBackground, actualBlockIndex]);
805
845
  // displayText を JSX に変換(PlaybackTextProvider 用)
@@ -954,7 +994,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
954
994
  aspectRatio: getAspectRatio(),
955
995
  width: `min(100vw, calc(100vh * ${getAspectRatioValue()}))`,
956
996
  height: `min(100vh, calc(100vw / ${getAspectRatioValue()}))`,
957
- }, children: _jsx(DataProvider, { data: dataContext, onSettingsUpdate: handleSettingsUpdate, onEmotionEffectUpdate: handleEmotionEffectUpdate, onCancelScenario: cancelScenario, onToggleAutoPlay: toggleAutoPlay, onSetAutoPlay: setAutoPlay, onSkipToNext: skipToNext, children: _jsx(AudioProvider, { settings: {
997
+ }, children: _jsx(DataProvider, { data: dataContext, onSettingsUpdate: handleSettingsUpdate, onEmotionEffectUpdate: handleEmotionEffectUpdate, onCancelScenario: cancelScenario, onToggleAutoPlay: toggleAutoPlay, onSetAutoPlay: setAutoPlay, onSkipToNext: skipToNext, onSkipScenario: skipScenario, children: _jsx(AudioProvider, { settings: {
958
998
  bgmVolume: mergedSettings.bgmVolume,
959
999
  seVolume: mergedSettings.seVolume,
960
1000
  voiceVolume: mergedSettings.voiceVolume,
@@ -6,6 +6,7 @@ type ScenarioCanceller = () => void;
6
6
  type AutoPlayToggler = () => void;
7
7
  type AutoPlaySetter = (enabled: boolean) => void;
8
8
  type SkipToNextHandler = () => void;
9
+ type SkipScenarioHandler = () => void;
9
10
  /**
10
11
  * データプロバイダー
11
12
  * プラグインがシナリオ情報にリアクティブにアクセスするためのProvider
@@ -27,6 +28,7 @@ export declare const DataProvider: React.FC<{
27
28
  onToggleAutoPlay?: AutoPlayToggler;
28
29
  onSetAutoPlay?: AutoPlaySetter;
29
30
  onSkipToNext?: SkipToNextHandler;
31
+ onSkipScenario?: SkipScenarioHandler;
30
32
  children: ReactNode;
31
33
  }>;
32
34
  /**
@@ -10,6 +10,7 @@ const ScenarioCancellerContext = createContext(null);
10
10
  const AutoPlayTogglerContext = createContext(null);
11
11
  const AutoPlaySetterContext = createContext(null);
12
12
  const SkipToNextContext = createContext(null);
13
+ const SkipScenarioContext = createContext(null);
13
14
  /**
14
15
  * データプロバイダー
15
16
  * プラグインがシナリオ情報にリアクティブにアクセスするためのProvider
@@ -23,7 +24,7 @@ const SkipToNextContext = createContext(null);
23
24
  * </DataProvider>
24
25
  * ```
25
26
  */
26
- export const DataProvider = ({ data, onSettingsUpdate, onEmotionEffectUpdate, onCancelScenario, onToggleAutoPlay, onSetAutoPlay, onSkipToNext, children }) => {
27
+ export const DataProvider = ({ data, onSettingsUpdate, onEmotionEffectUpdate, onCancelScenario, onToggleAutoPlay, onSetAutoPlay, onSkipToNext, onSkipScenario, children }) => {
27
28
  const subscribers = useMemo(() => new Map(), []);
28
29
  const previousDataRef = useRef(data);
29
30
  // 安定したデータ参照ホルダー(Context valueとして提供)
@@ -65,7 +66,7 @@ export const DataProvider = ({ data, onSettingsUpdate, onEmotionEffectUpdate, on
65
66
  }
66
67
  previousDataRef.current = data;
67
68
  }, [data, subscribers]);
68
- return (_jsx(SubscribersContext.Provider, { value: subscribers, children: _jsx(SettingsUpdaterContext.Provider, { value: onSettingsUpdate !== null && onSettingsUpdate !== void 0 ? onSettingsUpdate : null, children: _jsx(EmotionEffectUpdaterContext.Provider, { value: onEmotionEffectUpdate !== null && onEmotionEffectUpdate !== void 0 ? onEmotionEffectUpdate : null, children: _jsx(ScenarioCancellerContext.Provider, { value: onCancelScenario !== null && onCancelScenario !== void 0 ? onCancelScenario : null, children: _jsx(AutoPlayTogglerContext.Provider, { value: onToggleAutoPlay !== null && onToggleAutoPlay !== void 0 ? onToggleAutoPlay : null, children: _jsx(AutoPlaySetterContext.Provider, { value: onSetAutoPlay !== null && onSetAutoPlay !== void 0 ? onSetAutoPlay : null, children: _jsx(SkipToNextContext.Provider, { value: onSkipToNext !== null && onSkipToNext !== void 0 ? onSkipToNext : null, children: _jsx(DataRefContext.Provider, { value: dataRefHolder, children: _jsx(DataContextInstance.Provider, { value: data, children: children }) }) }) }) }) }) }) }) }));
69
+ return (_jsx(SubscribersContext.Provider, { value: subscribers, children: _jsx(SettingsUpdaterContext.Provider, { value: onSettingsUpdate !== null && onSettingsUpdate !== void 0 ? onSettingsUpdate : null, children: _jsx(EmotionEffectUpdaterContext.Provider, { value: onEmotionEffectUpdate !== null && onEmotionEffectUpdate !== void 0 ? onEmotionEffectUpdate : null, children: _jsx(ScenarioCancellerContext.Provider, { value: onCancelScenario !== null && onCancelScenario !== void 0 ? onCancelScenario : null, children: _jsx(AutoPlayTogglerContext.Provider, { value: onToggleAutoPlay !== null && onToggleAutoPlay !== void 0 ? onToggleAutoPlay : null, children: _jsx(AutoPlaySetterContext.Provider, { value: onSetAutoPlay !== null && onSetAutoPlay !== void 0 ? onSetAutoPlay : null, children: _jsx(SkipToNextContext.Provider, { value: onSkipToNext !== null && onSkipToNext !== void 0 ? onSkipToNext : null, children: _jsx(SkipScenarioContext.Provider, { value: onSkipScenario !== null && onSkipScenario !== void 0 ? onSkipScenario : null, children: _jsx(DataRefContext.Provider, { value: dataRefHolder, children: _jsx(DataContextInstance.Provider, { value: data, children: children }) }) }) }) }) }) }) }) }) }));
69
70
  };
70
71
  /**
71
72
  * DataAPI実装を提供するフック
@@ -83,6 +84,7 @@ export function useDataAPI() {
83
84
  const autoPlayToggler = useContext(AutoPlayTogglerContext);
84
85
  const autoPlaySetter = useContext(AutoPlaySetterContext);
85
86
  const skipToNextHandler = useContext(SkipToNextContext);
87
+ const skipScenarioHandler = useContext(SkipScenarioContext);
86
88
  // 注意: usePlaybackTextOptional()をここで呼ぶと、PlaybackTextContextの更新で
87
89
  // useDataAPIを使う全コンポーネントが再レンダリングされてしまう
88
90
  // 代わりに、displayTextが必要なコンポーネントはusePlaybackText()を直接使う
@@ -213,6 +215,14 @@ export function useDataAPI() {
213
215
  console.warn("skipToNext called but no skip handler provided");
214
216
  }
215
217
  }, [skipToNextHandler]);
218
+ const skipScenario = useCallback(() => {
219
+ if (skipScenarioHandler) {
220
+ skipScenarioHandler();
221
+ }
222
+ else {
223
+ console.warn("skipScenario called but no skip scenario handler provided");
224
+ }
225
+ }, [skipScenarioHandler]);
216
226
  return useMemo(() => ({
217
227
  get,
218
228
  subscribe,
@@ -228,6 +238,7 @@ export function useDataAPI() {
228
238
  toggleAutoPlay,
229
239
  setAutoPlay,
230
240
  skipToNext,
241
+ skipScenario,
231
242
  }), [
232
243
  get,
233
244
  subscribe,
@@ -243,5 +254,6 @@ export function useDataAPI() {
243
254
  toggleAutoPlay,
244
255
  setAutoPlay,
245
256
  skipToNext,
257
+ skipScenario,
246
258
  ]);
247
259
  }
@@ -272,4 +272,10 @@ export interface DataAPI {
272
272
  * 完了済みの場合は次のブロックに遷移する。
273
273
  */
274
274
  skipToNext(): void;
275
+ /**
276
+ * シナリオを最後までスキップして終了する
277
+ * onEnd / onScenarioEnd コールバックが発火される。
278
+ * cancelScenario と異なり、正常終了扱い。
279
+ */
280
+ skipScenario(): void;
275
281
  }
@@ -882,6 +882,9 @@ export class PluginManager {
882
882
  skipToNext: () => {
883
883
  throw new Error("DataAPI.skipToNext() can only be called from within DataProvider context");
884
884
  },
885
+ skipScenario: () => {
886
+ throw new Error("DataAPI.skipScenario() can only be called from within DataProvider context");
887
+ },
885
888
  },
886
889
  };
887
890
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luna-editor/engine",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "Luna Editor scenario playback engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",