add experiment what Karax would look like without DOM diff'ing but with barebones reactive programming
This commit is contained in:
parent
dd0ea311a3
commit
75739f8170
3 changed files with 946 additions and 0 deletions
752
experiments/trello/knete.nim
Normal file
752
experiments/trello/knete.nim
Normal file
|
|
@ -0,0 +1,752 @@
|
|||
## Raw DOM manipulation. No DOM diff'ing, no cry.
|
||||
|
||||
import macros, tables
|
||||
from strutils import startsWith, toLowerAscii, toUpperAscii
|
||||
|
||||
when defined(js):
|
||||
type kstring* = cstring
|
||||
else:
|
||||
type kstring* = string
|
||||
|
||||
type
|
||||
Tag* {.pure.} = enum
|
||||
text = "#text",
|
||||
|
||||
html, head, title, base, link, meta, style,
|
||||
script, noscript,
|
||||
body, section, nav, article, aside,
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
header, footer, address, main
|
||||
|
||||
p, hr, pre, blockquote, ol, ul, li,
|
||||
dl, dt, dd,
|
||||
figure, figcaption,
|
||||
|
||||
tdiv = "div",
|
||||
|
||||
a, em, strong, small,
|
||||
strikethrough = "s", cite, quote,
|
||||
dfn, abbr, data, time, code, `var` = "var", samp,
|
||||
kdb, sub, sup, italic = "i", bold = "b", underlined = "u",
|
||||
mark, ruby, rt, rp, bdi, dbo, span, br, wbr,
|
||||
ins, del, img, iframe, embed, `object` = "object",
|
||||
param, video, audio, source, track, canvas, map,
|
||||
area, svg, math,
|
||||
|
||||
table, caption, colgroup, col, tbody, thead,
|
||||
tfoot, tr, td, th,
|
||||
|
||||
form, fieldset, legend, label, input, button,
|
||||
select, datalist, optgroup, option, textarea,
|
||||
keygen, output, progress, meter,
|
||||
details, summary, command, menu
|
||||
|
||||
type
|
||||
EventKind* {.pure.} = enum ## The events supported by the virtual DOM.
|
||||
onclick, ## An element is clicked.
|
||||
oncontextmenu, ## An element is right-clicked.
|
||||
ondblclick, ## An element is double clicked.
|
||||
onkeyup, ## A key was released.
|
||||
onkeydown, ## A key is pressed.
|
||||
onkeypressed, # A key was pressed.
|
||||
onfocus, ## An element got the focus.
|
||||
onblur, ## An element lost the focus.
|
||||
onchange, ## The selected value of an element was changed.
|
||||
onscroll, ## The user scrolled within an element.
|
||||
|
||||
onmousedown, ## A pointing device button (usually a mouse) is pressed
|
||||
## on an element.
|
||||
onmouseenter, ## A pointing device is moved onto the element that
|
||||
## has the listener attached.
|
||||
onmouseleave, ## A pointing device is moved off the element that
|
||||
## has the listener attached.
|
||||
onmousemove, ## A pointing device is moved over an element.
|
||||
onmouseout, ## A pointing device is moved off the element that
|
||||
## has the listener attached or off one of its children.
|
||||
onmouseover, ## A pointing device is moved onto the element that has
|
||||
## the listener attached or onto one of its children.
|
||||
onmouseup, ## A pointing device button is released over an element.
|
||||
|
||||
ondrag, ## An element or text selection is being dragged (every 350ms).
|
||||
ondragend, ## A drag operation is being ended (by releasing a mouse button
|
||||
## or hitting the escape key).
|
||||
ondragenter, ## A dragged element or text selection enters a valid drop target.
|
||||
ondragleave, ## A dragged element or text selection leaves a valid drop target.
|
||||
ondragover, ## An element or text selection is being dragged over a valid
|
||||
## drop target (every 350ms).
|
||||
ondragstart, ## The user starts dragging an element or text selection.
|
||||
ondrop, ## An element is dropped on a valid drop target.
|
||||
|
||||
onsubmit, ## A form is submitted
|
||||
oninput, ## An input value changes
|
||||
|
||||
onkeyupenter, ## vdom extension: an input field received the ENTER key press
|
||||
onkeyuplater ## vdom extension: a key was pressed and some time
|
||||
## passed (useful for on-the-fly text completions)
|
||||
|
||||
|
||||
macro buildLookupTables(): untyped =
|
||||
var a = newTree(nnkBracket)
|
||||
for i in low(Tag)..high(Tag):
|
||||
let x = $i
|
||||
let y = if x[0] == '#': x else: toUpperAscii(x)
|
||||
a.add(newCall("kstring", newLit(y)))
|
||||
var e = newTree(nnkBracket)
|
||||
for i in low(EventKind)..high(EventKind):
|
||||
e.add(newCall("kstring", newLit(substr($i, 2))))
|
||||
|
||||
template tmpl(a, e) {.dirty.} =
|
||||
const
|
||||
toTagName*: array[Tag, kstring] = a
|
||||
toEventName*: array[EventKind, kstring] = e
|
||||
|
||||
result = getAst tmpl(a, e)
|
||||
|
||||
buildLookupTables()
|
||||
|
||||
# ---------------- CSS ----------------------------------------------
|
||||
|
||||
type
|
||||
StyleAttr* {.pure.} = enum
|
||||
## The style attributes supported by the virtual DOM.
|
||||
## Reference: https://www.w3schools.com/jsref/dom_obj_style.asp
|
||||
alignContent
|
||||
alignItems
|
||||
alignSelf
|
||||
animation
|
||||
animationDelay
|
||||
animationDirection
|
||||
animationDuration
|
||||
animationFillMode
|
||||
animationIterationCount
|
||||
animationName
|
||||
animationTimingFunction
|
||||
animationPlayState
|
||||
background
|
||||
backgroundAttachment
|
||||
backgroundColor
|
||||
backgroundImage
|
||||
backgroundPosition
|
||||
backgroundRepeat
|
||||
backgroundClip
|
||||
backgroundOrigin
|
||||
backgroundSize
|
||||
backfaceVisibility
|
||||
border
|
||||
borderBottom
|
||||
borderBottomColor
|
||||
borderBottomLeftRadius
|
||||
borderBottomRightRadius
|
||||
borderBottomStyle
|
||||
borderBottomWidth
|
||||
borderCollapse
|
||||
borderColor
|
||||
borderImage
|
||||
borderImageOutset
|
||||
borderImageRepeat
|
||||
borderImageSlice
|
||||
borderImageSource
|
||||
borderImageWidth
|
||||
borderLeft
|
||||
borderLeftColor
|
||||
borderLeftStyle
|
||||
borderLeftWidth
|
||||
borderRadius
|
||||
borderRight
|
||||
borderRightColor
|
||||
borderRightStyle
|
||||
borderRightWidth
|
||||
borderSpacing
|
||||
borderStyle
|
||||
borderTop
|
||||
borderTopColor
|
||||
borderTopLeftRadius
|
||||
borderTopRightRadius
|
||||
borderTopStyle
|
||||
borderTopWidth
|
||||
borderWidth
|
||||
bottom
|
||||
boxDecorationBreak
|
||||
boxShadow
|
||||
boxSizing
|
||||
captionSide
|
||||
clear
|
||||
clip
|
||||
color
|
||||
columnCount
|
||||
columnFill
|
||||
columnGap
|
||||
columnRule
|
||||
columnRuleColor
|
||||
columnRuleStyle
|
||||
columnRuleWidth
|
||||
columns
|
||||
columnSpan
|
||||
columnWidth
|
||||
content
|
||||
counterIncrement
|
||||
counterReset
|
||||
cursor
|
||||
direction
|
||||
display
|
||||
emptyCells
|
||||
filter
|
||||
flex
|
||||
flexBasis
|
||||
flexDirection
|
||||
flexFlow
|
||||
flexGrow
|
||||
flexShrink
|
||||
flexWrap
|
||||
cssFloat
|
||||
font
|
||||
fontFamily
|
||||
fontSize
|
||||
fontSizeAdjust
|
||||
fontStretch
|
||||
fontStyle
|
||||
fontVariant
|
||||
fontWeight
|
||||
hangingPunctuation
|
||||
height
|
||||
hyphens
|
||||
icon
|
||||
imageOrientation
|
||||
justifyContent
|
||||
left
|
||||
letterSpacing
|
||||
lineHeight
|
||||
listStyle
|
||||
listStyleImage
|
||||
listStylePosition
|
||||
listStyleType
|
||||
margin
|
||||
marginBottom
|
||||
marginLeft
|
||||
marginRight
|
||||
marginTop
|
||||
maxHeight
|
||||
maxWidth
|
||||
minHeight
|
||||
minWidth
|
||||
navDown
|
||||
navIndex
|
||||
navLeft
|
||||
navRight
|
||||
navUp
|
||||
opacity
|
||||
order
|
||||
orphans
|
||||
outline
|
||||
outlineColor
|
||||
outlineOffset
|
||||
outlineStyle
|
||||
outlineWidth
|
||||
overflow
|
||||
overflowX
|
||||
overflowY
|
||||
padding
|
||||
paddingBottom
|
||||
paddingLeft
|
||||
paddingRight
|
||||
paddingTop
|
||||
pageBreakAfter
|
||||
pageBreakBefore
|
||||
pageBreakInside
|
||||
perspective
|
||||
perspectiveOrigin
|
||||
pointerEvents
|
||||
position
|
||||
quotes
|
||||
resize
|
||||
right
|
||||
scrollbar3dLightColor # note: missing in w3schools reference
|
||||
scrollbarArrowColor # note: missing in w3schools reference
|
||||
scrollbarBaseColor # note: missing in w3schools reference
|
||||
scrollbarDarkshadowColor # note: missing in w3schools reference
|
||||
scrollbarFaceColor # note: missing in w3schools reference
|
||||
scrollbarHighlightColor # note: missing in w3schools reference
|
||||
scrollbarShadowColor # note: missing in w3schools reference
|
||||
scrollbarTrackColor # note: missing in w3schools reference
|
||||
tableLayout
|
||||
tabSize
|
||||
textAlign
|
||||
textAlignLast
|
||||
textDecoration
|
||||
textDecorationColor
|
||||
textDecorationLine
|
||||
textDecorationStyle
|
||||
textIndent
|
||||
textJustify
|
||||
textOverflow
|
||||
textShadow
|
||||
textTransform
|
||||
top
|
||||
transform
|
||||
transformOrigin
|
||||
transformStyle
|
||||
transition
|
||||
transitionDelay
|
||||
transitionDuration
|
||||
transitionProperty
|
||||
transitionTimingFunction
|
||||
unicodeBidi
|
||||
userSelect
|
||||
verticalAlign
|
||||
visibility
|
||||
whiteSpace
|
||||
width
|
||||
wordBreak
|
||||
wordSpacing
|
||||
wordWrap
|
||||
widows
|
||||
zIndex
|
||||
|
||||
macro buildStyleLookupTable(): untyped =
|
||||
var e = newTree(nnkBracket)
|
||||
for i in low(StyleAttr)..high(StyleAttr):
|
||||
e.add(newCall("kstring", newLit($i)))
|
||||
template tmpl(e) {.dirty.} =
|
||||
const
|
||||
toStyleAttrName: array[StyleAttr, kstring] = e
|
||||
result = getAst tmpl(e)
|
||||
|
||||
buildStyleLookupTable()
|
||||
|
||||
type
|
||||
StyleObj {.importc.} = object
|
||||
background*: cstring
|
||||
backgroundAttachment*: cstring
|
||||
backgroundColor*: cstring
|
||||
backgroundImage*: cstring
|
||||
backgroundPosition*: cstring
|
||||
backgroundRepeat*: cstring
|
||||
border*: cstring
|
||||
borderBottom*: cstring
|
||||
borderBottomColor*: cstring
|
||||
borderBottomStyle*: cstring
|
||||
borderBottomWidth*: cstring
|
||||
borderColor*: cstring
|
||||
borderLeft*: cstring
|
||||
borderLeftColor*: cstring
|
||||
borderLeftStyle*: cstring
|
||||
borderLeftWidth*: cstring
|
||||
borderRight*: cstring
|
||||
borderRightColor*: cstring
|
||||
borderRightStyle*: cstring
|
||||
borderRightWidth*: cstring
|
||||
borderStyle*: cstring
|
||||
borderTop*: cstring
|
||||
borderTopColor*: cstring
|
||||
borderTopStyle*: cstring
|
||||
borderTopWidth*: cstring
|
||||
borderWidth*: cstring
|
||||
bottom*: cstring
|
||||
captionSide*: cstring
|
||||
clear*: cstring
|
||||
clip*: cstring
|
||||
color*: cstring
|
||||
cursor*: cstring
|
||||
direction*: cstring
|
||||
display*: cstring
|
||||
emptyCells*: cstring
|
||||
cssFloat*: cstring
|
||||
font*: cstring
|
||||
fontFamily*: cstring
|
||||
fontSize*: cstring
|
||||
fontStretch*: cstring
|
||||
fontStyle*: cstring
|
||||
fontVariant*: cstring
|
||||
fontWeight*: cstring
|
||||
height*: cstring
|
||||
left*: cstring
|
||||
letterSpacing*: cstring
|
||||
lineHeight*: cstring
|
||||
listStyle*: cstring
|
||||
listStyleImage*: cstring
|
||||
listStylePosition*: cstring
|
||||
listStyleType*: cstring
|
||||
margin*: cstring
|
||||
marginBottom*: cstring
|
||||
marginLeft*: cstring
|
||||
marginRight*: cstring
|
||||
marginTop*: cstring
|
||||
maxHeight*: cstring
|
||||
maxWidth*: cstring
|
||||
minHeight*: cstring
|
||||
minWidth*: cstring
|
||||
overflow*: cstring
|
||||
padding*: cstring
|
||||
paddingBottom*: cstring
|
||||
paddingLeft*: cstring
|
||||
paddingRight*: cstring
|
||||
paddingTop*: cstring
|
||||
pageBreakAfter*: cstring
|
||||
pageBreakBefore*: cstring
|
||||
pointerEvents*: cstring
|
||||
position*: cstring
|
||||
right*: cstring
|
||||
scrollbar3dLightColor*: cstring
|
||||
scrollbarArrowColor*: cstring
|
||||
scrollbarBaseColor*: cstring
|
||||
scrollbarDarkshadowColor*: cstring
|
||||
scrollbarFaceColor*: cstring
|
||||
scrollbarHighlightColor*: cstring
|
||||
scrollbarShadowColor*: cstring
|
||||
scrollbarTrackColor*: cstring
|
||||
tableLayout*: cstring
|
||||
textAlign*: cstring
|
||||
textDecoration*: cstring
|
||||
textIndent*: cstring
|
||||
textTransform*: cstring
|
||||
transform*: cstring
|
||||
top*: cstring
|
||||
verticalAlign*: cstring
|
||||
visibility*: cstring
|
||||
width*: cstring
|
||||
wordSpacing*: cstring
|
||||
zIndex*: int
|
||||
Style* = ref StyleObj
|
||||
|
||||
ElementObj {.importc.} = object
|
||||
id*, class*, value*: cstring
|
||||
disabled*: bool
|
||||
style*: Style
|
||||
Element* = ref ElementObj
|
||||
|
||||
DocumentObj {.importc.} = object
|
||||
Document = ref DocumentObj
|
||||
|
||||
proc setStyle(s: Style; key, val: cstring) {.importcpp: "#[#] = #", noSideEffect.}
|
||||
|
||||
proc applyStyles*(e: Element; pairs: openArray[(StyleAttr, kstring)]) =
|
||||
for x in pairs:
|
||||
e.style.setStyle(toStyleAttrName[x[0]], x[1])
|
||||
|
||||
# ------- Tree manipulation ---------------------------
|
||||
|
||||
var document {.importc.}: Document
|
||||
|
||||
proc parent*(x: Element): Element {.importcpp: "#.parentNode".}
|
||||
|
||||
proc up*(x: Element; className: cstring): Element =
|
||||
result = x
|
||||
while result != nil and result.class != className:
|
||||
result = result.parent
|
||||
|
||||
proc len*(x: Element): int {.importcpp: "#.childNodes.length".}
|
||||
proc `[]`*(x: Element; idx: int): Element {.importcpp: "#.childNodes[#]".}
|
||||
proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.}
|
||||
proc add*(n, child: Element) {.importcpp: "appendChild".}
|
||||
|
||||
proc removeChild(n, child: Element) {.importcpp.}
|
||||
proc replaceChild(n, newNode, oldNode: Element) {.importcpp.}
|
||||
|
||||
proc replace*(self, by: Element) = replaceChild(self.parent, by, self)
|
||||
proc delete*(self: Element) = removeChild(self.parent, self)
|
||||
|
||||
proc insertBefore(n, newNode, before: Element) {.importcpp.}
|
||||
proc insert*(before, newNode: Element) = insertBefore(before.parent, newNode, before)
|
||||
|
||||
proc createTextNode(d: Document, text: cstring): Element {.importcpp.}
|
||||
proc text*(s: kstring): Element = createTextNode(document, s)
|
||||
|
||||
iterator items*(n: Element): Element =
|
||||
for i in 0..<n.len: yield n[i]
|
||||
|
||||
proc createElement(d: Document, identifier: cstring): Element {.importcpp.}
|
||||
proc setAttr*(n: Element; key, val: cstring) {.importcpp: "#.setAttribute(@)".}
|
||||
|
||||
proc tree*(kind: Tag; kids: varargs[Element]): Element =
|
||||
result = createElement(document, toTagName[kind])
|
||||
for k in kids: result.add k
|
||||
|
||||
proc tree*(kind: Tag; attrs: openarray[(kstring, kstring)];
|
||||
kids: varargs[Element]): Element =
|
||||
result = tree(kind, kids)
|
||||
for a in attrs: result.setAttr(a[0], a[1])
|
||||
|
||||
# other arbitrary stuff belonging to Element
|
||||
proc focus*(e: Element) {.importcpp.}
|
||||
proc scrollIntoView*(e: Element) {.importcpp.}
|
||||
proc blur*(e: Element) {.importcpp.}
|
||||
|
||||
proc toChecked*(checked: bool): cstring =
|
||||
(if checked: cstring"checked" else: cstring(nil))
|
||||
|
||||
proc toDisabled*(disabled: bool): cstring =
|
||||
(if disabled: cstring"disabled" else: cstring(nil))
|
||||
|
||||
# -------------- Timer handling --------------------
|
||||
|
||||
type
|
||||
Timeout* {.importc.} = ref object of RootObj
|
||||
|
||||
proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.}
|
||||
proc clearTimeout*(t: Timeout) {.importc, nodecl.}
|
||||
|
||||
# -------------- Event handling --------------------
|
||||
|
||||
type
|
||||
EventObj {.importc.} = object
|
||||
target*: Element
|
||||
altKey*, ctrlKey*, metaKey*, shiftKey*: bool
|
||||
code*: cstring
|
||||
isComposing*: bool
|
||||
key*: cstring
|
||||
keyCode*: int
|
||||
location*: int
|
||||
Event* = ref EventObj
|
||||
|
||||
EventHandler* = proc (ev: Event) {.closure.}
|
||||
|
||||
proc addEventListener(e: Element, ev: cstring, cb: proc(ev: Event),
|
||||
useCapture = false) {.importcpp.}
|
||||
|
||||
proc addEventHandler*(e: Element; k: EventKind; action: EventHandler) =
|
||||
case k
|
||||
of EventKind.onkeyuplater:
|
||||
proc laterWrapper(): EventHandler =
|
||||
let action = action
|
||||
var timer: Timeout
|
||||
result = proc (ev: Event) =
|
||||
proc wrapper() = action(ev)
|
||||
if timer != nil: clearTimeout(timer)
|
||||
timer = setTimeout(wrapper, 400)
|
||||
|
||||
e.addEventListener("keyup", laterWrapper())
|
||||
of EventKind.onkeyupenter:
|
||||
proc enterWrapper(): EventHandler =
|
||||
let action = action
|
||||
result = proc (ev: Event) =
|
||||
if ev.keyCode == 13: action(ev)
|
||||
|
||||
e.addEventListener("keyup", enterWrapper())
|
||||
else:
|
||||
e.addEventListener(toEventName[k], action)
|
||||
|
||||
proc addEventHandler*(e: Element; k: EventKind; action: proc()) =
|
||||
addEventHandler e, k, (proc (ev: Event) = action())
|
||||
|
||||
proc prepareDragData*(ev: Event; datatype, data: cstring)
|
||||
{.importcpp: "#.dataTransfer.setData(@)".}
|
||||
|
||||
proc recvDragData*(ev: Event; datatype: cstring): cstring
|
||||
{.importcpp: "#.dataTransfer.getData(@)".}
|
||||
|
||||
proc preventDefault*(ev: Event) {.importcpp.}
|
||||
|
||||
# ------------------ Init handling -----------------------------------
|
||||
|
||||
proc replaceById(id: cstring; newTree: Element) =
|
||||
let x = getElementById(id)
|
||||
x.parent.replaceChild(newTree, x)
|
||||
newTree.id = id
|
||||
|
||||
proc setWindowOnload(h: EventHandler) {.importcpp: "window.onload = #".}
|
||||
proc setInitializer*(initializer: proc (hashPart: kstring): Element;
|
||||
root: cstring = "ROOT") =
|
||||
var onhashChange {.importc: "window.onhashchange".}: proc()
|
||||
var hashPart {.importc: "window.location.hash".}: cstring
|
||||
|
||||
setWindowOnload proc (ev: Event) =
|
||||
replaceById root, initializer(hashPart)
|
||||
onhashchange = proc () =
|
||||
replaceById root, initializer(hashPart)
|
||||
|
||||
# ------------------ DSL section -------------------------------------
|
||||
|
||||
const
|
||||
StmtContext = ["inc", "echo", "dec", "!"]
|
||||
SpecialAttrs = ["id", "class", "value"]
|
||||
|
||||
type
|
||||
ComponentKind {.pure.} = enum
|
||||
None,
|
||||
Tag
|
||||
|
||||
var
|
||||
allcomponents {.compileTime.} = initTable[string, ComponentKind]()
|
||||
|
||||
proc isComponent(x: string): ComponentKind {.compileTime.} =
|
||||
allcomponents.getOrDefault(x)
|
||||
|
||||
proc addTags() {.compileTime.} =
|
||||
let x = (bindSym"Tag").getTypeImpl
|
||||
expectKind(x, nnkEnumTy)
|
||||
for i in ord(Tag.html)..ord(Tag.high):
|
||||
# +1 because of empty node at the start of the enum AST:
|
||||
let tag = $x[i+1]
|
||||
allcomponents[tag] = ComponentKind.Tag
|
||||
|
||||
static:
|
||||
addTags()
|
||||
|
||||
|
||||
proc getName(n: NimNode): string =
|
||||
case n.kind
|
||||
of nnkIdent:
|
||||
result = $n.ident
|
||||
of nnkAccQuoted:
|
||||
result = ""
|
||||
for i in 0..<n.len:
|
||||
result.add getName(n[i])
|
||||
of nnkStrLit..nnkTripleStrLit:
|
||||
result = n.strVal
|
||||
else:
|
||||
#echo repr n
|
||||
expectKind(n, nnkIdent)
|
||||
|
||||
proc newDotAsgn(tmp: NimNode, key: string, x: NimNode): NimNode =
|
||||
result = newTree(nnkAsgn, newDotExpr(tmp, newIdentNode key), x)
|
||||
|
||||
proc toKstring(n: NimNode): NimNode =
|
||||
if n.kind == nnkStrLit:
|
||||
result = newCall(bindSym"kstring", n)
|
||||
else:
|
||||
result = copyNimNode(n)
|
||||
for child in n:
|
||||
result.add toKstring(child)
|
||||
|
||||
proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||
# we need to distinguish statement and expression contexts:
|
||||
# every call statement 's' needs to be transformed to 'dest.add s'.
|
||||
# If expressions need to be distinguished from if statements. Since
|
||||
# we know we start in a statement context, it's pretty simple to
|
||||
# figure out expression contexts: In calls everything is an expression
|
||||
# (except for the last child of the macros we consider here),
|
||||
# lets, consts, types can be considered as expressions
|
||||
# case is complex, calls are assumed to produce a value.
|
||||
when defined(js):
|
||||
template evHandler(): untyped = bindSym"addEventHandler"
|
||||
else:
|
||||
template evHandler(): untyped = ident"addEventHandler"
|
||||
|
||||
case n.kind
|
||||
of nnkLiterals, nnkIdent, nnkSym, nnkDotExpr, nnkBracketExpr:
|
||||
if tmpContext != nil:
|
||||
result = newCall(bindSym"add", tmpContext, n)
|
||||
else:
|
||||
result = n
|
||||
of nnkForStmt, nnkIfExpr, nnkElifExpr, nnkElseExpr,
|
||||
nnkOfBranch, nnkElifBranch, nnkExceptBranch, nnkElse,
|
||||
nnkConstDef, nnkWhileStmt, nnkIdentDefs, nnkVarTuple:
|
||||
# recurse for the last son:
|
||||
result = copyNimTree(n)
|
||||
let L = n.len
|
||||
assert n.len == result.len
|
||||
if L > 0:
|
||||
result[L-1] = tcall2(result[L-1], tmpContext)
|
||||
of nnkStmtList, nnkStmtListExpr, nnkWhenStmt, nnkIfStmt, nnkTryStmt,
|
||||
nnkFinally:
|
||||
# recurse for every child:
|
||||
result = copyNimNode(n)
|
||||
for x in n:
|
||||
result.add tcall2(x, tmpContext)
|
||||
of nnkCaseStmt:
|
||||
# recurse for children, but don't add call for case ident
|
||||
result = copyNimNode(n)
|
||||
result.add n[0]
|
||||
for i in 1 ..< n.len:
|
||||
result.add tcall2(n[i], tmpContext)
|
||||
of nnkProcDef:
|
||||
let name = getName n[0]
|
||||
if name.startsWith"on":
|
||||
# turn it into an anon proc:
|
||||
let anon = copyNimTree(n)
|
||||
anon[0] = newEmptyNode()
|
||||
if tmpContext == nil:
|
||||
error "no Element to attach the event handler to"
|
||||
else:
|
||||
result = newCall(evHandler(), tmpContext,
|
||||
newDotExpr(bindSym"EventKind", n[0]), anon, ident("kxi"))
|
||||
else:
|
||||
result = n
|
||||
of nnkVarSection, nnkLetSection, nnkConstSection:
|
||||
result = n
|
||||
of nnkCallKinds - {nnkInfix}:
|
||||
let op = getName(n[0])
|
||||
if isComponent(op) == ComponentKind.Tag:
|
||||
let tmp = genSym(nskLet, "tmp")
|
||||
let call = newCall(bindSym"tree", newDotExpr(bindSym"Tag", n[0]))
|
||||
result = newTree(
|
||||
if tmpContext == nil: nnkStmtListExpr else: nnkStmtList,
|
||||
newLetStmt(tmp, call))
|
||||
for i in 1 ..< n.len:
|
||||
# named parameters are transformed into attributes or events:
|
||||
let x = n[i]
|
||||
if x.kind == nnkExprEqExpr:
|
||||
let key = getName x[0]
|
||||
if key.startsWith("on"):
|
||||
result.add newCall(evHandler(),
|
||||
tmp, newDotExpr(bindSym"EventKind", x[0]), x[1])
|
||||
elif key in SpecialAttrs:
|
||||
result.add newDotAsgn(tmp, key, x[1])
|
||||
elif eqIdent(key, "style"):
|
||||
result.add newCall(bindSym"applyStyles", tmp, toKstring x[1])
|
||||
elif eqIdent(key, "setFocus"):
|
||||
result.add newCall(bindSym"focus", tmp)
|
||||
else:
|
||||
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
||||
elif eqIdent(x, "setFocus"):
|
||||
result.add newCall(bindSym"focus", tmp)
|
||||
else:
|
||||
result.add tcall2(x, tmp)
|
||||
if tmpContext == nil:
|
||||
result.add tmp
|
||||
else:
|
||||
result.add newCall(bindSym"add", tmpContext, tmp)
|
||||
elif tmpContext != nil and op notin StmtContext:
|
||||
var hasEventHandlers = false
|
||||
for i in 1..<n.len:
|
||||
let it = n[i]
|
||||
if it.kind in {nnkProcDef, nnkStmtList}:
|
||||
hasEventHandlers = true
|
||||
break
|
||||
if not hasEventHandlers:
|
||||
result = newCall(bindSym"add", tmpContext, n)
|
||||
else:
|
||||
let tmp = genSym(nskLet, "tmp")
|
||||
var slicedCall = newCall(n[0])
|
||||
let ex = newTree(nnkStmtListExpr)
|
||||
ex.add newEmptyNode() # will become the let statement
|
||||
for i in 1..<n.len:
|
||||
let it = n[i]
|
||||
if it.kind in {nnkProcDef, nnkStmtList}:
|
||||
ex.add tcall2(it, tmp)
|
||||
else:
|
||||
slicedCall.add it
|
||||
ex[0] = newLetStmt(tmp, slicedCall)
|
||||
ex.add tmp
|
||||
result = newCall(bindSym"add", tmpContext, ex)
|
||||
elif op == "!" and n.len == 2:
|
||||
result = n[1]
|
||||
else:
|
||||
result = n
|
||||
else:
|
||||
result = n
|
||||
|
||||
macro buildHtml*(tag, children: untyped): Element =
|
||||
let kids = newProc(procType=nnkDo, body=children)
|
||||
expectKind kids, nnkDo
|
||||
var call: NimNode
|
||||
if tag.kind in nnkCallKinds:
|
||||
call = tag
|
||||
else:
|
||||
call = newCall(tag)
|
||||
call.add body(kids)
|
||||
result = tcall2(call, nil)
|
||||
when defined(debugKaraxDsl):
|
||||
echo repr result
|
||||
|
||||
macro buildHtml*(children: untyped): Element =
|
||||
let kids = newProc(procType=nnkDo, body=children)
|
||||
expectKind kids, nnkDo
|
||||
result = tcall2(body(kids), nil)
|
||||
when defined(debugKaraxDsl):
|
||||
echo repr result
|
||||
|
||||
macro flatHtml*(tag: untyped): Element =
|
||||
result = tcall2(tag, nil)
|
||||
when defined(debugKaraxDsl):
|
||||
echo repr result
|
||||
151
experiments/trello/trello.nim
Normal file
151
experiments/trello/trello.nim
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
|
||||
import knete, widgets
|
||||
import karax / jstrutils
|
||||
|
||||
type
|
||||
Attachable* = ref object of RootObj ## an element that is attachable to
|
||||
## DOM elements.
|
||||
attachedTo*: seq[Element]
|
||||
|
||||
TaskId = int
|
||||
Task = ref object
|
||||
id: TaskId
|
||||
name, desc: kstring
|
||||
|
||||
ColumnId = distinct int
|
||||
Column = ref object of Attachable
|
||||
id: ColumnId
|
||||
header: kstring
|
||||
tasks: seq[Task]
|
||||
|
||||
Board = ref object
|
||||
columns: seq[Column]
|
||||
|
||||
var b = Board(columns: @[Column(id: ColumnId 0, header: "To Do"),
|
||||
Column(id: ColumnId 1, header: "Doing"),
|
||||
Column(id: ColumnId 2, header: "Done")])
|
||||
|
||||
proc removeTask(t: TaskId) =
|
||||
for c in b.columns:
|
||||
for i in 0 ..< c.tasks.len:
|
||||
if c.tasks[i].id == t:
|
||||
delete(c.tasks, i)
|
||||
break
|
||||
|
||||
proc moveTask(t: TaskId; dest: Column) =
|
||||
for c in b.columns:
|
||||
for i in 0 ..< c.tasks.len:
|
||||
let task = c.tasks[i]
|
||||
if task.id == t:
|
||||
delete(c.tasks, i)
|
||||
dest.tasks.add task
|
||||
return
|
||||
|
||||
var nextTaskId = 0 ## negative because they are not yet backed
|
||||
## up by the database
|
||||
|
||||
proc createTask(c: Column; name, desc: kstring): Task =
|
||||
dec nextTaskId
|
||||
result = Task(id: TaskId nextTaskId, name: name, desc: desc)
|
||||
c.tasks.add result
|
||||
|
||||
# --------------------- UI -----------------------------------------------
|
||||
|
||||
proc renderTask(t: Task): Element =
|
||||
proc dodelete =
|
||||
removeTask t.id
|
||||
delete result
|
||||
|
||||
proc dragstart(ev: Event) =
|
||||
ev.prepareDragData("taskid", ev.target.id)
|
||||
|
||||
result = buildHtml():
|
||||
tdiv(draggable="true", ondragstart=dragstart, id = &t.id):
|
||||
bindField t.name
|
||||
br()
|
||||
bindField t.desc
|
||||
button(onclick = dodelete):
|
||||
text cross
|
||||
|
||||
type
|
||||
NewTaskPanel = object
|
||||
c: Column
|
||||
e: Element
|
||||
|
||||
proc open(p: var NewTaskPanel; c: Column) =
|
||||
p.e.style.display = "block"
|
||||
p.c = c
|
||||
|
||||
proc newTaskDialog(): NewTaskPanel =
|
||||
var nameInp = buildHtml():
|
||||
input(setFocus = true)
|
||||
var descInp = buildHtml():
|
||||
input()
|
||||
|
||||
proc close =
|
||||
result.e.style.display = "none"
|
||||
|
||||
proc submit =
|
||||
let t = createTask(result.c, nameInp.value, descInp.value)
|
||||
close()
|
||||
result.c.attachedTo[0].add renderTask(t)
|
||||
|
||||
result.e = buildHtml():
|
||||
tdiv(style={display: "none", position: "fixed",
|
||||
left: "0", top: "0", width: "100%", height: "100%",
|
||||
overflow: "auto",
|
||||
backgroundColor: "rgb(0,0,0)",
|
||||
backgroundColor: "rgba(0,0,0,0.4)", zIndex: "1"}):
|
||||
tdiv(style={backgroundColor: "#fefefe",
|
||||
margin: "15% auto", padding: "20px", border: "1px solid #888",
|
||||
width: "80%"}):
|
||||
span(onclick = close, style={color: "#aaa",
|
||||
cssFloat: "right",
|
||||
fontSize: "28px",
|
||||
fontWeight: "bold"}):
|
||||
text cross
|
||||
p:
|
||||
text "Task name"
|
||||
nameInp
|
||||
p:
|
||||
text "Task description"
|
||||
descInp
|
||||
span(onclick = submit, style={color: "#0f0",
|
||||
cssFloat: "left",
|
||||
fontWeight: "bold"}):
|
||||
text "Submit"
|
||||
|
||||
var dialog = newTaskDialog()
|
||||
|
||||
proc renderColumn(c: Column): Element =
|
||||
proc doadd(c: Column): proc () =
|
||||
result = proc() = dialog.open(c)
|
||||
|
||||
proc allowDrop(ev: Event) = ev.preventDefault()
|
||||
proc drop(ev: Event) =
|
||||
ev.preventDefault()
|
||||
let data = ev.recvDragData("taskid")
|
||||
moveTask(parseInt data, c)
|
||||
ev.target.up("mycolumn").add(getElementById(data))
|
||||
|
||||
result = buildHtml():
|
||||
tdiv(class = "mycolumn", style = {cssFloat: "left", width: "33%"},
|
||||
ondrop = drop, ondragover = allowDrop):
|
||||
tdiv(class = "myheader"):
|
||||
bindField c.header
|
||||
span(onclick = doadd(c)):
|
||||
text plus
|
||||
for t in c.tasks:
|
||||
renderTask(t)
|
||||
c.attachedTo.add result
|
||||
|
||||
proc renderBoard(b: Board): Element =
|
||||
result = buildHtml(tdiv):
|
||||
dialog.e
|
||||
for c in b.columns:
|
||||
renderColumn(c)
|
||||
|
||||
proc main(hashPart: kstring): Element =
|
||||
result = renderBoard(b)
|
||||
|
||||
setInitializer main
|
||||
43
experiments/trello/widgets.nim
Normal file
43
experiments/trello/widgets.nim
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
## Common widget implemenations
|
||||
|
||||
import knete
|
||||
|
||||
const
|
||||
cross* = "x"
|
||||
plus* = kstring"+"
|
||||
|
||||
const
|
||||
LEFT* = 37
|
||||
UP* = 38
|
||||
RIGHT* = 39
|
||||
DOWN* = 40
|
||||
TAB* = 9
|
||||
ESC* = 27
|
||||
ENTER* = 13
|
||||
|
||||
proc editable*(x: kstring; onchanged: proc (value: kstring);
|
||||
isEdited = false): Element =
|
||||
proc onenter(ev: Event) = ev.target.blur()
|
||||
proc submit(ev: Event) =
|
||||
onchanged(ev.target.value)
|
||||
# close the loop: This is a common pattern in Knete.
|
||||
replace(result, editable(ev.target.value, onchanged, false))
|
||||
|
||||
proc makeEditable() =
|
||||
# close the loop: This is a common pattern in Knete.
|
||||
replace(result, editable(x, onchanged, true))
|
||||
|
||||
result = buildHtml():
|
||||
if isEdited:
|
||||
input(class = "edit",
|
||||
onblur = submit,
|
||||
onkeyupenter = onenter,
|
||||
value = x,
|
||||
setFocus = true)
|
||||
else:
|
||||
span(onclick = makeEditable):
|
||||
text x
|
||||
|
||||
template bindField*(field): Element =
|
||||
editable field, proc (value: kstring) =
|
||||
field = value
|
||||
Loading…
Add table
Add a link
Reference in a new issue