@luna-editor/engine 0.5.4 → 0.5.6

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
@@ -781,12 +781,53 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
781
781
  scenario.id,
782
782
  scenario.name,
783
783
  ]);
784
+ // プラグインからAUTO再生を制御するためのコールバック
785
+ const toggleAutoPlay = useCallback(() => {
786
+ _togglePlay();
787
+ }, [_togglePlay]);
788
+ const setAutoPlay = useCallback((enabled) => {
789
+ if (enabled !== state.isPlaying) {
790
+ _togglePlay();
791
+ }
792
+ }, [state.isPlaying, _togglePlay]);
793
+ // プラグインから次のブロックへ進むためのコールバック
794
+ const skipToNext = useCallback(() => {
795
+ handleNext("click");
796
+ }, [handleNext]);
784
797
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
785
798
  const _handlePrevious = useCallback(() => {
786
799
  if (state.currentBlockIndex > 0) {
787
800
  handlePreviousInternal();
788
801
  }
789
802
  }, [state.currentBlockIndex, handlePreviousInternal]);
803
+ // AUTO再生: テキスト表示完了後にautoPlaySpeed秒待って自動で次へ進む
804
+ useEffect(() => {
805
+ if (!state.isPlaying ||
806
+ state.isEnded ||
807
+ isTyping ||
808
+ !isFirstRenderComplete ||
809
+ !pluginsLoaded ||
810
+ !currentBlock) {
811
+ return;
812
+ }
813
+ // 会話分岐中はAUTO進行しない
814
+ if (currentBlock.blockType === "conversation_branch") {
815
+ return;
816
+ }
817
+ const timer = setTimeout(() => {
818
+ handleNext("auto");
819
+ }, mergedSettings.autoPlaySpeed * 1000);
820
+ return () => clearTimeout(timer);
821
+ }, [
822
+ state.isPlaying,
823
+ state.isEnded,
824
+ isTyping,
825
+ isFirstRenderComplete,
826
+ pluginsLoaded,
827
+ currentBlock,
828
+ handleNext,
829
+ mergedSettings.autoPlaySpeed,
830
+ ]);
790
831
  // 現在の背景を計算
791
832
  const currentBackground = useMemo(() => calculateCurrentBackground(actualBlockIndex), [calculateCurrentBackground, actualBlockIndex]);
792
833
  // displayText を JSX に変換(PlaybackTextProvider 用)
@@ -811,6 +852,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
811
852
  displayText: displayTextElement,
812
853
  isTyping,
813
854
  displayedCharacters,
855
+ isAutoPlaying: state.isPlaying,
814
856
  },
815
857
  backlog: {
816
858
  entries: backlog.logs,
@@ -869,6 +911,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
869
911
  mergedSettings.selectedUIFontFamily,
870
912
  currentBackground,
871
913
  fontsLoaded,
914
+ state.isPlaying,
872
915
  ]);
873
916
  // DataContextの参照を更新(プラグインがapi.data.get()を呼び出せるようにする)
874
917
  useEffect(() => {
@@ -939,7 +982,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
939
982
  aspectRatio: getAspectRatio(),
940
983
  width: `min(100vw, calc(100vh * ${getAspectRatioValue()}))`,
941
984
  height: `min(100vh, calc(100vw / ${getAspectRatioValue()}))`,
942
- }, children: _jsx(DataProvider, { data: dataContext, onSettingsUpdate: handleSettingsUpdate, onEmotionEffectUpdate: handleEmotionEffectUpdate, onCancelScenario: cancelScenario, children: _jsx(AudioProvider, { settings: {
985
+ }, children: _jsx(DataProvider, { data: dataContext, onSettingsUpdate: handleSettingsUpdate, onEmotionEffectUpdate: handleEmotionEffectUpdate, onCancelScenario: cancelScenario, onToggleAutoPlay: toggleAutoPlay, onSetAutoPlay: setAutoPlay, onSkipToNext: skipToNext, children: _jsx(AudioProvider, { settings: {
943
986
  bgmVolume: mergedSettings.bgmVolume,
944
987
  seVolume: mergedSettings.seVolume,
945
988
  voiceVolume: mergedSettings.voiceVolume,
@@ -3,6 +3,9 @@ import type { DataAPI, DataContext, EmotionEffectState, PlayerSettingsData } fro
3
3
  type SettingsUpdater = (settings: Partial<PlayerSettingsData>) => void;
4
4
  type EmotionEffectUpdater = (state: EmotionEffectState | null) => void;
5
5
  type ScenarioCanceller = () => void;
6
+ type AutoPlayToggler = () => void;
7
+ type AutoPlaySetter = (enabled: boolean) => void;
8
+ type SkipToNextHandler = () => void;
6
9
  /**
7
10
  * データプロバイダー
8
11
  * プラグインがシナリオ情報にリアクティブにアクセスするためのProvider
@@ -21,6 +24,9 @@ export declare const DataProvider: React.FC<{
21
24
  onSettingsUpdate?: SettingsUpdater;
22
25
  onEmotionEffectUpdate?: EmotionEffectUpdater;
23
26
  onCancelScenario?: ScenarioCanceller;
27
+ onToggleAutoPlay?: AutoPlayToggler;
28
+ onSetAutoPlay?: AutoPlaySetter;
29
+ onSkipToNext?: SkipToNextHandler;
24
30
  children: ReactNode;
25
31
  }>;
26
32
  /**
@@ -7,6 +7,9 @@ const SubscribersContext = createContext(null);
7
7
  const SettingsUpdaterContext = createContext(null);
8
8
  const EmotionEffectUpdaterContext = createContext(null);
9
9
  const ScenarioCancellerContext = createContext(null);
10
+ const AutoPlayTogglerContext = createContext(null);
11
+ const AutoPlaySetterContext = createContext(null);
12
+ const SkipToNextContext = createContext(null);
10
13
  /**
11
14
  * データプロバイダー
12
15
  * プラグインがシナリオ情報にリアクティブにアクセスするためのProvider
@@ -20,7 +23,7 @@ const ScenarioCancellerContext = createContext(null);
20
23
  * </DataProvider>
21
24
  * ```
22
25
  */
23
- export const DataProvider = ({ data, onSettingsUpdate, onEmotionEffectUpdate, onCancelScenario, children }) => {
26
+ export const DataProvider = ({ data, onSettingsUpdate, onEmotionEffectUpdate, onCancelScenario, onToggleAutoPlay, onSetAutoPlay, onSkipToNext, children }) => {
24
27
  const subscribers = useMemo(() => new Map(), []);
25
28
  const previousDataRef = useRef(data);
26
29
  // 安定したデータ参照ホルダー(Context valueとして提供)
@@ -62,7 +65,7 @@ export const DataProvider = ({ data, onSettingsUpdate, onEmotionEffectUpdate, on
62
65
  }
63
66
  previousDataRef.current = data;
64
67
  }, [data, subscribers]);
65
- 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(DataRefContext.Provider, { value: dataRefHolder, children: _jsx(DataContextInstance.Provider, { value: data, children: children }) }) }) }) }) }));
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 }) }) }) }) }) }) }) }) }));
66
69
  };
67
70
  /**
68
71
  * DataAPI実装を提供するフック
@@ -77,6 +80,9 @@ export function useDataAPI() {
77
80
  const settingsUpdater = useContext(SettingsUpdaterContext);
78
81
  const emotionEffectUpdater = useContext(EmotionEffectUpdaterContext);
79
82
  const scenarioCanceller = useContext(ScenarioCancellerContext);
83
+ const autoPlayToggler = useContext(AutoPlayTogglerContext);
84
+ const autoPlaySetter = useContext(AutoPlaySetterContext);
85
+ const skipToNextHandler = useContext(SkipToNextContext);
80
86
  // 注意: usePlaybackTextOptional()をここで呼ぶと、PlaybackTextContextの更新で
81
87
  // useDataAPIを使う全コンポーネントが再レンダリングされてしまう
82
88
  // 代わりに、displayTextが必要なコンポーネントはusePlaybackText()を直接使う
@@ -183,6 +189,30 @@ export function useDataAPI() {
183
189
  console.warn("cancelScenario called but no scenario canceller provided");
184
190
  }
185
191
  }, [scenarioCanceller]);
192
+ const toggleAutoPlay = useCallback(() => {
193
+ if (autoPlayToggler) {
194
+ autoPlayToggler();
195
+ }
196
+ else {
197
+ console.warn("toggleAutoPlay called but no auto play toggler provided");
198
+ }
199
+ }, [autoPlayToggler]);
200
+ const setAutoPlay = useCallback((enabled) => {
201
+ if (autoPlaySetter) {
202
+ autoPlaySetter(enabled);
203
+ }
204
+ else {
205
+ console.warn("setAutoPlay called but no auto play setter provided");
206
+ }
207
+ }, [autoPlaySetter]);
208
+ const skipToNext = useCallback(() => {
209
+ if (skipToNextHandler) {
210
+ skipToNextHandler();
211
+ }
212
+ else {
213
+ console.warn("skipToNext called but no skip handler provided");
214
+ }
215
+ }, [skipToNextHandler]);
186
216
  return useMemo(() => ({
187
217
  get,
188
218
  subscribe,
@@ -195,6 +225,9 @@ export function useDataAPI() {
195
225
  getBlockOption,
196
226
  updateEmotionEffect,
197
227
  cancelScenario,
228
+ toggleAutoPlay,
229
+ setAutoPlay,
230
+ skipToNext,
198
231
  }), [
199
232
  get,
200
233
  subscribe,
@@ -207,5 +240,8 @@ export function useDataAPI() {
207
240
  getBlockOption,
208
241
  updateEmotionEffect,
209
242
  cancelScenario,
243
+ toggleAutoPlay,
244
+ setAutoPlay,
245
+ skipToNext,
210
246
  ]);
211
247
  }
@@ -21,6 +21,8 @@ export interface ScenarioPlaybackData {
21
21
  isTyping?: boolean;
22
22
  /** 表示中のキャラクター */
23
23
  displayedCharacters?: DisplayedCharacter[];
24
+ /** AUTO再生中かどうか */
25
+ isAutoPlaying?: boolean;
24
26
  }
25
27
  /**
26
28
  * バックログデータ
@@ -254,4 +256,20 @@ export interface DataAPI {
254
256
  * Player の onScenarioCancelled コールバックが発火される。
255
257
  */
256
258
  cancelScenario(): void;
259
+ /**
260
+ * AUTO再生のON/OFFを切り替える
261
+ * isPlaying を反転させる。シナリオ終了時は無視される。
262
+ */
263
+ toggleAutoPlay(): void;
264
+ /**
265
+ * AUTO再生の状態を設定する
266
+ * @param enabled - trueでAUTO ON、falseでOFF
267
+ */
268
+ setAutoPlay(enabled: boolean): void;
269
+ /**
270
+ * 次のブロックへ進む(スキップ)
271
+ * テキストアニメーション中はアニメーションを完了させ、
272
+ * 完了済みの場合は次のブロックに遷移する。
273
+ */
274
+ skipToNext(): void;
257
275
  }
@@ -873,6 +873,15 @@ export class PluginManager {
873
873
  cancelScenario: () => {
874
874
  throw new Error("DataAPI.cancelScenario() can only be called from within DataProvider context");
875
875
  },
876
+ toggleAutoPlay: () => {
877
+ throw new Error("DataAPI.toggleAutoPlay() can only be called from within DataProvider context");
878
+ },
879
+ setAutoPlay: () => {
880
+ throw new Error("DataAPI.setAutoPlay() can only be called from within DataProvider context");
881
+ },
882
+ skipToNext: () => {
883
+ throw new Error("DataAPI.skipToNext() can only be called from within DataProvider context");
884
+ },
876
885
  },
877
886
  };
878
887
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luna-editor/engine",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Luna Editor scenario playback engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",