@luna-editor/engine 0.5.3 → 0.5.5

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,6 +781,19 @@ 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) {
@@ -811,6 +824,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
811
824
  displayText: displayTextElement,
812
825
  isTyping,
813
826
  displayedCharacters,
827
+ isAutoPlaying: state.isPlaying,
814
828
  },
815
829
  backlog: {
816
830
  entries: backlog.logs,
@@ -869,6 +883,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
869
883
  mergedSettings.selectedUIFontFamily,
870
884
  currentBackground,
871
885
  fontsLoaded,
886
+ state.isPlaying,
872
887
  ]);
873
888
  // DataContextの参照を更新(プラグインがapi.data.get()を呼び出せるようにする)
874
889
  useEffect(() => {
@@ -939,7 +954,7 @@ export const Player = ({ scenario: scenarioProp, settings, plugins = EMPTY_PLUGI
939
954
  aspectRatio: getAspectRatio(),
940
955
  width: `min(100vw, calc(100vh * ${getAspectRatioValue()}))`,
941
956
  height: `min(100vh, calc(100vw / ${getAspectRatioValue()}))`,
942
- }, children: _jsx(DataProvider, { data: dataContext, onSettingsUpdate: handleSettingsUpdate, onEmotionEffectUpdate: handleEmotionEffectUpdate, onCancelScenario: cancelScenario, children: _jsx(AudioProvider, { settings: {
957
+ }, children: _jsx(DataProvider, { data: dataContext, onSettingsUpdate: handleSettingsUpdate, onEmotionEffectUpdate: handleEmotionEffectUpdate, onCancelScenario: cancelScenario, onToggleAutoPlay: toggleAutoPlay, onSetAutoPlay: setAutoPlay, onSkipToNext: skipToNext, children: _jsx(AudioProvider, { settings: {
943
958
  bgmVolume: mergedSettings.bgmVolume,
944
959
  seVolume: mergedSettings.seVolume,
945
960
  voiceVolume: mergedSettings.voiceVolume,
@@ -540,12 +540,12 @@ export const GameScreen = memo(function GameScreen({ scenario, currentBlock, pre
540
540
  return (_jsx("div", { style: Object.assign({ position: "absolute", visibility: shouldDisplay ? "visible" : "hidden", opacity: opacity, filter: `brightness(${brightness})`, zIndex: zIndex,
541
541
  // CharacterEditDialogと完全に同じ: left/topを0にしてtransformで位置制御
542
542
  // translate(%)は画像サイズに対する割合
543
- left: 0, top: 0, transform: `translate(${leftPx}px, ${topPx}px) translate(${translateXPercent}%, ${translateYPercent}%)`, transition: fadeState ? "none" : undefined }, cropMaskStyle), "data-character-id": image.objectId, "data-character-sprite": true, children: hasLayerFeature && image.baseBodyUrl ? (_jsxs(_Fragment, { children: [_jsx("img", { src: image.baseBodyUrl, alt: "", style: {
543
+ left: 0, top: 0, transform: `translate(${leftPx}px, ${topPx}px) translate(${translateXPercent}%, ${translateYPercent}%)`, transition: fadeState ? "none" : undefined }, cropMaskStyle), "data-character-id": image.objectId, "data-character-sprite": true, children: hasLayerFeature && image.baseBodyUrl ? (_jsxs(_Fragment, { children: [_jsx("img", { src: image.baseBodyUrl, alt: "", decoding: "sync", style: {
544
544
  height: `${baseHeight}px`,
545
545
  width: "auto",
546
546
  objectFit: "contain",
547
547
  transform: `translateX(${image.baseBodyTranslateX || 0}%) translateY(${image.baseBodyTranslateY || 0}%)`,
548
- } }), currentLayers === null || currentLayers === void 0 ? void 0 : currentLayers.filter((layer) => layer.imageUrl).sort((a, b) => a.sortOrder - b.sortOrder).map((layer) => (_jsx("img", { src: layer.imageUrl, alt: "", style: {
548
+ } }), currentLayers === null || currentLayers === void 0 ? void 0 : currentLayers.filter((layer) => layer.imageUrl).sort((a, b) => a.sortOrder - b.sortOrder).map((layer) => (_jsx("img", { src: layer.imageUrl, alt: "", decoding: "sync", style: {
549
549
  position: "absolute",
550
550
  left: 0,
551
551
  top: 0,
@@ -555,7 +555,7 @@ export const GameScreen = memo(function GameScreen({ scenario, currentBlock, pre
555
555
  transform: `translateX(${image.translateX || 0}%) translateY(${image.translateY || 0}%)`,
556
556
  } }, layer.id)))] })) : (
557
557
  /* 従来の単一画像表示 */
558
- _jsx("img", { src: image.url, alt: "", style: {
558
+ _jsx("img", { src: image.url, alt: "", decoding: "sync", style: {
559
559
  height: `${baseHeight}px`,
560
560
  width: "auto",
561
561
  objectFit: "contain",
@@ -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.3",
3
+ "version": "0.5.5",
4
4
  "description": "Luna Editor scenario playback engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",