@reactpy/client 0.3.2 → 1.0.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.
- package/dist/client.d.ts +29 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +60 -0
- package/dist/client.js.map +1 -0
- package/dist/components.d.ts +3 -4
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +38 -37
- package/dist/components.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -0
- package/dist/logger.js.map +1 -1
- package/dist/mount.d.ts +2 -2
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +29 -4
- package/dist/mount.js.map +1 -1
- package/dist/types.d.ts +126 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/vdom.d.ts +8 -0
- package/dist/vdom.d.ts.map +1 -0
- package/dist/vdom.js +174 -0
- package/dist/vdom.js.map +1 -0
- package/dist/websocket.d.ts +6 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +57 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +23 -22
- package/src/client.ts +83 -0
- package/src/components.tsx +40 -46
- package/src/index.ts +7 -3
- package/src/logger.ts +1 -0
- package/src/mount.tsx +37 -5
- package/src/types.ts +152 -0
- package/src/vdom.tsx +256 -0
- package/src/websocket.ts +75 -0
- package/tsconfig.json +3 -3
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/messages.d.ts +0 -15
- package/dist/messages.d.ts.map +0 -1
- package/dist/messages.js +0 -2
- package/dist/messages.js.map +0 -1
- package/dist/reactpy-client.d.ts +0 -94
- package/dist/reactpy-client.d.ts.map +0 -1
- package/dist/reactpy-client.js +0 -128
- package/dist/reactpy-client.js.map +0 -1
- package/dist/reactpy-vdom.d.ts +0 -54
- package/dist/reactpy-vdom.d.ts.map +0 -1
- package/dist/reactpy-vdom.js +0 -141
- package/dist/reactpy-vdom.js.map +0 -1
- package/src/messages.ts +0 -17
- package/src/reactpy-client.ts +0 -264
- package/src/reactpy-vdom.tsx +0 -261
package/dist/vdom.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import eventToObject from "event-to-object";
|
|
2
|
+
import log from "./logger";
|
|
3
|
+
export async function loadImportSource(vdomImportSource, client) {
|
|
4
|
+
let module;
|
|
5
|
+
if (vdomImportSource.sourceType === "URL") {
|
|
6
|
+
module = await import(vdomImportSource.source);
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
module = await client.loadModule(vdomImportSource.source);
|
|
10
|
+
}
|
|
11
|
+
if (typeof module.bind !== "function") {
|
|
12
|
+
throw new Error(`${vdomImportSource.source} did not export a function 'bind'`);
|
|
13
|
+
}
|
|
14
|
+
return (node) => {
|
|
15
|
+
const binding = module.bind(node, {
|
|
16
|
+
sendMessage: client.sendMessage,
|
|
17
|
+
onMessage: client.onMessage,
|
|
18
|
+
});
|
|
19
|
+
if (!(typeof binding.create === "function" &&
|
|
20
|
+
typeof binding.render === "function" &&
|
|
21
|
+
typeof binding.unmount === "function")) {
|
|
22
|
+
log.error(`${vdomImportSource.source} returned an impropper binding`);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
render: (model) => binding.render(createImportSourceElement({
|
|
27
|
+
client,
|
|
28
|
+
module,
|
|
29
|
+
binding,
|
|
30
|
+
model,
|
|
31
|
+
currentImportSource: vdomImportSource,
|
|
32
|
+
})),
|
|
33
|
+
unmount: binding.unmount,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createImportSourceElement(props) {
|
|
38
|
+
let type;
|
|
39
|
+
if (props.model.importSource) {
|
|
40
|
+
if (!isImportSourceEqual(props.currentImportSource, props.model.importSource)) {
|
|
41
|
+
log.error("Parent element import source " +
|
|
42
|
+
stringifyImportSource(props.currentImportSource) +
|
|
43
|
+
" does not match child's import source " +
|
|
44
|
+
stringifyImportSource(props.model.importSource));
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
type = getComponentFromModule(props.module, props.model.tagName, props.model.importSource);
|
|
49
|
+
if (!type) {
|
|
50
|
+
// Error message logged within getComponentFromModule
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
type = props.model.tagName;
|
|
57
|
+
}
|
|
58
|
+
return props.binding.create(type, createAttributes(props.model, props.client), createChildren(props.model, (child) => createImportSourceElement({
|
|
59
|
+
...props,
|
|
60
|
+
model: child,
|
|
61
|
+
})));
|
|
62
|
+
}
|
|
63
|
+
function getComponentFromModule(module, componentName, importSource) {
|
|
64
|
+
/* Gets the component with the provided name from the provided module.
|
|
65
|
+
|
|
66
|
+
Built specifically to work on inifinitely deep nested components.
|
|
67
|
+
For example, component "My.Nested.Component" is accessed from
|
|
68
|
+
ModuleA like so: ModuleA["My"]["Nested"]["Component"].
|
|
69
|
+
*/
|
|
70
|
+
const componentParts = componentName.split(".");
|
|
71
|
+
let Component = null;
|
|
72
|
+
for (let i = 0; i < componentParts.length; i++) {
|
|
73
|
+
const iterAttr = componentParts[i];
|
|
74
|
+
Component = i == 0 ? module[iterAttr] : Component[iterAttr];
|
|
75
|
+
if (!Component) {
|
|
76
|
+
if (i == 0) {
|
|
77
|
+
log.error("Module from source " +
|
|
78
|
+
stringifyImportSource(importSource) +
|
|
79
|
+
` does not export ${iterAttr}`);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.error(`Component ${componentParts.slice(0, i).join(".")} from source ` +
|
|
83
|
+
stringifyImportSource(importSource) +
|
|
84
|
+
` does not have subcomponent ${iterAttr}`);
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return Component;
|
|
90
|
+
}
|
|
91
|
+
function isImportSourceEqual(source1, source2) {
|
|
92
|
+
return (source1.source === source2.source &&
|
|
93
|
+
source1.sourceType === source2.sourceType);
|
|
94
|
+
}
|
|
95
|
+
function stringifyImportSource(importSource) {
|
|
96
|
+
return JSON.stringify({
|
|
97
|
+
source: importSource.source,
|
|
98
|
+
sourceType: importSource.sourceType,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
export function createChildren(model, createChild) {
|
|
102
|
+
if (!model.children) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return model.children.map((child) => {
|
|
107
|
+
switch (typeof child) {
|
|
108
|
+
case "object":
|
|
109
|
+
return createChild(child);
|
|
110
|
+
case "string":
|
|
111
|
+
return child;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function createAttributes(model, client) {
|
|
117
|
+
return Object.fromEntries(Object.entries({
|
|
118
|
+
// Normal HTML attributes
|
|
119
|
+
...model.attributes,
|
|
120
|
+
// Construct event handlers
|
|
121
|
+
...Object.fromEntries(Object.entries(model.eventHandlers || {}).map(([name, handler]) => createEventHandler(client, name, handler))),
|
|
122
|
+
...Object.fromEntries(Object.entries(model.inlineJavaScript || {}).map(([name, inlineJavaScript]) => createInlineJavaScript(name, inlineJavaScript))),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
function createEventHandler(client, name, { target, preventDefault, stopPropagation }) {
|
|
126
|
+
const eventHandler = function (...args) {
|
|
127
|
+
const data = Array.from(args).map((value) => {
|
|
128
|
+
const event = value;
|
|
129
|
+
if (preventDefault) {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
}
|
|
132
|
+
if (stopPropagation) {
|
|
133
|
+
event.stopPropagation();
|
|
134
|
+
}
|
|
135
|
+
// Convert JavaScript objects to plain JSON, if needed
|
|
136
|
+
if (typeof event === "object") {
|
|
137
|
+
return eventToObject(event);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
return event;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
client.sendMessage({ type: "layout-event", data, target });
|
|
144
|
+
};
|
|
145
|
+
eventHandler.isHandler = true;
|
|
146
|
+
return [name, eventHandler];
|
|
147
|
+
}
|
|
148
|
+
function createInlineJavaScript(name, inlineJavaScript) {
|
|
149
|
+
/* Function that will execute the string-like InlineJavaScript
|
|
150
|
+
via eval in the most appropriate way */
|
|
151
|
+
const wrappedExecutable = function (...args) {
|
|
152
|
+
function handleExecution(...args) {
|
|
153
|
+
const evalResult = eval(inlineJavaScript);
|
|
154
|
+
if (typeof evalResult == "function") {
|
|
155
|
+
return evalResult(...args);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (args.length > 0 && args[0] instanceof Event) {
|
|
159
|
+
/* If being triggered by an event, set the event's current
|
|
160
|
+
target to "this". This ensures that inline
|
|
161
|
+
javascript statements such as the following work:
|
|
162
|
+
html.button({"onclick": 'this.value = "Clicked!"'}, "Click Me")*/
|
|
163
|
+
return handleExecution.call(args[0].currentTarget, ...args);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
/* If not being triggered by an event, do not set "this" and
|
|
167
|
+
just call normally */
|
|
168
|
+
return handleExecution(...args);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
wrappedExecutable.isHandler = false;
|
|
172
|
+
return [name, wrappedExecutable];
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=vdom.js.map
|
package/dist/vdom.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vdom.js","sourceRoot":"","sources":["../src/vdom.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CreateReconnectingWebSocketProps } from "./types";
|
|
2
|
+
export declare function createReconnectingWebSocket(props: CreateReconnectingWebSocketProps): {
|
|
3
|
+
current?: WebSocket;
|
|
4
|
+
};
|
|
5
|
+
export declare function nextInterval(currentInterval: number, backoffMultiplier: number, maxInterval: number): number;
|
|
6
|
+
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,SAAS,CAAC;AAGhE,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,gCAAgC;cAOb,SAAS;EAkDpC;AAED,wBAAgB,YAAY,CAC1B,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,EACzB,WAAW,EAAE,MAAM,GAClB,MAAM,CAOR"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import log from "./logger";
|
|
2
|
+
export function createReconnectingWebSocket(props) {
|
|
3
|
+
const { interval, maxInterval, maxRetries, backoffMultiplier } = props;
|
|
4
|
+
let retries = 0;
|
|
5
|
+
let currentInterval = interval;
|
|
6
|
+
let everConnected = false;
|
|
7
|
+
const closed = false;
|
|
8
|
+
const socket = {};
|
|
9
|
+
const connect = () => {
|
|
10
|
+
if (closed) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
socket.current = new WebSocket(props.url);
|
|
14
|
+
socket.current.onopen = () => {
|
|
15
|
+
everConnected = true;
|
|
16
|
+
log.info("Connected!");
|
|
17
|
+
currentInterval = interval;
|
|
18
|
+
retries = 0;
|
|
19
|
+
if (props.onOpen) {
|
|
20
|
+
props.onOpen();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
socket.current.onmessage = (event) => {
|
|
24
|
+
if (props.onMessage) {
|
|
25
|
+
props.onMessage(event);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
socket.current.onclose = () => {
|
|
29
|
+
if (props.onClose) {
|
|
30
|
+
props.onClose();
|
|
31
|
+
}
|
|
32
|
+
if (!everConnected) {
|
|
33
|
+
log.info("Failed to connect!");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
log.info("Disconnected!");
|
|
37
|
+
if (retries >= maxRetries) {
|
|
38
|
+
log.info("Connection max retries exhausted!");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
log.info(`Reconnecting in ${(currentInterval / 1000).toPrecision(4)} seconds...`);
|
|
42
|
+
setTimeout(connect, currentInterval);
|
|
43
|
+
currentInterval = nextInterval(currentInterval, backoffMultiplier, maxInterval);
|
|
44
|
+
retries++;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
props.readyPromise.then(() => log.info("Starting client...")).then(connect);
|
|
48
|
+
return socket;
|
|
49
|
+
}
|
|
50
|
+
export function nextInterval(currentInterval, backoffMultiplier, maxInterval) {
|
|
51
|
+
return Math.min(
|
|
52
|
+
// increase interval by backoff multiplier
|
|
53
|
+
currentInterval * backoffMultiplier,
|
|
54
|
+
// don't exceed max interval
|
|
55
|
+
maxInterval);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=websocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AACA,OAAO,GAAG,MAAM,UAAU,CAAC;AAE3B,MAAM,UAAU,2BAA2B,CACzC,KAAuC;IAEvC,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,iBAAiB,EAAE,GAAG,KAAK,CAAC;IACvE,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,eAAe,GAAG,QAAQ,CAAC;IAC/B,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,MAAM,MAAM,GAAG,KAAK,CAAC;IACrB,MAAM,MAAM,GAA4B,EAAE,CAAC;IAE3C,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,IAAI,MAAM,EAAE,CAAC;YACX,OAAO;QACT,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE;YAC3B,aAAa,GAAG,IAAI,CAAC;YACrB,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACvB,eAAe,GAAG,QAAQ,CAAC;YAC3B,OAAO,GAAG,CAAC,CAAC;YACZ,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjB,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,CAAC;QACH,CAAC,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,KAAK,EAAE,EAAE;YACnC,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBACpB,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACH,CAAC,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE;YAC5B,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBAClB,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,CAAC;YACD,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,GAAG,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC1B,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;gBAC1B,GAAG,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;YACD,GAAG,CAAC,IAAI,CACN,mBAAmB,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CACxE,CAAC;YACF,UAAU,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;YACrC,eAAe,GAAG,YAAY,CAC5B,eAAe,EACf,iBAAiB,EACjB,WAAW,CACZ,CAAC;YACF,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAE5E,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,eAAuB,EACvB,iBAAyB,EACzB,WAAmB;IAEnB,OAAO,IAAI,CAAC,GAAG;IACb,0CAA0C;IAC1C,eAAe,GAAG,iBAAiB;IACnC,4BAA4B;IAC5B,WAAW,CACZ,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
"author": "
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"name": "@reactpy/client",
|
|
8
|
-
"type": "module",
|
|
9
|
-
"version": "0.3.2",
|
|
2
|
+
"author": "Mark Bakhit",
|
|
3
|
+
"contributors": [
|
|
4
|
+
"Ryan Morshead"
|
|
5
|
+
],
|
|
10
6
|
"dependencies": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
7
|
+
"json-pointer": "^0.6.2",
|
|
8
|
+
"preact": "^10.27.2",
|
|
9
|
+
"event-to-object": "file:./packages/event-to-object"
|
|
13
10
|
},
|
|
11
|
+
"description": "A client for ReactPy implemented in React",
|
|
14
12
|
"devDependencies": {
|
|
15
|
-
"@types/json-pointer": "^1.0.
|
|
16
|
-
"
|
|
17
|
-
"@types/react-dom": "^17.0",
|
|
18
|
-
"typescript": "^4.9.5"
|
|
19
|
-
},
|
|
20
|
-
"peerDependencies": {
|
|
21
|
-
"react": ">=16 <18",
|
|
22
|
-
"react-dom": ">=16 <18"
|
|
13
|
+
"@types/json-pointer": "^1.0.34",
|
|
14
|
+
"typescript": "^5.9.3"
|
|
23
15
|
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react",
|
|
18
|
+
"reactive",
|
|
19
|
+
"python",
|
|
20
|
+
"reactpy"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"main": "dist/index.js",
|
|
24
|
+
"name": "@reactpy/client",
|
|
24
25
|
"repository": {
|
|
25
26
|
"type": "git",
|
|
26
27
|
"url": "https://github.com/reactive-python/reactpy"
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
|
29
30
|
"build": "tsc -b",
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
"checkTypes": "tsc --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"type": "module",
|
|
34
|
+
"version": "1.0.1"
|
|
34
35
|
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import logger from "./logger";
|
|
2
|
+
import type {
|
|
3
|
+
ReactPyClientInterface,
|
|
4
|
+
ReactPyModule,
|
|
5
|
+
GenericReactPyClientProps,
|
|
6
|
+
ReactPyUrls,
|
|
7
|
+
} from "./types";
|
|
8
|
+
import { createReconnectingWebSocket } from "./websocket";
|
|
9
|
+
|
|
10
|
+
export abstract class BaseReactPyClient implements ReactPyClientInterface {
|
|
11
|
+
private readonly handlers: { [key: string]: ((message: any) => void)[] } = {};
|
|
12
|
+
protected readonly ready: Promise<void>;
|
|
13
|
+
private resolveReady: (value: undefined) => void;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
this.resolveReady = () => {};
|
|
17
|
+
this.ready = new Promise((resolve) => (this.resolveReady = resolve));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onMessage(type: string, handler: (message: any) => void): () => void {
|
|
21
|
+
(this.handlers[type] || (this.handlers[type] = [])).push(handler);
|
|
22
|
+
this.resolveReady(undefined);
|
|
23
|
+
return () => {
|
|
24
|
+
this.handlers[type] = this.handlers[type].filter((h) => h !== handler);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
abstract sendMessage(message: any): void;
|
|
29
|
+
abstract loadModule(moduleName: string): Promise<ReactPyModule>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Handle an incoming message.
|
|
33
|
+
*
|
|
34
|
+
* This should be called by subclasses when a message is received.
|
|
35
|
+
*
|
|
36
|
+
* @param message The message to handle. The message must have a `type` property.
|
|
37
|
+
*/
|
|
38
|
+
protected handleIncoming(message: any): void {
|
|
39
|
+
if (!message.type) {
|
|
40
|
+
logger.warn("Received message without type", message);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const messageHandlers: ((m: any) => void)[] | undefined =
|
|
45
|
+
this.handlers[message.type];
|
|
46
|
+
if (!messageHandlers) {
|
|
47
|
+
logger.warn("Received message without handler", message);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
messageHandlers.forEach((h) => h(message));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class ReactPyClient
|
|
56
|
+
extends BaseReactPyClient
|
|
57
|
+
implements ReactPyClientInterface
|
|
58
|
+
{
|
|
59
|
+
urls: ReactPyUrls;
|
|
60
|
+
socket: { current?: WebSocket };
|
|
61
|
+
mountElement: HTMLElement;
|
|
62
|
+
|
|
63
|
+
constructor(props: GenericReactPyClientProps) {
|
|
64
|
+
super();
|
|
65
|
+
|
|
66
|
+
this.urls = props.urls;
|
|
67
|
+
this.mountElement = props.mountElement;
|
|
68
|
+
this.socket = createReconnectingWebSocket({
|
|
69
|
+
url: this.urls.componentUrl,
|
|
70
|
+
readyPromise: this.ready,
|
|
71
|
+
...props.reconnectOptions,
|
|
72
|
+
onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
sendMessage(message: any): void {
|
|
77
|
+
this.socket.current?.send(JSON.stringify(message));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
loadModule(moduleName: string): Promise<ReactPyModule> {
|
|
81
|
+
return import(`${this.urls.jsModulesPath}${moduleName}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/components.tsx
CHANGED
|
@@ -1,29 +1,18 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
createElement,
|
|
3
|
-
createContext,
|
|
4
|
-
useState,
|
|
5
|
-
useRef,
|
|
6
|
-
useContext,
|
|
7
|
-
useEffect,
|
|
8
|
-
Fragment,
|
|
9
|
-
MutableRefObject,
|
|
10
|
-
ChangeEvent,
|
|
11
|
-
} from "react";
|
|
12
|
-
// @ts-ignore
|
|
13
1
|
import { set as setJsonPointer } from "json-pointer";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
createAttributes,
|
|
19
|
-
loadImportSource,
|
|
2
|
+
import type { ChangeEvent, MutableRefObject } from "preact/compat";
|
|
3
|
+
import { createContext, createElement, Fragment, type JSX } from "preact";
|
|
4
|
+
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
|
5
|
+
import type {
|
|
20
6
|
ImportSourceBinding,
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
ReactPyComponent,
|
|
8
|
+
ReactPyVdom,
|
|
9
|
+
ReactPyClientInterface,
|
|
10
|
+
} from "./types";
|
|
11
|
+
import { createAttributes, createChildren, loadImportSource } from "./vdom";
|
|
23
12
|
|
|
24
|
-
const ClientContext = createContext<
|
|
13
|
+
const ClientContext = createContext<ReactPyClientInterface>(null as any);
|
|
25
14
|
|
|
26
|
-
export function Layout(props: { client:
|
|
15
|
+
export function Layout(props: { client: ReactPyClientInterface }): JSX.Element {
|
|
27
16
|
const currentModel: ReactPyVdom = useState({ tagName: "" })[0];
|
|
28
17
|
const forceUpdate = useForceUpdate();
|
|
29
18
|
|
|
@@ -70,7 +59,7 @@ export function Element({ model }: { model: ReactPyVdom }): JSX.Element | null {
|
|
|
70
59
|
}
|
|
71
60
|
|
|
72
61
|
function StandardElement({ model }: { model: ReactPyVdom }) {
|
|
73
|
-
const client =
|
|
62
|
+
const client = useContext(ClientContext);
|
|
74
63
|
// Use createElement here to avoid warning about variable numbers of children not
|
|
75
64
|
// having keys. Warning about this must now be the responsibility of the client
|
|
76
65
|
// providing the models instead of the client rendering them.
|
|
@@ -86,16 +75,18 @@ function StandardElement({ model }: { model: ReactPyVdom }) {
|
|
|
86
75
|
function UserInputElement({ model }: { model: ReactPyVdom }): JSX.Element {
|
|
87
76
|
const client = useContext(ClientContext);
|
|
88
77
|
const props = createAttributes(model, client);
|
|
89
|
-
const [value, setValue] =
|
|
78
|
+
const [value, setValue] = useState(props.value);
|
|
90
79
|
|
|
91
80
|
// honor changes to value from the client via props
|
|
92
|
-
|
|
81
|
+
useEffect(() => setValue(props.value), [props.value]);
|
|
93
82
|
|
|
94
83
|
const givenOnChange = props.onChange;
|
|
95
84
|
if (typeof givenOnChange === "function") {
|
|
96
85
|
props.onChange = (event: ChangeEvent<any>) => {
|
|
97
86
|
// immediately update the value to give the user feedback
|
|
98
|
-
|
|
87
|
+
if (event.target) {
|
|
88
|
+
setValue((event.target as HTMLInputElement).value);
|
|
89
|
+
}
|
|
99
90
|
// allow the client to respond (and possibly change the value)
|
|
100
91
|
givenOnChange(event);
|
|
101
92
|
};
|
|
@@ -117,31 +108,34 @@ function UserInputElement({ model }: { model: ReactPyVdom }): JSX.Element {
|
|
|
117
108
|
function ScriptElement({ model }: { model: ReactPyVdom }) {
|
|
118
109
|
const ref = useRef<HTMLDivElement | null>(null);
|
|
119
110
|
|
|
120
|
-
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
// Don't run if the parent element is missing
|
|
121
113
|
if (!ref.current) {
|
|
122
114
|
return;
|
|
123
115
|
}
|
|
116
|
+
|
|
117
|
+
// Create the script element
|
|
118
|
+
const scriptElement: HTMLScriptElement = document.createElement("script");
|
|
119
|
+
for (const [k, v] of Object.entries(model.attributes || {})) {
|
|
120
|
+
scriptElement.setAttribute(k, v);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Add the script content as text
|
|
124
124
|
const scriptContent = model?.children?.filter(
|
|
125
125
|
(value): value is string => typeof value == "string",
|
|
126
126
|
)[0];
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (model.attributes) {
|
|
130
|
-
scriptElement = document.createElement("script");
|
|
131
|
-
for (const [k, v] of Object.entries(model.attributes)) {
|
|
132
|
-
scriptElement.setAttribute(k, v);
|
|
133
|
-
}
|
|
134
|
-
if (scriptContent) {
|
|
135
|
-
scriptElement.appendChild(document.createTextNode(scriptContent));
|
|
136
|
-
}
|
|
137
|
-
ref.current.appendChild(scriptElement);
|
|
138
|
-
} else if (scriptContent) {
|
|
139
|
-
const scriptResult = eval(scriptContent);
|
|
140
|
-
if (typeof scriptResult == "function") {
|
|
141
|
-
return scriptResult();
|
|
142
|
-
}
|
|
127
|
+
if (scriptContent) {
|
|
128
|
+
scriptElement.appendChild(document.createTextNode(scriptContent));
|
|
143
129
|
}
|
|
144
|
-
|
|
130
|
+
|
|
131
|
+
// Append the script element to the parent element
|
|
132
|
+
ref.current.appendChild(scriptElement);
|
|
133
|
+
|
|
134
|
+
// Remove the script element when the component is unmounted
|
|
135
|
+
return () => {
|
|
136
|
+
ref.current?.removeChild(scriptElement);
|
|
137
|
+
};
|
|
138
|
+
}, [model.key]);
|
|
145
139
|
|
|
146
140
|
return <div ref={ref} />;
|
|
147
141
|
}
|
|
@@ -179,10 +173,10 @@ function useImportSource(model: ReactPyVdom): MutableRefObject<any> {
|
|
|
179
173
|
const vdomImportSource = model.importSource;
|
|
180
174
|
const vdomImportSourceJsonString = JSON.stringify(vdomImportSource);
|
|
181
175
|
const mountPoint = useRef<HTMLElement>(null);
|
|
182
|
-
const client =
|
|
176
|
+
const client = useContext(ClientContext);
|
|
183
177
|
const [binding, setBinding] = useState<ImportSourceBinding | null>(null);
|
|
184
178
|
|
|
185
|
-
|
|
179
|
+
useEffect(() => {
|
|
186
180
|
let unmounted = false;
|
|
187
181
|
|
|
188
182
|
if (vdomImportSource) {
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
export * from "./client";
|
|
1
2
|
export * from "./components";
|
|
2
|
-
export * from "./messages";
|
|
3
3
|
export * from "./mount";
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
4
|
+
export * from "./types";
|
|
5
|
+
export * from "./vdom";
|
|
6
|
+
export * from "./websocket";
|
|
7
|
+
export { default as React } from "preact/compat";
|
|
8
|
+
export { default as ReactDOM } from "preact/compat";
|
|
9
|
+
export * as preact from "preact";
|
package/src/logger.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
log: (...args: any[]): void => console.log("[ReactPy]", ...args),
|
|
3
|
+
info: (...args: any[]): void => console.info("[ReactPy]", ...args),
|
|
3
4
|
warn: (...args: any[]): void => console.warn("[ReactPy]", ...args),
|
|
4
5
|
error: (...args: any[]): void => console.error("[ReactPy]", ...args),
|
|
5
6
|
};
|
package/src/mount.tsx
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { render } from "preact";
|
|
2
|
+
import { ReactPyClient } from "./client";
|
|
3
3
|
import { Layout } from "./components";
|
|
4
|
-
import {
|
|
4
|
+
import type { MountProps } from "./types";
|
|
5
5
|
|
|
6
|
-
export function
|
|
7
|
-
|
|
6
|
+
export function mountReactPy(props: MountProps) {
|
|
7
|
+
// WebSocket route for component rendering
|
|
8
|
+
const wsProtocol = `ws${window.location.protocol === "https:" ? "s" : ""}:`;
|
|
9
|
+
const wsOrigin = `${wsProtocol}//${window.location.host}`;
|
|
10
|
+
const componentUrl = new URL(
|
|
11
|
+
`${wsOrigin}${props.pathPrefix}${props.componentPath || ""}`,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// Embed the initial HTTP path into the WebSocket URL
|
|
15
|
+
componentUrl.searchParams.append("http_pathname", window.location.pathname);
|
|
16
|
+
if (window.location.search) {
|
|
17
|
+
componentUrl.searchParams.append(
|
|
18
|
+
"http_query_string",
|
|
19
|
+
window.location.search,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Configure a new ReactPy client
|
|
24
|
+
const client = new ReactPyClient({
|
|
25
|
+
urls: {
|
|
26
|
+
componentUrl: componentUrl,
|
|
27
|
+
jsModulesPath: `${window.location.origin}${props.pathPrefix}modules/`,
|
|
28
|
+
},
|
|
29
|
+
reconnectOptions: {
|
|
30
|
+
interval: props.reconnectInterval || 750,
|
|
31
|
+
maxInterval: props.reconnectMaxInterval || 60000,
|
|
32
|
+
maxRetries: props.reconnectMaxRetries || 150,
|
|
33
|
+
backoffMultiplier: props.reconnectBackoffMultiplier || 1.25,
|
|
34
|
+
},
|
|
35
|
+
mountElement: props.mountElement,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Start rendering the component
|
|
39
|
+
render(<Layout client={client} />, props.mountElement);
|
|
8
40
|
}
|