@luna-editor/engine 0.3.0 → 0.3.1

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.
@@ -21,6 +21,7 @@ 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";
24
25
  import { usePluginAPI, useUIVisibility } from "../hooks/usePluginAPI";
25
26
  import { useScreenScale, useScreenSize, useToPixel, } from "../hooks/useScreenSize";
26
27
  import { ComponentType, definePlugin, } from "../sdk";
@@ -31,6 +32,7 @@ export class PluginManager {
31
32
  constructor() {
32
33
  this.plugins = new Map();
33
34
  this.styleElements = new Map();
35
+ this.originalClassNames = new Map(); // 元のクラス名を保持
34
36
  this.injectedStyles = new Set();
35
37
  this.storage = new Map();
36
38
  this.componentRegistry = new Map();
@@ -38,6 +40,8 @@ export class PluginManager {
38
40
  this.uiVisibilityListeners = new Map();
39
41
  this.selectChoiceCallback = null;
40
42
  this.getBranchStateCallback = null;
43
+ this.emotionEffectUpdaterCallback = null;
44
+ this.dataContextGetter = null;
41
45
  this.preloadPromises = [];
42
46
  this.muteAudio = false;
43
47
  // 作品のサウンドデータ(ID → URL)
@@ -49,6 +53,18 @@ export class PluginManager {
49
53
  this.clickSoundOverridden = false;
50
54
  this.initializeReactRuntime();
51
55
  }
56
+ /**
57
+ * 開発環境かどうかを判定
58
+ * localhost、127.0.0.1、またはポート3000で実行されている場合に true を返す
59
+ */
60
+ isDevelopment() {
61
+ if (typeof window === "undefined") {
62
+ return false;
63
+ }
64
+ return (window.location.hostname === "localhost" ||
65
+ window.location.hostname === "127.0.0.1" ||
66
+ window.location.port === "3000");
67
+ }
52
68
  /**
53
69
  * グローバルReactランタイムを初期化(クライアントサイドのみ)
54
70
  * 既に初期化済みの場合はスキップ
@@ -92,50 +108,62 @@ export class PluginManager {
92
108
  useScreenScale,
93
109
  useScreenSizeAtom,
94
110
  ComponentType,
95
- definePlugin });
96
- console.log("🔧 Luna React Runtime initialized for JSX support with hooks and screen size system");
111
+ definePlugin,
112
+ getFontFamilyStyle,
113
+ getUIFontFamilyStyle });
97
114
  }
98
- loadPlugin(packageName, bundleUrl, config) {
115
+ loadPlugin(packageName, bundleUrl, config, assets) {
99
116
  return __awaiter(this, void 0, void 0, function* () {
100
- // グローバルに既にロード済みの場合はローカルにコピーしてスキップ
101
- const globalLoaded = globalLoadedPlugins.get(packageName);
102
- if (globalLoaded) {
103
- console.log(`Plugin ${packageName} is already loaded globally, reusing.`);
104
- // 新しいconfigで更新したコピーを作成
105
- const updatedPlugin = Object.assign(Object.assign({}, globalLoaded), { config: config !== null && config !== void 0 ? config : globalLoaded.config });
106
- this.plugins.set(packageName, updatedPlugin);
107
- // コンポーネントレジストリにも登録
108
- for (const [type, component] of globalLoaded.instance.components) {
109
- this.componentRegistry.set(type, component);
110
- }
111
- return;
112
- }
113
- // ローカルに既にロード済みの場合はconfigだけ更新してスキップ
114
- const localLoaded = this.plugins.get(packageName);
115
- if (localLoaded) {
116
- console.log(`Plugin ${packageName} is already loaded, updating config.`);
117
- // configを更新
118
- localLoaded.config = config !== null && config !== void 0 ? config : localLoaded.config;
119
- return;
117
+ // 開発環境ではキャッシュをバイパスして常に最新コードをロード
118
+ const isDevelopment = this.isDevelopment();
119
+ if (isDevelopment) {
120
+ // 開発環境では強制リロード(キャッシュをクリア)
121
+ globalLoadedPlugins.delete(packageName);
122
+ this.plugins.delete(packageName);
123
+ // ロード中のPromiseもクリア(再ロードを確実にするため)
124
+ globalLoadingPlugins.delete(packageName);
120
125
  }
121
- // グローバルでロード中の場合はそのPromiseを待つ
122
- const existingLoadPromise = globalLoadingPlugins.get(packageName);
123
- if (existingLoadPromise) {
124
- console.log(`Plugin ${packageName} is currently loading, waiting...`);
125
- yield existingLoadPromise;
126
- // ロード完了後にグローバルからコピー(新しいconfigで更新)
127
- const loaded = globalLoadedPlugins.get(packageName);
128
- if (loaded) {
129
- const updatedPlugin = Object.assign(Object.assign({}, loaded), { config: config !== null && config !== void 0 ? config : loaded.config });
126
+ else {
127
+ // 本番環境のみキャッシュを使用
128
+ // グローバルに既にロード済みの場合はローカルにコピーしてスキップ
129
+ const globalLoaded = globalLoadedPlugins.get(packageName);
130
+ if (globalLoaded) {
131
+ // 新しいconfigで更新したコピーを作成
132
+ const updatedPlugin = Object.assign(Object.assign({}, globalLoaded), { config: config !== null && config !== void 0 ? config : globalLoaded.config });
130
133
  this.plugins.set(packageName, updatedPlugin);
131
- for (const [type, component] of loaded.instance.components) {
134
+ // コンポーネントレジストリにも登録
135
+ for (const [type, component] of globalLoaded.instance.components) {
132
136
  this.componentRegistry.set(type, component);
133
137
  }
138
+ return;
139
+ }
140
+ }
141
+ if (!isDevelopment) {
142
+ // 本番環境のみ:ローカルに既にロード済みの場合はconfigだけ更新してスキップ
143
+ const localLoaded = this.plugins.get(packageName);
144
+ if (localLoaded) {
145
+ // configを更新
146
+ localLoaded.config = config !== null && config !== void 0 ? config : localLoaded.config;
147
+ return;
148
+ }
149
+ // 本番環境のみ:グローバルでロード中の場合はそのPromiseを待つ
150
+ const existingLoadPromise = globalLoadingPlugins.get(packageName);
151
+ if (existingLoadPromise) {
152
+ yield existingLoadPromise;
153
+ // ロード完了後にグローバルからコピー(新しいconfigで更新)
154
+ const loaded = globalLoadedPlugins.get(packageName);
155
+ if (loaded) {
156
+ const updatedPlugin = Object.assign(Object.assign({}, loaded), { config: config !== null && config !== void 0 ? config : loaded.config });
157
+ this.plugins.set(packageName, updatedPlugin);
158
+ for (const [type, component] of loaded.instance.components) {
159
+ this.componentRegistry.set(type, component);
160
+ }
161
+ }
162
+ return;
134
163
  }
135
- return;
136
164
  }
137
165
  // ロード処理をPromiseとして保存
138
- const loadPromise = this.doLoadPlugin(packageName, bundleUrl, config);
166
+ const loadPromise = this.doLoadPlugin(packageName, bundleUrl, config, assets);
139
167
  globalLoadingPlugins.set(packageName, loadPromise);
140
168
  try {
141
169
  yield loadPromise;
@@ -146,7 +174,7 @@ export class PluginManager {
146
174
  }
147
175
  });
148
176
  }
149
- doLoadPlugin(packageName, bundleUrl, config) {
177
+ doLoadPlugin(packageName, bundleUrl, config, assets) {
150
178
  return __awaiter(this, void 0, void 0, function* () {
151
179
  try {
152
180
  // スクリプトタグで動的にプラグインを読み込む
@@ -160,11 +188,16 @@ export class PluginManager {
160
188
  components: new Map(),
161
189
  assetUrls: new Map(),
162
190
  };
191
+ // 外部アプリから渡されたアセットURLを事前にキャッシュ
192
+ if (assets) {
193
+ for (const asset of assets) {
194
+ instance.assetUrls.set(asset.filename, asset.url);
195
+ }
196
+ }
163
197
  // プラグインAPIを作成
164
198
  const api = this.createPluginAPI(packageName, instance);
165
199
  // プラグインをセットアップ
166
200
  plugin.setup(api);
167
- console.log(`Plugin ${packageName} setup completed. Hooks:`, Object.keys(instance.hooks));
168
201
  const loadedPlugin = {
169
202
  plugin,
170
203
  instance,
@@ -203,9 +236,7 @@ export class PluginManager {
203
236
  };
204
237
  // プラグインバンドルをfetchで取得して実行
205
238
  // 開発環境ではキャッシュを無効化してホットリロードを確実に動作させる
206
- const isDevelopment = window.location.hostname === "localhost" ||
207
- window.location.hostname === "127.0.0.1" ||
208
- window.location.port === "3000";
239
+ const isDevelopment = this.isDevelopment();
209
240
  const fetchOptions = isDevelopment
210
241
  ? {
211
242
  cache: "no-cache",
@@ -215,7 +246,6 @@ export class PluginManager {
215
246
  },
216
247
  }
217
248
  : {};
218
- console.log(`🔄 Loading plugin ${packageName} (dev: ${isDevelopment}, cache: ${isDevelopment ? "disabled" : "enabled"})`);
219
249
  fetch(url, fetchOptions)
220
250
  .then((response) => {
221
251
  if (!response.ok) {
@@ -589,16 +619,26 @@ export class PluginManager {
589
619
  preload: (filenames) => {
590
620
  const preloadPromise = (() => __awaiter(this, void 0, void 0, function* () {
591
621
  const promises = filenames.map((filename) => __awaiter(this, void 0, void 0, function* () {
592
- // APIエンドポイントURL
593
- const encodedPackageName = encodeURIComponent(packageName);
594
- const encodedFilename = encodeURIComponent(filename);
595
- const apiUrl = `/api/plugin/${encodedPackageName}/assets/${encodedFilename}`;
596
- // fetchでリダイレクト後の最終URLを取得
622
+ // 既にキャッシュされたURLがあればAPIフェッチをスキップ
623
+ let finalUrl = instance.assetUrls.get(filename);
624
+ if (!finalUrl) {
625
+ // APIエンドポイントURL
626
+ const encodedPackageName = encodeURIComponent(packageName);
627
+ const encodedFilename = encodeURIComponent(filename);
628
+ const apiUrl = `/api/plugin/${encodedPackageName}/assets/${encodedFilename}`;
629
+ // fetchでリダイレクト後の最終URLを取得
630
+ try {
631
+ const response = yield fetch(apiUrl, { method: "HEAD" });
632
+ finalUrl = response.url; // リダイレクト後の最終URL
633
+ // 最終URLをキャッシュに保存
634
+ instance.assetUrls.set(filename, finalUrl);
635
+ }
636
+ catch (error) {
637
+ console.error(`Failed to preload asset: ${filename}`, error);
638
+ return Promise.resolve();
639
+ }
640
+ }
597
641
  try {
598
- const response = yield fetch(apiUrl, { method: "HEAD" });
599
- const finalUrl = response.url; // リダイレクト後の最終URL
600
- // 最終URLをキャッシュに保存
601
- instance.assetUrls.set(filename, finalUrl);
602
642
  // 画像をプリロード
603
643
  if (filename.match(/\.(png|jpg|jpeg|gif|webp)$/i)) {
604
644
  return new Promise((resolve, reject) => {
@@ -621,7 +661,6 @@ export class PluginManager {
621
661
  catch (error) {
622
662
  console.error(`Failed to preload asset: ${filename}`, error);
623
663
  }
624
- return Promise.resolve();
625
664
  }));
626
665
  yield Promise.all(promises);
627
666
  }))();
@@ -679,7 +718,6 @@ export class PluginManager {
679
718
  }
680
719
  instance.components.set(type, component);
681
720
  this.componentRegistry.set(type, component);
682
- console.log(`Plugin ${packageName} registered component: ${type}`);
683
721
  },
684
722
  };
685
723
  const uiAPI = {
@@ -777,8 +815,14 @@ export class PluginManager {
777
815
  },
778
816
  },
779
817
  data: {
780
- get: () => {
781
- throw new Error("DataAPI.get() can only be called from within DataProvider context");
818
+ get: (key) => {
819
+ if (this.dataContextGetter) {
820
+ const context = this.dataContextGetter();
821
+ const value = context === null || context === void 0 ? void 0 : context[key];
822
+ return value;
823
+ }
824
+ console.warn("DataContext getter is not registered. Make sure to call setDataContextGetter.");
825
+ return undefined;
782
826
  },
783
827
  subscribe: () => {
784
828
  throw new Error("DataAPI.subscribe() can only be called from within DataProvider context");
@@ -804,13 +848,26 @@ export class PluginManager {
804
848
  setVolumes: () => {
805
849
  throw new Error("DataAPI.setVolumes() can only be called from within DataProvider context");
806
850
  },
851
+ updateEmotionEffect: (state) => {
852
+ if (this.emotionEffectUpdaterCallback) {
853
+ this.emotionEffectUpdaterCallback(state);
854
+ }
855
+ else {
856
+ console.warn("updateEmotionEffect callback is not registered. Make sure to call setEmotionEffectUpdaterCallback.");
857
+ }
858
+ },
807
859
  },
808
860
  };
809
861
  }
810
862
  applyStyles(elementName, classNames) {
811
863
  const element = this.styleElements.get(elementName);
812
864
  if (element) {
813
- element.className = classNames.join(" ");
865
+ // 元のクラス名を取得し、プラグインから追加されたクラスと結合
866
+ const originalClassName = this.originalClassNames.get(elementName) || "";
867
+ const pluginClassNames = classNames.join(" ");
868
+ element.className = originalClassName
869
+ ? `${originalClassName} ${pluginClassNames}`
870
+ : pluginClassNames;
814
871
  }
815
872
  }
816
873
  hideEffect(effectId, instance) {
@@ -831,17 +888,17 @@ export class PluginManager {
831
888
  // DOM要素にdata-style-element-name属性を追加
832
889
  element.setAttribute("data-style-element-name", name);
833
890
  this.styleElements.set(name, element);
891
+ // 元のクラス名を保存(初回登録時のみ)
892
+ if (!this.originalClassNames.has(name)) {
893
+ this.originalClassNames.set(name, element.className);
894
+ }
834
895
  }
835
896
  // フック呼び出しメソッド
836
897
  callHook(hookName, ...args) {
837
- console.log(`Calling hook: ${hookName}`, args);
838
- console.log(`Number of plugins: ${this.plugins.size}`);
839
898
  this.plugins.forEach((loadedPlugin, packageName) => {
840
899
  const { instance, enabled } = loadedPlugin;
841
- console.log(`Plugin ${packageName}: enabled=${enabled}, has hook=${!!instance.hooks[hookName]}`);
842
900
  if (enabled && instance.hooks[hookName]) {
843
901
  try {
844
- console.log(`Executing hook ${hookName} for plugin ${packageName}`);
845
902
  const hook = instance.hooks[hookName];
846
903
  if (hook) {
847
904
  hook(...args);
@@ -1043,6 +1100,12 @@ export class PluginManager {
1043
1100
  setGetBranchStateCallback(callback) {
1044
1101
  this.getBranchStateCallback = callback;
1045
1102
  }
1103
+ setEmotionEffectUpdaterCallback(callback) {
1104
+ this.emotionEffectUpdaterCallback = callback;
1105
+ }
1106
+ setDataContextGetter(callback) {
1107
+ this.dataContextGetter = callback;
1108
+ }
1046
1109
  /**
1047
1110
  * 全てのアセットプリロードが完了するまで待機
1048
1111
  * @returns プリロード完了時にresolveするPromise
@@ -0,0 +1,41 @@
1
+ /**
2
+ * プラグインAPIからReactインスタンスを設定
3
+ */
4
+ export declare function setReactRuntime(react: any): void;
5
+ /**
6
+ * JSX Transform用のjsx関数
7
+ */
8
+ export declare function jsx(type: any, props: any, key?: any): any;
9
+ /**
10
+ * JSX Transform用のjsxs関数(複数子要素用)
11
+ */
12
+ export declare function jsxs(type: any, props: any, key?: any): any;
13
+ /**
14
+ * Fragment用
15
+ */
16
+ export declare function Fragment(props: {
17
+ children?: any;
18
+ }): any;
19
+ /**
20
+ * Reactフックと関数のプロキシ
21
+ */
22
+ export declare const useState: (...args: any[]) => any;
23
+ export declare const useEffect: (...args: any[]) => any;
24
+ export declare const useCallback: (...args: any[]) => any;
25
+ export declare const useMemo: (...args: any[]) => any;
26
+ export declare const useRef: (...args: any[]) => any;
27
+ export declare const useContext: (...args: any[]) => any;
28
+ export declare const useReducer: (...args: any[]) => any;
29
+ export declare const createElement: (...args: any[]) => any;
30
+ declare const _default: {
31
+ createElement: (...args: any[]) => any;
32
+ Fragment: typeof Fragment;
33
+ useState: (...args: any[]) => any;
34
+ useEffect: (...args: any[]) => any;
35
+ useCallback: (...args: any[]) => any;
36
+ useMemo: (...args: any[]) => any;
37
+ useRef: (...args: any[]) => any;
38
+ useContext: (...args: any[]) => any;
39
+ useReducer: (...args: any[]) => any;
40
+ };
41
+ export default _default;
@@ -0,0 +1,99 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable import/no-anonymous-default-export */
3
+ let runtimeReact = null;
4
+ /**
5
+ * プラグインAPIからReactインスタンスを設定
6
+ */
7
+ export function setReactRuntime(react) {
8
+ runtimeReact = react;
9
+ }
10
+ /**
11
+ * JSX Transform用のjsx関数
12
+ */
13
+ export function jsx(type, props, key) {
14
+ if (!runtimeReact) {
15
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
16
+ }
17
+ return runtimeReact.createElement(type, key ? Object.assign(Object.assign({}, props), { key }) : props);
18
+ }
19
+ /**
20
+ * JSX Transform用のjsxs関数(複数子要素用)
21
+ */
22
+ export function jsxs(type, props, key) {
23
+ if (!runtimeReact) {
24
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
25
+ }
26
+ return runtimeReact.createElement(type, key ? Object.assign(Object.assign({}, props), { key }) : props);
27
+ }
28
+ /**
29
+ * Fragment用
30
+ */
31
+ export function Fragment(props) {
32
+ if (!runtimeReact) {
33
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
34
+ }
35
+ return runtimeReact.createElement(runtimeReact.Fragment, null, props.children);
36
+ }
37
+ /**
38
+ * Reactフックと関数のプロキシ
39
+ */
40
+ export const useState = (...args) => {
41
+ if (!runtimeReact) {
42
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
43
+ }
44
+ return runtimeReact.useState(...args);
45
+ };
46
+ export const useEffect = (...args) => {
47
+ if (!runtimeReact) {
48
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
49
+ }
50
+ return runtimeReact.useEffect(...args);
51
+ };
52
+ export const useCallback = (...args) => {
53
+ if (!runtimeReact) {
54
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
55
+ }
56
+ return runtimeReact.useCallback(...args);
57
+ };
58
+ export const useMemo = (...args) => {
59
+ if (!runtimeReact) {
60
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
61
+ }
62
+ return runtimeReact.useMemo(...args);
63
+ };
64
+ export const useRef = (...args) => {
65
+ if (!runtimeReact) {
66
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
67
+ }
68
+ return runtimeReact.useRef(...args);
69
+ };
70
+ export const useContext = (...args) => {
71
+ if (!runtimeReact) {
72
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
73
+ }
74
+ return runtimeReact.useContext(...args);
75
+ };
76
+ export const useReducer = (...args) => {
77
+ if (!runtimeReact) {
78
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
79
+ }
80
+ return runtimeReact.useReducer(...args);
81
+ };
82
+ export const createElement = (...args) => {
83
+ if (!runtimeReact) {
84
+ throw new Error("React runtime not initialized. Make sure plugin is loaded properly.");
85
+ }
86
+ return runtimeReact.createElement(...args);
87
+ };
88
+ // デフォルトエクスポート(互換性用)
89
+ export default {
90
+ createElement,
91
+ Fragment,
92
+ useState,
93
+ useEffect,
94
+ useCallback,
95
+ useMemo,
96
+ useRef,
97
+ useContext,
98
+ useReducer,
99
+ };