Skip to content

Instantly share code, notes, and snippets.

@antimatter15
Last active September 2, 2018 12:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antimatter15/87e9f88c07e8e4c4f8ed1afc934f2748 to your computer and use it in GitHub Desktop.
Save antimatter15/87e9f88c07e8e4c4f8ed1afc934f2748 to your computer and use it in GitHub Desktop.
FakeTalk
<title>FakeTalk</title>
<style>
body {
background: #eee;
}
* {
box-sizing: border-box;
}
.paper {
padding: 10px;
border: 1px solid #ccc;
width: 350px;
white-space: pre-wrap;
font-family: monospace;
background: white;
display: inline-block;
}
.paper h1 {
margin: 0;
font: inherit;
font-weight: bold;
background: #ddd;
padding: 5px 10px;
margin: -10px;
margin-bottom: 5px;
}
.label {
background: #fff0d0;
padding: 5px 10px;
margin: 5px 0;
border-radius: 10px;
}
.paper textarea {
width: 100%;
padding: 0;
border: 0;
min-height: 150px;
background: transparent;
}
#background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
textarea.stale:not(:focus) {
font-style: italic;
background: #fff0d0;
}
</style>
<div id="background"></div>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/preact/dist/preact.min.js"></script>
<script>
const { Component, h } = window.preact;
let functionCache = {};
function render(){
let _ = Symbol('_'), $ = Symbol('$');
let $db = new Map(), $hooks = new Map();
function match_data(obj, pattern, cb, args=[]){
if(pattern.length === 0){
if(obj.has($)) cb(...args);
}else if(pattern[0] === _){
for(let [key, val] of obj.entries())
match_data(val, pattern.slice(1), cb, [...args, key]);
}else{
let val = obj.get(pattern[0]);
if(val) match_data(val, pattern.slice(1), cb, args);
}
}
function match_patterns(obj, datum, args=[]){
if(datum.length === 0){
for(let cb of obj.get($)) cb(...args);
}else{
let next = datum.slice(1);
let a = obj.get(datum[0]),
b = obj.get(_)
if(a) match_patterns(a, next, args);
if(b) match_patterns(b, next, [...args, datum[0]]);
}
}
function fill_path(obj, path){
let ptr = obj;
for(let part of path){
if(!ptr.has(part)) ptr.set(part, new Map());
ptr = ptr.get(part)
}
return ptr
}
function when(...args){
let cb = args[args.length - 1], pattern = args.slice(0, -1);
claim('when', ...pattern.map(k => k === _ ? '_' : k))
let ptr = fill_path($hooks, pattern)
if(!ptr.has($)) ptr.set($, []);
ptr.get($).push(cb);
match_data($db, pattern, cb)
}
function claim(...args){
let ptr = fill_path($db, args)
if(ptr.has($)) return;
ptr.set($, true)
match_patterns($hooks, args)
}
let latercb = []
const later = cb => latercb.push(cb)
let newFunctionCache = {}
papers.map((code, i) => {
let me = { index: i }
claim(me, 'paper')
claim(me, 'code', code)
try {
newFunctionCache[code] = functionCache[code] ||
new Function('_', 'me', 'when', 'claim', 'later', code)
newFunctionCache[code](_, me, when, claim, later)
} catch (err) {
console.log(code)
console.error(err)
claim(me, 'error', err)
}
})
functionCache = newFunctionCache;
for(let cb of latercb)
try { cb(); } catch (err) { console.error(err) };
}
const FILENAME = 'papers.js'
function savePapers(){
fetch(FILENAME, {
method: 'PUT',
body: papers.join('\n\n///////////////////////////////////////\n\n')
})
.then(res => res.text())
.then(res => console.log(res))
}
let papers;
fetch(FILENAME)
.then(res => res.text())
.then(text => text.split(/\n+\/{30,}\n+/g))
.then(list => {
papers = list;
render()
})
</script>
claim(me, 'name', 'mouse')
claim(me, 'x', 901)
claim(me, 'y', 29)
when('fox is out', () => {
claim(me, 'wish', 'labelled', 'squeak')
claim(me, 'wish', 'outlined', 'red')
})
///////////////////////////////////////
claim(me, 'name', 'fox')
claim(me, 'x', 1075)
claim(me, 'y', 114)
claim('fox is out')
///////////////////////////////////////
claim(me, 'name', 'leprechaun labeler')
claim(me, 'x', 1531)
claim(me, 'y', 103)
when(_, 'is a leprechaun', (lep) => {
claim(lep, 'wish', 'labelled', 'is a leprechaun')
claim(lep, 'wish', 'background', 'rgb(200, 255, 200)')
})
///////////////////////////////////////
claim(me, 'name', 'leprechaun')
claim(me, 'x', 1482)
claim(me, 'y', 370)
claim(me, 'is a leprechaun')
///////////////////////////////////////
claim(me, 'name', 'bob')
claim(me, 'x', 1306)
claim(me, 'y', 649)
///////////////////////////////////////
claim(me, 'name', 'bob the leper')
claim(me, 'x', 1726)
claim(me, 'y', 647)
when(_, 'name', 'bob', (person) => {
claim(person, 'is a leprechaun')
})
///////////////////////////////////////
claim(me, 'name', 'frog')
claim(me, 'x', 2287)
claim(me, 'y', 25)
claim(me, 'is a frog')
///////////////////////////////////////
claim(me, 'name', 'label frogs')
claim(me, 'x', 2688)
claim(me, 'y', 174)
when(_, 'is a frog', (lep) => {
claim(lep, 'wish', 'labelled', 'is a frog')
})
///////////////////////////////////////
claim(me, 'name', 'hungry snek')
claim(me, 'x', 2259)
claim(me, 'y', 334)
return //disabled
when(_, 'is a frog', (frog) => {
claim(me, 'eats', frog)
claim(frog, 'wish', 'labelled', 'ded')
claim(frog, 'wish', 'outlined', 'red')
})
///////////////////////////////////////
claim(me, 'name', 'poison frog')
claim(me, 'x', 2303)
claim(me, 'y', 624)
claim(me, 'is a frog')
claim(me, 'is poison')
///////////////////////////////////////
claim(me, 'name', 'poison behavior')
claim(me, 'x', 2660)
claim(me, 'y', 356)
when(_, 'is poison', (poison_frog) => {
when(_, 'eats', poison_frog, (snek) => {
claim(snek, 'wish', 'labelled', 'ded')
claim(snek, 'wish', 'outlined', 'red')
})
})
///////////////////////////////////////
claim(me, 'name', 'read-only renderer')
claim(me, 'x', 16)
claim(me, 'y', 258)
return //disabled
when(_, 'paper', obj =>
when(obj, 'code', _, code => {
let el = h('div', { class: 'paper' }, code)
claim('wish', 'preact render', el)
claim(obj, 'el', el)
}))
///////////////////////////////////////
claim(me, 'name', 'editable renderer')
claim(me, 'x', 87)
claim(me, 'y', 353)
when(_, 'paper', obj =>
when(obj, 'code', _, code => {
let textarea;
function save(){
textarea.lastValue = textarea.value;
papers[obj.index] = textarea.value
render()
savePapers()
}
let el = h('div', { class: 'paper' }, [
h('textarea', {
ref: e => {
textarea = e;
if(e){
if(e.lastValue == e.value || !e.value){
e.lastValue = code;
e.value = code;
}
textarea.classList.toggle('stale',
textarea.lastValue != textarea.value)
}
},
onInput: e => {
if(textarea){
textarea.classList.toggle('stale',
textarea.lastValue != textarea.value)
}
},
onKeyDown: e => {
if((e.metaKey || e.ctrlKey) && e.keyCode == 83){ // Cmd-S
e.preventDefault();
save()
}
}}),
h('button', {
onClick: e => save()
}, 'Save')
])
claim('wish', 'preact render', el)
claim(obj, 'el', el)
}))
///////////////////////////////////////
claim(me, 'name', 'preact')
claim(me, 'x', 38)
claim(me, 'y', 9)
let elements = []
when('wish', 'preact render', _, el => {
elements.push(el)
})
later(() => {
let root = document.getElementById('root');
preact.render(
h('div', { }, elements),
root,
root.firstChild)
})
///////////////////////////////////////
claim(me, 'name', 'titlebar')
claim(me, 'x', 508)
claim(me, 'y', 702)
when(_, 'name', _, (obj, name) =>
when(obj, 'el', _, el => {
let h1 = h('h1', { class: 'title' }, name)
claim(obj, 'title', h1)
el.children.unshift(h1)
}))
///////////////////////////////////////
claim(me, 'name', 'position absolute')
claim(me, 'x', 32)
claim(me, 'y', 626)
when(_, 'paper', obj =>
when(obj, 'x', _, x =>
when(obj, 'y', _, y =>
when(obj, 'el', _, el => {
el.attributes.style = {
...el.attributes.style,
position: 'absolute',
top: y,
left: x
}
}))))
///////////////////////////////////////
claim(me, 'name', 'close button')
claim(me, 'x', 504)
claim(me, 'y', 1024)
when(_, 'paper', obj =>
when(obj, 'code', _, code =>
when(obj, 'title', _, el => {
el.children.push(h('button', { style: { float: 'right' }, onClick: () => {
papers.splice(obj.index, 1)
render()
}}, '×'))
})))
///////////////////////////////////////
claim(me, 'name', 'fork button')
claim(me, 'x', 109)
claim(me, 'y', 1026)
when(_, 'paper', obj =>
when(obj, 'code', _, code =>
when(obj, 'el', _, el => {
el.children.push(h('button', {
onClick: () => {
papers.push(code
.replace(/claim\s*\(\s*me\s*,\s*'x'\s*,\s*(\d+)\)/, (a, b) => a.replace(b, +b + 20))
.replace(/claim\s*\(\s*me\s*,\s*'y'\s*,\s*(\d+)\)/, (a, b) => a.replace(b, +b + 40))
)
render()
}
}, 'Fork'))
})))
///////////////////////////////////////
claim(me, 'name', 'toggle button')
claim(me, 'x', 893)
claim(me, 'y', 1041)
when(_, 'paper', obj =>
when(obj, 'code', _, code =>
when(obj, 'title', _, el => {
let enabled = !/\n\nreturn/.test(code);
if(!enabled) claim(obj, 'disabled');
el.children.unshift(h('input', {
type: 'checkbox',
checked: enabled,
onClick: e => {
papers[obj.index] = code.replace(/\n\n+(return[^\n]*)?/,
e.target.checked ? '\n\n' : '\n\nreturn //disabled\n')
render()
}
}))
})))
///////////////////////////////////////
claim(me, 'name', 'outliner')
claim(me, 'x', 475)
claim(me, 'y', 281)
when(_, 'wish', 'outlined', _, (obj, color) =>
when(obj, 'el', _, el => {
el.attributes.style = {
...el.attributes.style,
borderColor: color,
borderWidth: 3
}
}))
///////////////////////////////////////
claim(me, 'name', 'labeler')
claim(me, 'x', 551)
claim(me, 'y', 338)
when(_, 'wish', 'labelled', _, (obj, text) =>
when(obj, 'el', _, el => {
el.children.push(h('div', { class: 'label' }, text + ''))
}))
///////////////////////////////////////
claim(me, 'name', 'draw backgrounds')
claim(me, 'x', 628)
claim(me, 'y', 414)
when(_, 'wish', 'background', _, (obj, color) =>
when(obj, 'el', _, el => {
el.attributes.style = {
...el.attributes.style,
background: color,
}
}))
///////////////////////////////////////
claim(me, 'name', 'make titlebar draggable')
claim(me, 'x', 712)
claim(me, 'y', 750)
when(_, 'title', _, (obj, h1) =>
when(obj, 'x', _, x =>
when(obj, 'y', _, y =>
when(obj, 'code', _, code => {
h1.attributes.style = {
...h1.attributes.style,
userSelect: 'none',
cursor: 'move'
}
h1.attributes.onMouseDown = e => {
document.body.onmousemove = k => {
papers[obj.index] = code
.replace(/claim\s*\(\s*me\s*,\s*'x'\s*,\s*(\d+)\)/,
(a, b) => a.replace(b, x + k.pageX - e.pageX))
.replace(/claim\s*\(\s*me\s*,\s*'y'\s*,\s*(\d+)\)/,
(a, b) => a.replace(b, y + k.pageY - e.pageY))
render()
}
document.body.onmouseup = k => {
document.body.onmousemove = null;
document.body.onmouseup = null;
savePapers()
}
}
}))))
///////////////////////////////////////
claim(me, 'name', 'thingy')
claim(me, 'x', 1484)
claim(me, 'y', 1239)
///////////////////////////////////////
claim(me, 'name', 'glow when near thingy')
claim(me, 'x', 1960)
claim(me, 'y', 1210)
when(_, 'name', 'thingy', thingy =>
when(me, 'near', thingy, 'with radius', 400, () => {
claim(me, 'wish', 'glow', 'green')
claim(me, 'wish', 'labelled', 'is near thingy')
}))
///////////////////////////////////////
claim(me, 'name', 'handle proximity')
claim(me, 'x', 2478)
claim(me, 'y', 1240)
when('when', _, 'near', _, 'with radius', _, (page, other, radius) => {
if(other === '_') return; // don't trigger for literal wildcard
//claim(page, 'wish', 'labelled', 'has a geofence')
when(page, 'x', _, page_x =>
when(page, 'y', _, page_y => { // got page xy
when(other, 'x', _, x =>
when(other, 'y', _, y => { // got obj xy
if(Math.abs(page_x - x) < radius
&& Math.abs(page_y - y) < radius){
claim(page, 'near', other, 'with radius', radius)
}
}))
}))
})
///////////////////////////////////////
claim(me, 'name', 'periodically re-render')
claim(me, 'x', 2062)
claim(me, 'y', 2181)
return //disabled
clearTimeout(window.renderTimeout)
window.renderTimeout = setTimeout(() => render(), 100)
///////////////////////////////////////
claim(me, 'name', 'current date and time')
claim(me, 'x', 2469)
claim(me, 'y', 2185)
claim(me, 'wish', 'labelled', new Date)
///////////////////////////////////////
claim(me, 'name', 'display value at whisker')
claim(me, 'x', 311)
claim(me, 'y', 1973)
when(me, 'points up at', _, page =>
when(page, 'value', _, value => {
claim(me, 'wish', 'labelled', value)
}))
///////////////////////////////////////
claim(me, 'name', 'the answer to life the universe and everything')
claim(me, 'x', 35)
claim(me, 'y', 1666)
claim(me, 'value', 42)
///////////////////////////////////////
claim(me, 'name', 'whisker behavior')
claim(me, 'x', 457)
claim(me, 'y', 1373)
const WHISKER_LENGTH = 150;
const PAPER_HEIGHT = 213;
const PAPER_WIDTH = 350;
when('when', _, 'points up at', '_', page => {
when(page, 'x', _, page_x =>
when(page, 'y', _, page_y => { // got page xy
let el = h('div', {
style: {
position: 'absolute',
top: page_y - WHISKER_LENGTH,
zIndex: -1,
left: page_x + (PAPER_WIDTH/2),
width: 5,
height: WHISKER_LENGTH,
background: '#ccc'
}
})
claim('wish', 'preact render', el)
when(_, 'x', _, (obj, x) =>
when(obj, 'y', _, y => {
if(Math.abs(page_x - x) < PAPER_WIDTH / 2
&& Math.abs(page_y - PAPER_HEIGHT - y) < WHISKER_LENGTH
&& obj !== page){
el.attributes.style = {
...el.attributes.style,
background: '#acacff'
}
claim(page, 'points up at', obj)
}
}))
}))
})
///////////////////////////////////////
claim(me, 'name', 'extract x coordinate')
claim(me, 'x', 455)
claim(me, 'y', 1686)
when(me, 'points up at', _, page =>
when(page, 'x', _, value => {
claim(me, 'value', value)
}))
///////////////////////////////////////
claim(me, 'name', 'extract y coordinate')
claim(me, 'x', 887)
claim(me, 'y', 1647)
when(me, 'points up at', _, page =>
when(page, 'y', _, value => {
claim(me, 'value', value)
}))
///////////////////////////////////////
claim(me, 'name', 'output as hue')
claim(me, 'x', 598)
claim(me, 'y', 2017)
when(me, 'points up at', _, page =>
when(page, 'value', _, value => {
claim(me, 'wish', 'background', 'hsl(' + value + ', 100%, 50%)')
}))
///////////////////////////////////////
claim(me, 'name', 'number of pages')
claim(me, 'x', 1299)
claim(me, 'y', 1653)
claim(me, 'value', papers.length)
///////////////////////////////////////
claim(me, 'name', 'UNIX timestamp')
claim(me, 'x', 1683)
claim(me, 'y', 1615)
claim(me, 'value', Date.now() / 100)
///////////////////////////////////////
claim(me, 'name', 'Slider Input')
claim(me, 'x', 2100)
claim(me, 'y', 1585)
claim(me, 'value', 24)
when(me, 'value', _, value =>
when(me, 'code', _, code =>
when(me, 'el', _, el => {
el.children.push(h('input', {
type: 'range',
value: value,
min: 0,
max: 100,
step: 1,
onInput: e => {
papers[me.index] = code
.replace(/claim\s*\(\s*me\s*,\s*'value'\s*,\s*(\d+)\)/,
(a, b) => a.replace(b, e.target.value))
render()
}
}))
})))
///////////////////////////////////////
claim(me, 'name', 'output as progress bar')
claim(me, 'x', 2108)
claim(me, 'y', 1899)
when(me, 'points up at', _, page =>
when(page, 'value', _, value =>
when(me, 'el', _, el => {
el.children.push(h('progress', {
value: value % 100,
max: 100
}))
})))
///////////////////////////////////////
claim(me, 'name', 'extract code length')
claim(me, 'x', 2471)
claim(me, 'y', 1535)
when(me, 'points up at', _, page =>
when(page, 'code', _, value => {
claim(me, 'value', value.length)
}))
///////////////////////////////////////
claim(me, 'name', 'output as my rotation')
claim(me, 'x', 1674)
claim(me, 'y', 1934)
when(me, 'points up at', _, page =>
when(page, 'value', _, value =>
when(me, 'el', _, el => {
el.attributes.style = {
...el.attributes.style,
transform: `rotate(${value}deg)`
}
})))
///////////////////////////////////////
// based on https://rsnous.com/posts/notes-from-dynamicland-geokit/
claim(me, 'name', 'Made-up claim demo')
claim(me, 'x', 3442)
claim(me, 'y', 1527)
claim(me, 'blahblahblah')
///////////////////////////////////////
// based on https://rsnous.com/posts/notes-from-dynamicland-geokit/
claim(me, 'name', 'Made-up claim when demo')
claim(me, 'x', 3488)
claim(me, 'y', 1763)
when(_, 'blahblahblah', page => {
claim(page, 'wish', 'background', '#007fff')
})
///////////////////////////////////////
claim(me, 'name', 'Button Counter')
claim(me, 'x', 2533)
claim(me, 'y', 1798)
claim(me, 'value', 8)
when(me, 'value', _, value =>
when(me, 'code', _, code =>
when(me, 'el', _, el => {
el.children.push(h('button', {
style: {
display: 'block',
fontSize: 32
},
onClick: e => {
papers[me.index] = code
.replace(/claim\s*\(\s*me\s*,\s*'value'\s*,\s*(\d+)\)/,
(a, b) => a.replace(b, (+b)+1))
render()
}
}, `Clicked ${value} times`))
})))
///////////////////////////////////////
claim(me, 'name', 'Button Counter Resetter')
claim(me, 'x', 2921)
claim(me, 'y', 1884)
claim(me, 'value', 82)
when(_, 'name', 'Button Counter', page =>
when(page, 'value', _, value =>
when(page, 'code', _, code =>
when(me, 'el', _, el => {
el.children.push(h('button', {
style: {
display: 'block',
fontSize: 32
},
onClick: e => {
papers[page.index] = code
.replace(/claim\s*\(\s*me\s*,\s*'value'\s*,\s*(\d+)\)/,
(a, b) => a.replace(b, 0))
render()
}
}, `Reset Button`))
}))))
///////////////////////////////////////
claim(me, 'name', 'display value at whisker')
claim(me, 'x', 1287)
claim(me, 'y', 1915)
when(me, 'points up at', _, page =>
when(page, 'value', _, value => {
claim(me, 'wish', 'labelled', value)
}))
///////////////////////////////////////
claim(me, 'name', 'highlight long code snippets')
claim(me, 'x', 2967)
claim(me, 'y', 1413)
when(_, 'code', _, (page, value) => {
if(value.split('\n').length > 25){
claim(page, 'wish', 'background', 'rgb(255,200,255)')
}
})
///////////////////////////////////////
claim(me, 'name', 'frog')
claim(me, 'x', 3083)
claim(me, 'y', 84)
claim(me, 'is a frog')
///////////////////////////////////////
claim(me, 'name', 'frog')
claim(me, 'x', 3103)
claim(me, 'y', 124)
claim(me, 'is a frog')
///////////////////////////////////////
claim(me, 'name', 'frog')
claim(me, 'x', 3123)
claim(me, 'y', 164)
claim(me, 'is a frog')
///////////////////////////////////////
claim(me, 'name', 'frog')
claim(me, 'x', 3143)
claim(me, 'y', 204)
claim(me, 'is a frog')
///////////////////////////////////////
claim(me, 'name', 'frog')
claim(me, 'x', 3163)
claim(me, 'y', 244)
claim(me, 'is a frog')
///////////////////////////////////////
claim(me, 'name', 'frog')
claim(me, 'x', 3191)
claim(me, 'y', 287)
claim(me, 'is a frog')
///////////////////////////////////////
claim(me, 'name', 'glower')
claim(me, 'x', 712)
claim(me, 'y', 468)
when(_, 'wish', 'glow', _, (obj, color) =>
when(obj, 'el', _, el => {
el.attributes.style = {
...el.attributes.style,
boxShadow: `0 0 30px ${color}`,
}
}))
# based on https://gist.github.com/mildred/67d22d7289ae8f16cae7
import argparse
import http.server
import os
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_PUT(self):
path = self.translate_path(self.path)
if path.endswith('/'):
self.send_response(405, "Method Not Allowed")
self.end_headers()
self.wfile.write("PUT not allowed on a directory\n".encode())
return
else:
try:
os.makedirs(os.path.dirname(path))
except FileExistsError: pass
length = int(self.headers['Content-Length'])
with open(path, 'wb') as f:
f.write(self.rfile.read(length))
self.send_response(201, "Created")
self.end_headers()
self.wfile.write(("Saved %d bytes to '%s'\n" % (length, path)).encode())
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--bind', '-b', default='', metavar='ADDRESS',
help='Specify alternate bind address '
'[default: all interfaces]')
parser.add_argument('port', action='store',
default=8000, type=int,
nargs='?',
help='Specify alternate port [default: 8000]')
args = parser.parse_args()
http.server.test(HandlerClass=HTTPRequestHandler, port=args.port, bind=args.bind)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment