@reactpy/client 0.1.0 → 0.2.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/package.json +22 -20
- package/src/components.tsx +231 -0
- package/src/index.ts +5 -0
- package/src/logger.ts +5 -0
- package/src/messages.ts +32 -0
- package/src/mount.tsx +8 -0
- package/src/reactpy-client.ts +274 -0
- package/src/reactpy-vdom.tsx +261 -0
- package/tsconfig.json +14 -0
- package/src/components.js +0 -220
- package/src/contexts.js +0 -6
- package/src/element-utils.js +0 -82
- package/src/event-to-object.js +0 -240
- package/src/import-source.js +0 -134
- package/src/index.js +0 -4
- package/src/mount.js +0 -105
- package/src/server.js +0 -46
package/src/event-to-object.js
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
export function serializeEvent(event) {
|
|
2
|
-
const data = {};
|
|
3
|
-
|
|
4
|
-
if (event.type in eventTransforms) {
|
|
5
|
-
Object.assign(data, eventTransforms[event.type](event));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
data.target = serializeDomElement(event.target);
|
|
9
|
-
data.currentTarget =
|
|
10
|
-
event.target === event.currentTarget
|
|
11
|
-
? data.target
|
|
12
|
-
: serializeDomElement(event.currentTarget);
|
|
13
|
-
data.relatedTarget = serializeDomElement(event.relatedTarget);
|
|
14
|
-
|
|
15
|
-
return data;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function serializeDomElement(element) {
|
|
19
|
-
let elementData = null;
|
|
20
|
-
if (element) {
|
|
21
|
-
elementData = defaultElementTransform(element);
|
|
22
|
-
if (element.tagName in elementTransforms) {
|
|
23
|
-
elementTransforms[element.tagName].forEach((trans) =>
|
|
24
|
-
Object.assign(elementData, trans(element))
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return elementData;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const elementTransformCategories = {
|
|
32
|
-
hasValue: (element) => ({
|
|
33
|
-
value: element.value,
|
|
34
|
-
}),
|
|
35
|
-
hasCurrentTime: (element) => ({
|
|
36
|
-
currentTime: element.currentTime,
|
|
37
|
-
}),
|
|
38
|
-
hasFiles: (element) => {
|
|
39
|
-
if (element?.type === "file") {
|
|
40
|
-
return {
|
|
41
|
-
files: Array.from(element.files).map((file) => ({
|
|
42
|
-
lastModified: file.lastModified,
|
|
43
|
-
name: file.name,
|
|
44
|
-
size: file.size,
|
|
45
|
-
type: file.type,
|
|
46
|
-
})),
|
|
47
|
-
};
|
|
48
|
-
} else {
|
|
49
|
-
return {};
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
hasElements: (element) => {
|
|
53
|
-
const { elements } = element;
|
|
54
|
-
const indices = [...Array(elements.length).keys()];
|
|
55
|
-
return {
|
|
56
|
-
elements: indices.map((index) => serializeDomElement(elements[index])),
|
|
57
|
-
};
|
|
58
|
-
},
|
|
59
|
-
hasName: (element) => {
|
|
60
|
-
const { name } = element;
|
|
61
|
-
// In some edge cases, "name" may not be a string. For example, in the case of
|
|
62
|
-
// `<form><input name="name"></form>`, the "name" attribute of the `<form>` will
|
|
63
|
-
// be the `<input>` element.
|
|
64
|
-
return typeof name === "string" ? { name } : {};
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
function defaultElementTransform(element) {
|
|
69
|
-
return { boundingClientRect: element.getBoundingClientRect() };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const elementTagCategories = {
|
|
73
|
-
hasValue: [
|
|
74
|
-
"BUTTON",
|
|
75
|
-
"INPUT",
|
|
76
|
-
"OPTION",
|
|
77
|
-
"LI",
|
|
78
|
-
"METER",
|
|
79
|
-
"PROGRESS",
|
|
80
|
-
"PARAM",
|
|
81
|
-
"SELECT",
|
|
82
|
-
"TEXTAREA",
|
|
83
|
-
],
|
|
84
|
-
hasCurrentTime: ["AUDIO", "VIDEO"],
|
|
85
|
-
hasFiles: ["INPUT"],
|
|
86
|
-
hasElements: ["FORM"],
|
|
87
|
-
hasName: [
|
|
88
|
-
"BUTTON",
|
|
89
|
-
"FORM",
|
|
90
|
-
"FIELDSET",
|
|
91
|
-
"IFRAME",
|
|
92
|
-
"INPUT",
|
|
93
|
-
"KEYGEN",
|
|
94
|
-
"OBJECT",
|
|
95
|
-
"OUTPUT",
|
|
96
|
-
"SELECT",
|
|
97
|
-
"TEXTAREA",
|
|
98
|
-
"MAP",
|
|
99
|
-
"META",
|
|
100
|
-
"PARAM",
|
|
101
|
-
],
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const elementTransforms = {};
|
|
105
|
-
|
|
106
|
-
Object.keys(elementTagCategories).forEach((category) => {
|
|
107
|
-
elementTagCategories[category].forEach((type) => {
|
|
108
|
-
const transforms =
|
|
109
|
-
elementTransforms[type] || (elementTransforms[type] = []);
|
|
110
|
-
transforms.push(elementTransformCategories[category]);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
function EventTransformCategories() {
|
|
115
|
-
this.clipboard = (event) => ({
|
|
116
|
-
clipboardData: event.clipboardData,
|
|
117
|
-
});
|
|
118
|
-
this.composition = (event) => ({
|
|
119
|
-
data: event.data,
|
|
120
|
-
});
|
|
121
|
-
this.keyboard = (event) => ({
|
|
122
|
-
altKey: event.altKey,
|
|
123
|
-
charCode: event.charCode,
|
|
124
|
-
ctrlKey: event.ctrlKey,
|
|
125
|
-
key: event.key,
|
|
126
|
-
keyCode: event.keyCode,
|
|
127
|
-
locale: event.locale,
|
|
128
|
-
location: event.location,
|
|
129
|
-
metaKey: event.metaKey,
|
|
130
|
-
repeat: event.repeat,
|
|
131
|
-
shiftKey: event.shiftKey,
|
|
132
|
-
which: event.which,
|
|
133
|
-
});
|
|
134
|
-
this.mouse = (event) => ({
|
|
135
|
-
altKey: event.altKey,
|
|
136
|
-
button: event.button,
|
|
137
|
-
buttons: event.buttons,
|
|
138
|
-
clientX: event.clientX,
|
|
139
|
-
clientY: event.clientY,
|
|
140
|
-
ctrlKey: event.ctrlKey,
|
|
141
|
-
metaKey: event.metaKey,
|
|
142
|
-
pageX: event.pageX,
|
|
143
|
-
pageY: event.pageY,
|
|
144
|
-
screenX: event.screenX,
|
|
145
|
-
screenY: event.screenY,
|
|
146
|
-
shiftKey: event.shiftKey,
|
|
147
|
-
});
|
|
148
|
-
this.pointer = (event) => ({
|
|
149
|
-
...this.mouse(event),
|
|
150
|
-
pointerId: event.pointerId,
|
|
151
|
-
width: event.width,
|
|
152
|
-
height: event.height,
|
|
153
|
-
pressure: event.pressure,
|
|
154
|
-
tiltX: event.tiltX,
|
|
155
|
-
tiltY: event.tiltY,
|
|
156
|
-
pointerType: event.pointerType,
|
|
157
|
-
isPrimary: event.isPrimary,
|
|
158
|
-
});
|
|
159
|
-
this.selection = () => {
|
|
160
|
-
return { selectedText: window.getSelection().toString() };
|
|
161
|
-
};
|
|
162
|
-
this.touch = (event) => ({
|
|
163
|
-
altKey: event.altKey,
|
|
164
|
-
ctrlKey: event.ctrlKey,
|
|
165
|
-
metaKey: event.metaKey,
|
|
166
|
-
shiftKey: event.shiftKey,
|
|
167
|
-
});
|
|
168
|
-
this.ui = (event) => ({
|
|
169
|
-
detail: event.detail,
|
|
170
|
-
});
|
|
171
|
-
this.wheel = (event) => ({
|
|
172
|
-
deltaMode: event.deltaMode,
|
|
173
|
-
deltaX: event.deltaX,
|
|
174
|
-
deltaY: event.deltaY,
|
|
175
|
-
deltaZ: event.deltaZ,
|
|
176
|
-
});
|
|
177
|
-
this.animation = (event) => ({
|
|
178
|
-
animationName: event.animationName,
|
|
179
|
-
pseudoElement: event.pseudoElement,
|
|
180
|
-
elapsedTime: event.elapsedTime,
|
|
181
|
-
});
|
|
182
|
-
this.transition = (event) => ({
|
|
183
|
-
propertyName: event.propertyName,
|
|
184
|
-
pseudoElement: event.pseudoElement,
|
|
185
|
-
elapsedTime: event.elapsedTime,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const eventTypeCategories = {
|
|
190
|
-
clipboard: ["copy", "cut", "paste"],
|
|
191
|
-
composition: ["compositionend", "compositionstart", "compositionupdate"],
|
|
192
|
-
keyboard: ["keydown", "keypress", "keyup"],
|
|
193
|
-
mouse: [
|
|
194
|
-
"click",
|
|
195
|
-
"contextmenu",
|
|
196
|
-
"doubleclick",
|
|
197
|
-
"drag",
|
|
198
|
-
"dragend",
|
|
199
|
-
"dragenter",
|
|
200
|
-
"dragexit",
|
|
201
|
-
"dragleave",
|
|
202
|
-
"dragover",
|
|
203
|
-
"dragstart",
|
|
204
|
-
"drop",
|
|
205
|
-
"mousedown",
|
|
206
|
-
"mouseenter",
|
|
207
|
-
"mouseleave",
|
|
208
|
-
"mousemove",
|
|
209
|
-
"mouseout",
|
|
210
|
-
"mouseover",
|
|
211
|
-
"mouseup",
|
|
212
|
-
],
|
|
213
|
-
pointer: [
|
|
214
|
-
"pointerdown",
|
|
215
|
-
"pointermove",
|
|
216
|
-
"pointerup",
|
|
217
|
-
"pointercancel",
|
|
218
|
-
"gotpointercapture",
|
|
219
|
-
"lostpointercapture",
|
|
220
|
-
"pointerenter",
|
|
221
|
-
"pointerleave",
|
|
222
|
-
"pointerover",
|
|
223
|
-
"pointerout",
|
|
224
|
-
],
|
|
225
|
-
selection: ["select"],
|
|
226
|
-
touch: ["touchcancel", "touchend", "touchmove", "touchstart"],
|
|
227
|
-
ui: ["scroll"],
|
|
228
|
-
wheel: ["wheel"],
|
|
229
|
-
animation: ["animationstart", "animationend", "animationiteration"],
|
|
230
|
-
transition: ["transitionend"],
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const eventTransforms = {};
|
|
234
|
-
|
|
235
|
-
const eventTransformCategories = new EventTransformCategories();
|
|
236
|
-
Object.keys(eventTypeCategories).forEach((category) => {
|
|
237
|
-
eventTypeCategories[category].forEach((type) => {
|
|
238
|
-
eventTransforms[type] = eventTransformCategories[category];
|
|
239
|
-
});
|
|
240
|
-
});
|
package/src/import-source.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
import { LayoutContext } from "./contexts.js";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
createElementAttributes,
|
|
7
|
-
createElementChildren,
|
|
8
|
-
} from "./element-utils.js";
|
|
9
|
-
|
|
10
|
-
export function useImportSource(modelImportSource) {
|
|
11
|
-
const layoutContext = React.useContext(LayoutContext);
|
|
12
|
-
const [importSource, setImportSource] = React.useState(null);
|
|
13
|
-
|
|
14
|
-
React.useEffect(() => {
|
|
15
|
-
let unmounted = false;
|
|
16
|
-
|
|
17
|
-
loadModelImportSource(layoutContext, modelImportSource).then((src) => {
|
|
18
|
-
if (!unmounted) {
|
|
19
|
-
setImportSource(src);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
return () => {
|
|
24
|
-
unmounted = true;
|
|
25
|
-
};
|
|
26
|
-
}, [layoutContext, modelImportSource, setImportSource]);
|
|
27
|
-
|
|
28
|
-
return importSource;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function loadModelImportSource(layoutContext, importSource) {
|
|
32
|
-
return layoutContext
|
|
33
|
-
.loadImportSource(importSource.source, importSource.sourceType)
|
|
34
|
-
.then((module) => {
|
|
35
|
-
if (typeof module.bind === "function") {
|
|
36
|
-
return {
|
|
37
|
-
data: importSource,
|
|
38
|
-
bind: (node) => {
|
|
39
|
-
const shortImportSource = {
|
|
40
|
-
source: importSource.source,
|
|
41
|
-
sourceType: importSource.sourceType,
|
|
42
|
-
};
|
|
43
|
-
const binding = module.bind(node, layoutContext);
|
|
44
|
-
if (
|
|
45
|
-
typeof binding.create === "function" &&
|
|
46
|
-
typeof binding.render === "function" &&
|
|
47
|
-
typeof binding.unmount === "function"
|
|
48
|
-
) {
|
|
49
|
-
return {
|
|
50
|
-
render: (model) =>
|
|
51
|
-
binding.render(
|
|
52
|
-
createElementFromModuleBinding(
|
|
53
|
-
layoutContext,
|
|
54
|
-
importSource,
|
|
55
|
-
module,
|
|
56
|
-
binding,
|
|
57
|
-
model
|
|
58
|
-
)
|
|
59
|
-
),
|
|
60
|
-
unmount: binding.unmount,
|
|
61
|
-
};
|
|
62
|
-
} else {
|
|
63
|
-
console.error(
|
|
64
|
-
`${importSource.source} returned an impropper binding`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
} else {
|
|
70
|
-
console.error(
|
|
71
|
-
`${importSource.source} did not export a function 'bind'`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function createElementFromModuleBinding(
|
|
78
|
-
layoutContext,
|
|
79
|
-
currentImportSource,
|
|
80
|
-
module,
|
|
81
|
-
binding,
|
|
82
|
-
model
|
|
83
|
-
) {
|
|
84
|
-
let type;
|
|
85
|
-
if (model.importSource) {
|
|
86
|
-
if (!isImportSourceEqual(currentImportSource, model.importSource)) {
|
|
87
|
-
console.error(
|
|
88
|
-
"Parent element import source " +
|
|
89
|
-
stringifyImportSource(currentImportSource) +
|
|
90
|
-
" does not match child's import source " +
|
|
91
|
-
stringifyImportSource(model.importSource)
|
|
92
|
-
);
|
|
93
|
-
return null;
|
|
94
|
-
} else if (!module[model.tagName]) {
|
|
95
|
-
console.error(
|
|
96
|
-
"Module from source " +
|
|
97
|
-
stringifyImportSource(currentImportSource) +
|
|
98
|
-
` does not export ${model.tagName}`
|
|
99
|
-
);
|
|
100
|
-
return null;
|
|
101
|
-
} else {
|
|
102
|
-
type = module[model.tagName];
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
type = model.tagName;
|
|
106
|
-
}
|
|
107
|
-
return binding.create(
|
|
108
|
-
type,
|
|
109
|
-
createElementAttributes(model, layoutContext.sendEvent),
|
|
110
|
-
createElementChildren(model, (child) =>
|
|
111
|
-
createElementFromModuleBinding(
|
|
112
|
-
layoutContext,
|
|
113
|
-
currentImportSource,
|
|
114
|
-
module,
|
|
115
|
-
binding,
|
|
116
|
-
child
|
|
117
|
-
)
|
|
118
|
-
)
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function isImportSourceEqual(source1, source2) {
|
|
123
|
-
return (
|
|
124
|
-
source1.source === source2.source &&
|
|
125
|
-
source1.sourceType === source2.sourceType
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function stringifyImportSource(importSource) {
|
|
130
|
-
return JSON.stringify({
|
|
131
|
-
source: importSource.source,
|
|
132
|
-
sourceType: importSource.sourceType,
|
|
133
|
-
});
|
|
134
|
-
}
|
package/src/index.js
DELETED
package/src/mount.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import ReactDOM from "react-dom";
|
|
3
|
-
import { Layout } from "./components.js";
|
|
4
|
-
|
|
5
|
-
export function mountLayout(mountElement, layoutProps) {
|
|
6
|
-
ReactDOM.render(React.createElement(Layout, layoutProps), mountElement);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function mountLayoutWithWebSocket(
|
|
10
|
-
element,
|
|
11
|
-
endpoint,
|
|
12
|
-
loadImportSource,
|
|
13
|
-
maxReconnectTimeout
|
|
14
|
-
) {
|
|
15
|
-
mountLayoutWithReconnectingWebSocket(
|
|
16
|
-
element,
|
|
17
|
-
endpoint,
|
|
18
|
-
loadImportSource,
|
|
19
|
-
maxReconnectTimeout
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function mountLayoutWithReconnectingWebSocket(
|
|
24
|
-
element,
|
|
25
|
-
endpoint,
|
|
26
|
-
loadImportSource,
|
|
27
|
-
maxReconnectTimeout,
|
|
28
|
-
mountState = {
|
|
29
|
-
everMounted: false,
|
|
30
|
-
reconnectAttempts: 0,
|
|
31
|
-
reconnectTimeoutRange: 0,
|
|
32
|
-
}
|
|
33
|
-
) {
|
|
34
|
-
const socket = new WebSocket(endpoint);
|
|
35
|
-
|
|
36
|
-
const updateHookPromise = new LazyPromise();
|
|
37
|
-
|
|
38
|
-
socket.onopen = (event) => {
|
|
39
|
-
console.info(`ReactPy WebSocket connected.`);
|
|
40
|
-
|
|
41
|
-
if (mountState.everMounted) {
|
|
42
|
-
ReactDOM.unmountComponentAtNode(element);
|
|
43
|
-
}
|
|
44
|
-
_resetOpenMountState(mountState);
|
|
45
|
-
|
|
46
|
-
mountLayout(element, {
|
|
47
|
-
loadImportSource,
|
|
48
|
-
saveUpdateHook: updateHookPromise.resolve,
|
|
49
|
-
sendEvent: (event) => socket.send(JSON.stringify(event)),
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
socket.onmessage = (event) => {
|
|
54
|
-
const message = JSON.parse(event.data);
|
|
55
|
-
updateHookPromise.promise.then((update) => update(message));
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
socket.onclose = (event) => {
|
|
59
|
-
if (!maxReconnectTimeout) {
|
|
60
|
-
console.info(`ReactPy WebSocket connection lost.`);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const reconnectTimeout = _nextReconnectTimeout(
|
|
65
|
-
maxReconnectTimeout,
|
|
66
|
-
mountState
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
console.info(
|
|
70
|
-
`ReactPy WebSocket connection lost. Reconnecting in ${reconnectTimeout} seconds...`
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
setTimeout(function () {
|
|
74
|
-
mountState.reconnectAttempts++;
|
|
75
|
-
mountLayoutWithReconnectingWebSocket(
|
|
76
|
-
element,
|
|
77
|
-
endpoint,
|
|
78
|
-
loadImportSource,
|
|
79
|
-
maxReconnectTimeout,
|
|
80
|
-
mountState
|
|
81
|
-
);
|
|
82
|
-
}, reconnectTimeout * 1000);
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function _resetOpenMountState(mountState) {
|
|
87
|
-
mountState.everMounted = true;
|
|
88
|
-
mountState.reconnectAttempts = 0;
|
|
89
|
-
mountState.reconnectTimeoutRange = 0;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function _nextReconnectTimeout(maxReconnectTimeout, mountState) {
|
|
93
|
-
const timeout =
|
|
94
|
-
Math.floor(Math.random() * mountState.reconnectTimeoutRange) || 1;
|
|
95
|
-
mountState.reconnectTimeoutRange =
|
|
96
|
-
(mountState.reconnectTimeoutRange + 5) % maxReconnectTimeout;
|
|
97
|
-
return timeout;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function LazyPromise() {
|
|
101
|
-
this.promise = new Promise((resolve, reject) => {
|
|
102
|
-
this.resolve = resolve;
|
|
103
|
-
this.reject = reject;
|
|
104
|
-
});
|
|
105
|
-
}
|
package/src/server.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import ReactDOM from "react-dom";
|
|
3
|
-
import { mountLayoutWithWebSocket } from "./mount.js";
|
|
4
|
-
|
|
5
|
-
export function mountWithLayoutServer(
|
|
6
|
-
element,
|
|
7
|
-
serverInfo,
|
|
8
|
-
maxReconnectTimeout
|
|
9
|
-
) {
|
|
10
|
-
const loadImportSource = (source, sourceType) =>
|
|
11
|
-
import(
|
|
12
|
-
sourceType == "NAME" ? serverInfo.path.module(source) : source
|
|
13
|
-
).catch((error) => {
|
|
14
|
-
// Added a catch to silence a build warning caller so we just re-throw.
|
|
15
|
-
// The caller is actually responsible for catching this error.
|
|
16
|
-
throw error;
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
mountLayoutWithWebSocket(
|
|
20
|
-
element,
|
|
21
|
-
serverInfo.path.stream,
|
|
22
|
-
loadImportSource,
|
|
23
|
-
maxReconnectTimeout
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function LayoutServerInfo({ host, port, path, query, secure }) {
|
|
28
|
-
const wsProtocol = `ws${secure ? "s" : ""}`;
|
|
29
|
-
const wsBaseUrl = `${wsProtocol}://${host}:${port}`;
|
|
30
|
-
|
|
31
|
-
let pathName = path || new URL(document.baseURI).pathname;
|
|
32
|
-
if (pathName.endsWith("/")) {
|
|
33
|
-
pathName = pathName.slice(0, -1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (query) {
|
|
37
|
-
query = `?${query}`;
|
|
38
|
-
} else {
|
|
39
|
-
query = "";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
this.path = {
|
|
43
|
-
stream: `${wsBaseUrl}/_reactpy/stream${pathName}${query}`,
|
|
44
|
-
module: (source) => `/_reactpy/modules/${source}`,
|
|
45
|
-
};
|
|
46
|
-
}
|