Skip to content

Commit 7e3345a

Browse files
committed
initial work for ES6 compat with snowpack
1 parent 3a5374b commit 7e3345a

File tree

7 files changed

+2444
-0
lines changed

7 files changed

+2444
-0
lines changed

src/idom/client/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
js/node_modules
2+
js/web_modules

src/idom/client/README.md

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head><title>Snowpack - Simple Example</title></head>
4+
<body>
5+
<div id="app"></div>
6+
<script type="module">
7+
import React from "../js/web_modules/react.js";
8+
import ReactDOM from "../js/web_modules/react-dom";
9+
import Layout from "../js/index.js";
10+
11+
const uri = document.location.hostname + ":" + document.location.port;
12+
const url = (uri + document.location.pathname).split("/").slice(0, -1);
13+
url[url.length - 1] = "stream";
14+
const secure = document.location.protocol === "https:";
15+
16+
let protocol;
17+
if (secure) {
18+
protocol = "wss:";
19+
} else {
20+
protocol = "ws:";
21+
}
22+
let endpoint = protocol + "//" + url.join("/");
23+
24+
const mount = document.getElementById("root");
25+
ReactDOM.render(React.createElement(Layout, {"endpoint": endpoint}), mount);
26+
</script>
27+
</body>
28+
</html>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
function serializeEvent(event) {
2+
const data = {};
3+
if (event.target.hasOwnProperty("value")) {
4+
data.value = event.target.value;
5+
}
6+
if (eventTransforms.hasOwnProperty(event.type)) {
7+
Object.assign(data, eventTransforms[event.type](event));
8+
}
9+
return data;
10+
}
11+
12+
const eventCategoryTransforms = {
13+
clipboard: event => ({
14+
clipboardData: event.clipboardData
15+
}),
16+
composition: event => ({
17+
data: event.data
18+
}),
19+
keyboard: event => ({
20+
altKey: event.altKey,
21+
charCode: event.charCode,
22+
ctrlKey: event.ctrlKey,
23+
key: event.key,
24+
keyCode: event.keyCode,
25+
locale: event.locale,
26+
location: event.location,
27+
metaKey: event.metaKey,
28+
repeat: event.repeat,
29+
shiftKey: event.shiftKey,
30+
which: event.which
31+
}),
32+
mouse: event => ({
33+
altKey: event.altKey,
34+
button: event.button,
35+
buttons: event.buttons,
36+
clientX: event.clientX,
37+
clientY: event.clientY,
38+
ctrlKey: event.ctrlKey,
39+
metaKey: event.metaKey,
40+
pageX: event.pageX,
41+
pageY: event.pageY,
42+
screenX: event.screenX,
43+
screenY: event.screenY,
44+
shiftKey: event.shiftKey
45+
}),
46+
pointer: event => ({
47+
pointerId: event.pointerId,
48+
width: event.width,
49+
height: event.height,
50+
pressure: event.pressure,
51+
tiltX: event.tiltX,
52+
tiltY: event.tiltY,
53+
pointerType: event.pointerType,
54+
isPrimary: event.isPrimary
55+
}),
56+
touch: event => ({
57+
altKey: event.altKey,
58+
ctrlKey: event.ctrlKey,
59+
metaKey: event.metaKey,
60+
shiftKey: event.shiftKey
61+
}),
62+
ui: event => ({
63+
detail: event.detail
64+
}),
65+
wheel: event => ({
66+
deltaMode: event.deltaMode,
67+
deltaX: event.deltaX,
68+
deltaY: event.deltaY,
69+
deltaZ: event.deltaZ
70+
}),
71+
animation: event => ({
72+
animationName: event.animationName,
73+
pseudoElement: event.pseudoElement,
74+
elapsedTime: event.elapsedTime
75+
}),
76+
transition: event => ({
77+
propertyName: event.propertyName,
78+
pseudoElement: event.pseudoElement,
79+
elapsedTime: event.elapsedTime
80+
})
81+
};
82+
83+
const eventTypeCategories = {
84+
clipboard: ["copy", "cut", "paste"],
85+
composition: ["compositionend", "compositionstart", "compositionupdate"],
86+
keyboard: ["keydown", "keypress", "keyup"],
87+
mouse: [
88+
"click",
89+
"contextmenu",
90+
"doubleclick",
91+
"drag",
92+
"dragend",
93+
"dragenter",
94+
"dragexit",
95+
"dragleave",
96+
"dragover",
97+
"dragstart",
98+
"drop",
99+
"mousedown",
100+
"mouseenter",
101+
"mouseleave",
102+
"mousemove",
103+
"mouseout",
104+
"mouseover",
105+
"mouseup"
106+
],
107+
pointer: [
108+
"pointerdown",
109+
"pointermove",
110+
"pointerup",
111+
"pointercancel",
112+
"gotpointercapture",
113+
"lostpointercapture",
114+
"pointerenter",
115+
"pointerleave",
116+
"pointerover",
117+
"pointerout"
118+
],
119+
selection: ["select"],
120+
touch: ["touchcancel", "touchend", "touchmove", "touchstart"],
121+
ui: ["scroll"],
122+
wheel: ["wheel"],
123+
animation: ["animationstart", "animationend", "animationiteration"],
124+
transition: ["transitionend"]
125+
};
126+
127+
const eventTransforms = {};
128+
129+
Object.keys(eventTypeCategories).forEach(category => {
130+
eventTypeCategories[category].forEach(type => {
131+
eventTransforms[type] = eventCategoryTransforms[category];
132+
});
133+
});
134+
135+
export default serializeEvent;

src/idom/client/js/index.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import React, {
2+
useReducer,
3+
useEffect,
4+
useState,
5+
useMemo,
6+
Suspense
7+
} from "./web_modules/react.js";
8+
import htm from './web_modules/htm.js'
9+
import serializeEvent from "./event-to-object.js";
10+
11+
const html = htm.bind(React.createElement);
12+
13+
const allUpdateTriggers = {};
14+
const allModels = {};
15+
16+
function updateDynamicElement(elementId) {
17+
if (allUpdateTriggers.hasOwnProperty(elementId)) {
18+
allUpdateTriggers[elementId]();
19+
}
20+
}
21+
22+
function Layout({ endpoint }) {
23+
// handle relative endpoint URI
24+
if (endpoint.startsWith(".") || endpoint.startsWith("/")) {
25+
let loc = window.location;
26+
let protocol;
27+
if (loc.protocol === "https:") {
28+
protocol = "wss:";
29+
} else {
30+
protocol = "ws:";
31+
}
32+
let new_uri = protocol + "//" + loc.host;
33+
if (endpoint.startsWith(".")) {
34+
new_url += loc.pathname + "/";
35+
}
36+
endpoint = new_uri + endpoint;
37+
}
38+
39+
const socket = useMemo(() => {
40+
return new WebSocket(endpoint);
41+
}, [endpoint]);
42+
43+
const [root, setRoot] = useState(null);
44+
45+
socket.onmessage = event => {
46+
const msg = JSON.parse(event.data);
47+
Object.assign(allModels, msg.body.render.new);
48+
msg.body.render.old.forEach(elementId => {
49+
delete allModels[elementId];
50+
});
51+
updateDynamicElement(msg.body.render.src);
52+
if (!root) {
53+
setRoot(msg.body.render.root);
54+
}
55+
};
56+
57+
const sendMsg = msg => {
58+
socket.send(JSON.stringify(msg));
59+
};
60+
const sendEvent = event => {
61+
sendMsg({
62+
header: {},
63+
body: { event: event }
64+
});
65+
};
66+
67+
if (root) {
68+
return html`<DynamicElement elementId={root} sendEvent={sendEvent} />`;
69+
} else {
70+
return html`<div />`;
71+
}
72+
}
73+
74+
function DynamicElement({ elementId, sendEvent }) {
75+
allUpdateTriggers[elementId] = useForceUpdate();
76+
const model = allModels[elementId];
77+
if (model) {
78+
return html`<Element model={model} sendEvent={sendEvent} />`;
79+
} else {
80+
return html`<div />`;
81+
}
82+
}
83+
84+
function Element({ model, sendEvent }) {
85+
const children = elementChildren(model, sendEvent);
86+
const attributes = elementAttributes(model, sendEvent);
87+
if (model.importSource) {
88+
const lazy = lazyComponent(model);
89+
return html`
90+
<Suspense fallback={model.importSource.fallback}>
91+
{React.createElement(lazy, attributes, children)}
92+
</Suspense>
93+
`;
94+
} else if (model.children && model.children.length) {
95+
return React.createElement(model.tagName, attributes, children);
96+
} else {
97+
return React.createElement(model.tagName, attributes);
98+
}
99+
}
100+
101+
function elementChildren(model, sendEvent) {
102+
if (!model.children) {
103+
return [];
104+
} else {
105+
return model.children.map(child => {
106+
switch (child.type) {
107+
case "ref":
108+
return html`
109+
<DynamicElement
110+
elementId={child.data}
111+
sendEvent={sendEvent}
112+
/>
113+
`;
114+
case "obj":
115+
return html`<Element model={child.data} sendEvent={sendEvent} />`;
116+
case "str":
117+
return child.data;
118+
}
119+
});
120+
}
121+
}
122+
123+
function elementAttributes(model, sendEvent) {
124+
const attributes = Object.assign({}, model.attributes);
125+
126+
if (model.eventHandlers) {
127+
Object.keys(model.eventHandlers).forEach(eventName => {
128+
const eventSpec = model.eventHandlers[eventName];
129+
attributes[eventName] = function(event) {
130+
const data = Array.from(arguments).map(value => {
131+
if (typeof value === "object" && value.nativeEvent) {
132+
if (eventSpec["preventDefault"]) {
133+
value.preventDefault();
134+
}
135+
if (eventSpec["stopPropagation"]) {
136+
value.stopPropagation();
137+
}
138+
return serializeEvent(value);
139+
} else {
140+
return value;
141+
}
142+
});
143+
const sentEvent = new Promise((resolve, reject) => {
144+
const msg = {
145+
data: data,
146+
target: eventSpec["target"]
147+
};
148+
sendEvent(msg);
149+
resolve(msg);
150+
});
151+
};
152+
});
153+
}
154+
155+
return attributes;
156+
}
157+
function useForceUpdate() {
158+
const [, setState] = useState(true);
159+
const forceUpdate = () => {
160+
setState(state => !state);
161+
};
162+
return forceUpdate;
163+
}
164+
165+
export default Layout;

0 commit comments

Comments
 (0)