@luna-editor/engine 0.3.0 → 0.3.2

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,73 @@ 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);
117
+ // 開発環境ではキャッシュをバイパスして常に最新コードをロード
118
+ const isDevelopment = this.isDevelopment();
119
+ if (isDevelopment) {
120
+ // 開発環境では強制リロード(キャッシュをクリア)
121
+ globalLoadedPlugins.delete(packageName);
122
+ this.plugins.delete(packageName);
123
+ // ロード中の場合はそのPromiseを待って完了させる(二重ロード防止)
124
+ const existingLoadPromise = globalLoadingPlugins.get(packageName);
125
+ if (existingLoadPromise) {
126
+ yield existingLoadPromise;
127
+ const loaded = globalLoadedPlugins.get(packageName);
128
+ if (loaded) {
129
+ this.plugins.set(packageName, Object.assign(Object.assign({}, loaded), { config: config !== null && config !== void 0 ? config : loaded.config }));
130
+ for (const [type, component] of loaded.instance.components) {
131
+ this.componentRegistry.set(type, component);
132
+ }
133
+ }
134
+ return;
110
135
  }
111
- return;
112
136
  }
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;
120
- }
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 });
137
+ else {
138
+ // 本番環境のみキャッシュを使用
139
+ // グローバルに既にロード済みの場合はローカルにコピーしてスキップ
140
+ const globalLoaded = globalLoadedPlugins.get(packageName);
141
+ if (globalLoaded) {
142
+ // 新しいconfigで更新したコピーを作成
143
+ const updatedPlugin = Object.assign(Object.assign({}, globalLoaded), { config: config !== null && config !== void 0 ? config : globalLoaded.config });
130
144
  this.plugins.set(packageName, updatedPlugin);
131
- for (const [type, component] of loaded.instance.components) {
145
+ // コンポーネントレジストリにも登録
146
+ for (const [type, component] of globalLoaded.instance.components) {
132
147
  this.componentRegistry.set(type, component);
133
148
  }
149
+ return;
150
+ }
151
+ }
152
+ if (!isDevelopment) {
153
+ // 本番環境のみ:ローカルに既にロード済みの場合はconfigだけ更新してスキップ
154
+ const localLoaded = this.plugins.get(packageName);
155
+ if (localLoaded) {
156
+ // configを更新
157
+ localLoaded.config = config !== null && config !== void 0 ? config : localLoaded.config;
158
+ return;
159
+ }
160
+ // 本番環境のみ:グローバルでロード中の場合はそのPromiseを待つ
161
+ const existingLoadPromise = globalLoadingPlugins.get(packageName);
162
+ if (existingLoadPromise) {
163
+ yield existingLoadPromise;
164
+ // ロード完了後にグローバルからコピー(新しいconfigで更新)
165
+ const loaded = globalLoadedPlugins.get(packageName);
166
+ if (loaded) {
167
+ const updatedPlugin = Object.assign(Object.assign({}, loaded), { config: config !== null && config !== void 0 ? config : loaded.config });
168
+ this.plugins.set(packageName, updatedPlugin);
169
+ for (const [type, component] of loaded.instance.components) {
170
+ this.componentRegistry.set(type, component);
171
+ }
172
+ }
173
+ return;
134
174
  }
135
- return;
136
175
  }
137
176
  // ロード処理をPromiseとして保存
138
- const loadPromise = this.doLoadPlugin(packageName, bundleUrl, config);
177
+ const loadPromise = this.doLoadPlugin(packageName, bundleUrl, config, assets);
139
178
  globalLoadingPlugins.set(packageName, loadPromise);
140
179
  try {
141
180
  yield loadPromise;
@@ -146,7 +185,7 @@ export class PluginManager {
146
185
  }
147
186
  });
148
187
  }
149
- doLoadPlugin(packageName, bundleUrl, config) {
188
+ doLoadPlugin(packageName, bundleUrl, config, assets) {
150
189
  return __awaiter(this, void 0, void 0, function* () {
151
190
  try {
152
191
  // スクリプトタグで動的にプラグインを読み込む
@@ -160,11 +199,16 @@ export class PluginManager {
160
199
  components: new Map(),
161
200
  assetUrls: new Map(),
162
201
  };
202
+ // 外部アプリから渡されたアセットURLを事前にキャッシュ
203
+ if (assets) {
204
+ for (const asset of assets) {
205
+ instance.assetUrls.set(asset.filename, asset.url);
206
+ }
207
+ }
163
208
  // プラグインAPIを作成
164
209
  const api = this.createPluginAPI(packageName, instance);
165
210
  // プラグインをセットアップ
166
211
  plugin.setup(api);
167
- console.log(`Plugin ${packageName} setup completed. Hooks:`, Object.keys(instance.hooks));
168
212
  const loadedPlugin = {
169
213
  plugin,
170
214
  instance,
@@ -203,9 +247,7 @@ export class PluginManager {
203
247
  };
204
248
  // プラグインバンドルをfetchで取得して実行
205
249
  // 開発環境ではキャッシュを無効化してホットリロードを確実に動作させる
206
- const isDevelopment = window.location.hostname === "localhost" ||
207
- window.location.hostname === "127.0.0.1" ||
208
- window.location.port === "3000";
250
+ const isDevelopment = this.isDevelopment();
209
251
  const fetchOptions = isDevelopment
210
252
  ? {
211
253
  cache: "no-cache",
@@ -215,7 +257,6 @@ export class PluginManager {
215
257
  },
216
258
  }
217
259
  : {};
218
- console.log(`🔄 Loading plugin ${packageName} (dev: ${isDevelopment}, cache: ${isDevelopment ? "disabled" : "enabled"})`);
219
260
  fetch(url, fetchOptions)
220
261
  .then((response) => {
221
262
  if (!response.ok) {
@@ -589,16 +630,26 @@ export class PluginManager {
589
630
  preload: (filenames) => {
590
631
  const preloadPromise = (() => __awaiter(this, void 0, void 0, function* () {
591
632
  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を取得
633
+ // 既にキャッシュされたURLがあればAPIフェッチをスキップ
634
+ let finalUrl = instance.assetUrls.get(filename);
635
+ if (!finalUrl) {
636
+ // APIエンドポイントURL
637
+ const encodedPackageName = encodeURIComponent(packageName);
638
+ const encodedFilename = encodeURIComponent(filename);
639
+ const apiUrl = `/api/plugin/${encodedPackageName}/assets/${encodedFilename}`;
640
+ // fetchでリダイレクト後の最終URLを取得
641
+ try {
642
+ const response = yield fetch(apiUrl, { method: "HEAD" });
643
+ finalUrl = response.url; // リダイレクト後の最終URL
644
+ // 最終URLをキャッシュに保存
645
+ instance.assetUrls.set(filename, finalUrl);
646
+ }
647
+ catch (error) {
648
+ console.error(`Failed to preload asset: ${filename}`, error);
649
+ return Promise.resolve();
650
+ }
651
+ }
597
652
  try {
598
- const response = yield fetch(apiUrl, { method: "HEAD" });
599
- const finalUrl = response.url; // リダイレクト後の最終URL
600
- // 最終URLをキャッシュに保存
601
- instance.assetUrls.set(filename, finalUrl);
602
653
  // 画像をプリロード
603
654
  if (filename.match(/\.(png|jpg|jpeg|gif|webp)$/i)) {
604
655
  return new Promise((resolve, reject) => {
@@ -621,7 +672,6 @@ export class PluginManager {
621
672
  catch (error) {
622
673
  console.error(`Failed to preload asset: ${filename}`, error);
623
674
  }
624
- return Promise.resolve();
625
675
  }));
626
676
  yield Promise.all(promises);
627
677
  }))();
@@ -679,7 +729,6 @@ export class PluginManager {
679
729
  }
680
730
  instance.components.set(type, component);
681
731
  this.componentRegistry.set(type, component);
682
- console.log(`Plugin ${packageName} registered component: ${type}`);
683
732
  },
684
733
  };
685
734
  const uiAPI = {
@@ -777,8 +826,14 @@ export class PluginManager {
777
826
  },
778
827
  },
779
828
  data: {
780
- get: () => {
781
- throw new Error("DataAPI.get() can only be called from within DataProvider context");
829
+ get: (key) => {
830
+ if (this.dataContextGetter) {
831
+ const context = this.dataContextGetter();
832
+ const value = context === null || context === void 0 ? void 0 : context[key];
833
+ return value;
834
+ }
835
+ console.warn("DataContext getter is not registered. Make sure to call setDataContextGetter.");
836
+ return undefined;
782
837
  },
783
838
  subscribe: () => {
784
839
  throw new Error("DataAPI.subscribe() can only be called from within DataProvider context");
@@ -804,13 +859,26 @@ export class PluginManager {
804
859
  setVolumes: () => {
805
860
  throw new Error("DataAPI.setVolumes() can only be called from within DataProvider context");
806
861
  },
862
+ updateEmotionEffect: (state) => {
863
+ if (this.emotionEffectUpdaterCallback) {
864
+ this.emotionEffectUpdaterCallback(state);
865
+ }
866
+ else {
867
+ console.warn("updateEmotionEffect callback is not registered. Make sure to call setEmotionEffectUpdaterCallback.");
868
+ }
869
+ },
807
870
  },
808
871
  };
809
872
  }
810
873
  applyStyles(elementName, classNames) {
811
874
  const element = this.styleElements.get(elementName);
812
875
  if (element) {
813
- element.className = classNames.join(" ");
876
+ // 元のクラス名を取得し、プラグインから追加されたクラスと結合
877
+ const originalClassName = this.originalClassNames.get(elementName) || "";
878
+ const pluginClassNames = classNames.join(" ");
879
+ element.className = originalClassName
880
+ ? `${originalClassName} ${pluginClassNames}`
881
+ : pluginClassNames;
814
882
  }
815
883
  }
816
884
  hideEffect(effectId, instance) {
@@ -831,17 +899,17 @@ export class PluginManager {
831
899
  // DOM要素にdata-style-element-name属性を追加
832
900
  element.setAttribute("data-style-element-name", name);
833
901
  this.styleElements.set(name, element);
902
+ // 元のクラス名を保存(初回登録時のみ)
903
+ if (!this.originalClassNames.has(name)) {
904
+ this.originalClassNames.set(name, element.className);
905
+ }
834
906
  }
835
907
  // フック呼び出しメソッド
836
908
  callHook(hookName, ...args) {
837
- console.log(`Calling hook: ${hookName}`, args);
838
- console.log(`Number of plugins: ${this.plugins.size}`);
839
909
  this.plugins.forEach((loadedPlugin, packageName) => {
840
910
  const { instance, enabled } = loadedPlugin;
841
- console.log(`Plugin ${packageName}: enabled=${enabled}, has hook=${!!instance.hooks[hookName]}`);
842
911
  if (enabled && instance.hooks[hookName]) {
843
912
  try {
844
- console.log(`Executing hook ${hookName} for plugin ${packageName}`);
845
913
  const hook = instance.hooks[hookName];
846
914
  if (hook) {
847
915
  hook(...args);
@@ -1043,6 +1111,12 @@ export class PluginManager {
1043
1111
  setGetBranchStateCallback(callback) {
1044
1112
  this.getBranchStateCallback = callback;
1045
1113
  }
1114
+ setEmotionEffectUpdaterCallback(callback) {
1115
+ this.emotionEffectUpdaterCallback = callback;
1116
+ }
1117
+ setDataContextGetter(callback) {
1118
+ this.dataContextGetter = callback;
1119
+ }
1046
1120
  /**
1047
1121
  * 全てのアセットプリロードが完了するまで待機
1048
1122
  * @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
+ };