@decky/ui 4.2.2 → 4.3.0
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/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/patcher.d.ts +1 -2
- package/dist/utils/react-patching.d.ts +7 -0
- package/dist/utils/react-patching.js +75 -0
- package/package.json +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/patcher.ts +1 -1
- package/src/utils/react-patching.ts +100 -0
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/dist/utils/patcher.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export declare let callOriginal: symbol;
|
|
|
2
2
|
export interface PatchOptions {
|
|
3
3
|
singleShot?: boolean;
|
|
4
4
|
}
|
|
5
|
-
type GenericPatchHandler = (args: any[], ret?: any) => any;
|
|
5
|
+
export type GenericPatchHandler = (args: any[], ret?: any) => any;
|
|
6
6
|
export interface Patch {
|
|
7
7
|
original: Function;
|
|
8
8
|
property: string;
|
|
@@ -15,4 +15,3 @@ export interface Patch {
|
|
|
15
15
|
export declare function beforePatch(object: any, property: string, handler: (args: any[]) => any, options?: PatchOptions): Patch;
|
|
16
16
|
export declare function afterPatch(object: any, property: string, handler: (args: any[], ret: any) => any, options?: PatchOptions): Patch;
|
|
17
17
|
export declare function replacePatch(object: any, property: string, handler: (args: any[]) => any, options?: PatchOptions): Patch;
|
|
18
|
-
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GenericPatchHandler } from './patcher';
|
|
2
|
+
export type GenericNodeStep = (node: any) => any;
|
|
3
|
+
export type NodeStep = GenericNodeStep;
|
|
4
|
+
export type ReactPatchHandler = GenericPatchHandler;
|
|
5
|
+
export declare function setReactPatcherLoggingEnabled(value?: boolean): void;
|
|
6
|
+
export declare function setReactPatcherPerformanceLoggingEnabled(value?: boolean): void;
|
|
7
|
+
export declare function createReactTreePatcher(steps: NodeStep[], handler: GenericPatchHandler, debugName?: string): GenericPatchHandler;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Logger from '../logger';
|
|
2
|
+
import { afterPatch } from './patcher';
|
|
3
|
+
import { wrapReactClass, wrapReactType } from './react';
|
|
4
|
+
let loggingEnabled = false;
|
|
5
|
+
let perfLoggingEnabled = false;
|
|
6
|
+
export function setReactPatcherLoggingEnabled(value = true) { loggingEnabled = value; }
|
|
7
|
+
;
|
|
8
|
+
export function setReactPatcherPerformanceLoggingEnabled(value = true) { perfLoggingEnabled = value; }
|
|
9
|
+
;
|
|
10
|
+
function patchComponent(node, handler, steps, step, caches, logger, prop = 'type') {
|
|
11
|
+
loggingEnabled && logger.group('Patching node:', node);
|
|
12
|
+
switch (typeof node?.[prop]) {
|
|
13
|
+
case 'function':
|
|
14
|
+
const patch = afterPatch(node, prop, steps[step + 1] ? createStepHandler(handler, steps, step + 1, caches, logger) : handler);
|
|
15
|
+
loggingEnabled && logger.debug('Patched a function component', patch);
|
|
16
|
+
break;
|
|
17
|
+
case 'object':
|
|
18
|
+
if (node[prop]?.prototype?.render) {
|
|
19
|
+
wrapReactClass(node);
|
|
20
|
+
const patch = afterPatch(node[prop].prototype, 'render', steps[step + 1] ? createStepHandler(handler, steps, step + 1, caches, logger) : handler);
|
|
21
|
+
loggingEnabled && logger.debug('Patched class component', patch);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
loggingEnabled && logger.debug('Patching forwardref/memo');
|
|
25
|
+
wrapReactType(node, prop);
|
|
26
|
+
patchComponent(node[prop], handler, steps, step, caches, logger, node[prop]?.render ? 'render' : 'type');
|
|
27
|
+
}
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
logger.error('Unhandled component type', node);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
loggingEnabled && logger.groupEnd();
|
|
34
|
+
}
|
|
35
|
+
function handleStep(tree, handler, steps, step, caches, logger) {
|
|
36
|
+
const startTime = (loggingEnabled || perfLoggingEnabled) ? performance.now() : 0;
|
|
37
|
+
const stepHandler = steps[step];
|
|
38
|
+
const cache = caches[step] || (caches[step] = new Map());
|
|
39
|
+
loggingEnabled && logger.debug(`Patch step ${step} running`, { tree, stepHandler, step, caches });
|
|
40
|
+
const node = stepHandler(tree);
|
|
41
|
+
if (node && node.type) {
|
|
42
|
+
loggingEnabled && logger.debug('Found node', node);
|
|
43
|
+
}
|
|
44
|
+
else if (node) {
|
|
45
|
+
loggingEnabled && logger.error('Found node without type. Something is probably wrong.', node);
|
|
46
|
+
return tree;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
loggingEnabled && logger.warn('Found no node. Depending on your usecase, this might be fine.', node);
|
|
50
|
+
return tree;
|
|
51
|
+
}
|
|
52
|
+
let cachedType;
|
|
53
|
+
if (cachedType = cache.get(node.type)) {
|
|
54
|
+
loggingEnabled && logger.debug('Found cached patched component', node);
|
|
55
|
+
node.type = cachedType;
|
|
56
|
+
(loggingEnabled || perfLoggingEnabled) && logger.debug(`Patch step ${step} took ${performance.now() - startTime}ms with cache`);
|
|
57
|
+
return tree;
|
|
58
|
+
}
|
|
59
|
+
const originalType = node.type;
|
|
60
|
+
patchComponent(node, handler, steps, step, caches, logger);
|
|
61
|
+
cache.set(originalType, node.type);
|
|
62
|
+
(loggingEnabled || perfLoggingEnabled) && logger.debug(`Patch step ${step} took ${performance.now() - startTime}ms`);
|
|
63
|
+
return tree;
|
|
64
|
+
}
|
|
65
|
+
;
|
|
66
|
+
function createStepHandler(handler, steps, step, caches, logger) {
|
|
67
|
+
loggingEnabled && logger.debug(`Creating handler for step ${step}`);
|
|
68
|
+
return (_, tree) => handleStep(tree, handler, steps, step, caches, logger);
|
|
69
|
+
}
|
|
70
|
+
export function createReactTreePatcher(steps, handler, debugName = 'ReactPatch') {
|
|
71
|
+
const caches = [];
|
|
72
|
+
const logger = new Logger(`ReactTreePatcher -> ${debugName}`);
|
|
73
|
+
loggingEnabled && logger.debug('Init with options:', steps, debugName);
|
|
74
|
+
return createStepHandler(handler, steps, 0, caches, logger);
|
|
75
|
+
}
|
package/package.json
CHANGED
package/src/utils/index.ts
CHANGED
package/src/utils/patcher.ts
CHANGED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import Logger from '../logger';
|
|
2
|
+
import { GenericPatchHandler, afterPatch } from './patcher';
|
|
3
|
+
import { wrapReactClass, wrapReactType } from './react';
|
|
4
|
+
|
|
5
|
+
// TODO max size limit? could implement as a class extending map perhaps
|
|
6
|
+
type PatchedComponentCache = Map<any, any>;
|
|
7
|
+
export type GenericNodeStep = (node: any) => any;
|
|
8
|
+
// to patch a specific method of a class component other than render. TODO implement
|
|
9
|
+
// export type ClassNodeStep = [finder: GenericNodeStep, method: string];
|
|
10
|
+
// export type NodeStep = GenericNodeStep | ClassNodeStep;
|
|
11
|
+
export type NodeStep = GenericNodeStep;
|
|
12
|
+
export type ReactPatchHandler = GenericPatchHandler;
|
|
13
|
+
|
|
14
|
+
// These will get *very* spammy.
|
|
15
|
+
let loggingEnabled = false;
|
|
16
|
+
let perfLoggingEnabled = false;
|
|
17
|
+
|
|
18
|
+
export function setReactPatcherLoggingEnabled(value: boolean = true) { loggingEnabled = value };
|
|
19
|
+
export function setReactPatcherPerformanceLoggingEnabled(value: boolean = true) { perfLoggingEnabled = value };
|
|
20
|
+
|
|
21
|
+
function patchComponent(node: any, handler: GenericPatchHandler, steps: NodeStep[], step: number, caches: PatchedComponentCache[], logger: Logger, prop: string = 'type') {
|
|
22
|
+
loggingEnabled && logger.group('Patching node:', node);
|
|
23
|
+
// We need to take extra care to not mutate the original node.type
|
|
24
|
+
switch (typeof node?.[prop]) {
|
|
25
|
+
case 'function':
|
|
26
|
+
// Function component
|
|
27
|
+
const patch = afterPatch(node, prop, steps[step + 1] ? createStepHandler(handler, steps, step + 1, caches, logger) : handler);
|
|
28
|
+
loggingEnabled && logger.debug('Patched a function component', patch);
|
|
29
|
+
break;
|
|
30
|
+
case 'object':
|
|
31
|
+
if (node[prop]?.prototype?.render) {
|
|
32
|
+
// Class component
|
|
33
|
+
// TODO handle patching custom methods
|
|
34
|
+
wrapReactClass(node);
|
|
35
|
+
const patch = afterPatch(node[prop].prototype, 'render', steps[step + 1] ? createStepHandler(handler, steps, step + 1, caches, logger) : handler);
|
|
36
|
+
loggingEnabled && logger.debug('Patched class component', patch);
|
|
37
|
+
} else {
|
|
38
|
+
loggingEnabled && logger.debug('Patching forwardref/memo');
|
|
39
|
+
wrapReactType(node, prop);
|
|
40
|
+
// Step down the object
|
|
41
|
+
patchComponent(node[prop], handler, steps, step, caches, logger, node[prop]?.render ? 'render' : 'type');
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
default:
|
|
45
|
+
logger.error('Unhandled component type', node);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
loggingEnabled && logger.groupEnd();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleStep(tree: any, handler: GenericPatchHandler, steps: NodeStep[], step: number, caches: PatchedComponentCache[], logger: Logger): any {
|
|
52
|
+
const startTime = (loggingEnabled || perfLoggingEnabled) ? performance.now() : 0;
|
|
53
|
+
const stepHandler = steps[step];
|
|
54
|
+
const cache = caches[step] || (caches[step] = new Map());
|
|
55
|
+
loggingEnabled && logger.debug(`Patch step ${step} running`, { tree, stepHandler, step, caches });
|
|
56
|
+
|
|
57
|
+
const node = stepHandler(tree);
|
|
58
|
+
if (node && node.type) {
|
|
59
|
+
loggingEnabled && logger.debug('Found node', node);
|
|
60
|
+
} else if (node) {
|
|
61
|
+
loggingEnabled && logger.error('Found node without type. Something is probably wrong.', node);
|
|
62
|
+
return tree;
|
|
63
|
+
} else {
|
|
64
|
+
loggingEnabled && logger.warn('Found no node. Depending on your usecase, this might be fine.', node);
|
|
65
|
+
return tree;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let cachedType;
|
|
69
|
+
if (cachedType = cache.get(node.type)) {
|
|
70
|
+
loggingEnabled && logger.debug('Found cached patched component', node);
|
|
71
|
+
node.type = cachedType;
|
|
72
|
+
(loggingEnabled || perfLoggingEnabled) && logger.debug(`Patch step ${step} took ${performance.now() - startTime}ms with cache`);
|
|
73
|
+
return tree;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const originalType = node.type;
|
|
77
|
+
|
|
78
|
+
patchComponent(node, handler, steps, step, caches, logger);
|
|
79
|
+
|
|
80
|
+
cache.set(originalType, node.type);
|
|
81
|
+
|
|
82
|
+
(loggingEnabled || perfLoggingEnabled) && logger.debug(`Patch step ${step} took ${performance.now() - startTime}ms`);
|
|
83
|
+
return tree;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function createStepHandler(handler: GenericPatchHandler, steps: NodeStep[], step: number, caches: PatchedComponentCache[], logger: Logger) {
|
|
87
|
+
loggingEnabled && logger.debug(`Creating handler for step ${step}`);
|
|
88
|
+
return (_: any, tree: any) => handleStep(tree, handler, steps, step, caches, logger);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// TODO handle createReactTreePatcher inside handler and cache it (or warn)
|
|
92
|
+
export function createReactTreePatcher(steps: NodeStep[], handler: GenericPatchHandler, debugName: string = 'ReactPatch'): GenericPatchHandler {
|
|
93
|
+
const caches: PatchedComponentCache[] = [];
|
|
94
|
+
|
|
95
|
+
const logger = new Logger(`ReactTreePatcher -> ${debugName}`);
|
|
96
|
+
|
|
97
|
+
loggingEnabled && logger.debug('Init with options:', steps, debugName);
|
|
98
|
+
|
|
99
|
+
return createStepHandler(handler, steps, 0, caches, logger);
|
|
100
|
+
}
|