Skip to content

Commit

Permalink
fix(ui): fix react crash when uninstalling a plugin
Browse files Browse the repository at this point in the history
words can't describe how much i love react
  • Loading branch information
pylixonly committed Aug 28, 2024
1 parent 93df24e commit b629261
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/core/ui/settings/pages/Plugins/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface UnifiedPluginModel {

isEnabled(): boolean;
usePluginState(): void;
isInstalled(): boolean;
toggle(start: boolean): void;
resolveSheetComponent(): Promise<{ default: React.ComponentType<any>; }>;
getPluginSettingsComponent(): React.ComponentType<any> | null | undefined;
Expand Down
5 changes: 2 additions & 3 deletions src/core/ui/settings/pages/Plugins/models/bunny.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ export default function unifyBunnyPlugin(manifest: BunnyPluginManifest): Unified
name: manifest.name,
description: manifest.description,
authors: manifest.authors,
isEnabled() {
return isPluginEnabled(getId(manifest));
},
isEnabled: () => isPluginEnabled(getId(manifest)),
isInstalled: () => manifest.id in pluginSettings,
usePluginState() {
useProxy(pluginSettings);
},
Expand Down
1 change: 1 addition & 0 deletions src/core/ui/settings/pages/Plugins/models/vendetta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function unifyVdPlugin(vdPlugin: VendettaPlugin): UnifiedPluginMo
icon: vdPlugin.manifest.vendetta?.icon,

isEnabled: () => vdPlugin.enabled,
isInstalled: () => Boolean(vdPlugin && VdPluginManager.plugins[vdPlugin.id]),
usePluginState() {
useProxy(VdPluginManager.plugins[vdPlugin.id]);
},
Expand Down
41 changes: 34 additions & 7 deletions src/lib/api/storage/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { StorageBackend } from "@lib/api/storage/backends";
import { Emitter } from "@lib/utils/Emitter";
import { Emitter, EmitterEvent, EmitterListener, EmitterListenerData } from "@lib/utils/Emitter";

const emitterSymbol = Symbol.for("vendetta.storage.emitter");
const syncAwaitSymbol = Symbol.for("vendetta.storage.accessor");

export function createProxy(target: any = {}): { proxy: any; emitter: Emitter; } {
const emitter = new Emitter();
const parentTarget = target;

const childrens = new WeakMap<any, any>();

function createProxy(target: any, path: string[]): any {
return new Proxy(target, {
Expand All @@ -17,21 +20,38 @@ export function createProxy(target: any = {}): { proxy: any; emitter: Emitter; }

if (value !== undefined && value !== null) {
emitter.emit("GET", {
parent: parentTarget,
path: newPath,
value,
});

if (typeof value === "object") {
return createProxy(value, newPath);
if (childrens.has(value)) return childrens.get(value);

const childrenProxy = createProxy(value, newPath);
childrens.set(value, childrenProxy);
return childrenProxy;
}

return value;
}

return value;
},

set(target, prop: string, value) {
target[prop] = value;
if (typeof value === "object") {
if (childrens.has(value)) return childrens.get(value);

const childrenProxy = createProxy(value, [...path, prop]);
childrens.set(value, childrenProxy);
target[prop] = childrenProxy;
} else {
target[prop] = value;
}

emitter.emit("SET", {
parent: parentTarget,
path: [...path, prop],
value,
});
Expand All @@ -40,9 +60,12 @@ export function createProxy(target: any = {}): { proxy: any; emitter: Emitter; }
},

deleteProperty(target, prop: string) {
const value = typeof target[prop] === "object" ? childrens.get(target[prop])! : target[prop];
const success = delete target[prop];
if (success)
emitter.emit("DEL", {
value,
parent: parentTarget,
path: [...path, prop],
});
return success;
Expand All @@ -57,13 +80,17 @@ export function createProxy(target: any = {}): { proxy: any; emitter: Emitter; }
}

export function useProxy<T>(storage: T): T {
const emitter = (storage as any)[emitterSymbol] as Emitter;
if (!emitter) throw new Error("storage[emitterSymbol] is undefined");
const emitter = (storage as any)?.[emitterSymbol] as Emitter;
if (!emitter) throw new Error("storage?.[emitterSymbol] is undefined");

const [, forceUpdate] = React.useReducer(n => ~n, 0);

React.useEffect(() => {
const listener = () => forceUpdate();
const listener: EmitterListener = (event: EmitterEvent, data: EmitterListenerData) => {
if (event === "DEL" && data.value === storage) return;
console.log({ event, data });
forceUpdate();
};

emitter.on("SET", listener);
emitter.on("DEL", listener);
Expand All @@ -72,7 +99,7 @@ export function useProxy<T>(storage: T): T {
emitter.off("SET", listener);
emitter.off("DEL", listener);
};
}, []);
}, [storage]);

return storage;
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib/utils/Emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export type EmitterEvent = "SET" | "GET" | "DEL";

export interface EmitterListenerData {
path: string[];
value?: any;
value: any;
parent: any;
}

export type EmitterListener = (
Expand Down

0 comments on commit b629261

Please sign in to comment.