added .component macro to karax
This commit is contained in:
parent
1c242332da
commit
a799413f7f
6 changed files with 169 additions and 186 deletions
|
|
@ -1,17 +1,14 @@
|
|||
|
||||
import vdom, karax, karaxdsl, jdict, jstrutils
|
||||
import vdom, karax, karaxdsl, jdict, jstrutils, components
|
||||
|
||||
type
|
||||
Filter = enum
|
||||
all, active, completed
|
||||
|
||||
const
|
||||
WindowSize = 50 # overestimating it does not harm
|
||||
|
||||
var
|
||||
entries: seq[(cstring, bool)]
|
||||
visibleA = 0
|
||||
visibleB = 10_000
|
||||
visibleB = 10
|
||||
selectedEntry = -1
|
||||
filter: Filter
|
||||
|
||||
|
|
@ -52,34 +49,15 @@ proc toChecked(checked: bool): cstring =
|
|||
proc selected(v: Filter): cstring =
|
||||
(if filter == v: cstring"selected" else: cstring(nil))
|
||||
|
||||
when false:
|
||||
proc entry(id: int, d: cstring; completed, selected: bool): proc (): VNode =
|
||||
var lastid: type(id)
|
||||
var lastd: type(d)
|
||||
var lastCompleted: type(completed)
|
||||
var lastSelected: type(selected)
|
||||
proc inner(): VNode =
|
||||
if lastid != id or lastd != d or lastCompleted != completed or lastSelected != selected:
|
||||
result = logic(id, d, completed, selected)
|
||||
lastid = id
|
||||
lastd = d
|
||||
lastCompleted = completed
|
||||
lastSelected = selected
|
||||
|
||||
result = inner
|
||||
|
||||
proc lazyEntry(args: seq[VNode]): VNode =
|
||||
let id = args[0].intValue
|
||||
let d = args[1].text
|
||||
let completed = args[2].intValue != 0
|
||||
let selected = args[3].intValue != 0
|
||||
proc createEntry(id: int; d: cstring;
|
||||
completed: bool, selected: bool): VNode {.component.} =
|
||||
result = buildHtml(tr):
|
||||
li(class=toClass(completed), key = id):
|
||||
if not selected:
|
||||
tdiv(class = "view"):
|
||||
input(class = "toggle", `type` = "checkbox", checked = toChecked(completed),
|
||||
onclick=toggleEntry, key = id)
|
||||
label(onDblClick=editHandler, key = id):
|
||||
label(onclick=editHandler, key = id):
|
||||
text d
|
||||
button(class = "destroy", key = id, onclick = removeHandler)
|
||||
else:
|
||||
|
|
@ -87,9 +65,6 @@ proc lazyEntry(args: seq[VNode]): VNode =
|
|||
onfocusLost = focusLost,
|
||||
onenter = editEntry, value = d, setFocus)
|
||||
|
||||
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode =
|
||||
result = thunk(lazyEntry, id, d, completed, selected)
|
||||
|
||||
proc myscroll(ev: Event; n: VNode) =
|
||||
kout cstring"scrolling event here"
|
||||
|
||||
|
|
@ -105,10 +80,6 @@ proc createDom(): VNode =
|
|||
input(class = "toggle-all", `type` = "checkbox", name = "toggle")
|
||||
label(`for` = "toggle-all", onclick = onAllDone):
|
||||
text "Mark all as complete"
|
||||
tdiv(onscroll = myscroll, style = "overflow: visible; width: 0px; display: block; max-height: 1em"):
|
||||
for i in 0..4:
|
||||
tdiv:
|
||||
text entries[i][0]
|
||||
|
||||
var entriesCount = 0
|
||||
var completedCount = 0
|
||||
|
|
|
|||
103
src/components.nim
Normal file
103
src/components.nim
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
## Components in Karax are built by the ``.component`` macro annotation.
|
||||
|
||||
import macros, jdict, dom, vdom, tables, strutils
|
||||
|
||||
var
|
||||
vcomponents* = newJDict[cstring, proc(args: seq[VNode]): VNode]()
|
||||
dcomponents* = newJDict[cstring, proc(args: seq[VNode]): Node]()
|
||||
|
||||
type
|
||||
ComponentKind* {.pure.} = enum
|
||||
None,
|
||||
VNode,
|
||||
Node
|
||||
|
||||
var
|
||||
allcomponents {.compileTime.} = initTable[string, ComponentKind]()
|
||||
|
||||
proc isComponent*(x: string): ComponentKind {.compileTime.} =
|
||||
allcomponents.getOrDefault(x)
|
||||
|
||||
proc unpack(symbolicType: NimNode; index: int): NimNode {.compileTime.} =
|
||||
#let t = symbolicType.getTypeImpl
|
||||
let t = repr(symbolicType)
|
||||
case t
|
||||
of "cstring":
|
||||
result = quote do:
|
||||
args[`index`].text
|
||||
of "int", "VKey":
|
||||
result = quote do:
|
||||
args[`index`].intValue
|
||||
of "bool":
|
||||
result = quote do:
|
||||
args[`index`].intValue != 0
|
||||
elif t.endsWith"Kind":
|
||||
result = quote do:
|
||||
`symbolicType`(args[`index`].intValue)
|
||||
else:
|
||||
# just pass it along, maybe there is some conversion for it:
|
||||
result = quote do:
|
||||
args[`index`]
|
||||
|
||||
proc newname*(n: NimNode): NimNode =
|
||||
if n.kind == nnkPostfix:
|
||||
n[1] = newname(n[1])
|
||||
result = n
|
||||
elif n.kind == nnkSym:
|
||||
result = ident($n.symbol)
|
||||
else:
|
||||
result = n
|
||||
|
||||
macro component*(prc: untyped): untyped =
|
||||
## A component takes an proc body and registers it as a component to the
|
||||
## virtual dom.
|
||||
var n = prc.copyNimNode
|
||||
for i in 0..6: n.add prc[i].copyNimTree
|
||||
expectKind(n, nnkProcDef)
|
||||
if n[0].kind == nnkEmpty:
|
||||
error("please pass a non anonymous proc")
|
||||
let name = n[0]
|
||||
let params = params(n)
|
||||
let rettype = repr params[0]
|
||||
var isvirtual = ComponentKind.None
|
||||
if rettype == "VNode":
|
||||
isvirtual = ComponentKind.VNode
|
||||
elif rettype == "Node":
|
||||
isvirtual = ComponentKind.Node
|
||||
else:
|
||||
error "component must return VNode or Node"
|
||||
n[0] = ident("inner" & $name)
|
||||
var unpackCall = newCall(n[0])
|
||||
var counter = 0
|
||||
for i in 1.. <params.len:
|
||||
let param = params[i]
|
||||
let L = param.len
|
||||
let typ = param[L-2]
|
||||
for j in 0 .. L-3:
|
||||
unpackCall.add unpack(typ, counter)
|
||||
inc counter
|
||||
|
||||
template vwrapper(wname, nameStrLit, unpackCall) {.dirty.} =
|
||||
proc wname*(args: seq[VNode]): VNode =
|
||||
unpackCall
|
||||
vcomponents[cstring(nameStrLit)] = wname
|
||||
|
||||
template dwrapper(wname, nameStrLit, unpackCall) {.dirty.} =
|
||||
proc wname(args: seq[VNode]): Node =
|
||||
unpackCall
|
||||
dcomponents[cstring(nameStrLit)] = wname
|
||||
|
||||
result = newTree(nnkStmtList, n)
|
||||
if isvirtual == ComponentKind.VNode:
|
||||
result.add getAst(vwrapper(newname name, newLit($name), unpackCall))
|
||||
else:
|
||||
result.add getAst(dwrapper(newname name, newLit($name), unpackCall))
|
||||
allcomponents[$name] = isvirtual
|
||||
when defined(debugKaraxDsl):
|
||||
echo repr result
|
||||
|
||||
when isMainModule:
|
||||
proc foo(x, y: int, b: bool; s: cstring): VNode {.component.} =
|
||||
discard
|
||||
|
||||
|
||||
|
|
@ -520,6 +520,8 @@ proc item*(list: TouchList, i: int): Touch
|
|||
|
||||
{.pop.}
|
||||
|
||||
proc setAttr*(n: Node; key, val: cstring) {.importcpp: "#.setAttribute(@)".}
|
||||
|
||||
var
|
||||
window* {.importc, nodecl.}: Window
|
||||
document* {.importc, nodecl.}: Document
|
||||
|
|
|
|||
170
src/karax.nim
170
src/karax.nim
|
|
@ -1,6 +1,6 @@
|
|||
# Simple lib to write JS UIs
|
||||
|
||||
import dom, vdom, jstrutils
|
||||
import dom, vdom, jstrutils, components, jdict
|
||||
|
||||
export dom.Event, dom.cloneNode, dom
|
||||
|
||||
|
|
@ -68,8 +68,12 @@ proc isElementInViewport(el: Node; h: var int): bool =
|
|||
proc vnodeToDom(n: VNode): Node =
|
||||
if n.kind == VNodeKind.text:
|
||||
result = document.createTextNode(n.text)
|
||||
elif n.kind == VNodeKind.thunk:
|
||||
return vnodeToDom(n.callThunk)
|
||||
elif n.kind == VNodeKind.vthunk:
|
||||
let x = callThunk(vcomponents[n.text], n)
|
||||
return vnodeToDom(x)
|
||||
elif n.kind == VNodeKind.dthunk:
|
||||
let x = callThunk(dcomponents[n.text], n)
|
||||
return x
|
||||
else:
|
||||
result = document.createElement(toTag[n.kind])
|
||||
for k in n:
|
||||
|
|
@ -85,7 +89,7 @@ proc vnodeToDom(n: VNode): Node =
|
|||
result.key = n.key
|
||||
for k, v in attrs(n):
|
||||
if v != nil:
|
||||
result.setAttribute(k, v)
|
||||
result.setAttr(k, v)
|
||||
let myn = n
|
||||
for e, h in items(n.events):
|
||||
proc wrapper(): proc (ev: Event) =
|
||||
|
|
@ -136,23 +140,26 @@ proc equals(a, b: VNode): bool =
|
|||
if a.key != b.key: return false
|
||||
if a.kind == VNodeKind.text:
|
||||
if a.text != b.text: return false
|
||||
elif a.kind == VNodeKind.thunk:
|
||||
if a.thunk != b.thunk: return false
|
||||
elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk:
|
||||
if a.text != b.text: return false
|
||||
if a.len != b.len: return false
|
||||
for i in 0..<a.len:
|
||||
if not equals(a[i], b[i]): return false
|
||||
if not sameAttrs(a, b): return false
|
||||
if a.class != b.class: return false
|
||||
# XXX test event listeners here?
|
||||
# --> maybe give nodes a hash?
|
||||
return true
|
||||
|
||||
proc equalsTree(a, b: VNode): bool =
|
||||
if not a.validHash:
|
||||
a.calcHash()
|
||||
if not b.validHash:
|
||||
b.calcHash()
|
||||
return a.hash == b.hash
|
||||
when false:
|
||||
# hashing is too fragile now with component support:
|
||||
if not a.validHash:
|
||||
a.calcHash()
|
||||
if not b.validHash:
|
||||
b.calcHash()
|
||||
return a.hash == b.hash
|
||||
else:
|
||||
return eq(a, b)
|
||||
|
||||
proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
||||
if not equals(newNode, oldNode):
|
||||
|
|
@ -177,12 +184,14 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
|||
current.removeChild(current.lastChild)
|
||||
else:
|
||||
var commonPrefix = 0
|
||||
while commonPrefix < minLength and equalsTree(newNode[commonPrefix], oldNode[commonPrefix]):
|
||||
while commonPrefix < minLength and
|
||||
equalsTree(newNode[commonPrefix], oldNode[commonPrefix]):
|
||||
inc commonPrefix
|
||||
|
||||
var oldPos = oldLength - 1
|
||||
var newPos = newLength - 1
|
||||
while oldPos >= commonPrefix and newPos >= commonPrefix and equalsTree(newNode[newPos], oldNode[oldPos]):
|
||||
while oldPos >= commonPrefix and newPos >= commonPrefix and
|
||||
equalsTree(newNode[newPos], oldNode[oldPos]):
|
||||
dec oldPos
|
||||
dec newPos
|
||||
|
||||
|
|
@ -254,18 +263,6 @@ proc redraw*() =
|
|||
proc init*() =
|
||||
reqFrame(dodraw)
|
||||
|
||||
#proc prepend*(parent, kid: Element) =
|
||||
# parent.insertBefore(kid, parent.firstChild)
|
||||
# prependChild(parent, kid)
|
||||
|
||||
proc suffix*(s, prefix: cstring): cstring =
|
||||
if s.startsWith(prefix):
|
||||
result = s.substr(prefix.len)
|
||||
else:
|
||||
kout(cstring"bug! " & s & cstring" does not start with " & prefix)
|
||||
|
||||
proc suffixAsInt*(s, prefix: cstring): int = parseInt(suffix(s, prefix))
|
||||
|
||||
proc scrollTop*(e: Node): int {.importcpp: "#.scrollTop", nodecl.}
|
||||
proc offsetHeight*(e: Node): int {.importcpp: "#.offsetHeight", nodecl.}
|
||||
proc offsetTop*(e: Node): int {.importcpp: "#.offsetTop", nodecl.}
|
||||
|
|
@ -291,8 +288,6 @@ proc setOnchanged*(e: VNode; action: EventHandler) =
|
|||
proc setOnscroll*(e: VNode; action: EventHandler) =
|
||||
onImpl EventKind.onscroll
|
||||
|
||||
#proc ceil(f: float): int {.importc: "Math.ceil", nodecl.}
|
||||
|
||||
proc setOnscroll*(action: proc(min, max: VKey; diff: int)) =
|
||||
var oldY = window.pageYOffset
|
||||
|
||||
|
|
@ -321,98 +316,6 @@ proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
|||
redraw()
|
||||
onhashchange = wrapper
|
||||
|
||||
var
|
||||
linkCounter: int
|
||||
|
||||
proc link*(id: int): VNode =
|
||||
result = newVNode(VNodeKind.anchor)
|
||||
result.setAttr("href", "#")
|
||||
inc linkCounter
|
||||
result.setAttr("id", $linkCounter & ":" & $id)
|
||||
|
||||
proc link*(action: EventHandler): VNode =
|
||||
result = newVNode(VNodeKind.anchor)
|
||||
result.setAttr("href", "#")
|
||||
result.setOnclick action
|
||||
|
||||
when false:
|
||||
proc button*(caption: cstring; action: EventHandler; disabled=false): VNode =
|
||||
result = newVNode(VNodeKind.button)
|
||||
result.add text(caption)
|
||||
if action != nil:
|
||||
result.setOnClick action
|
||||
if disabled:
|
||||
result.setAttr("disabled", "true")
|
||||
|
||||
proc select*(choices: openarray[cstring]): VNode =
|
||||
result = newVNode(VNodeKind.select)
|
||||
var i = 0
|
||||
for c in choices:
|
||||
result.add tree(VNodeKind.option, [(cstring"value", toCstr(i))], text(c))
|
||||
inc i
|
||||
|
||||
proc select*(choices: openarray[(int, cstring)]): VNode =
|
||||
result = newVNode(VNodeKind.select)
|
||||
for c in choices:
|
||||
result.add tree(VNodeKind.option, [(cstring"value", toCstr(c[0]))], text(c[1]))
|
||||
|
||||
var radioCounter: int
|
||||
|
||||
proc radio*(choices: openarray[(int, cstring)]): VNode =
|
||||
result = newVNode(VNodeKind.fieldset)
|
||||
var i = 0
|
||||
inc radioCounter
|
||||
for c in choices:
|
||||
let id = cstring"radio_" & c[1] & toCstr(i)
|
||||
var kid = tree(VNodeKind.input, [(cstring"type", cstring"radio"),
|
||||
(cstring"id", id), (cstring"name", cstring"radio" & toCStr(radioCounter)),
|
||||
(cstring"value", toCStr(c[0]))])
|
||||
if i == 0:
|
||||
kid.setAttr(cstring"checked", cstring"checked")
|
||||
var lab = tree(VNodeKind.label, [(cstring"for", id)], text(c[1]))
|
||||
kid.add lab
|
||||
result.add kid
|
||||
inc i
|
||||
|
||||
proc tag*(kind: VNodeKind; id=cstring(nil), class=cstring(nil)): VNode =
|
||||
result = newVNode(kind)
|
||||
result.id = id
|
||||
result.class = class
|
||||
|
||||
proc tdiv*(id=cstring(nil), class=cstring(nil)): VNode = tag(VNodeKind.tdiv, id, class)
|
||||
proc span*(id=cstring(nil), class=cstring(nil)): VNode = tag(VNodeKind.span, id, class)
|
||||
|
||||
proc valueAsInt*(e: Node): int = parseInt(e.value)
|
||||
|
||||
proc th*(s: cstring): VNode =
|
||||
result = newVNode(VNodeKind.th)
|
||||
result.add text(s)
|
||||
|
||||
proc td*(s: string): VNode =
|
||||
result = newVNode(VNodeKind.td)
|
||||
result.add text(s)
|
||||
|
||||
proc td*(s: VNode): VNode =
|
||||
result = newVNode(VNodeKind.td)
|
||||
result.add s
|
||||
|
||||
proc td*(class: cstring; s: VNode): VNode =
|
||||
result = newVNode(VNodeKind.td)
|
||||
result.add s
|
||||
result.class = class
|
||||
|
||||
proc table*(class=cstring(nil), kids: varargs[VNode]): VNode =
|
||||
result = tag(VNodeKind.table, nil, class)
|
||||
for k in kids: result.add k
|
||||
|
||||
proc tr*(kids: varargs[VNode]): VNode =
|
||||
result = newVNode(VNodeKind.tr)
|
||||
for k in kids:
|
||||
if k.kind in {VNodeKind.td, VNodeKind.th}:
|
||||
result.add k
|
||||
else:
|
||||
result.add td(k)
|
||||
|
||||
proc getAttr(e: Node; key: cstring): cstring {.
|
||||
importcpp: "#.getAttribute(#)", nodecl.}
|
||||
|
||||
|
|
@ -426,9 +329,6 @@ template keyeventBody() =
|
|||
redraw()
|
||||
|
||||
proc realtimeInput*(val: cstring; action: EventHandler): VNode =
|
||||
#let oldElem = getElementById(id)
|
||||
#if oldElem != nil: return oldElem
|
||||
#let newVal = if oldElem.isNil: val else: $oldElem.value
|
||||
var timer: Timeout
|
||||
proc onkeyup(ev: Event; n: VNode) =
|
||||
proc wrapper() = keyeventBody()
|
||||
|
|
@ -440,9 +340,6 @@ proc realtimeInput*(val: cstring; action: EventHandler): VNode =
|
|||
result.addEventListener(EventKind.onkeyup, onkeyup)
|
||||
|
||||
proc enterInput*(id, val: cstring; action: EventHandler): VNode =
|
||||
#let oldElem = getElementById(id)
|
||||
#if oldElem != nil: return oldElem
|
||||
#let newVal = if oldElem.isNil: val else: $oldElem.value
|
||||
proc onkeyup(ev: Event; n: VNode) =
|
||||
if ev.keyCode == 13: keyeventBody()
|
||||
|
||||
|
|
@ -508,16 +405,13 @@ proc setupErrorHandler*(useAlert=false) =
|
|||
|
||||
{.pop.}
|
||||
|
||||
when false:
|
||||
var plugins {.exportc.}: seq[(string, proc())] = @[]
|
||||
|
||||
proc onInput(val: cstring) =
|
||||
kout val
|
||||
if val == "dyn":
|
||||
let body = getElementById("body")
|
||||
body.prepend(tree("script", [("type", "text/javascript"), ("src", "nimcache/dyn.js")]))
|
||||
redraw()
|
||||
kout(plugins.len)
|
||||
if plugins.len > 0:
|
||||
plugins[0][1]()
|
||||
proc prepend*(parent, kid: Element) =
|
||||
parent.insertBefore(kid, parent.firstChild)
|
||||
|
||||
proc loadScript*(jsfilename: cstring) =
|
||||
let body = getElementById("body")
|
||||
let s = document.createElement("script")
|
||||
s.setAttr "type", "text/javascript"
|
||||
s.setAttr "src", jsfilename
|
||||
body.prepend(s)
|
||||
redraw()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import macros, karax, vdom
|
||||
import macros, karax, vdom, components
|
||||
from strutils import startsWith, toLowerAscii
|
||||
|
||||
const
|
||||
|
|
@ -61,11 +61,19 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
|||
result = n
|
||||
of nnkCallKinds:
|
||||
let op = getName(n[0])
|
||||
if isTag(op):
|
||||
let ck = isComponent(op)
|
||||
let isTag = isTag(op)
|
||||
if isTag or ck != ComponentKind.None:
|
||||
let tmp = genSym(nskLet, "tmp")
|
||||
let call = if isTag:
|
||||
newCall(bindSym"tree", newDotExpr(bindSym"VNodeKind", n[0]))
|
||||
elif ck == ComponentKind.VNode:
|
||||
newCall(bindSym"vthunk", newLit(op))
|
||||
else:
|
||||
newCall(bindSym"dthunk", newLit(op))
|
||||
result = newTree(
|
||||
if tmpContext == nil: nnkStmtListExpr else: nnkStmtList,
|
||||
newLetStmt(tmp, newCall(bindSym"tree", newDotExpr(bindSym"VNodeKind", n[0]))))
|
||||
newLetStmt(tmp, call))
|
||||
for i in 1 ..< n.len:
|
||||
# named parameters are transformed into attributes or events:
|
||||
let x = n[i]
|
||||
|
|
@ -77,7 +85,9 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
|||
result.add newDotAsgn(tmp, key, x[1])
|
||||
else:
|
||||
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
||||
elif x.kind == nnkIdent:
|
||||
elif not isTag:
|
||||
call.add x
|
||||
elif eqIdent(x, "setFocus"):
|
||||
result.add newCall(x, tmp)
|
||||
else:
|
||||
result.add tcall2(x, tmp)
|
||||
|
|
|
|||
23
src/vdom.nim
23
src/vdom.nim
|
|
@ -5,7 +5,7 @@ import shash
|
|||
|
||||
type
|
||||
VNodeKind* {.pure.} = enum
|
||||
text, int, bool, thunk,
|
||||
text, int, bool, vthunk, dthunk,
|
||||
anchor,
|
||||
tdiv,
|
||||
table, tr, td, th, thead, tbody,
|
||||
|
|
@ -15,7 +15,8 @@ type
|
|||
|
||||
const
|
||||
toTag*: array[VNodeKind, cstring] = [
|
||||
cstring"#text", "#int", "#bool", "#thunk", "A", "DIV", "TABLE", "TR", "TD",
|
||||
cstring"#text", "#int", "#bool", "#vthunk", "#dthunk",
|
||||
"A", "DIV", "TABLE", "TR", "TD",
|
||||
"TH", "THEAD",
|
||||
"TBODY", "LINK",
|
||||
"SPAN", "LABEL", "BR", "SELECT", "OPTION", "FIELDSET", "INPUT", "BUTTON", "UL",
|
||||
|
|
@ -43,7 +44,6 @@ type
|
|||
# even index: key, odd index: value; done this way for memory efficiency:
|
||||
attrs: seq[cstring]
|
||||
events*: seq[(EventKind, EventHandler)]
|
||||
thunk*: proc (args: seq[VNode]): VNode
|
||||
hash*: Hash
|
||||
validHash*: bool
|
||||
|
||||
|
|
@ -55,23 +55,26 @@ proc vn*(i: int): VNode = VNode(kind: VNodeKind.int, key: i)
|
|||
proc vn*(b: bool): VNode = VNode(kind: VNodeKind.int, key: ord(b))
|
||||
proc vn*(x: cstring): VNode = VNode(kind: VNodeKind.text, key: -1, text: x)
|
||||
|
||||
proc callThunk*(n: VNode): VNode =
|
||||
assert n.kind == VNodeKind.thunk
|
||||
result = n.thunk(n.kids)
|
||||
template callThunk*(fn: typed; n: VNode): untyped =
|
||||
## for internal usage only.
|
||||
fn(n.kids)
|
||||
|
||||
proc thunk*(cb: proc(args: seq[VNode]): VNode; args: varargs[VNode, vn]): VNode =
|
||||
VNode(kind: VNodeKind.thunk, thunk: cb, kids: @args)
|
||||
proc vthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||
VNode(kind: VNodeKind.vthunk, text: name, kids: @args)
|
||||
|
||||
proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||
VNode(kind: VNodeKind.dthunk, text: name, kids: @args)
|
||||
|
||||
proc eq*(a, b: VNode): bool =
|
||||
if a.kind != b.kind: return false
|
||||
if a.id != b.id: return false
|
||||
if a.class != b.class: return false
|
||||
if a.key != b.key: return false
|
||||
if a.kind != VNodeKind.text:
|
||||
if a.kids.len != b.kids.len: return false
|
||||
for i in 0..<a.kids.len:
|
||||
if not eq(a.kids[i], b.kids[i]): return false
|
||||
else:
|
||||
if a.text != b.text: return false
|
||||
if a.text != b.text: return false
|
||||
if a.attrs.len != b.attrs.len: return false
|
||||
for i in 0..<a.attrs.len:
|
||||
if a.attrs[i] != b.attrs[i]: return false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue