Skip to content

Commit 9d13628

Browse files
committed
release new version
1 parent e856258 commit 9d13628

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1444
-367
lines changed

bun.lockb

-92 KB
Binary file not shown.

src/Actions/Stories.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Environment } from "Utils/HotReloader/Environment";
2+
3+
export function CreateScheduler() {
4+
const environment = new Environment();
5+
environment.EnableGlobalInjection();
6+
}
7+
8+
export function LoadStoryPreview(preview: PreviewEntry) {}
9+
10+
export function MountInSharedEnvironment(preview: PreviewEntry) {}
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import Signal from "@rbxts/lemon-signal";
2+
import { LoadVirtualModule } from "./Utils";
3+
4+
const HttpService = game.GetService("HttpService");
5+
6+
type Dependencies = Map<ModuleScript, { Result: unknown }>;
7+
type DependencyLoaders = Map<ModuleScript, Promise<unknown>>;
8+
type Listeners = Map<ModuleScript, RBXScriptConnection>;
9+
10+
export class Environment {
11+
private _ActiveConnections = true;
12+
private _Dependencies: Dependencies = new Map();
13+
private _DependencyLoaders: DependencyLoaders = new Map();
14+
private _Listeners: Listeners = new Map();
15+
16+
readonly EnvironmentUID: string;
17+
private _GlobalInjection?: Record<keyof any, unknown>;
18+
19+
readonly Shared: {} = {};
20+
OnDependencyChanged = new Signal<[module: ModuleScript]>();
21+
private _DestroyedHooked?: () => void;
22+
23+
constructor() {
24+
const uid = HttpService.GenerateGUID(false);
25+
this.EnvironmentUID = uid;
26+
}
27+
EnableGlobalInjection() {
28+
if (!this._GlobalInjection) {
29+
this._GlobalInjection = {};
30+
}
31+
}
32+
InjectGlobal(key: keyof any, value: unknown) {
33+
this.EnableGlobalInjection();
34+
this._GlobalInjection![key] = value;
35+
}
36+
GetGlobalInjection() {
37+
return this._GlobalInjection;
38+
}
39+
40+
private _RegistryDependency(module: ModuleScript, result?: any) {
41+
this._Dependencies.set(module, { Result: result });
42+
}
43+
44+
IsDependency(module: ModuleScript) {
45+
return this._Dependencies.has(module);
46+
}
47+
GetDependencyResult<T = unknown>(module: ModuleScript): T | undefined {
48+
return this._Dependencies.get(module)?.Result as T;
49+
}
50+
51+
ListenDependency(module: ModuleScript) {
52+
if (!this._ActiveConnections) return;
53+
54+
const listener = module.GetPropertyChangedSignal("Source").Connect(() => {
55+
if (!this._ActiveConnections) return;
56+
this.OnDependencyChanged.Fire(module);
57+
});
58+
this._Listeners.set(module, listener);
59+
}
60+
61+
LoadDependency<T = unknown>(dependency: ModuleScript): Promise<T> {
62+
const cached = this.GetDependencyResult(dependency);
63+
if (cached !== undefined) {
64+
return Promise.resolve(cached as T);
65+
}
66+
const cachedLoader = this._DependencyLoaders.get(dependency) as Promise<T>;
67+
if (cachedLoader) {
68+
return cachedLoader.tap(() => {});
69+
}
70+
71+
this.ListenDependency(dependency);
72+
73+
const promise = LoadVirtualModule(dependency, this).tap((result) => {
74+
this._RegistryDependency(dependency, result);
75+
});
76+
this._DependencyLoaders.set(dependency, promise);
77+
78+
return promise as Promise<T>;
79+
}
80+
81+
HookOnDestroyed(callback: () => void) {
82+
this._DestroyedHooked = callback;
83+
}
84+
85+
Destroy() {
86+
if (this._DestroyedHooked) {
87+
this._DestroyedHooked();
88+
}
89+
90+
this._ActiveConnections = false;
91+
this._Listeners.forEach((connection) => {
92+
connection.Disconnect();
93+
});
94+
this.OnDependencyChanged.Destroy();
95+
}
96+
}

src/Actions/StoryLoading/Holder.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { RunService } from "@rbxts/services";
2+
import { Environment } from "./Environment";
3+
4+
export class EnvironmentHolder {
5+
private Environment?: Environment;
6+
private ShouldReload = false;
7+
8+
LoadEntries: string[] = [];
9+
10+
constructor(target: string) {}
11+
12+
ClearEnvironment() {}
13+
14+
ScheduleStory(uid: string) {
15+
if (this.LoadEntries.includes(uid)) return;
16+
17+
this.LoadEntries.push(uid);
18+
}
19+
20+
private Reload() {
21+
// clear
22+
let promiseHandler;
23+
}
24+
25+
QueueReload() {
26+
if (this.ShouldReload) return;
27+
this.ShouldReload = true;
28+
RunService.Stepped.Once(() => {
29+
this.ShouldReload = false;
30+
this.Reload();
31+
});
32+
}
33+
34+
Destroy() {}
35+
}
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { EnvironmentHolder } from "./Holder";
2+
3+
export class HotReloader {
4+
EnvironmentHolder: EnvironmentHolder;
5+
6+
constructor(module: ModuleScript, holder: EnvironmentHolder) {
7+
this.EnvironmentHolder = holder;
8+
}
9+
}

src/Actions/StoryLoading/Utils.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { Environment } from "./Environment";
2+
3+
/**
4+
* Replaces the environment of a loadstring'ed function
5+
* @param virtualModule function result of loadstring()
6+
* @param module module that was loaded with loadstring()
7+
* @param environment Environment handler object
8+
*/
9+
export function SetEnvironment(virtualModule: Callback, module: ModuleScript, environment: Environment) {
10+
const globals = {
11+
require: (dependency: ModuleScript) => {
12+
return environment.LoadDependency(dependency).expect();
13+
},
14+
script: module,
15+
_G: environment.Shared,
16+
};
17+
const env = getfenv();
18+
const injection = environment.GetGlobalInjection();
19+
const index = injection ? setmetatable(injection, { __index: env }) : env;
20+
21+
const newEnvironment = setmetatable(globals, {
22+
__index: index, //defaults any global variables to the current global environment
23+
});
24+
setfenv(virtualModule, newEnvironment);
25+
}
26+
27+
/**
28+
* Requires a module by using loadstring, this also replaces the _G table and the function "require()"
29+
* @param module the module to laod
30+
* @param environment Environment handler object
31+
*/
32+
export async function LoadVirtualModule(module: ModuleScript, environment: Environment) {
33+
const [virtualModule, err] = loadstring(module.Source, module.GetFullName());
34+
35+
if (virtualModule === undefined) {
36+
throw err;
37+
}
38+
39+
SetEnvironment(virtualModule, module, environment);
40+
41+
const [sucess, result] = pcall(virtualModule);
42+
if (sucess) {
43+
return result as unknown;
44+
} else {
45+
throw result;
46+
}
47+
}

src/Context/UserInputContext.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,12 @@ export function useInputSignals() {
101101
useEventListener(UserInputService.InputChanged, (input, processed) => onInputChanged.Fire(input, processed));
102102

103103
const signals = useMemo(() => {
104-
const value: InputSignals = {
104+
const value = {
105105
InputBegan: onInputBegan,
106106
InputEnded: onInputEnded,
107107
InputChanged: onInputChanged,
108108
MouseMoved: onMouseMoved,
109-
};
109+
} as InputSignals;
110110
return value;
111111
}, []);
112112

src/Context/WidgetStateContext.tsx

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import Signal from "@rbxts/lemon-signal";
2+
import React, { useEffect, useMemo, useState } from "@rbxts/react";
3+
import { UserInputService } from "@rbxts/services";
4+
import { useSignal } from "Hooks/Utils/Signal";
5+
6+
interface WidgetStateContext {
7+
WidgetFocused: boolean;
8+
ViewportFocused: boolean;
9+
OnViewportFocusChanged: Signal<[boolean]>;
10+
OnWidgetFocusChanged: Signal<[boolean]>;
11+
}
12+
13+
const WidgetStateContext = React.createContext<WidgetStateContext>({
14+
WidgetFocused: true,
15+
ViewportFocused: true,
16+
OnViewportFocusChanged: new Signal<boolean>(),
17+
OnWidgetFocusChanged: new Signal<boolean>()
18+
});
19+
20+
interface WidgetStateProviderProps extends React.PropsWithChildren {
21+
DockWidget: DockWidgetPluginGui;
22+
}
23+
24+
export function WidgetStateProvider(props: WidgetStateProviderProps) {
25+
const [widgetFocused, setWidgetFocused] = useState(false);
26+
const [viewportFocused, setViewportFocused] = useState(false);
27+
28+
const focusedSignal = useSignal<[boolean]>();
29+
const viewportFocusedSignal = useSignal<[boolean]>();
30+
useEffect(() => {
31+
const widgetFocused = props.DockWidget.WindowFocused.Connect(() => {
32+
focusedSignal.Fire(true);
33+
setWidgetFocused(true);
34+
});
35+
const widgetFocusReleased = props.DockWidget.WindowFocusReleased.Connect(
36+
() => {
37+
focusedSignal.Fire(false);
38+
setWidgetFocused(false);
39+
}
40+
);
41+
42+
const viewportFocused = UserInputService.WindowFocused.Connect(() => {
43+
viewportFocusedSignal.Fire(true);
44+
setViewportFocused(true);
45+
});
46+
const viewportFocusReleased = UserInputService.WindowFocusReleased.Connect(
47+
() => {
48+
viewportFocusedSignal.Fire(false);
49+
setViewportFocused(false);
50+
}
51+
);
52+
53+
return () => {
54+
widgetFocused.Disconnect();
55+
widgetFocusReleased.Disconnect();
56+
viewportFocused.Disconnect();
57+
viewportFocusReleased.Disconnect();
58+
};
59+
}, [props.DockWidget]);
60+
61+
const context = useMemo(() => {
62+
return {
63+
WidgetFocused: widgetFocused,
64+
ViewportFocused: viewportFocused,
65+
66+
OnViewportFocusChanged: viewportFocusedSignal,
67+
OnWidgetFocusChanged: focusedSignal
68+
};
69+
}, [widgetFocused, viewportFocused]);
70+
71+
return (
72+
<WidgetStateContext.Provider value={context}>
73+
{props.children}
74+
</WidgetStateContext.Provider>
75+
);
76+
}
77+
78+
export function useWidgetStateContext() {
79+
const context = React.useContext(WidgetStateContext);
80+
return context;
81+
}

src/Hooks/Reflex/Control/ModuleRequire/StorybookLoader.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { HotReloader } from "@rbxts/hmr";
21
import { Signal } from "@rbxts/lemon-signal";
2+
import { HotReloader } from "Utils/HotReloader/HotReloader";
33

44
export class StorybookLoader {
55
private StorybookResult?: unknown = undefined;

src/Hooks/Reflex/Control/PluginSettings.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,27 @@ import { PluginSettingsState, selectPluginSettings } from "Reflex/PluginSettings
44
import { usePlugin } from "../Use/Plugin";
55
import { useMemo } from "@rbxts/react";
66
import Configs from "Plugin/Configs";
7+
import { selectTheme } from "Reflex/Theme";
78

89
export function controlPluginSettings() {
910
const plugin = usePlugin();
10-
const { setPluginSettings } = useProducer<RootProducer>();
11+
const { setPluginSettings, setThemeIndex } = useProducer<RootProducer>();
1112
const pluginSettings = useSelector(selectPluginSettings);
13+
const theme = useSelector(selectTheme);
1214

1315
useMemo(() => {
1416
if (plugin === undefined) return;
15-
const settings = plugin.GetSetting(Configs.PluginSettingsKey);
17+
const settings = plugin.GetSetting(Configs.PluginSettingsKey) as PluginSettingsState;
1618
if (settings === undefined) return;
17-
setPluginSettings(settings as PluginSettingsState);
19+
20+
setPluginSettings(settings);
21+
setThemeIndex(settings.theme ?? "Dark");
1822
}, [plugin]);
1923

24+
useUpdateEffect(() => {
25+
setPluginSettings({ theme: theme.themeIndex });
26+
}, [theme, plugin]);
27+
2028
useUpdateEffect(() => {
2129
if (plugin === undefined) return;
2230
plugin.SetSetting(Configs.PluginSettingsKey, pluginSettings);

src/Plugin/Warnings.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
export const WARNINGS = {
22
NoStory: "Story module didn't return anything",
33
RequireError: "Story module errored when required",
4+
StoryTypeError: "Story is not a valid valid story type",
5+
NoControlsReconciler:
6+
"Error Reconciling controls, New controls expected, got %s",
7+
NoReconcileResult:
8+
"Invalid controls returned from reconciler, table expected, got %s",
49

510
Yielding: "%s yielded when called.",
611
StoryError: "%s errored when called.",

src/Reflex/Overlay.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { createProducer } from "@rbxts/reflex";
21
import React from "@rbxts/react";
2+
import { createProducer } from "@rbxts/reflex";
33

44
interface OverlayEntry {
55
Key: string;
@@ -26,7 +26,12 @@ const initialState: OverlayState = {
2626
export const selectPopup = (state: RootState) => state.overlay.popup;
2727

2828
export const OverlayProducer = createProducer(initialState, {
29-
setPopup: (state, key: string, element: React.Element, identifier?: unknown) => {
29+
setPopup: (
30+
state,
31+
key: string,
32+
element: React.Element,
33+
identifier?: unknown,
34+
) => {
3035
return {
3136
...state,
3237
popup: {

0 commit comments

Comments
 (0)