From 8844d29223626240ff99a0cdaccf1ee5a36cb3f6 Mon Sep 17 00:00:00 2001 From: s-ol Date: Fri, 19 Oct 2018 01:16:10 +1100 Subject: towards self-compiling --- app/canvasapp.moon | 34 ----------- app/center_of_mass.moon | 6 +- app/color.moon | 20 ------- app/component.client.moon | 128 ---------------------------------------- app/component.server.moon | 115 ------------------------------------ app/html.client.moon | 36 ------------ app/html.server.moon | 37 ------------ app/index.moon | 87 +++++++++++++-------------- app/play_tags.moon | 85 --------------------------- app/realities.moon | 15 +---- app/tags.moon | 146 ---------------------------------------------- app/tags/init.moon | 86 +++++++++++++++++++++++++++ app/tags/tags.moon | 146 ++++++++++++++++++++++++++++++++++++++++++++++ app/test_component.moon | 2 +- app/todo.moon | 2 +- app/twisted.moon | 4 +- 16 files changed, 281 insertions(+), 668 deletions(-) delete mode 100644 app/canvasapp.moon delete mode 100644 app/color.moon delete mode 100644 app/component.client.moon delete mode 100644 app/component.server.moon delete mode 100644 app/html.client.moon delete mode 100644 app/html.server.moon delete mode 100644 app/play_tags.moon delete mode 100644 app/tags.moon create mode 100644 app/tags/init.moon create mode 100644 app/tags/tags.moon (limited to 'app') diff --git a/app/canvasapp.moon b/app/canvasapp.moon deleted file mode 100644 index f05bfec..0000000 --- a/app/canvasapp.moon +++ /dev/null @@ -1,34 +0,0 @@ -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/app/center_of_mass.moon b/app/center_of_mass.moon index 13559a1..6e81cd2 100644 --- a/app/center_of_mass.moon +++ b/app/center_of_mass.moon @@ -1,9 +1,9 @@ window = js.global document = window.document -import CanvasApp from require 'app.canvasapp' -import rgb from require 'app.color' -import h1, p, div, span, input, button from require 'app.html' +import CanvasApp from require 'lib.canvasapp' +import rgb from require 'lib.color' +import h1, p, div, span, input, button from require 'lib.html' fast = true center = true diff --git a/app/color.moon b/app/color.moon deleted file mode 100644 index 6233b47..0000000 --- a/app/color.moon +++ /dev/null @@ -1,20 +0,0 @@ -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/app/component.client.moon b/app/component.client.moon deleted file mode 100644 index c012427..0000000 --- a/app/component.client.moon +++ /dev/null @@ -1,128 +0,0 @@ -{ :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/app/component.server.moon b/app/component.server.moon deleted file mode 100644 index 456ca71..0000000 --- a/app/component.server.moon +++ /dev/null @@ -1,115 +0,0 @@ --- 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 ..= "" - 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/app/html.client.moon b/app/html.client.moon deleted file mode 100644 index 77d6c51..0000000 --- a/app/html.client.moon +++ /dev/null @@ -1,36 +0,0 @@ -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'} 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/app/html.server.moon b/app/html.server.moon deleted file mode 100644 index 3a27b37..0000000 --- a/app/html.server.moon +++ /dev/null @@ -1,37 +0,0 @@ -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 ..= "" - 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'} 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/app/index.moon b/app/index.moon index 581a4db..3f4e80c 100644 --- a/app/index.moon +++ b/app/index.moon @@ -1,53 +1,48 @@ -window = js.global -document = window.document +require = relative ... -export MODE, append, print -MODE = 'CLIENT' -append = document.body\appendChild -print = window.console\log +on_client -> + import h1, p, a, br, ul, tt, li from require 'lib.html' -import h1, p, a, br, ul, tt, li from require 'app.html' + moon = '\xe2\x98\xbd' + demos = + 'twisted': 'canvas animation', + 'todo': 'Todo demo of a simple reactive UI framework', + 'realities': 'draft of a paper on virtual and other realities', + 'center-of-mass': 'aligning characters by their centers of mass', + 'test-component': 'Test suite for the UI framework', + 'play-tags': 'Playground for Functional Tags', -moon = '\xe2\x98\xbd' -demos = - 'twisted': 'canvas animation', - 'todo': 'Todo demo of a simple reactive UI framework', - 'realities': 'draft of a paper on virtual and other realities', - 'center-of-mass': 'aligning characters by their centers of mass', - 'test-component': 'Test suite for the UI framework', - 'play-tags': 'Playground for Functional Tags', + back_button = -> + append p a { '< back', href: '/' } -back_button = -> - append p a { '< back', href: '/' } - -if window.location.search and #window.location.search > 1 - name = window.location.search\sub 2 - if demos[name] - back_button! - filename = name\gsub '-', '_' - require "app.#{filename}" + if window.location.search and #window.location.search > 1 + name = window.location.search\sub 2 + if demos[name] + back_button! + filename = name\gsub '-', '_' + require "app.#{filename}" + else + append h1 'are you lost?' + append p a { '(go home)', href: '/' } else - append h1 'are you lost?' - append p a { '(go home)', href: '/' } -else - append h1 'mmm' + append h1 'mmm' + append p { + tt 'mmm' + ' is not the ' + tt 'www' + ', because it runs on ' + a { 'MoonScript', href: 'https://moonscript.org' } + '.' + br! + 'You can find the source code of everything ' + a { 'here', href: 'https://github.com/s-ol/mmm' } + '.' + } + append p 'current demos:' + append ul for name, desc in pairs demos + li (a name, href: "/?#{name}"), ': ', desc + append p { - tt 'mmm' - ' is not the ' - tt 'www' - ', because it runs on ' - a { 'MoonScript', href: 'https://moonscript.org' } - '.' - br! - 'You can find the source code of everything ' - a { 'here', href: 'https://github.com/s-ol/mmm' } - '.' + "made with #{moon} by " + a { 's-ol', href: 'https://twitter.com/S0lll0s' } } - append p 'current demos:' - append ul for name, desc in pairs demos - li (a name, href: "/?#{name}"), ': ', desc - -append p { - "made with #{moon} by " - a { 's-ol', href: 'https://twitter.com/S0lll0s' } -} diff --git a/app/play_tags.moon b/app/play_tags.moon deleted file mode 100644 index 088f429..0000000 --- a/app/play_tags.moon +++ /dev/null @@ -1,85 +0,0 @@ -import add_tag, rmv_tag, Node, Hierarchy, Toggle, NamespacedToggle from require 'app.tags' -import ReactiveVar, append, tohtml, text, div, form, span, h3, a, input, textarea, button from require 'app.component' - -clone = (set) -> - assert set and 'table' == (type set), 'not a set' - { k,v for k,v in pairs set } -set_append = (val) -> (set) -> - with copy = clone set - copy[val] = val -set_remove = (val) -> (set) -> - with copy = clone set - copy[val] = nil -set_join = (tbl, sep=' ') -> - ret = '' - for tag in pairs tbl - ret ..= (tostring tag) .. sep - ret - -entries = div! -append entries - -class ReactiveNode extends Node - new: (...) => - super ... - @tags = ReactiveVar @tags - @_node = div { - span @name, style: { 'font-weight': 'bold' }, - @tags\map (tags) -> with div! - for tag,_ in pairs tags - \append a (text tag), href: '#', style: { - display: 'inline-block', - margin: '0 5px', - } - } - @node = tohtml @_node - - has: (tag) => @tags\get![tag] - add: (tag) => @tags\transform set_append tag - rmv: (tag) => @tags\transform set_remove tag - -rules = { - Hierarchy 'home', 'sol' - Hierarchy 'sol', 'desktop' - Hierarchy 'desktop', 'vacation' - Hierarchy 'desktop', 'documents' - NamespacedToggle 'documents', 'work', 'personal' - -- Toggle 'work', 'personal' - -- Hierarchy 'documents', 'work' - -- Hierarchy 'documents', 'personal' -} - -pictures = for i=1,10 - with node = ReactiveNode "picture#{i}.jpg" - entries\append node - add_tag node, 'vacation' - -pers = ReactiveNode 'mypersonalfile.doc' -entries\append pers - -append div do - yield = coroutine.yield - step = coroutine.wrap -> - yield "mark document" - add_tag pers, 'documents' - - yield "mark personal" - add_tag pers, 'personal' - - yield "mark work" - add_tag pers, 'work' - - yield "unmark work" - rmv_tag pers, 'work' - - yield "remove from documents" - rmv_tag pers, 'documents' - - yield false - - next_step = ReactiveVar step! - next_step\map (desc) -> - if desc - button (text desc), onclick: (e) => next_step\set step! - else - text '' diff --git a/app/realities.moon b/app/realities.moon index 78db6e6..773f62f 100644 --- a/app/realities.moon +++ b/app/realities.moon @@ -1,17 +1,4 @@ -import append, h1, h2, p, a, i, div, ol, li, br, hr, span, button, section, article from require 'app.component' - -on_client = switch MODE - when 'SERVER' - import compile from require 'duct_tape' - (fn, ...) -> - -- warn code - append "" - when 'CLIENT' - (fn, ...) -> fn ... +import append, h1, h2, p, a, i, div, ol, li, br, hr, span, button, section, article from require 'lib.component' if MODE == 'CLIENT' require 'svg.js' diff --git a/app/tags.moon b/app/tags.moon deleted file mode 100644 index 198392e..0000000 --- a/app/tags.moon +++ /dev/null @@ -1,146 +0,0 @@ -join = (tbl, sep) -> - ret = '' - for tag in pairs tbl - ret ..= (tostring tag) .. sep - ret - -handlers = { - add: {} - rmv: {} -} - -class Node - new: (@name) => - @tags = {} - - inspect: => - "#{@name}: [#{join @tags, ' '}]" - - has: (tag) => @tags[tag] - add: (tag) => @tags[tag] = tag - rmv: (tag) => @tags[tag] = nil - -any = -> true -literal = (def) -> (val) -> def == val -oneof = (defs) -> (val) -> - for def in *defs - return true if def == val - false -has = (tag) -> (node) -> node\has tag - -add_tag = (node, tag) -> - return if node\has tag - node\add tag - for hand, _ in pairs handlers.add - hand\match node, tag - -rmv_tag = (node, tag) -> - return if not node\has tag - node\rmv tag - for hand, _ in pairs handlers.rmv - hand\match node, tag - -class Handler - new: (@rule, @action, match, @func) => - @args = for arg in *match - if 'string' == type arg - literal arg - elseif 'table' == type arg - oneof arg - else - arg - - match: (...) => - supplied = { ... } - assert #supplied == #@args, 'length of arguments doesnt match' - for i = 1, #supplied - return false if not @args[i] supplied[i] - - @.func @rule, ... - - name: => "#{@rule.name}:#{@action}" - -class Rule - new: (@name="#{@@__name}") => - @owned_handlers = {} - - hook: (action, ...) => - handler = Handler @, action, ... - table.insert @owned_handlers, handler - handlers[action][handler] = handler - - destroy: => - for hand in *@owned_handlers - handlers[hand.action][hand] = nil - -class Hierarchy extends Rule - new: (@parent, @child) => - super! - - -- when something is tagged with the child-tag, apply the parent tag - @hook 'add', { any, @child }, (node) => - add_tag node, @parent - - -- when child tag is removed, remove parent tag - @hook 'rmv', { any, @child }, (node) => - rmv_tag node, @parent - - -- when parent tag is removed, remove child tag - @hook 'rmv', { any, @parent }, (node) => - rmv_tag node, @child - -class Toggle extends Rule - new: (@a, @b) => - super! - - either = { @a, @b } - opposite = (tag) -> if tag == @a then @b else @a - - -- when a is added, remove b and vice-versa - @hook 'add', { any, either }, (node, tag) => - rmv_tag node, opposite tag - - -- when a is removed, add b and vice-versa - @hook 'rmv', { any, either }, (node, tag) => - add_tag node, opposite tag - -class NamespacedToggle extends Rule - new: (@ns, @a, @b) => - super! - - namespaced = has @ns - either = { @a, @b } - opposite = (tag) -> if tag == @a then @b else @a - - -- when node enters namespace, add default tag - @hook 'add', { any, @ns }, (node) => - add_tag node, @a - - -- when node leaves namespace, remove tags - @hook 'rmv', { any, @ns }, (node) => - rmv_tag node, @a - rmv_tag node, @b - - -- when a is added, remove b and vice-versa - @hook 'add', { namespaced, either }, (node, tag) => - rmv_tag node, opposite tag - - -- when a is removed, add b and vice-versa - @hook 'rmv', { namespaced, either }, (node, tag) => - add_tag node, opposite tag - -{ - :Node, - :Rule, - - :any, - :literal, - :oneof, - :has, - :add_tag, - :rmv_tag, - - :Hierarchy, - :Toggle, - :NamespacedToggle, -} diff --git a/app/tags/init.moon b/app/tags/init.moon new file mode 100644 index 0000000..9cbe53f --- /dev/null +++ b/app/tags/init.moon @@ -0,0 +1,86 @@ +require = relative ... +import add_tag, rmv_tag, Node, Hierarchy, Toggle, NamespacedToggle from require '.tags' +import ReactiveVar, append, tohtml, text, div, form, span, h3, a, input, textarea, button from require 'lib.component' + +clone = (set) -> + assert set and 'table' == (type set), 'not a set' + { k,v for k,v in pairs set } +set_append = (val) -> (set) -> + with copy = clone set + copy[val] = val +set_remove = (val) -> (set) -> + with copy = clone set + copy[val] = nil +set_join = (tbl, sep=' ') -> + ret = '' + for tag in pairs tbl + ret ..= (tostring tag) .. sep + ret + +entries = div! +append entries + +class ReactiveNode extends Node + new: (...) => + super ... + @tags = ReactiveVar @tags + @_node = div { + span @name, style: { 'font-weight': 'bold' }, + @tags\map (tags) -> with div! + for tag,_ in pairs tags + \append a (text tag), href: '#', style: { + display: 'inline-block', + margin: '0 5px', + } + } + @node = tohtml @_node + + has: (tag) => @tags\get![tag] + add: (tag) => @tags\transform set_append tag + rmv: (tag) => @tags\transform set_remove tag + +rules = { + Hierarchy 'home', 'sol' + Hierarchy 'sol', 'desktop' + Hierarchy 'desktop', 'vacation' + Hierarchy 'desktop', 'documents' + NamespacedToggle 'documents', 'work', 'personal' + -- Toggle 'work', 'personal' + -- Hierarchy 'documents', 'work' + -- Hierarchy 'documents', 'personal' +} + +pictures = for i=1,10 + with node = ReactiveNode "picture#{i}.jpg" + entries\append node + add_tag node, 'vacation' + +pers = ReactiveNode 'mypersonalfile.doc' +entries\append pers + +append div do + yield = coroutine.yield + step = coroutine.wrap -> + yield "mark document" + add_tag pers, 'documents' + + yield "mark personal" + add_tag pers, 'personal' + + yield "mark work" + add_tag pers, 'work' + + yield "unmark work" + rmv_tag pers, 'work' + + yield "remove from documents" + rmv_tag pers, 'documents' + + yield false + + next_step = ReactiveVar step! + next_step\map (desc) -> + if desc + button (text desc), onclick: (e) => next_step\set step! + else + text '' diff --git a/app/tags/tags.moon b/app/tags/tags.moon new file mode 100644 index 0000000..198392e --- /dev/null +++ b/app/tags/tags.moon @@ -0,0 +1,146 @@ +join = (tbl, sep) -> + ret = '' + for tag in pairs tbl + ret ..= (tostring tag) .. sep + ret + +handlers = { + add: {} + rmv: {} +} + +class Node + new: (@name) => + @tags = {} + + inspect: => + "#{@name}: [#{join @tags, ' '}]" + + has: (tag) => @tags[tag] + add: (tag) => @tags[tag] = tag + rmv: (tag) => @tags[tag] = nil + +any = -> true +literal = (def) -> (val) -> def == val +oneof = (defs) -> (val) -> + for def in *defs + return true if def == val + false +has = (tag) -> (node) -> node\has tag + +add_tag = (node, tag) -> + return if node\has tag + node\add tag + for hand, _ in pairs handlers.add + hand\match node, tag + +rmv_tag = (node, tag) -> + return if not node\has tag + node\rmv tag + for hand, _ in pairs handlers.rmv + hand\match node, tag + +class Handler + new: (@rule, @action, match, @func) => + @args = for arg in *match + if 'string' == type arg + literal arg + elseif 'table' == type arg + oneof arg + else + arg + + match: (...) => + supplied = { ... } + assert #supplied == #@args, 'length of arguments doesnt match' + for i = 1, #supplied + return false if not @args[i] supplied[i] + + @.func @rule, ... + + name: => "#{@rule.name}:#{@action}" + +class Rule + new: (@name="#{@@__name}") => + @owned_handlers = {} + + hook: (action, ...) => + handler = Handler @, action, ... + table.insert @owned_handlers, handler + handlers[action][handler] = handler + + destroy: => + for hand in *@owned_handlers + handlers[hand.action][hand] = nil + +class Hierarchy extends Rule + new: (@parent, @child) => + super! + + -- when something is tagged with the child-tag, apply the parent tag + @hook 'add', { any, @child }, (node) => + add_tag node, @parent + + -- when child tag is removed, remove parent tag + @hook 'rmv', { any, @child }, (node) => + rmv_tag node, @parent + + -- when parent tag is removed, remove child tag + @hook 'rmv', { any, @parent }, (node) => + rmv_tag node, @child + +class Toggle extends Rule + new: (@a, @b) => + super! + + either = { @a, @b } + opposite = (tag) -> if tag == @a then @b else @a + + -- when a is added, remove b and vice-versa + @hook 'add', { any, either }, (node, tag) => + rmv_tag node, opposite tag + + -- when a is removed, add b and vice-versa + @hook 'rmv', { any, either }, (node, tag) => + add_tag node, opposite tag + +class NamespacedToggle extends Rule + new: (@ns, @a, @b) => + super! + + namespaced = has @ns + either = { @a, @b } + opposite = (tag) -> if tag == @a then @b else @a + + -- when node enters namespace, add default tag + @hook 'add', { any, @ns }, (node) => + add_tag node, @a + + -- when node leaves namespace, remove tags + @hook 'rmv', { any, @ns }, (node) => + rmv_tag node, @a + rmv_tag node, @b + + -- when a is added, remove b and vice-versa + @hook 'add', { namespaced, either }, (node, tag) => + rmv_tag node, opposite tag + + -- when a is removed, add b and vice-versa + @hook 'rmv', { namespaced, either }, (node, tag) => + add_tag node, opposite tag + +{ + :Node, + :Rule, + + :any, + :literal, + :oneof, + :has, + :add_tag, + :rmv_tag, + + :Hierarchy, + :Toggle, + :NamespacedToggle, +} diff --git a/app/test_component.moon b/app/test_component.moon index a8f8934..78556a2 100644 --- a/app/test_component.moon +++ b/app/test_component.moon @@ -1,4 +1,4 @@ -import div, h1, ul, li, pre from require 'app.html' +import div, h1, ul, li, pre from require 'lib.html' last = nil test_group = (name) -> diff --git a/app/todo.moon b/app/todo.moon index 0310e0d..2173087 100644 --- a/app/todo.moon +++ b/app/todo.moon @@ -1,4 +1,4 @@ -import ReactiveVar, append, text, div, form, span, h3, a, input, textarea, button from require 'app.component' +import ReactiveVar, append, text, div, form, span, h3, a, input, textarea, button from require 'lib.component' parent = div! todoItem = (desc, done) -> diff --git a/app/twisted.moon b/app/twisted.moon index 1bc7a20..5c34914 100644 --- a/app/twisted.moon +++ b/app/twisted.moon @@ -3,8 +3,8 @@ document = window.document Math = window.Math -import CanvasApp from require 'app.canvasapp' -import hsl from require 'app.color' +import CanvasApp from require 'lib.canvasapp' +import hsl from require 'lib.color' class TwistedDemo extends CanvasApp width: 500 -- cgit v1.2.3