aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2018-10-18 14:16:10 +0000
committers-ol <s-ol@users.noreply.github.com>2018-10-18 14:16:10 +0000
commit8844d29223626240ff99a0cdaccf1ee5a36cb3f6 (patch)
tree1740fd0291959aa8d3d0b266158d7eaaa685224d /lib
parentupdate dependencies (diff)
downloadmmm-8844d29223626240ff99a0cdaccf1ee5a36cb3f6.tar.gz
mmm-8844d29223626240ff99a0cdaccf1ee5a36cb3f6.zip
towards self-compiling
Diffstat (limited to 'lib')
-rw-r--r--lib/canvasapp.shared.moon34
-rw-r--r--lib/color.shared.moon20
-rw-r--r--lib/component.client.moon128
-rw-r--r--lib/component.server.moon115
-rw-r--r--lib/html.client.moon36
-rw-r--r--lib/html.server.moon37
6 files changed, 370 insertions, 0 deletions
diff --git a/lib/canvasapp.shared.moon b/lib/canvasapp.shared.moon
new file mode 100644
index 0000000..f05bfec
--- /dev/null
+++ b/lib/canvasapp.shared.moon
@@ -0,0 +1,34 @@
+window = js.global
+
+class CanvasApp
+ width: 500
+ height: 400
+ new: =>
+ @canvas = window.document\createElement 'canvas'
+ @canvas.width, @canvas.height = @width, @height
+ @ctx = @canvas\getContext '2d'
+ @time = 0
+
+ @canvas.tabIndex = 0
+ @canvas\addEventListener 'click', (_, e) -> @click and @click e.offsetX, e.offsetY, e.button
+ @canvas\addEventListener 'keydown', (_, e) -> @keydown and @keydown e.key, e.code
+
+ lastMillis = window.performance\now!
+ t = @
+ animationFrame = (_, millis) ->
+ if not @paused
+ @update (millis - lastMillis) / 1000
+ @ctx\resetTransform!
+ @draw!
+ window\setTimeout (->
+ window\requestAnimationFrame animationFrame
+ ), 0
+ lastMillis = millis
+ window\requestAnimationFrame animationFrame
+
+ update: (dt) =>
+ @time += dt
+
+{
+ :CanvasApp
+}
diff --git a/lib/color.shared.moon b/lib/color.shared.moon
new file mode 100644
index 0000000..6233b47
--- /dev/null
+++ b/lib/color.shared.moon
@@ -0,0 +1,20 @@
+rgb = (r, g, b) ->
+ r, g, b = table.unpack r if 'table' == type r
+ "rgb(#{r * 255}, #{g * 255}, #{b * 255})"
+
+rgba = (r, g, b, a) ->
+ r, g, b, a = table.unpack r if 'table' == type r
+ "rgba(#{r * 255}, #{g * 255}, #{b * 255}, #{a or 1})"
+
+hsl = (h, s, l) ->
+ h, s, l = table.unpack h if 'table' == type h
+ "hsl(#{h * 360}, #{s * 100}%, #{l * 100}%)"
+
+hsla = (h, s, l, a) ->
+ h, s, l, a = table.unpack h if 'table' == type h
+ "hsla(#{h * 360}, #{s * 100}%, #{l * 100}%, #{a or 1})"
+
+{
+ :rgb, :rgba,
+ :hsl, :hsla,
+}
diff --git a/lib/component.client.moon b/lib/component.client.moon
new file mode 100644
index 0000000..c012427
--- /dev/null
+++ b/lib/component.client.moon
@@ -0,0 +1,128 @@
+{ :document } = js.global
+
+-- convert anything to a DOM Node
+-- val must be one of:
+-- * DOM Node (instanceof window.Node)
+-- * MMMElement (have a .node value that is instanceof window.Node)
+-- * string
+-- note that strings won't survive identity comparisons after tohtml
+tohtml = (val) ->
+ if 'string' == type val
+ return document\createTextNode val
+ if 'table' == type val
+ assert val.node, "Table doesn't have .node"
+ val = val.node
+ if 'userdata' == type val
+ assert (js.instanceof val, js.global.Node), "userdata is not a Node"
+ val
+ else
+ error "not a Node: #{val}, #{type val}"
+
+-- overloaded append
+-- see tohtml for acceptable values
+g_append = append
+append = (value) -> g_append tohtml value
+
+-- shorthand to form a text node from strings
+text = (...) -> document\createTextNode table.concat { ... }, ' '
+
+class ReactiveVar
+ @isinstance: (val) -> 'table' == (type val) and val.subscribe
+
+ new: (@value) =>
+ @listeners = setmetatable {}, __mode: 'kv'
+
+ set: (value) =>
+ old = @value
+ @value = value
+ for k, callback in pairs @listeners
+ callback @value, old
+
+ get: => @value
+
+ transform: (transform) => @set transform @get!
+
+ subscribe: (callback) =>
+ with -> @listeners[callback] = nil
+ @listeners[callback] = callback
+
+ map: (transform) =>
+ with ReactiveVar transform @value
+ .upstream = @subscribe (...) -> \set transform ...
+
+class ReactiveElement
+ @isinstance: (val) -> 'table' == (type val) and val.node
+
+ new: (element, ...) =>
+ @node = document\createElement element
+ @_subscriptions = {}
+
+ children = { ... }
+ -- attributes are last arguments but mustn't be a ReactiveVar
+ attributes = children[#children]
+ if 'table' == (type attributes) and
+ (not ReactiveElement.isinstance attributes) and
+ (not ReactiveVar.isinstance attributes)
+ table.remove children
+ else
+ attributes = {}
+
+ for k,v in pairs attributes
+ @set k, v if 'string' == type k
+
+ -- if there is only one argument,
+ -- children can be in attributes table too
+ if #children == 0
+ children = attributes
+
+ for child in *children
+ @append child
+
+ destroy: =>
+ for unsub in *@_subscriptions do unsub!
+
+ set: (attr, value) =>
+ if 'table' == (type value) and ReactiveVar.isinstance value
+ table.insert @_subscriptions, value\subscribe (...) -> @set attr, ...
+ value = value\get!
+
+ if attr == 'style' and 'table' == type value
+ for k,v in pairs value
+ @node.style[k] = v
+ return
+
+ @node[attr] = value
+
+ append: (child, last) =>
+ if ReactiveVar.isinstance child
+ table.insert @_subscriptions, child\subscribe (...) -> @append ...
+ child = child\get!
+
+ if 'string' == type last
+ error 'cannot replace string node'
+
+ child = tohtml child
+ ok, last = pcall tohtml, last
+ if ok
+ @node\replaceChild child, last
+ else
+ @node\appendChild child
+
+ remove: (child) =>
+ @node\removeChild tohtml child
+ if 'table' == (type child) and child.destroy
+ child\destroy!
+
+with exports = {
+ :ReactiveVar,
+ :ReactiveElement,
+ :tohtml,
+ :append,
+ :text,
+ }
+ add = (e) -> exports[e] = (...) -> ReactiveElement e, ...
+
+ for e in *{'div', 'form', 'span', 'a', 'p', 'button', 'ul', 'ol', 'li', 'i', 'b', 'u', 'tt'} do add e
+ for e in *{'article', 'section', 'header', 'footer', 'content'} do add e
+ for e in *{'br', 'hr', 'img', 'input', 'p', 'textarea'} do add e
+ for i=1,8 do add "h" .. i
diff --git a/lib/component.server.moon b/lib/component.server.moon
new file mode 100644
index 0000000..456ca71
--- /dev/null
+++ b/lib/component.server.moon
@@ -0,0 +1,115 @@
+-- convert anything to HTML string
+-- val must be one of:
+-- * MMMElement (have a .render method that returns a string)
+-- * string
+tohtml = (val) ->
+ if 'table' == type val
+ assert val.render, "Table doesn't have .render"
+ val = val\render!
+ if 'string' == type val
+ val
+ else
+ error "not a Node: #{val}, #{type val}"
+
+-- overloaded append
+-- see tohtml for acceptable values
+g_append = append
+append = (value) -> g_append tohtml value
+
+-- shorthand to form a text node from strings
+text = (...) -> table.concat { ... }, ' '
+
+class ReactiveVar
+ @isinstance: (val) -> 'table' == (type val) and val.subscribe
+
+ new: (@value) =>
+
+ set: (value) =>
+ error "attempting to update ReactiveVar serverside"
+
+ get: => @value
+
+ subscribe: (callback) =>
+ error "attempting to subscribe to ReactiveVar serverside"
+
+ map: (transform) =>
+ ReactiveVar transform @value
+
+class ReactiveElement
+ @isinstance: (val) -> 'table' == (type val) and val.render
+
+ new: (element, ...) =>
+ @element = element
+ @attrs = { style: {} }
+ @children = {}
+
+ children = { ... }
+
+ -- attributes are last arguments but mustn't be a ReactiveVar
+ attributes = children[#children]
+ if 'table' == (type attributes) and
+ (not ReactiveElement.isinstance attributes) and
+ (not ReactiveVar.isinstance attributes)
+ table.remove children
+ else
+ attributes = {}
+
+ for k,v in pairs attributes
+ @set k, v if 'string' == type k
+
+ -- if there is only one argument,
+ -- children can be in attributes table too
+ if #children == 0
+ children = attributes
+
+ for child in *children
+ @append child
+
+ destroy: =>
+
+ set: (attr, value) =>
+ if attr == 'style' and 'table' == type value
+ for k,v in pairs value
+ @attrs.style[k] = v
+ return
+
+ @attrs[attr] = value
+
+ append: (child, last) =>
+ assert not last, "last passed to append on server"
+ if ReactiveVar.isinstance child
+ child = child\get!
+
+ child = tohtml child
+ table.insert @children, child
+
+ remove: (child) =>
+ error "remove called serverside"
+
+ render: =>
+ b = "<#{@element}"
+ for k,v in pairs @attrs
+ if 'table' == type v
+ tmp = ''
+ for kk, vv in pairs v
+ tmp ..= "#{kk}: #{vv}; "
+ v = tmp
+ b ..= " #{k}=\"#{v}\""
+ b ..= ">" .. table.concat @children, ''
+ b ..= "</#{@element}>"
+ b
+
+with exports = {
+ :ReactiveVar,
+ :ReactiveElement,
+ :tohtml,
+ :flush,
+ :append,
+ :text,
+ }
+ add = (e) -> exports[e] = (...) -> ReactiveElement e, ...
+
+ for e in *{'div', 'form', 'span', 'a', 'p', 'button', 'ul', 'ol', 'li', 'i', 'b', 'u', 'tt'} do add e
+ for e in *{'article', 'section', 'header', 'footer', 'content'} do add e
+ for e in *{'br', 'hr', 'img', 'input', 'p', 'textarea'} do add e
+ for i=1,8 do add "h" .. i
diff --git a/lib/html.client.moon b/lib/html.client.moon
new file mode 100644
index 0000000..2538f8d
--- /dev/null
+++ b/lib/html.client.moon
@@ -0,0 +1,36 @@
+document = js.global.document
+
+element = (element) -> (...) ->
+ children = { ... }
+
+ -- attributes are last arguments but mustn't be a ReactiveVar
+ attributes = children[#children]
+ if 'table' == (type attributes) and not attributes.node
+ table.remove children
+ else
+ attributes = {}
+
+ with e = document\createElement element
+ for k,v in pairs attributes
+ e[k] = v
+
+ -- if there is only one argument,
+ -- children can be in attributes table too
+ if #children == 0
+ children = attributes
+
+ for child in *children
+ if 'string' == type child
+ e.innerHTML ..= child
+ else
+ e\appendChild child
+
+elements = {}
+add = (e) -> elements[e] = element e
+
+for e in *{'div', 'form', 'span', 'a', 'p', 'button', 'ul', 'ol', 'li', 'i', 'b', 'u', 'tt'} do add e
+for e in *{'article', 'section', 'header', 'footer', 'content', 'pre'} do add e
+for e in *{'br', 'hr', 'img', 'input', 'p', 'textarea'} do add e
+for i=1,8 do add "h" .. i
+
+elements
diff --git a/lib/html.server.moon b/lib/html.server.moon
new file mode 100644
index 0000000..bbeaa9d
--- /dev/null
+++ b/lib/html.server.moon
@@ -0,0 +1,37 @@
+element = (element) -> (...) ->
+ children = { ... }
+
+ -- attributes are last arguments but mustn't be a ReactiveVar
+ attributes = children[#children]
+ if 'table' == (type attributes) and not attributes.node
+ table.remove children
+ else
+ attributes = {}
+
+ b = "<#{element}"
+ for k,v in pairs attributes
+ if 'table' == type v
+ tmp = ''
+ for kk, vv in pairs v
+ tmp ..= "#{kk}: #{vv}; "
+ v = tmp
+ b ..= " #{k}=\"#{v}\""
+
+ -- if there is only one argument,
+ -- children can be in attributes table too
+ if #children == 0
+ children = attributes
+
+ b ..= ">" .. table.concat children, ''
+ b ..= "</#{element}>"
+ b
+
+elements = {}
+add = (e) -> elements[e] = element e
+
+for e in *{'div', 'form', 'span', 'a', 'p', 'button', 'ul', 'ol', 'li', 'i', 'b', 'u', 'tt'} do add e
+for e in *{'article', 'section', 'header', 'footer', 'content', 'pre'} do add e
+for e in *{'br', 'hr', 'img', 'input', 'p', 'textarea'} do add e
+for i=1,8 do add "h" .. i
+
+elements