React playground
tasks
Name | Assign | Property | Status |
---|---|---|---|
modular column-based UI? | In progress | ||
Live Program an email client | Not started | ||
Live Program my idea for a kanban-based conversation UI | Not started | ||
sketch out how to integrate this into real projects(modules, pointing to files etc.)(maybe scope to create-react-app?) | Not started | ||
zoom/pan | Not started | ||
displaying component | Not started | ||
React babel integration | Not started | ||
forking/merging component code | Not started | ||
hot reloading/state persistence | Not started | ||
forking component states | Not started | ||
lines between components | Not started | ||
renaming components | Not started | ||
drag-drop windows into components | Not started | ||
drag-drop windows out of components | Not started | ||
state inspector window | Not started | ||
props inspector window | Not started | ||
json table editor | Not started | ||
vdom inspector | Not started | ||
click on vdom element to open it in new window | Not started |
As a developer I want to:
- - make a new instance to debug two behaviors side by side
- - control the width/height of instances to confirm responsive behavior
- - toggle seeing the state for a component
- - toggle seeing the props for a component
- - see a history of the state changes
- - see child components and access them
- - leave notes in places
- - use multiple cursors to simulate weird states, for e.g. drag-n-drop
- - create forks of components to try out different behaviors, and then merge the fork back
- - copy state from one component and paste it into another?
- - drag child winddows along with parent windows
- - diff between state objects?
- - create visualizations of state objects?
- create cascading chains of different states to see the effect of them
- fork a component and rebind it
Development thoughts
- If I fork React, maybe I can implement logic to render a component to multiple targets?
- or perhaps use https://reactjs.org/docs/react-api.html?#cloneelement
- Use this to implement zooming/panning: https://codesandbox.io/s/rkgzi?file=/src/index.js
- this could be used for self-explaining UI? UI that explains how its code works???
var RECYCLED_NODE = 1;
var LAZY_NODE = 2;
var TEXT_NODE = 3;
var EMPTY_OBJ = {};
var EMPTY_ARR = [];
var map = EMPTY_ARR.map;
var isArray = Array.isArray;
var defer =
typeof requestAnimationFrame !== "undefined"
? requestAnimationFrame
: setTimeout;
var createClass = function(obj) {
var out = "";
if (typeof obj === "string") return obj;
if (isArray(obj) && obj.length > 0) {
for (var k = 0, tmp; k < obj.length; k++) {
if ((tmp = createClass(obj[k])) !== "") {
out += (out && " ") + tmp;
}
}
} else {
for (var k in obj) {
if (obj[k]) {
out += (out && " ") + k;
}
}
}
return out;
};
var merge = function(a, b) {
var out = {};
for (var k in a) out[k] = a[k];
for (var k in b) out[k] = b[k];
return out;
};
var batch = function(list) {
return list.reduce(function(out, item) {
return out.concat(
!item || item === true
? 0
: typeof item[0] === "function"
? [item]
: batch(item)
);
}, EMPTY_ARR);
};
var isSameAction = function(a, b) {
return (
isArray(a) && isArray(b) && a[0] === b[0] && typeof a[0] === "function"
);
};
var shouldRestart = function(a, b) {
if (a !== b) {
for (var k in merge(a, b)) {
if (a[k] !== b[k] && !isSameAction(a[k], b[k])) return true;
b[k] = a[k];
}
}
};
var patchSubs = function(oldSubs, newSubs, dispatch) {
for (
var i = 0, oldSub, newSub, subs = [];
i < oldSubs.length || i < newSubs.length;
i++
) {
oldSub = oldSubs[i];
newSub = newSubs[i];
subs.push(
newSub
? !oldSub ||
newSub[0] !== oldSub[0] ||
shouldRestart(newSub[1], oldSub[1])
? [
newSub[0],
newSub[1],
newSub[0](dispatch, newSub[1]),
oldSub && oldSub[2]()
]
: oldSub
: oldSub && oldSub[2]()
);
}
return subs;
};
var patchProperty = function(node, key, oldValue, newValue, listener, isSvg) {
if (key === "key") {
} else if (key === "style") {
for (var k in merge(oldValue, newValue)) {
oldValue = newValue == null || newValue[k] == null ? "" : newValue[k];
if (k[0] === "-") {
node[key].setProperty(k, oldValue);
} else {
node[key][k] = oldValue;
}
}
} else if (key[0] === "o" && key[1] === "n") {
if (
!((node.actions || (node.actions = {}))[(key = key.slice(2))] = newValue)
) {
node.removeEventListener(key, listener);
} else if (!oldValue) {
node.addEventListener(key, listener);
}
} else if (!isSvg && key !== "list" && key !== "form" && key in node) {
node[key] = newValue == null ? "" : newValue;
} else if (
newValue == null ||
newValue === false ||
(key === "class" && !(newValue = createClass(newValue)))
) {
node.removeAttribute(key);
} else {
node.setAttribute(key, newValue);
}
};
var createNode = function(vdom, listener, isSvg) {
var ns = "http://www.w3.org/2000/svg";
var props = vdom.props;
var node =
vdom.type === TEXT_NODE
? document.createTextNode(vdom.name)
: (isSvg = isSvg || vdom.name === "svg")
? document.createElementNS(ns, vdom.name, { is: props.is })
: document.createElement(vdom.name, { is: props.is });
for (var k in props) {
patchProperty(node, k, null, props[k], listener, isSvg);
}
for (var i = 0, len = vdom.children.length; i < len; i++) {
node.appendChild(
createNode(
(vdom.children[i] = getVNode(vdom.children[i])),
listener,
isSvg
)
);
}
return (vdom.node = node);
};
var getKey = function(vdom) {
return vdom == null ? null : vdom.key;
};
var patch = function(
parent,
node,
oldVNode,
newVNode,
listener,
isSvg,
nodeMap
) {
// console.log(parent)
//nodeMap[parent].push({ vNode: newVNode, node });
//nodeMap[node] = [];
if (oldVNode === newVNode) {
} else if (
oldVNode != null &&
oldVNode.type === TEXT_NODE &&
newVNode.type === TEXT_NODE
) {
if (oldVNode.name !== newVNode.name) node.nodeValue = newVNode.name;
} else if (oldVNode == null || oldVNode.name !== newVNode.name) {
node = parent.insertBefore(
createNode((newVNode = getVNode(newVNode)), listener, isSvg),
node
);
if (oldVNode != null) {
parent.removeChild(oldVNode.node);
}
} else {
var tmpVKid;
var oldVKid;
var oldKey;
var newKey;
var oldVProps = oldVNode.props;
var newVProps = newVNode.props;
var oldVKids = oldVNode.children;
var newVKids = newVNode.children;
var oldHead = 0;
var newHead = 0;
var oldTail = oldVKids.length - 1;
var newTail = newVKids.length - 1;
isSvg = isSvg || newVNode.name === "svg";
for (var i in merge(oldVProps, newVProps)) {
if (
(i === "value" || i === "selected" || i === "checked"
? node[i]
: oldVProps[i]) !== newVProps[i]
) {
patchProperty(node, i, oldVProps[i], newVProps[i], listener, isSvg);
}
}
while (newHead <= newTail && oldHead <= oldTail) {
if (
(oldKey = getKey(oldVKids[oldHead])) == null ||
oldKey !== getKey(newVKids[newHead])
) {
break;
}
patch(
node,
oldVKids[oldHead].node,
oldVKids[oldHead],
(newVKids[newHead] = getVNode(
newVKids[newHead++],
oldVKids[oldHead++]
)),
listener,
isSvg,
nodeMap
);
}
while (newHead <= newTail && oldHead <= oldTail) {
if (
(oldKey = getKey(oldVKids[oldTail])) == null ||
oldKey !== getKey(newVKids[newTail])
) {
break;
}
patch(
node,
oldVKids[oldTail].node,
oldVKids[oldTail],
(newVKids[newTail] = getVNode(
newVKids[newTail--],
oldVKids[oldTail--]
)),
listener,
isSvg,
nodeMap
);
}
if (oldHead > oldTail) {
while (newHead <= newTail) {
node.insertBefore(
createNode(
(newVKids[newHead] = getVNode(newVKids[newHead++])),
listener,
isSvg
),
(oldVKid = oldVKids[oldHead]) && oldVKid.node
);
}
} else if (newHead > newTail) {
while (oldHead <= oldTail) {
node.removeChild(oldVKids[oldHead++].node);
}
} else {
for (var i = oldHead, keyed = {}, newKeyed = {}; i <= oldTail; i++) {
if ((oldKey = oldVKids[i].key) != null) {
keyed[oldKey] = oldVKids[i];
}
}
while (newHead <= newTail) {
oldKey = getKey((oldVKid = oldVKids[oldHead]));
newKey = getKey(
(newVKids[newHead] = getVNode(newVKids[newHead], oldVKid))
);
if (
newKeyed[oldKey] ||
(newKey != null && newKey === getKey(oldVKids[oldHead + 1]))
) {
if (oldKey == null) {
node.removeChild(oldVKid.node);
}
oldHead++;
continue;
}
if (newKey == null || oldVNode.type === RECYCLED_NODE) {
if (oldKey == null) {
patch(
node,
oldVKid && oldVKid.node,
oldVKid,
newVKids[newHead],
listener,
isSvg,
nodeMap
);
newHead++;
}
oldHead++;
} else {
if (oldKey === newKey) {
patch(
node,
oldVKid.node,
oldVKid,
newVKids[newHead],
listener,
isSvg,
nodeMap
);
newKeyed[newKey] = true;
oldHead++;
} else {
if ((tmpVKid = keyed[newKey]) != null) {
patch(
node,
node.insertBefore(tmpVKid.node, oldVKid && oldVKid.node),
tmpVKid,
newVKids[newHead],
listener,
isSvg,
nodeMap
);
newKeyed[newKey] = true;
} else {
patch(
node,
oldVKid && oldVKid.node,
null,
newVKids[newHead],
listener,
isSvg,
nodeMap
);
}
}
newHead++;
}
}
while (oldHead <= oldTail) {
if (getKey((oldVKid = oldVKids[oldHead++])) == null) {
node.removeChild(oldVKid.node);
}
}
for (var i in keyed) {
if (newKeyed[i] == null) {
node.removeChild(keyed[i].node);
}
}
}
}
return (newVNode.node = node);
};
var propsChanged = function(a, b) {
for (var k in a) if (a[k] !== b[k]) return true;
for (var k in b) if (a[k] !== b[k]) return true;
};
var getTextVNode = function(node) {
return typeof node === "object" ? node : createTextVNode(node);
};
var getVNode = function(newVNode, oldVNode) {
return newVNode.type === LAZY_NODE
? ((!oldVNode ||
!oldVNode.lazy ||
propsChanged(oldVNode.lazy, newVNode.lazy)) &&
((oldVNode = getTextVNode(newVNode.lazy.view(newVNode.lazy))).lazy =
newVNode.lazy),
oldVNode)
: newVNode;
};
var createVNode = function(name, props, children, node, key, type) {
return {
name: name,
props: props,
children: children,
node: node,
type: type,
key: key
};
};
var createTextVNode = function(value, node) {
return createVNode(value, EMPTY_OBJ, EMPTY_ARR, node, undefined, TEXT_NODE);
};
var recycleNode = function(node) {
return node.nodeType === TEXT_NODE
? createTextVNode(node.nodeValue, node)
: createVNode(
node.nodeName.toLowerCase(),
EMPTY_OBJ,
map.call(node.childNodes, recycleNode),
node,
undefined,
RECYCLED_NODE
);
};
export var Lazy = function(props) {
return {
lazy: props,
type: LAZY_NODE
};
};
export var h = function(name, props) {
for (var vdom, rest = [], children = [], i = arguments.length; i-- > 2; ) {
rest.push(arguments[i]);
}
while (rest.length > 0) {
if (isArray((vdom = rest.pop()))) {
for (var i = vdom.length; i-- > 0; ) {
rest.push(vdom[i]);
}
} else if (vdom === false || vdom === true || vdom == null) {
} else {
children.push(getTextVNode(vdom));
}
}
props = props || EMPTY_OBJ;
return typeof name === "function"
? name(props, children)
: createVNode(name, props, children, undefined, props.key);
};
export var app = function(props) {
// console.log(props)
var state = {};
var lock = false;
var view = props.view;
var node = props.node;
var vdom = node && recycleNode(node);
var subscriptions = props.subscriptions;
var subs = [];
//let nodeMap = {};
var listener = function(event) {
dispatch(this.actions[event.type], event);
};
var setState = function(newState) {
if (state !== newState) {
state = newState;
if (subscriptions) {
subs = patchSubs(subs, batch([subscriptions(state)]), dispatch);
}
if (view && !lock) defer(render, (lock = true));
}
return state;
};
var dispatch = (props.middleware ||
function(obj) {
return obj;
})(function(action, props) {
return typeof action === "function"
? dispatch(action(state, props))
: isArray(action)
? typeof action[0] === "function" || isArray(action[0])
? dispatch(
action[0],
typeof action[1] === "function" ? action[1](props) : action[1]
)
: (batch(action.slice(1)).map(function(fx) {
fx && fx[0](dispatch, fx[1]);
}, setState(action[0])),
state)
: setState(action);
});
var render = function() {
lock = false;
//console.log(node)
let oldVdom = vdom;
vdom = getTextVNode(view(state));
//nodeMap = { [node.parentNode]: [] };
node = patch(
node.parentNode,
node,
oldVdom,
vdom,
listener,
false
//nodeMap
);
props.onRender && props.onRender(vdom, state);
//console.log(JSON.stringify(vdom, null, 2))
//console.log(node)
};
dispatch(props.init);
return setState;
};
import { h, app } from "./hyperapp";
let componentMap = {}
const component = (name, body) => {
componentMap[name] = body
return props => {
const vNode = body(props);
vNode.componentName = name;
console.log(vNode);
return vNode;
};
};
const Node = props => {
if (!props) {
return h("h1", {}, "nothing");
}
return h(
"div",
{ style: { marginLeft: "10px" } },
h("div", { style: { display: "flex", flexDirection: "row" } }, [
h("div", {}, [props.name + " " + (props.componentName || "")]),
Object.keys(props.props).length > 0 && h("div", {}, JSON.stringify(props.props))
]),
//h("div", {}, props.node),
props.children.map(child => {
return Node(child);
})
);
};
const SetText = (state, event) => {
return ({...state, text: event.target.value})
}
const AddTodo = (state, event) => {
return ({...state, text: "", tasks: [{done: false, text: state.text}, ...state.tasks]})
}
const TodoForm = component("TodoForm", (props) => {
return h("div", {},
h("input", {oninput: SetText, value: props.text}, "hi"),
h("button", {onclick: AddTodo}, "add"))
})
const TodoApp = component("Todo", (props) => {
return h("div", {},
"new todo:",
h("div", {}, TodoForm(props)),
"todos:",
h("ul", {}, props.tasks.map(task => {
return h("li", {style: {textDecoration: task.done && "line-through"}}, task.text)
})))
})
const setState = app({
node: document.getElementById("inspector"),
view: state => {
if (!state) {
return h("h1", {}, "nothing");
}
return h("div", {}, [
h("pre", {}, JSON.stringify(state.state, null, 2)),
Node(state.vdom)
]);
}
});
// -- RUN --
app({
onRender: (vdom, state) => {
setState({ vdom: vdom, state: state });
},
node: document.getElementById("app"),
init: [
{
text: "",
tasks: [{text: "do this", done: false}, {text: "do that", done: true}]
}
],
view: state =>
TodoApp(state),
});