Merge branch 'master' of github.com:pragmagic/karax
This commit is contained in:
commit
55b551678a
15 changed files with 1737 additions and 108 deletions
|
|
@ -15,7 +15,7 @@ proc `width=`(n: Node, w: int) {.importcpp: "#.width = #".}
|
||||||
proc mplayer*(id, resource: cstring): VNode {.compact.} =
|
proc mplayer*(id, resource: cstring): VNode {.compact.} =
|
||||||
proc handler(ev: Event; n: VNode) =
|
proc handler(ev: Event; n: VNode) =
|
||||||
let myVideo = document.getElementById(id)
|
let myVideo = document.getElementById(id)
|
||||||
case n.key
|
case n.index
|
||||||
of Play:
|
of Play:
|
||||||
if myVideo.paused:
|
if myVideo.paused:
|
||||||
myVideo.play()
|
myVideo.play()
|
||||||
|
|
@ -27,13 +27,13 @@ proc mplayer*(id, resource: cstring): VNode {.compact.} =
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
result = buildHtml(tdiv):
|
result = buildHtml(tdiv):
|
||||||
button(onclick=handler, key=Play):
|
button(onclick=handler, index=Play):
|
||||||
text "Play/Pause"
|
text "Play/Pause"
|
||||||
button(onclick=handler, key=Big):
|
button(onclick=handler, index=Big):
|
||||||
text "Big"
|
text "Big"
|
||||||
button(onclick=handler, key=Small):
|
button(onclick=handler, index=Small):
|
||||||
text "Small"
|
text "Small"
|
||||||
button(onclick=handler, key=Normal):
|
button(onclick=handler, index=Normal):
|
||||||
text "Normal"
|
text "Normal"
|
||||||
br()
|
br()
|
||||||
br()
|
br()
|
||||||
|
|
|
||||||
|
|
@ -45,19 +45,19 @@ proc onTodoEnter(ev: Event; n: VNode) =
|
||||||
n.value = ""
|
n.value = ""
|
||||||
|
|
||||||
proc removeHandler(ev: Event; n: VNode) =
|
proc removeHandler(ev: Event; n: VNode) =
|
||||||
updateEntry(n.key, cstring(nil), false)
|
updateEntry(n.index, cstring(nil), false)
|
||||||
|
|
||||||
proc editHandler(ev: Event; n: VNode) =
|
proc editHandler(ev: Event; n: VNode) =
|
||||||
selectedEntry = n.key
|
selectedEntry = n.index
|
||||||
|
|
||||||
proc focusLost(ev: Event; n: VNode) = selectedEntry = -1
|
proc focusLost(ev: Event; n: VNode) = selectedEntry = -1
|
||||||
|
|
||||||
proc editEntry(ev: Event; n: VNode) =
|
proc editEntry(ev: Event; n: VNode) =
|
||||||
setEntryContent(n.key, n.value)
|
setEntryContent(n.index, n.value)
|
||||||
selectedEntry = -1
|
selectedEntry = -1
|
||||||
|
|
||||||
proc toggleEntry(ev: Event; n: VNode) =
|
proc toggleEntry(ev: Event; n: VNode) =
|
||||||
let id = n.key
|
let id = n.index
|
||||||
markAsCompleted(id, not isCompleted(id))
|
markAsCompleted(id, not isCompleted(id))
|
||||||
|
|
||||||
proc onAllDone(ev: Event; n: VNode) =
|
proc onAllDone(ev: Event; n: VNode) =
|
||||||
|
|
@ -83,12 +83,12 @@ proc createEntry(id: int; d: cstring; completed, selected: bool): VNode {.compac
|
||||||
if not selected:
|
if not selected:
|
||||||
tdiv(class = "view"):
|
tdiv(class = "view"):
|
||||||
input(class = "toggle", `type` = "checkbox", checked = toChecked(completed),
|
input(class = "toggle", `type` = "checkbox", checked = toChecked(completed),
|
||||||
onclick=toggleEntry, key=id)
|
onclick=toggleEntry, index=id)
|
||||||
label(onDblClick=editHandler, key=id):
|
label(onDblClick=editHandler, index=id):
|
||||||
text d
|
text d
|
||||||
button(class = "destroy", key=id, onclick=removeHandler)
|
button(class = "destroy", index=id, onclick=removeHandler)
|
||||||
else:
|
else:
|
||||||
input(class = "edit", name = "title", key=id,
|
input(class = "edit", name = "title", index=id,
|
||||||
onblur = focusLost,
|
onblur = focusLost,
|
||||||
onkeyupenter = editEntry, value = d, setFocus=true)
|
onkeyupenter = editEntry, value = d, setFocus=true)
|
||||||
|
|
||||||
|
|
|
||||||
120
experiments/nextgen.nim
Normal file
120
experiments/nextgen.nim
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
|
||||||
|
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils, reactive
|
||||||
|
|
||||||
|
proc textInput*(text: RString; focus: RBool): VNode {.track.} =
|
||||||
|
proc onFlip(ev: Event; target: VNode) =
|
||||||
|
focus <- not focus.value
|
||||||
|
|
||||||
|
proc onKeyupEnter(ev: Event; target: VNode) =
|
||||||
|
text <- target.value
|
||||||
|
|
||||||
|
proc onkeyup(ev: Event; n: VNode) =
|
||||||
|
# keep displayValue up to date, but do not tell the client yet!
|
||||||
|
text.value = n.value
|
||||||
|
|
||||||
|
result = buildHtml(input(`type`="text",
|
||||||
|
value=text.value, onblur=onFlip, onfocus=onFlip,
|
||||||
|
onkeyupenter=onkeyupenter, onkeyup=onkeyup, setFocus=focus.value))
|
||||||
|
|
||||||
|
var
|
||||||
|
errmsg = rstr("")
|
||||||
|
|
||||||
|
makeReactive:
|
||||||
|
type
|
||||||
|
User = ref object
|
||||||
|
firstname, lastname: cstring
|
||||||
|
selected: bool
|
||||||
|
|
||||||
|
var gu = newRSeq(@[ (User(rawFirstname: "Some", rawLastname: "Body")),
|
||||||
|
(User(rawFirstname: "Some", rawLastname: "One")),
|
||||||
|
(User(rawFirstname: "Some", rawLastname: "Two"))])
|
||||||
|
var prevSelected: User = nil #newReactive[User](nil)
|
||||||
|
|
||||||
|
proc unselect() =
|
||||||
|
if prevSelected != nil:
|
||||||
|
prevSelected.selected = false
|
||||||
|
prevSelected = nil
|
||||||
|
|
||||||
|
proc select(u: User) =
|
||||||
|
unselect()
|
||||||
|
u.selected = true
|
||||||
|
prevSelected = u
|
||||||
|
|
||||||
|
proc toUI*(isFirstname: bool): RString =
|
||||||
|
result = RString()
|
||||||
|
result.subscribe proc (v: cstring) =
|
||||||
|
if v.len > 0:
|
||||||
|
let p = prevSelected #selected.value
|
||||||
|
if p != nil:
|
||||||
|
if isFirstName:
|
||||||
|
p.firstname = v
|
||||||
|
else:
|
||||||
|
p.lastname = v
|
||||||
|
unselect()
|
||||||
|
errmsg <- ""
|
||||||
|
else:
|
||||||
|
errmsg <- "name must not be empty"
|
||||||
|
|
||||||
|
var inpFirstname = toUI(true)
|
||||||
|
var inpLastname = toUI(false)
|
||||||
|
|
||||||
|
proc adaptFocus(def = false): RBool =
|
||||||
|
result = RBool()
|
||||||
|
result.value = def
|
||||||
|
when false:
|
||||||
|
result.subscribe proc (hasFocus: bool) =
|
||||||
|
if not hasFocus:
|
||||||
|
unselect()
|
||||||
|
|
||||||
|
var focusA = adaptFocus()
|
||||||
|
var focusB = adaptFocus()
|
||||||
|
|
||||||
|
proc styler(): VStyle =
|
||||||
|
result = style(
|
||||||
|
(StyleAttr.position, cstring"relative"),
|
||||||
|
(StyleAttr.paddingLeft, cstring"10px"),
|
||||||
|
(StyleAttr.paddingRight, cstring"5px"),
|
||||||
|
(StyleAttr.height, cstring"30px"),
|
||||||
|
(StyleAttr.lineHeight, cstring"30px"),
|
||||||
|
(StyleAttr.border, cstring"solid 8px " & (if focusA.value: cstring"red" else: cstring"black")),
|
||||||
|
(StyleAttr.fontSize, cstring"12px"),
|
||||||
|
(StyleAttr.fontWeight, cstring"600")
|
||||||
|
)
|
||||||
|
|
||||||
|
var clicks = 0
|
||||||
|
|
||||||
|
proc renderUser(u: User): VNode {.track.} =
|
||||||
|
result = buildHtml(tdiv):
|
||||||
|
if u.selected:
|
||||||
|
inpFirstname <- u.firstname
|
||||||
|
tdiv:
|
||||||
|
textInput inpFirstname, focusA
|
||||||
|
inpLastname <- u.lastname
|
||||||
|
tdiv:
|
||||||
|
textInput inpLastname, focusB
|
||||||
|
else:
|
||||||
|
button:
|
||||||
|
text "..."
|
||||||
|
proc onclick(ev: Event; n: VNode) =
|
||||||
|
select(u)
|
||||||
|
text u.firstname & " " & u.lastname
|
||||||
|
button:
|
||||||
|
text "(x)"
|
||||||
|
proc onclick(ev: Event; n: VNode) =
|
||||||
|
gu.deleteElem(u)
|
||||||
|
|
||||||
|
proc main(gu: RSeq[User]): VNode =
|
||||||
|
result = buildHtml(tdiv):
|
||||||
|
tdiv:
|
||||||
|
button:
|
||||||
|
text "Add User"
|
||||||
|
proc onclick(ev: Event; n: VNode) =
|
||||||
|
inc clicks
|
||||||
|
gu.add User(rawFirstname: "Added", rawLastname: &clicks)
|
||||||
|
tdiv:
|
||||||
|
text errmsg
|
||||||
|
vmapIt(gu, tdiv, renderUser(it))
|
||||||
|
|
||||||
|
proc init(): VNode = main(gu)
|
||||||
|
|
||||||
|
setInitializer(init)
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
--path: "../src"
|
|
||||||
152
src/i18n.nim
Normal file
152
src/i18n.nim
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
## Karax -- Single page applications for Nim.
|
||||||
|
|
||||||
|
## i18n support for Karax applications. We distinguish between:
|
||||||
|
##
|
||||||
|
## - translate: Lookup text translations
|
||||||
|
## - localize: Localize Date and Time objects to local formats
|
||||||
|
##
|
||||||
|
## Localization has not yet been implemented.
|
||||||
|
|
||||||
|
import languages, jdict, jstrutils
|
||||||
|
|
||||||
|
var currentLanguage = Language.enUS
|
||||||
|
|
||||||
|
proc getLanguage(): cstring =
|
||||||
|
{.emit: "`result` = (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.language;".}
|
||||||
|
|
||||||
|
proc detectLanguage*(): Language =
|
||||||
|
let x = getLanguage()
|
||||||
|
for i in low(Language)..high(Language):
|
||||||
|
if languageToCode[i] == x: return i
|
||||||
|
return Language.enUS
|
||||||
|
|
||||||
|
proc setCurrentLanguage*(newLanguage = detectLanguage()) =
|
||||||
|
currentLanguage = newLanguage
|
||||||
|
|
||||||
|
proc getCurrentLanguage*(): Language = currentLanguage
|
||||||
|
|
||||||
|
type Translation* = JDict[cstring, cstring]
|
||||||
|
|
||||||
|
var
|
||||||
|
translations: array[Language, Translation]
|
||||||
|
|
||||||
|
proc registerTranslation*(lang: Language; t: Translation) = translations[lang] = t
|
||||||
|
|
||||||
|
proc addTranslation*(lang: Language; key, val: cstring) =
|
||||||
|
if translations[lang].isNil: translations[lang] = newJDict[cstring, cstring]()
|
||||||
|
translations[lang][key] = val
|
||||||
|
|
||||||
|
proc translate(x: cstring): cstring =
|
||||||
|
let y = translations[currentLanguage]
|
||||||
|
if y != nil and y.contains(x):
|
||||||
|
result = y[x]
|
||||||
|
else:
|
||||||
|
result = x
|
||||||
|
|
||||||
|
type TranslatedString* = distinct cstring
|
||||||
|
|
||||||
|
template i18n*(x: string): TranslatedString = TranslatedString(translate(cstring x))
|
||||||
|
|
||||||
|
proc raiseInvalidFormat(errmsg: string) =
|
||||||
|
raise newException(ValueError, errmsg)
|
||||||
|
|
||||||
|
discard """
|
||||||
|
$[1:item|items]1$ selected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
proc `{}`(x: cstring; i: int): char {.importcpp: "#.charCodeAt(#)".}
|
||||||
|
proc sadd(s: JSeq[char]; c: char) {.importcpp: "#.push(String.fromCharCode(#))".}
|
||||||
|
|
||||||
|
proc parseChoice(f: cstring; i, choice: int,
|
||||||
|
r: JSeq[char]) =
|
||||||
|
var i = i
|
||||||
|
while i < f.len:
|
||||||
|
var n = 0
|
||||||
|
let oldI = i
|
||||||
|
var toAdd = false
|
||||||
|
while i < f.len and f{i} >= '0' and f{i} <= '9':
|
||||||
|
n = n * 10 + ord(f{i}) - ord('0')
|
||||||
|
inc i
|
||||||
|
if oldI != i:
|
||||||
|
if f{i} == ':':
|
||||||
|
inc i
|
||||||
|
else:
|
||||||
|
raiseInvalidFormat"':' after number expected"
|
||||||
|
toAdd = choice == n
|
||||||
|
else:
|
||||||
|
# an else section does not start with a number:
|
||||||
|
toAdd = true
|
||||||
|
while i < f.len and f{i} != ']' and f{i} != '|':
|
||||||
|
if toAdd: r.sadd f{i}
|
||||||
|
inc i
|
||||||
|
if toAdd: break
|
||||||
|
inc i
|
||||||
|
|
||||||
|
proc join(x: JSeq[char]): cstring {.importcpp: "#.join(\"\")".}
|
||||||
|
proc add(x: JSeq[char]; y: cstring) =
|
||||||
|
for i in 0..<y.len: x.sadd y{i}
|
||||||
|
|
||||||
|
proc `%`*(formatString: TranslatedString; args: openArray[cstring]): cstring =
|
||||||
|
let f = cstring(formatString)
|
||||||
|
var i = 0
|
||||||
|
var num = 0
|
||||||
|
var r = newJSeq[char]()
|
||||||
|
while i < f.len:
|
||||||
|
if f{i} == '$' and i+1 < f.len:
|
||||||
|
inc i
|
||||||
|
case f{i}
|
||||||
|
of '#':
|
||||||
|
r.add args[num]
|
||||||
|
inc i
|
||||||
|
inc num
|
||||||
|
of '1'..'9', '-':
|
||||||
|
var j = 0
|
||||||
|
var negative = f{i} == '-'
|
||||||
|
if negative: inc i
|
||||||
|
while f{i} >= '0' and f{i} <= '9':
|
||||||
|
j = j * 10 + ord(f{i}) - ord('0')
|
||||||
|
inc i
|
||||||
|
let idx = if not negative: j-1 else: args.len-j
|
||||||
|
r.add args[idx]
|
||||||
|
of '$':
|
||||||
|
inc(i)
|
||||||
|
r.add '$'
|
||||||
|
of '[':
|
||||||
|
let start = i+1
|
||||||
|
while i < f.len and f{i} != ']': inc i
|
||||||
|
inc i
|
||||||
|
if i >= f.len: raiseInvalidFormat"']' expected"
|
||||||
|
case f{i}
|
||||||
|
of '#':
|
||||||
|
parseChoice(f, start, parseInt args[num], r)
|
||||||
|
inc i
|
||||||
|
inc num
|
||||||
|
of '1'..'9', '-':
|
||||||
|
var j = 0
|
||||||
|
var negative = f{i} == '-'
|
||||||
|
if negative: inc i
|
||||||
|
while f{i} >= '0' and f{i} <= '9':
|
||||||
|
j = j * 10 + ord(f{i}) - ord('0')
|
||||||
|
inc i
|
||||||
|
let idx = if not negative: j-1 else: args.len-j
|
||||||
|
parseChoice(f, start, parseInt args[idx], r)
|
||||||
|
else: raiseInvalidFormat"argument index expected after ']'"
|
||||||
|
else:
|
||||||
|
raiseInvalidFormat("'#', '$', or number expected")
|
||||||
|
if i < f.len and f{i} == '$': inc i
|
||||||
|
else:
|
||||||
|
r.sadd f{i}
|
||||||
|
inc i
|
||||||
|
result = join(r)
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
addTranslation(Language.deDE, "$#$ -> $#$", "$2 macht $1")
|
||||||
|
setCurrentLanguage(Language.deDE)
|
||||||
|
|
||||||
|
echo(i18n"$#$ -> $#$" % [cstring"1", "2"])
|
||||||
|
echo(i18n"$[1:item |items ]1$ -> $1" % [cstring"1", "2"])
|
||||||
|
echo(i18n"$[1:item |items ]1 -> $1" % [cstring"0", "2"])
|
||||||
|
echo(i18n"$[1:item |items ]1 -> $1" % [cstring"2", "2"])
|
||||||
|
echo(i18n"$[1:item |items ]1$ -> $1" % [cstring"3", "2"])
|
||||||
|
echo(i18n"$1 $[1:item |4:huha |items ]1$ -> $1" % [cstring"4", "2"])
|
||||||
|
|
@ -23,7 +23,7 @@ proc ajax(meth, url: cstring; headers: openarray[(cstring, cstring)];
|
||||||
proc statechange(r: HttpRequest; cb: proc()) {.importcpp: "#.onreadystatechange = #".}
|
proc statechange(r: HttpRequest; cb: proc()) {.importcpp: "#.onreadystatechange = #".}
|
||||||
proc send(r: HttpRequest; data: cstring) {.importcpp: "#.send(#)".}
|
proc send(r: HttpRequest; data: cstring) {.importcpp: "#.send(#)".}
|
||||||
proc open(r: HttpRequest; meth, url: cstring; async: bool) {.importcpp: "#.open(@)".}
|
proc open(r: HttpRequest; meth, url: cstring; async: bool) {.importcpp: "#.open(@)".}
|
||||||
proc newRequest(): HttpRequest {.importcpp: "new XMLHttpRequest(@)",}
|
proc newRequest(): HttpRequest {.importcpp: "new XMLHttpRequest(@)".}
|
||||||
|
|
||||||
var this {.importc: "this".}: ThisObj
|
var this {.importc: "this".}: ThisObj
|
||||||
let ajax = newRequest()
|
let ajax = newRequest()
|
||||||
|
|
|
||||||
271
src/karax.nim
271
src/karax.nim
|
|
@ -7,13 +7,6 @@ export kdom.Event
|
||||||
proc kout*[T](x: T) {.importc: "console.log", varargs.}
|
proc kout*[T](x: T) {.importc: "console.log", varargs.}
|
||||||
## the preferred way of debugging karax applications.
|
## the preferred way of debugging karax applications.
|
||||||
|
|
||||||
proc hasProp(e: Node; prop: cstring): bool {.importcpp: "(#.hasOwnProperty(#))".}
|
|
||||||
proc rawkey(e: Node): VKey {.importcpp: "#.karaxKey", nodecl.}
|
|
||||||
proc key*(e: Node): VKey =
|
|
||||||
if e.hasProp"karaxKey": result = e.rawkey
|
|
||||||
else: result = -1
|
|
||||||
proc `key=`*(e: Node; x: VKey) {.importcpp: "#.karaxKey = #", nodecl.}
|
|
||||||
|
|
||||||
type
|
type
|
||||||
PatchKind = enum
|
PatchKind = enum
|
||||||
pkReplace, pkRemove, pkAppend, pkInsertBefore, pkDetach
|
pkReplace, pkRemove, pkAppend, pkInsertBefore, pkDetach
|
||||||
|
|
@ -24,6 +17,9 @@ type
|
||||||
PatchV = object
|
PatchV = object
|
||||||
parent, newChild: VNode
|
parent, newChild: VNode
|
||||||
pos: int
|
pos: int
|
||||||
|
ComponentPair = object
|
||||||
|
oldNode, newNode: VComponent
|
||||||
|
parent, current: Node
|
||||||
|
|
||||||
type
|
type
|
||||||
KaraxInstance* = ref object ## underlying karax instance. Usually you don't have
|
KaraxInstance* = ref object ## underlying karax instance. Usually you don't have
|
||||||
|
|
@ -40,6 +36,8 @@ type
|
||||||
patchesV: seq[PatchV]
|
patchesV: seq[PatchV]
|
||||||
patchLenV: int
|
patchLenV: int
|
||||||
runCount: int
|
runCount: int
|
||||||
|
components: seq[ComponentPair]
|
||||||
|
surpressRedraws*: bool
|
||||||
when defined(stats):
|
when defined(stats):
|
||||||
recursion: int
|
recursion: int
|
||||||
|
|
||||||
|
|
@ -64,6 +62,7 @@ template setNativeValue(ev, val) = cast[Element](ev.target).value = val
|
||||||
template keyeventBody() =
|
template keyeventBody() =
|
||||||
let v = nativeValue(ev)
|
let v = nativeValue(ev)
|
||||||
n.value = v
|
n.value = v
|
||||||
|
assert action != nil
|
||||||
action(ev, n)
|
action(ev, n)
|
||||||
if n.value != v:
|
if n.value != v:
|
||||||
setNativeValue(ev, n.value)
|
setNativeValue(ev, n.value)
|
||||||
|
|
@ -76,7 +75,7 @@ proc wrapEvent(d: Node; n: VNode; k: EventKind;
|
||||||
let action = action
|
let action = action
|
||||||
let n = n
|
let n = n
|
||||||
result = proc (ev: Event) =
|
result = proc (ev: Event) =
|
||||||
if n.kind == VNodeKind.textarea or n.kind == VNodeKind.input:
|
if n.kind == VNodeKind.textarea or n.kind == VNodeKind.input or n.kind == VNodeKind.select:
|
||||||
keyeventBody()
|
keyeventBody()
|
||||||
else: action(ev, n)
|
else: action(ev, n)
|
||||||
|
|
||||||
|
|
@ -119,7 +118,7 @@ proc applyEvents(n: VNode; kxi: KaraxInstance) =
|
||||||
for i in 0..<len(n.events):
|
for i in 0..<len(n.events):
|
||||||
n.events[i][2] = wrapEvent(dest, n, n.events[i][0], n.events[i][1])
|
n.events[i][2] = wrapEvent(dest, n, n.events[i][0], n.events[i][1])
|
||||||
|
|
||||||
proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
proc vnodeToDom*(n: VNode; kxi: KaraxInstance): Node =
|
||||||
if n.kind == VNodeKind.text:
|
if n.kind == VNodeKind.text:
|
||||||
result = document.createTextNode(n.text)
|
result = document.createTextNode(n.text)
|
||||||
attach n
|
attach n
|
||||||
|
|
@ -136,6 +135,7 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
||||||
return result
|
return result
|
||||||
elif n.kind == VNodeKind.component:
|
elif n.kind == VNodeKind.component:
|
||||||
let x = VComponent(n)
|
let x = VComponent(n)
|
||||||
|
if x.realDomImpl != nil: return x.realDomImpl(x)
|
||||||
if x.onAttachImpl != nil: x.onAttachImpl(x)
|
if x.onAttachImpl != nil: x.onAttachImpl(x)
|
||||||
assert x.renderImpl != nil
|
assert x.renderImpl != nil
|
||||||
if x.expanded == nil:
|
if x.expanded == nil:
|
||||||
|
|
@ -177,28 +177,50 @@ proc same(n: VNode, e: Node; nesting = 0): bool =
|
||||||
result = true
|
result = true
|
||||||
if n.kind != VNodeKind.text:
|
if n.kind != VNodeKind.text:
|
||||||
if e.len != n.len:
|
if e.len != n.len:
|
||||||
kout e.len, n.len, toTag[n.kind], nesting
|
echo "expected ", e.len, " real ", n.len, toTag[n.kind], " nesting ", nesting
|
||||||
return false
|
return false
|
||||||
for i in 0 ..< n.len:
|
for i in 0 ..< n.len:
|
||||||
if not same(n[i], e[i], nesting+1): return false
|
if not same(n[i], e[i], nesting+1): return false
|
||||||
else:
|
else:
|
||||||
kout toTag[n.kind], e.nodename
|
echo "VDOM: ", toTag[n.kind], " DOM: ", e.nodename
|
||||||
|
|
||||||
proc replaceById(id: cstring; newTree: Node) =
|
proc replaceById(id: cstring; newTree: Node) =
|
||||||
let x = document.getElementById(id)
|
let x = document.getElementById(id)
|
||||||
x.parentNode.replaceChild(newTree, x)
|
x.parentNode.replaceChild(newTree, x)
|
||||||
|
newTree.id = id
|
||||||
|
|
||||||
type
|
type
|
||||||
EqResult = enum
|
EqResult = enum
|
||||||
changed, different, similar, identical, usenewNode
|
componentsIdentical, different, similar, identical, usenewNode
|
||||||
|
|
||||||
|
when defined(profileKarax):
|
||||||
|
type
|
||||||
|
DifferEnum = enum
|
||||||
|
deKind, deId, deIndex, deText, deComponent, deClass,
|
||||||
|
deSimilar
|
||||||
|
|
||||||
|
var
|
||||||
|
reasons: array[DifferEnum, int]
|
||||||
|
|
||||||
|
proc echa(a: array[DifferEnum, int]) =
|
||||||
|
for i in low(DifferEnum)..high(DifferEnum):
|
||||||
|
echo i, " value: ", a[i]
|
||||||
|
|
||||||
proc eq(a, b: VNode): EqResult =
|
proc eq(a, b: VNode): EqResult =
|
||||||
if a.kind != b.kind: return different
|
if a.kind != b.kind:
|
||||||
if a.id != b.id: return different
|
when defined(profileKarax): inc reasons[deKind]
|
||||||
|
return different
|
||||||
|
if a.id != b.id:
|
||||||
|
when defined(profileKarax): inc reasons[deId]
|
||||||
|
return different
|
||||||
result = identical
|
result = identical
|
||||||
if a.key != b.key: return different
|
if a.index != b.index:
|
||||||
|
when defined(profileKarax): inc reasons[deIndex]
|
||||||
|
return different
|
||||||
if a.kind == VNodeKind.text:
|
if a.kind == VNodeKind.text:
|
||||||
if a.text != b.text: return different
|
if a.text != b.text:
|
||||||
|
when defined(profileKarax): inc reasons[deText]
|
||||||
|
return similar
|
||||||
elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk:
|
elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk:
|
||||||
if a.text != b.text: return different
|
if a.text != b.text: return different
|
||||||
if a.len != b.len: return different
|
if a.len != b.len: return different
|
||||||
|
|
@ -206,13 +228,22 @@ proc eq(a, b: VNode): EqResult =
|
||||||
if eq(a[i], b[i]) == different: return different
|
if eq(a[i], b[i]) == different: return different
|
||||||
elif b.kind == VNodeKind.component:
|
elif b.kind == VNodeKind.component:
|
||||||
# different component names mean different components:
|
# different component names mean different components:
|
||||||
if a.text != b.text: return different
|
if a.text != b.text:
|
||||||
let x = VComponent(b)
|
when defined(profileKarax): inc reasons[deComponent]
|
||||||
assert x.changedImpl != nil
|
return different
|
||||||
return if x.changedImpl(x, VComponent(a)): changed else: identical
|
#if VComponent(a).key.isNil and VComponent(b).key.isNil:
|
||||||
if not sameAttrs(a, b): return different
|
# when defined(profileKarax): inc reasons[deComponent]
|
||||||
if a.class != b.class: return different
|
# return different
|
||||||
if not eq(a.style, b.style): return similar
|
if VComponent(a).key != VComponent(b).key:
|
||||||
|
when defined(profileKarax): inc reasons[deComponent]
|
||||||
|
return different
|
||||||
|
return componentsIdentical
|
||||||
|
if a.class != b.class:
|
||||||
|
when defined(profileKarax): inc reasons[deClass]
|
||||||
|
return different
|
||||||
|
if not eq(a.style, b.style) or not sameAttrs(a, b):
|
||||||
|
when defined(profileKarax): inc reasons[deSimilar]
|
||||||
|
return similar
|
||||||
# Do not test event listeners here!
|
# Do not test event listeners here!
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -223,19 +254,29 @@ proc updateStyles(newNode, oldNode: VNode) =
|
||||||
else: oldNode.dom.style = Style()
|
else: oldNode.dom.style = Style()
|
||||||
oldNode.style = newNode.style
|
oldNode.style = newNode.style
|
||||||
|
|
||||||
|
proc updateAttributes(newNode, oldNode: VNode) =
|
||||||
|
# we keep the oldNode, but take over the attributes from the new node:
|
||||||
|
if oldNode.dom != nil:
|
||||||
|
for k, _ in attrs(oldNode):
|
||||||
|
oldNode.dom.removeAttribute(k)
|
||||||
|
for k, v in attrs(newNode):
|
||||||
|
if v != nil:
|
||||||
|
oldNode.dom.setAttr(k, v)
|
||||||
|
takeOverAttr(newNode, oldNode)
|
||||||
|
|
||||||
proc mergeEvents(newNode, oldNode: VNode; kxi: KaraxInstance) =
|
proc mergeEvents(newNode, oldNode: VNode; kxi: KaraxInstance) =
|
||||||
let d = oldNode.dom
|
let d = oldNode.dom
|
||||||
for i in 0..<oldNode.events.len:
|
for i in 0..<oldNode.events.len:
|
||||||
let k = oldNode.events[i][0]
|
let k = oldNode.events[i][0]
|
||||||
let name = case k
|
let name = case k
|
||||||
of EventKind.onkeyuplater, EventKind.onkeyupenter: cstring"keyup"
|
of EventKind.onkeyuplater, EventKind.onkeyupenter: cstring"keyup"
|
||||||
else: toEventName[k]
|
else: toEventName[k]
|
||||||
d.removeEventListener(name, oldNode.events[i][2])
|
d.removeEventListener(name, oldNode.events[i][2])
|
||||||
shallowCopy(oldNode.events, newNode.events)
|
shallowCopy(oldNode.events, newNode.events)
|
||||||
applyEvents(oldNode, kxi)
|
applyEvents(oldNode, kxi)
|
||||||
|
|
||||||
proc printV(n: VNode; depth: cstring = "") =
|
proc printV(n: VNode; depth: cstring = "") =
|
||||||
kout depth, cstring($n.kind), cstring"key ", n.key
|
kout depth, cstring($n.kind), cstring"key ", n.index
|
||||||
#for k, v in pairs(n.style):
|
#for k, v in pairs(n.style):
|
||||||
# kout depth, "style: ", k, v
|
# kout depth, "style: ", k, v
|
||||||
if n.kind == VNodeKind.component:
|
if n.kind == VNodeKind.component:
|
||||||
|
|
@ -270,7 +311,7 @@ proc addPatchV(kxi: KaraxInstance; parent: VNode; pos: int; newChild: VNode) =
|
||||||
kxi.patchesV[L].pos = pos
|
kxi.patchesV[L].pos = pos
|
||||||
inc kxi.patchLenV
|
inc kxi.patchLenV
|
||||||
|
|
||||||
proc apply(kxi: KaraxInstance) =
|
proc applyPatch(kxi: KaraxInstance) =
|
||||||
for i in 0..<kxi.patchLen:
|
for i in 0..<kxi.patchLen:
|
||||||
let p = kxi.patches[i]
|
let p = kxi.patches[i]
|
||||||
case p.k
|
case p.k
|
||||||
|
|
@ -293,7 +334,8 @@ proc apply(kxi: KaraxInstance) =
|
||||||
if n.kind == VNodeKind.component:
|
if n.kind == VNodeKind.component:
|
||||||
let x = VComponent(n)
|
let x = VComponent(n)
|
||||||
if x.onDetachImpl != nil: x.onDetachImpl(x)
|
if x.onDetachImpl != nil: x.onDetachImpl(x)
|
||||||
n.dom = nil
|
# XXX for some reason this causes assertion errors otherwise:
|
||||||
|
if not kxi.surpressRedraws: n.dom = nil
|
||||||
kxi.patchLen = 0
|
kxi.patchLen = 0
|
||||||
for i in 0..<kxi.patchLenV:
|
for i in 0..<kxi.patchLenV:
|
||||||
let p = kxi.patchesV[i]
|
let p = kxi.patchesV[i]
|
||||||
|
|
@ -312,15 +354,27 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E
|
||||||
inc kxi.recursion
|
inc kxi.recursion
|
||||||
result = eq(newNode, oldNode)
|
result = eq(newNode, oldNode)
|
||||||
case result
|
case result
|
||||||
|
of componentsIdentical:
|
||||||
|
kxi.components.add ComponentPair(oldNode: VComponent(oldNode),
|
||||||
|
newNode: VComponent(newNode),
|
||||||
|
parent: parent,
|
||||||
|
current: current)
|
||||||
of identical, similar:
|
of identical, similar:
|
||||||
newNode.dom = oldNode.dom
|
newNode.dom = oldNode.dom
|
||||||
if result == similar: updateStyles(newNode, oldNode)
|
if result == similar:
|
||||||
|
updateStyles(newNode, oldNode)
|
||||||
|
updateAttributes(newNode, oldNode)
|
||||||
|
if oldNode.kind == VNodeKind.text:
|
||||||
|
oldNode.text = newNode.text
|
||||||
|
oldNode.dom.nodeValue = newNode.text
|
||||||
|
|
||||||
if newNode.events.len != 0 or oldNode.events.len != 0:
|
if newNode.events.len != 0 or oldNode.events.len != 0:
|
||||||
mergeEvents(newNode, oldNode, kxi)
|
mergeEvents(newNode, oldNode, kxi)
|
||||||
if oldNode.kind == VNodeKind.input or oldNode.kind == VNodeKind.textarea:
|
when false:
|
||||||
if oldNode.text != newNode.text:
|
if oldNode.kind == VNodeKind.input or oldNode.kind == VNodeKind.textarea:
|
||||||
oldNode.text = newNode.text
|
if oldNode.text != newNode.text:
|
||||||
oldNode.dom.value = newNode.text
|
oldNode.text = newNode.text
|
||||||
|
oldNode.dom.value = newNode.text
|
||||||
|
|
||||||
let newLength = newNode.len
|
let newLength = newNode.len
|
||||||
let oldLength = oldNode.len
|
let oldLength = oldNode.len
|
||||||
|
|
@ -343,7 +397,7 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E
|
||||||
else:
|
else:
|
||||||
diff(a[i], b[j], current, current.childNodes[j], kxi)
|
diff(a[i], b[j], current, current.childNodes[j], kxi)
|
||||||
case r
|
case r
|
||||||
of identical, changed, similar:
|
of identical, componentsIdentical, similar:
|
||||||
a[i] = b[j]
|
a[i] = b[j]
|
||||||
action
|
action
|
||||||
of usenewNode:
|
of usenewNode:
|
||||||
|
|
@ -397,27 +451,6 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E
|
||||||
#doAssert i < current.childNodes.len
|
#doAssert i < current.childNodes.len
|
||||||
kxi.addPatch(pkRemove, current, current.childNodes[i], nil)
|
kxi.addPatch(pkRemove, current, current.childNodes[i], nil)
|
||||||
result = usenewNode
|
result = usenewNode
|
||||||
|
|
||||||
of changed:
|
|
||||||
assert oldNode.kind == VNodeKind.component
|
|
||||||
let x = VComponent(oldNode)
|
|
||||||
x.updatedImpl(x, VComponent newNode)
|
|
||||||
let oldExpanded = x.expanded
|
|
||||||
x.expanded = x.renderImpl(x)
|
|
||||||
x.renderedVersion = x.version
|
|
||||||
if oldExpanded == nil:
|
|
||||||
detach(oldNode)
|
|
||||||
kxi.addPatch(pkReplace, parent, current, x.expanded)
|
|
||||||
else:
|
|
||||||
let res = diff(x.expanded, oldExpanded, parent, current, kxi)
|
|
||||||
if res == usenewNode:
|
|
||||||
#kxi.addPatch(pkReplace, parent, current, x.expanded)
|
|
||||||
discard "diff created a patchset for us, so this is fine"
|
|
||||||
elif res != different:
|
|
||||||
x.expanded = oldExpanded
|
|
||||||
assert oldExpanded.dom != nil, "old expanded.dom is nil"
|
|
||||||
else:
|
|
||||||
assert x.expanded.dom != nil, "expanded.dom is nil"
|
|
||||||
of different:
|
of different:
|
||||||
detach(oldNode)
|
detach(oldNode)
|
||||||
kxi.addPatch(pkReplace, parent, current, newNode)
|
kxi.addPatch(pkReplace, parent, current, newNode)
|
||||||
|
|
@ -425,6 +458,56 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E
|
||||||
when defined(stats):
|
when defined(stats):
|
||||||
dec kxi.recursion
|
dec kxi.recursion
|
||||||
|
|
||||||
|
proc applyComponents(kxi: KaraxInstance) =
|
||||||
|
# the first 'diff' pass detects components in the VDOM. The
|
||||||
|
# 'applyComponents' expands components and so on until no
|
||||||
|
# components are left to check.
|
||||||
|
var i = 0
|
||||||
|
# beware: 'diff' appends to kxi.components!
|
||||||
|
# So this is actually a fixpoint iteration:
|
||||||
|
while i < kxi.components.len:
|
||||||
|
let x = kxi.components[i].oldNode
|
||||||
|
let newNode = kxi.components[i].newNode
|
||||||
|
when defined(karaxDebug):
|
||||||
|
echo "Processing component ", newNode.text, " changed impl set ", x.changedImpl != nil
|
||||||
|
if x.realDomImpl != nil:
|
||||||
|
let current = kxi.components[i].current
|
||||||
|
let parent = kxi.components[i].parent
|
||||||
|
kxi.addPatch(pkReplace, parent, current, x)
|
||||||
|
elif x.changedImpl != nil and x.changedImpl(x, newNode):
|
||||||
|
when defined(karaxDebug):
|
||||||
|
echo "Component ", newNode.text, " did change"
|
||||||
|
let current = kxi.components[i].current
|
||||||
|
let parent = kxi.components[i].parent
|
||||||
|
x.updatedImpl(x, newNode)
|
||||||
|
let oldExpanded = x.expanded
|
||||||
|
x.expanded = x.renderImpl(x)
|
||||||
|
when defined(karaxDebug):
|
||||||
|
echo "Component ", newNode.text, " re-rendered"
|
||||||
|
x.renderedVersion = x.version
|
||||||
|
if oldExpanded == nil:
|
||||||
|
detach(x)
|
||||||
|
kxi.addPatch(pkReplace, parent, current, x.expanded)
|
||||||
|
when defined(karaxDebug):
|
||||||
|
echo "Component ", newNode.text, ": old expansion didn't exist"
|
||||||
|
else:
|
||||||
|
let res = diff(x.expanded, oldExpanded, parent, current, kxi)
|
||||||
|
if res == usenewNode:
|
||||||
|
when defined(karaxDebug):
|
||||||
|
echo "Component ", newNode.text, ": re-render triggered a DOM change (case A)"
|
||||||
|
discard "diff created a patchset for us, so this is fine"
|
||||||
|
elif res != different:
|
||||||
|
when defined(karaxDebug):
|
||||||
|
echo "Component ", newNode.text, ": re-render triggered no DOM change whatsoever"
|
||||||
|
x.expanded = oldExpanded
|
||||||
|
assert oldExpanded.dom != nil, "old expanded.dom is nil"
|
||||||
|
else:
|
||||||
|
when defined(karaxDebug):
|
||||||
|
echo "Component ", newNode.text, ": re-render triggered a DOM change (case B)"
|
||||||
|
assert x.expanded.dom != nil, "expanded.dom is nil"
|
||||||
|
inc i
|
||||||
|
setLen(kxi.components, 0)
|
||||||
|
|
||||||
when defined(stats):
|
when defined(stats):
|
||||||
proc depth(n: VNode; total: var int): int =
|
proc depth(n: VNode; total: var int): int =
|
||||||
var m = 0
|
var m = 0
|
||||||
|
|
@ -433,6 +516,40 @@ when defined(stats):
|
||||||
result = m + 1
|
result = m + 1
|
||||||
inc total
|
inc total
|
||||||
|
|
||||||
|
proc runDel*(kxi: KaraxInstance; parent: VNode; position: int) =
|
||||||
|
detach(parent[position])
|
||||||
|
let current = parent.dom
|
||||||
|
kxi.addPatch(pkRemove, current, current.childNodes[position], nil)
|
||||||
|
parent.delete(position)
|
||||||
|
applyPatch(kxi)
|
||||||
|
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
|
proc runIns*(kxi: KaraxInstance; parent, kid: VNode; position: int) =
|
||||||
|
let current = parent.dom
|
||||||
|
if position >= parent.len:
|
||||||
|
kxi.addPatch(pkAppend, current, nil, kid)
|
||||||
|
parent.add(kid)
|
||||||
|
else:
|
||||||
|
let before = current.childNodes[position]
|
||||||
|
kxi.addPatch(pkInsertBefore, current, before, kid)
|
||||||
|
parent.insert(kid, position)
|
||||||
|
applyPatch(kxi)
|
||||||
|
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
|
proc runDiff*(kxi: KaraxInstance; oldNode, newNode: VNode) =
|
||||||
|
let olddom = oldNode.dom
|
||||||
|
doAssert olddom != nil
|
||||||
|
discard diff(newNode, oldNode, nil, olddom, kxi)
|
||||||
|
# this is a bit nasty: Since we cannot patch the 'parent' of
|
||||||
|
# the current VNode (because we don't store it at all!), we
|
||||||
|
# need to override the fields individually:
|
||||||
|
takeOverFields(newNode, oldNode)
|
||||||
|
applyComponents(kxi)
|
||||||
|
applyPatch(kxi)
|
||||||
|
if kxi.currentTree == oldNode:
|
||||||
|
kxi.currentTree = newNode
|
||||||
|
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
proc dodraw(kxi: KaraxInstance) =
|
proc dodraw(kxi: KaraxInstance) =
|
||||||
if kxi.renderer.isNil: return
|
if kxi.renderer.isNil: return
|
||||||
let newtree = kxi.renderer()
|
let newtree = kxi.renderer()
|
||||||
|
|
@ -440,16 +557,23 @@ proc dodraw(kxi: KaraxInstance) =
|
||||||
newtree.id = kxi.rootId
|
newtree.id = kxi.rootId
|
||||||
kxi.toFocus = nil
|
kxi.toFocus = nil
|
||||||
if kxi.currentTree == nil:
|
if kxi.currentTree == nil:
|
||||||
kxi.currentTree = newtree
|
let asdom = vnodeToDom(newtree, kxi)
|
||||||
let asdom = vnodeToDom(kxi.currentTree, kxi)
|
|
||||||
replaceById(kxi.rootId, asdom)
|
replaceById(kxi.rootId, asdom)
|
||||||
else:
|
else:
|
||||||
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
let olddom = document.getElementById(kxi.rootId)
|
let olddom = document.getElementById(kxi.rootId)
|
||||||
discard diff(newtree, kxi.currentTree, nil, olddom, kxi)
|
discard diff(newtree, kxi.currentTree, nil, olddom, kxi)
|
||||||
#kout cstring"patch len ", patches.len
|
#kout cstring"patch len ", patches.len
|
||||||
apply(kxi)
|
when defined(profileKarax):
|
||||||
kxi.currentTree = newtree
|
echo "<<<<<<<<<<<<<<"
|
||||||
|
echa reasons
|
||||||
|
applyComponents(kxi)
|
||||||
|
when defined(profileKarax):
|
||||||
|
echo "--------------"
|
||||||
|
echa reasons
|
||||||
|
echo ">>>>>>>>>>>>>>"
|
||||||
|
applyPatch(kxi)
|
||||||
|
kxi.currentTree = newtree
|
||||||
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
||||||
if not kxi.postRenderCallback.isNil:
|
if not kxi.postRenderCallback.isNil:
|
||||||
|
|
@ -490,7 +614,20 @@ proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT",
|
||||||
result = KaraxInstance(rootId: root, renderer: renderer,
|
result = KaraxInstance(rootId: root, renderer: renderer,
|
||||||
postRenderCallback: clientPostRenderCallback,
|
postRenderCallback: clientPostRenderCallback,
|
||||||
patches: newSeq[Patch](60),
|
patches: newSeq[Patch](60),
|
||||||
patchesV: newSeq[PatchV](30))
|
patchesV: newSeq[PatchV](30),
|
||||||
|
components: @[])
|
||||||
|
kxi = result
|
||||||
|
window.onload = init
|
||||||
|
|
||||||
|
proc setInitializer*(renderer: proc (): VNode, root: cstring = "ROOT",
|
||||||
|
clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} =
|
||||||
|
## Setup Karax. Usually the return value can be ignored.
|
||||||
|
result = KaraxInstance(rootId: root, renderer: renderer,
|
||||||
|
postRenderCallback: clientPostRenderCallback,
|
||||||
|
patches: newSeq[Patch](60),
|
||||||
|
patchesV: newSeq[PatchV](30),
|
||||||
|
components: @[],
|
||||||
|
surpressRedraws: true)
|
||||||
kxi = result
|
kxi = result
|
||||||
window.onload = init
|
window.onload = init
|
||||||
|
|
||||||
|
|
@ -503,7 +640,19 @@ proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler;
|
||||||
## a ``redraw``.
|
## a ``redraw``.
|
||||||
proc wrapper(ev: Event; n: VNode) =
|
proc wrapper(ev: Event; n: VNode) =
|
||||||
action(ev, n)
|
action(ev, n)
|
||||||
redraw(kxi)
|
if not kxi.surpressRedraws: redraw(kxi)
|
||||||
|
addEventListener(n, k, wrapper)
|
||||||
|
|
||||||
|
proc addEventHandler*(n: VNode; k: EventKind; action: proc();
|
||||||
|
kxi: KaraxInstance = kxi) =
|
||||||
|
## Implements the foundation of Karax's event management.
|
||||||
|
## Karax DSL transforms ``tag(onEvent = handler)`` to
|
||||||
|
## ``tempNode.addEventHandler(tagNode, EventKind.onEvent, wrapper)``
|
||||||
|
## where ``wrapper`` calls the passed ``action`` and then triggers
|
||||||
|
## a ``redraw``.
|
||||||
|
proc wrapper(ev: Event; n: VNode) =
|
||||||
|
action()
|
||||||
|
if not kxi.surpressRedraws: redraw(kxi)
|
||||||
addEventListener(n, k, wrapper)
|
addEventListener(n, k, wrapper)
|
||||||
|
|
||||||
proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from strutils import startsWith, toLowerAscii
|
||||||
|
|
||||||
const
|
const
|
||||||
StmtContext = ["kout", "inc", "echo", "dec", "!"]
|
StmtContext = ["kout", "inc", "echo", "dec", "!"]
|
||||||
SpecialAttrs = ["id", "class", "value", "key", "style"]
|
SpecialAttrs = ["id", "class", "value", "index", "style"]
|
||||||
|
|
||||||
proc getName(n: NimNode): string =
|
proc getName(n: NimNode): string =
|
||||||
case n.kind
|
case n.kind
|
||||||
|
|
@ -59,9 +59,22 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
result.add n[0]
|
result.add n[0]
|
||||||
for i in 1 ..< n.len:
|
for i in 1 ..< n.len:
|
||||||
result.add tcall2(n[i], tmpContext)
|
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 VNode to attach the event handler to"
|
||||||
|
else:
|
||||||
|
result = newCall(bindSym"addEventHandler", tmpContext,
|
||||||
|
newDotExpr(bindSym"EventKind", n[0]), anon, ident("kxi"))
|
||||||
|
else:
|
||||||
|
result = n
|
||||||
of nnkVarSection, nnkLetSection, nnkConstSection:
|
of nnkVarSection, nnkLetSection, nnkConstSection:
|
||||||
result = n
|
result = n
|
||||||
of nnkCallKinds:
|
of nnkCallKinds - {nnkInfix}:
|
||||||
let op = getName(n[0])
|
let op = getName(n[0])
|
||||||
let ck = isComponent(op)
|
let ck = isComponent(op)
|
||||||
if ck != ComponentKind.None:
|
if ck != ComponentKind.None:
|
||||||
|
|
@ -87,6 +100,8 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
result.add newDotAsgn(tmp, key, x[1])
|
result.add newDotAsgn(tmp, key, x[1])
|
||||||
elif eqIdent(key, "setFocus"):
|
elif eqIdent(key, "setFocus"):
|
||||||
result.add newCall(key, tmp, x[1], ident"kxi")
|
result.add newCall(key, tmp, x[1], ident"kxi")
|
||||||
|
elif eqIdent(key, "events"):
|
||||||
|
result.add newCall(bindSym"mergeEvents", tmp, x[1])
|
||||||
else:
|
else:
|
||||||
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
||||||
elif ck != ComponentKind.Tag:
|
elif ck != ComponentKind.Tag:
|
||||||
|
|
@ -100,7 +115,28 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||||
else:
|
else:
|
||||||
result.add newCall(bindSym"add", tmpContext, tmp)
|
result.add newCall(bindSym"add", tmpContext, tmp)
|
||||||
elif tmpContext != nil and op notin StmtContext:
|
elif tmpContext != nil and op notin StmtContext:
|
||||||
result = newCall(bindSym"add", tmpContext, n)
|
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:
|
elif op == "!" and n.len == 2:
|
||||||
result = n[1]
|
result = n[1]
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -583,7 +583,7 @@ proc javaEnabled*(h: Navigator): bool
|
||||||
# ClassList "methods"
|
# ClassList "methods"
|
||||||
proc add*(c: ClassList, class: cstring)
|
proc add*(c: ClassList, class: cstring)
|
||||||
proc remove*(c: ClassList, class: cstring)
|
proc remove*(c: ClassList, class: cstring)
|
||||||
proc contains*(c: ClassList, class: cstring):bool
|
proc contains*(c: ClassList, class: cstring): bool
|
||||||
proc toggle*(c: ClassList, class: cstring)
|
proc toggle*(c: ClassList, class: cstring)
|
||||||
|
|
||||||
# Style "methods"
|
# Style "methods"
|
||||||
|
|
@ -644,9 +644,9 @@ type
|
||||||
proc getBoundingClientRect*(e: Node): BoundingRect {.
|
proc getBoundingClientRect*(e: Node): BoundingRect {.
|
||||||
importcpp: "getBoundingClientRect", nodecl.}
|
importcpp: "getBoundingClientRect", nodecl.}
|
||||||
proc clientHeight*(): int {.
|
proc clientHeight*(): int {.
|
||||||
importcpp: "(window.innerHeight || document.documentElement.clientHeight)@", nodecl}
|
importcpp: "(window.innerHeight || document.documentElement.clientHeight)@", nodecl.}
|
||||||
proc clientWidth*(): int {.
|
proc clientWidth*(): int {.
|
||||||
importcpp: "(window.innerWidth || document.documentElement.clientWidth)@", nodecl}
|
importcpp: "(window.innerWidth || document.documentElement.clientWidth)@", nodecl.}
|
||||||
|
|
||||||
proc inViewport*(el: Node): bool =
|
proc inViewport*(el: Node): bool =
|
||||||
let rect = el.getBoundingClientRect()
|
let rect = el.getBoundingClientRect()
|
||||||
|
|
|
||||||
616
src/languages.nim
Normal file
616
src/languages.nim
Normal file
|
|
@ -0,0 +1,616 @@
|
||||||
|
## Karax -- Single page applications for Nim.
|
||||||
|
|
||||||
|
type
|
||||||
|
Language* {.pure.} = enum ## A pure enum that contains most (all?)
|
||||||
|
## known human languages in current use.
|
||||||
|
afZA ## "South Africa - Afrikaans"
|
||||||
|
amET ## "Ethiopia - Amharic"
|
||||||
|
arAE ## "U.A.E. - Arabic"
|
||||||
|
arBH ## "Bahrain - Arabic"
|
||||||
|
arDZ ## "Algeria - Arabic"
|
||||||
|
arEG ## "Egypt - Arabic"
|
||||||
|
arIQ ## "Iraq - Arabic"
|
||||||
|
arJO ## "Jordan - Arabic"
|
||||||
|
arKW ## "Kuwait - Arabic"
|
||||||
|
arLB ## "Lebanon - Arabic"
|
||||||
|
arLY ## "Libya - Arabic"
|
||||||
|
arMA ## "Morocco - Arabic"
|
||||||
|
arOM ## "Oman - Arabic"
|
||||||
|
arQA ## "Qatar - Arabic"
|
||||||
|
arSA ## "Saudi Arabia - Arabic"
|
||||||
|
arSY ## "Syria - Arabic"
|
||||||
|
arTN ## "Tunisia - Arabic"
|
||||||
|
arYE ## "Yemen - Arabic"
|
||||||
|
arnCL ## "Chile - Mapudungun"
|
||||||
|
asIN ## "India - Assamese"
|
||||||
|
azAZ ## "Azerbaijan - Azeri (Cyrillic)"
|
||||||
|
baRU ## "Russia - Bashkir"
|
||||||
|
beBY ## "Belarus - Belarusian"
|
||||||
|
bgBG ## "Bulgaria - Bulgarian"
|
||||||
|
bnBD ## "Bangladesh - Bengali"
|
||||||
|
bnIN ## "India - Bengali"
|
||||||
|
boCN ## "People's Republic of China - Tibetan"
|
||||||
|
brFR ## "France - Breton"
|
||||||
|
bsBA ## "Bosnia and Herzegovina - Bosnian (Cyrillic)"
|
||||||
|
caES ## "Spain - Catalan"
|
||||||
|
coFR ## "France - Corsican"
|
||||||
|
csCZ ## "Czech Republic - Czech"
|
||||||
|
cyGB ## "United Kingdom - Welsh"
|
||||||
|
daDK ## "Denmark - Danish"
|
||||||
|
deAT ## "Austria - German"
|
||||||
|
deCH ## "Switzerland - German"
|
||||||
|
deDE ## "Germany - German"
|
||||||
|
deLI ## "Liechtenstein - German"
|
||||||
|
deLU ## "Luxembourg - German"
|
||||||
|
dsbDE ## "Germany - Lower Sorbian"
|
||||||
|
dvMV ## "Maldives - Divehi"
|
||||||
|
elGR ## "Greece - Greek"
|
||||||
|
en029 ## "Caribbean - English"
|
||||||
|
enAU ## "Australia - English"
|
||||||
|
enBZ ## "Belize - English"
|
||||||
|
enCA ## "Canada - English"
|
||||||
|
enGB ## "United Kingdom - English"
|
||||||
|
enIE ## "Ireland - English"
|
||||||
|
enIN ## "India - English"
|
||||||
|
enJM ## "Jamaica - English"
|
||||||
|
enMY ## "Malaysia - English"
|
||||||
|
enNZ ## "New Zealand - English"
|
||||||
|
enPH ## "Republic of the Philippines - English"
|
||||||
|
enSG ## "Singapore - English"
|
||||||
|
enTT ## "Trinidad and Tobago - English"
|
||||||
|
enUS ## "United States - English"
|
||||||
|
enZA ## "South Africa - English"
|
||||||
|
enZW ## "Zimbabwe - English"
|
||||||
|
esAR ## "Argentina - Spanish"
|
||||||
|
esBO ## "Bolivia - Spanish"
|
||||||
|
esCL ## "Chile - Spanish"
|
||||||
|
esCO ## "Colombia - Spanish"
|
||||||
|
esCR ## "Costa Rica - Spanish"
|
||||||
|
esDO ## "Dominican Republic - Spanish"
|
||||||
|
esEC ## "Ecuador - Spanish"
|
||||||
|
esES ## "Spain - Spanish"
|
||||||
|
esGT ## "Guatemala - Spanish"
|
||||||
|
esHN ## "Honduras - Spanish"
|
||||||
|
esMX ## "Mexico - Spanish"
|
||||||
|
esNI ## "Nicaragua - Spanish"
|
||||||
|
esPA ## "Panama - Spanish"
|
||||||
|
esPE ## "Peru - Spanish"
|
||||||
|
esPR ## "Puerto Rico - Spanish"
|
||||||
|
esPY ## "Paraguay - Spanish"
|
||||||
|
esSV ## "El Salvador - Spanish"
|
||||||
|
esUS ## "United States - Spanish"
|
||||||
|
esUY ## "Uruguay - Spanish"
|
||||||
|
esVE ## "Bolivarian Republic of Venezuela - Spanish"
|
||||||
|
etEE ## "Estonia - Estonian"
|
||||||
|
euES ## "Spain - Basque"
|
||||||
|
faIR ## "Iran - Persian"
|
||||||
|
fiFI ## "Finland - Finnish"
|
||||||
|
filPH ## "Philippines - Filipino"
|
||||||
|
foFO ## "Faroe Islands - Faroese"
|
||||||
|
frBE ## "Belgium - French"
|
||||||
|
frCA ## "Canada - French"
|
||||||
|
frCH ## "Switzerland - French"
|
||||||
|
frFR ## "France - French"
|
||||||
|
frLU ## "Luxembourg - French"
|
||||||
|
frMC ## "Principality of Monaco - French"
|
||||||
|
fyNL ## "Netherlands - Frisian"
|
||||||
|
gaIE ## "Ireland - Irish"
|
||||||
|
gdGB ## "United Kingdom - Scottish Gaelic"
|
||||||
|
glES ## "Spain - Galician"
|
||||||
|
gswFR ## "France - Alsatian"
|
||||||
|
guIN ## "India - Gujarati"
|
||||||
|
haNG ## "Nigeria - Hausa (Latin)"
|
||||||
|
heIL ## "Israel - Hebrew"
|
||||||
|
hiIN ## "India - Hindi"
|
||||||
|
hrBA ## "Bosnia and Herzegovina - Croatian"
|
||||||
|
hrHR ## "Croatia - Croatian"
|
||||||
|
hsbDE ## "Germany - Upper Sorbian"
|
||||||
|
huHU ## "Hungary - Hungarian"
|
||||||
|
hyAM ## "Armenia - Armenian"
|
||||||
|
idID ## "Indonesia - Indonesian"
|
||||||
|
igNG ## "Nigeria - Igbo"
|
||||||
|
iiCN ## "People's Republic of China - Yi"
|
||||||
|
isIS ## "Iceland - Icelandic"
|
||||||
|
itCH ## "Switzerland - Italian"
|
||||||
|
itIT ## "Italy - Italian"
|
||||||
|
iuCA ## "Canada - Inuktitut (Latin)"
|
||||||
|
jaJP ## "Japan - Japanese"
|
||||||
|
kaGE ## "Georgia - Georgian"
|
||||||
|
kkKZ ## "Kazakhstan - Kazakh"
|
||||||
|
klGL ## "Greenland - Greenlandic"
|
||||||
|
kmKH ## "Cambodia - Khmer"
|
||||||
|
knIN ## "India - Kannada"
|
||||||
|
koKR ## "Korea - Korean"
|
||||||
|
kokIN ## "India - Konkani"
|
||||||
|
kyKG ## "Kyrgyzstan - Kyrgyz"
|
||||||
|
lbLU ## "Luxembourg - Luxembourgish"
|
||||||
|
loLA ## "Lao P.D.R. - Lao"
|
||||||
|
ltLT ## "Lithuania - Lithuanian"
|
||||||
|
lvLV ## "Latvia - Latvian"
|
||||||
|
miNZ ## "New Zealand - Maori"
|
||||||
|
mkMK ## "Macedonia (FYROM) - Macedonian (FYROM)"
|
||||||
|
mlIN ## "India - Malayalam"
|
||||||
|
mnCN ## "People's Republic of China - Mongolian (Traditional Mongolian)"
|
||||||
|
mnMN ## "Mongolia - Mongolian (Cyrillic)"
|
||||||
|
mohCA ## "Canada - Mohawk"
|
||||||
|
mrIN ## "India - Marathi"
|
||||||
|
msBN ## "Brunei Darussalam - Malay"
|
||||||
|
msMY ## "Malaysia - Malay"
|
||||||
|
mtMT ## "Malta - Maltese"
|
||||||
|
nbNO ## "Norway - Norwegian (Bokmal)"
|
||||||
|
neNP ## "Nepal - Nepali"
|
||||||
|
nlBE ## "Belgium - Dutch"
|
||||||
|
nlNL ## "Netherlands - Dutch"
|
||||||
|
nnNO ## "Norway - Norwegian (Nynorsk)"
|
||||||
|
nsoZA ## "South Africa - Sesotho sa Leboa"
|
||||||
|
ocFR ## "France - Occitan"
|
||||||
|
orIN ## "India - Oriya"
|
||||||
|
paIN ## "India - Punjabi"
|
||||||
|
plPL ## "Poland - Polish"
|
||||||
|
prsAF ## "Afghanistan - Dari"
|
||||||
|
psAF ## "Afghanistan - Pashto"
|
||||||
|
ptBR ## "Brazil - Portuguese"
|
||||||
|
ptPT ## "Portugal - Portuguese"
|
||||||
|
qutGT ## "Guatemala - K'iche"
|
||||||
|
quzBO ## "Bolivia - Quechua"
|
||||||
|
quzEC ## "Ecuador - Quechua"
|
||||||
|
quzPE ## "Peru - Quechua"
|
||||||
|
rmCH ## "Switzerland - Romansh"
|
||||||
|
roRO ## "Romania - Romanian"
|
||||||
|
ruRU ## "Russia - Russian"
|
||||||
|
rwRW ## "Rwanda - Kinyarwanda"
|
||||||
|
saIN ## "India - Sanskrit"
|
||||||
|
sahRU ## "Russia - Yakut"
|
||||||
|
seFI ## "Finland - Sami (Northern)"
|
||||||
|
seNO ## "Norway - Sami (Northern)"
|
||||||
|
seSE ## "Sweden - Sami (Northern)"
|
||||||
|
siLK ## "Sri Lanka - Sinhala"
|
||||||
|
skSK ## "Slovakia - Slovak"
|
||||||
|
slSI ## "Slovenia - Slovenian"
|
||||||
|
smaNO ## "Norway - Sami (Southern)"
|
||||||
|
smaSE ## "Sweden - Sami (Southern)"
|
||||||
|
smjNO ## "Norway - Sami (Lule)"
|
||||||
|
smjSE ## "Sweden - Sami (Lule)"
|
||||||
|
smnFI ## "Finland - Sami (Inari)"
|
||||||
|
smsFI ## "Finland - Sami (Skolt)"
|
||||||
|
sqAL ## "Albania - Albanian"
|
||||||
|
srBA ## "Bosnia and Herzegovina - Serbian (Cyrillic)"
|
||||||
|
srCS ## "Serbia and Montenegro (Former) - Serbian (Cyrillic)"
|
||||||
|
srME ## "Montenegro - Serbian (Cyrillic)"
|
||||||
|
srRS ## "Serbia - Serbian (Cyrillic)"
|
||||||
|
svFI ## "Finland - Swedish"
|
||||||
|
svSE ## "Sweden - Swedish"
|
||||||
|
swKE ## "Kenya - Kiswahili"
|
||||||
|
syrSY ## "Syria - Syriac"
|
||||||
|
taIN ## "India - Tamil"
|
||||||
|
teIN ## "India - Telugu"
|
||||||
|
tgTJ ## "Tajikistan - Tajik (Cyrillic)"
|
||||||
|
thTH ## "Thailand - Thai"
|
||||||
|
tkTM ## "Turkmenistan - Turkmen"
|
||||||
|
tnZA ## "South Africa - Setswana"
|
||||||
|
trTR ## "Turkey - Turkish"
|
||||||
|
ttRU ## "Russia - Tatar"
|
||||||
|
tzmDZ ## "Algeria - Tamazight (Latin)"
|
||||||
|
ugCN ## "People's Republic of China - Uyghur"
|
||||||
|
ukUA ## "Ukraine - Ukrainian"
|
||||||
|
urPK ## "Islamic Republic of Pakistan - Urdu"
|
||||||
|
uzUZ ## "Uzbekistan - Uzbek (Cyrillic)"
|
||||||
|
viVN ## "Vietnam - Vietnamese"
|
||||||
|
woSN ## "Senegal - Wolof"
|
||||||
|
xhZA ## "South Africa - isiXhosa"
|
||||||
|
yoNG ## "Nigeria - Yoruba"
|
||||||
|
zhCN ## "People's Republic of China - Chinese (Simplified) Legacy"
|
||||||
|
zhHK ## "Hong Kong S.A.R. - Chinese (Traditional) Legacy"
|
||||||
|
zhMO ## "Macao S.A.R. - Chinese (Traditional) Legacy"
|
||||||
|
zhSG ## "Singapore - Chinese (Simplified) Legacy"
|
||||||
|
zhTW ## "Taiwan - Chinese (Traditional) Legacy"
|
||||||
|
zuZA ## "South Africa - isiZulu"
|
||||||
|
|
||||||
|
const
|
||||||
|
languageToName*: array[Language, cstring] = [
|
||||||
|
Language.afZA: cstring"South Africa - Afrikaans",
|
||||||
|
Language.amET: cstring"Ethiopia - Amharic",
|
||||||
|
Language.arAE: cstring"U.A.E. - Arabic",
|
||||||
|
Language.arBH: cstring"Bahrain - Arabic",
|
||||||
|
Language.arDZ: cstring"Algeria - Arabic",
|
||||||
|
Language.arEG: cstring"Egypt - Arabic",
|
||||||
|
Language.arIQ: cstring"Iraq - Arabic",
|
||||||
|
Language.arJO: cstring"Jordan - Arabic",
|
||||||
|
Language.arKW: cstring"Kuwait - Arabic",
|
||||||
|
Language.arLB: cstring"Lebanon - Arabic",
|
||||||
|
Language.arLY: cstring"Libya - Arabic",
|
||||||
|
Language.arMA: cstring"Morocco - Arabic",
|
||||||
|
Language.arOM: cstring"Oman - Arabic",
|
||||||
|
Language.arQA: cstring"Qatar - Arabic",
|
||||||
|
Language.arSA: cstring"Saudi Arabia - Arabic",
|
||||||
|
Language.arSY: cstring"Syria - Arabic",
|
||||||
|
Language.arTN: cstring"Tunisia - Arabic",
|
||||||
|
Language.arYE: cstring"Yemen - Arabic",
|
||||||
|
Language.arnCL: cstring"Chile - Mapudungun",
|
||||||
|
Language.asIN: cstring"India - Assamese",
|
||||||
|
Language.azAZ: cstring"Azerbaijan - Azeri (Cyrillic)",
|
||||||
|
Language.baRU: cstring"Russia - Bashkir",
|
||||||
|
Language.beBY: cstring"Belarus - Belarusian",
|
||||||
|
Language.bgBG: cstring"Bulgaria - Bulgarian",
|
||||||
|
Language.bnBD: cstring"Bangladesh - Bengali",
|
||||||
|
Language.bnIN: cstring"India - Bengali",
|
||||||
|
Language.boCN: cstring"People's Republic of China - Tibetan",
|
||||||
|
Language.brFR: cstring"France - Breton",
|
||||||
|
Language.bsBA: cstring"Bosnia and Herzegovina - Bosnian (Cyrillic)",
|
||||||
|
Language.caES: cstring"Spain - Catalan",
|
||||||
|
Language.coFR: cstring"France - Corsican",
|
||||||
|
Language.csCZ: cstring"Czech Republic - Czech",
|
||||||
|
Language.cyGB: cstring"United Kingdom - Welsh",
|
||||||
|
Language.daDK: cstring"Denmark - Danish",
|
||||||
|
Language.deAT: cstring"Austria - German",
|
||||||
|
Language.deCH: cstring"Switzerland - German",
|
||||||
|
Language.deDE: cstring"Germany - German",
|
||||||
|
Language.deLI: cstring"Liechtenstein - German",
|
||||||
|
Language.deLU: cstring"Luxembourg - German",
|
||||||
|
Language.dsbDE: cstring"Germany - Lower Sorbian",
|
||||||
|
Language.dvMV: cstring"Maldives - Divehi",
|
||||||
|
Language.elGR: cstring"Greece - Greek",
|
||||||
|
Language.en029: cstring"Caribbean - English",
|
||||||
|
Language.enAU: cstring"Australia - English",
|
||||||
|
Language.enBZ: cstring"Belize - English",
|
||||||
|
Language.enCA: cstring"Canada - English",
|
||||||
|
Language.enGB: cstring"United Kingdom - English",
|
||||||
|
Language.enIE: cstring"Ireland - English",
|
||||||
|
Language.enIN: cstring"India - English",
|
||||||
|
Language.enJM: cstring"Jamaica - English",
|
||||||
|
Language.enMY: cstring"Malaysia - English",
|
||||||
|
Language.enNZ: cstring"New Zealand - English",
|
||||||
|
Language.enPH: cstring"Republic of the Philippines - English",
|
||||||
|
Language.enSG: cstring"Singapore - English",
|
||||||
|
Language.enTT: cstring"Trinidad and Tobago - English",
|
||||||
|
Language.enUS: cstring"United States - English",
|
||||||
|
Language.enZA: cstring"South Africa - English",
|
||||||
|
Language.enZW: cstring"Zimbabwe - English",
|
||||||
|
Language.esAR: cstring"Argentina - Spanish",
|
||||||
|
Language.esBO: cstring"Bolivia - Spanish",
|
||||||
|
Language.esCL: cstring"Chile - Spanish",
|
||||||
|
Language.esCO: cstring"Colombia - Spanish",
|
||||||
|
Language.esCR: cstring"Costa Rica - Spanish",
|
||||||
|
Language.esDO: cstring"Dominican Republic - Spanish",
|
||||||
|
Language.esEC: cstring"Ecuador - Spanish",
|
||||||
|
Language.esES: cstring"Spain - Spanish",
|
||||||
|
Language.esGT: cstring"Guatemala - Spanish",
|
||||||
|
Language.esHN: cstring"Honduras - Spanish",
|
||||||
|
Language.esMX: cstring"Mexico - Spanish",
|
||||||
|
Language.esNI: cstring"Nicaragua - Spanish",
|
||||||
|
Language.esPA: cstring"Panama - Spanish",
|
||||||
|
Language.esPE: cstring"Peru - Spanish",
|
||||||
|
Language.esPR: cstring"Puerto Rico - Spanish",
|
||||||
|
Language.esPY: cstring"Paraguay - Spanish",
|
||||||
|
Language.esSV: cstring"El Salvador - Spanish",
|
||||||
|
Language.esUS: cstring"United States - Spanish",
|
||||||
|
Language.esUY: cstring"Uruguay - Spanish",
|
||||||
|
Language.esVE: cstring"Bolivarian Republic of Venezuela - Spanish",
|
||||||
|
Language.etEE: cstring"Estonia - Estonian",
|
||||||
|
Language.euES: cstring"Spain - Basque",
|
||||||
|
Language.faIR: cstring"Iran - Persian",
|
||||||
|
Language.fiFI: cstring"Finland - Finnish",
|
||||||
|
Language.filPH: cstring"Philippines - Filipino",
|
||||||
|
Language.foFO: cstring"Faroe Islands - Faroese",
|
||||||
|
Language.frBE: cstring"Belgium - French",
|
||||||
|
Language.frCA: cstring"Canada - French",
|
||||||
|
Language.frCH: cstring"Switzerland - French",
|
||||||
|
Language.frFR: cstring"France - French",
|
||||||
|
Language.frLU: cstring"Luxembourg - French",
|
||||||
|
Language.frMC: cstring"Principality of Monaco - French",
|
||||||
|
Language.fyNL: cstring"Netherlands - Frisian",
|
||||||
|
Language.gaIE: cstring"Ireland - Irish",
|
||||||
|
Language.gdGB: cstring"United Kingdom - Scottish Gaelic",
|
||||||
|
Language.glES: cstring"Spain - Galician",
|
||||||
|
Language.gswFR: cstring"France - Alsatian",
|
||||||
|
Language.guIN: cstring"India - Gujarati",
|
||||||
|
Language.haNG: cstring"Nigeria - Hausa (Latin)",
|
||||||
|
Language.heIL: cstring"Israel - Hebrew",
|
||||||
|
Language.hiIN: cstring"India - Hindi",
|
||||||
|
Language.hrBA: cstring"Bosnia and Herzegovina - Croatian",
|
||||||
|
Language.hrHR: cstring"Croatia - Croatian",
|
||||||
|
Language.hsbDE: cstring"Germany - Upper Sorbian",
|
||||||
|
Language.huHU: cstring"Hungary - Hungarian",
|
||||||
|
Language.hyAM: cstring"Armenia - Armenian",
|
||||||
|
Language.idID: cstring"Indonesia - Indonesian",
|
||||||
|
Language.igNG: cstring"Nigeria - Igbo",
|
||||||
|
Language.iiCN: cstring"People's Republic of China - Yi",
|
||||||
|
Language.isIS: cstring"Iceland - Icelandic",
|
||||||
|
Language.itCH: cstring"Switzerland - Italian",
|
||||||
|
Language.itIT: cstring"Italy - Italian",
|
||||||
|
Language.iuCA: cstring"Canada - Inuktitut (Latin)",
|
||||||
|
Language.jaJP: cstring"Japan - Japanese",
|
||||||
|
Language.kaGE: cstring"Georgia - Georgian",
|
||||||
|
Language.kkKZ: cstring"Kazakhstan - Kazakh",
|
||||||
|
Language.klGL: cstring"Greenland - Greenlandic",
|
||||||
|
Language.kmKH: cstring"Cambodia - Khmer",
|
||||||
|
Language.knIN: cstring"India - Kannada",
|
||||||
|
Language.koKR: cstring"Korea - Korean",
|
||||||
|
Language.kokIN: cstring"India - Konkani",
|
||||||
|
Language.kyKG: cstring"Kyrgyzstan - Kyrgyz",
|
||||||
|
Language.lbLU: cstring"Luxembourg - Luxembourgish",
|
||||||
|
Language.loLA: cstring"Lao P.D.R. - Lao",
|
||||||
|
Language.ltLT: cstring"Lithuania - Lithuanian",
|
||||||
|
Language.lvLV: cstring"Latvia - Latvian",
|
||||||
|
Language.miNZ: cstring"New Zealand - Maori",
|
||||||
|
Language.mkMK: cstring"Macedonia (FYROM) - Macedonian (FYROM)",
|
||||||
|
Language.mlIN: cstring"India - Malayalam",
|
||||||
|
Language.mnCN: cstring"People's Republic of China - Mongolian (Traditional Mongolian)",
|
||||||
|
Language.mnMN: cstring"Mongolia - Mongolian (Cyrillic)",
|
||||||
|
Language.mohCA: cstring"Canada - Mohawk",
|
||||||
|
Language.mrIN: cstring"India - Marathi",
|
||||||
|
Language.msBN: cstring"Brunei Darussalam - Malay",
|
||||||
|
Language.msMY: cstring"Malaysia - Malay",
|
||||||
|
Language.mtMT: cstring"Malta - Maltese",
|
||||||
|
Language.nbNO: cstring"Norway - Norwegian (Bokmal)",
|
||||||
|
Language.neNP: cstring"Nepal - Nepali",
|
||||||
|
Language.nlBE: cstring"Belgium - Dutch",
|
||||||
|
Language.nlNL: cstring"Netherlands - Dutch",
|
||||||
|
Language.nnNO: cstring"Norway - Norwegian (Nynorsk)",
|
||||||
|
Language.nsoZA: cstring"South Africa - Sesotho sa Leboa",
|
||||||
|
Language.ocFR: cstring"France - Occitan",
|
||||||
|
Language.orIN: cstring"India - Oriya",
|
||||||
|
Language.paIN: cstring"India - Punjabi",
|
||||||
|
Language.plPL: cstring"Poland - Polish",
|
||||||
|
Language.prsAF: cstring"Afghanistan - Dari",
|
||||||
|
Language.psAF: cstring"Afghanistan - Pashto",
|
||||||
|
Language.ptBR: cstring"Brazil - Portuguese",
|
||||||
|
Language.ptPT: cstring"Portugal - Portuguese",
|
||||||
|
Language.qutGT: cstring"Guatemala - K'iche",
|
||||||
|
Language.quzBO: cstring"Bolivia - Quechua",
|
||||||
|
Language.quzEC: cstring"Ecuador - Quechua",
|
||||||
|
Language.quzPE: cstring"Peru - Quechua",
|
||||||
|
Language.rmCH: cstring"Switzerland - Romansh",
|
||||||
|
Language.roRO: cstring"Romania - Romanian",
|
||||||
|
Language.ruRU: cstring"Russia - Russian",
|
||||||
|
Language.rwRW: cstring"Rwanda - Kinyarwanda",
|
||||||
|
Language.saIN: cstring"India - Sanskrit",
|
||||||
|
Language.sahRU: cstring"Russia - Yakut",
|
||||||
|
Language.seFI: cstring"Finland - Sami (Northern)",
|
||||||
|
Language.seNO: cstring"Norway - Sami (Northern)",
|
||||||
|
Language.seSE: cstring"Sweden - Sami (Northern)",
|
||||||
|
Language.siLK: cstring"Sri Lanka - Sinhala",
|
||||||
|
Language.skSK: cstring"Slovakia - Slovak",
|
||||||
|
Language.slSI: cstring"Slovenia - Slovenian",
|
||||||
|
Language.smaNO: cstring"Norway - Sami (Southern)",
|
||||||
|
Language.smaSE: cstring"Sweden - Sami (Southern)",
|
||||||
|
Language.smjNO: cstring"Norway - Sami (Lule)",
|
||||||
|
Language.smjSE: cstring"Sweden - Sami (Lule)",
|
||||||
|
Language.smnFI: cstring"Finland - Sami (Inari)",
|
||||||
|
Language.smsFI: cstring"Finland - Sami (Skolt)",
|
||||||
|
Language.sqAL: cstring"Albania - Albanian",
|
||||||
|
Language.srBA: cstring"Bosnia and Herzegovina - Serbian (Cyrillic)",
|
||||||
|
Language.srCS: cstring"Serbia and Montenegro (Former) - Serbian (Cyrillic)",
|
||||||
|
Language.srME: cstring"Montenegro - Serbian (Cyrillic)",
|
||||||
|
Language.srRS: cstring"Serbia - Serbian (Cyrillic)",
|
||||||
|
Language.svFI: cstring"Finland - Swedish",
|
||||||
|
Language.svSE: cstring"Sweden - Swedish",
|
||||||
|
Language.swKE: cstring"Kenya - Kiswahili",
|
||||||
|
Language.syrSY: cstring"Syria - Syriac",
|
||||||
|
Language.taIN: cstring"India - Tamil",
|
||||||
|
Language.teIN: cstring"India - Telugu",
|
||||||
|
Language.tgTJ: cstring"Tajikistan - Tajik (Cyrillic)",
|
||||||
|
Language.thTH: cstring"Thailand - Thai",
|
||||||
|
Language.tkTM: cstring"Turkmenistan - Turkmen",
|
||||||
|
Language.tnZA: cstring"South Africa - Setswana",
|
||||||
|
Language.trTR: cstring"Turkey - Turkish",
|
||||||
|
Language.ttRU: cstring"Russia - Tatar",
|
||||||
|
Language.tzmDZ: cstring"Algeria - Tamazight (Latin)",
|
||||||
|
Language.ugCN: cstring"People's Republic of China - Uyghur",
|
||||||
|
Language.ukUA: cstring"Ukraine - Ukrainian",
|
||||||
|
Language.urPK: cstring"Islamic Republic of Pakistan - Urdu",
|
||||||
|
Language.uzUZ: cstring"Uzbekistan - Uzbek (Cyrillic)",
|
||||||
|
Language.viVN: cstring"Vietnam - Vietnamese",
|
||||||
|
Language.woSN: cstring"Senegal - Wolof",
|
||||||
|
Language.xhZA: cstring"South Africa - isiXhosa",
|
||||||
|
Language.yoNG: cstring"Nigeria - Yoruba",
|
||||||
|
Language.zhCN: cstring"People's Republic of China - Chinese (Simplified) Legacy",
|
||||||
|
Language.zhHK: cstring"Hong Kong S.A.R. - Chinese (Traditional) Legacy",
|
||||||
|
Language.zhMO: cstring"Macao S.A.R. - Chinese (Traditional) Legacy",
|
||||||
|
Language.zhSG: cstring"Singapore - Chinese (Simplified) Legacy",
|
||||||
|
Language.zhTW: cstring"Taiwan - Chinese (Traditional) Legacy",
|
||||||
|
Language.zuZA: cstring"South Africa - isiZulu"]
|
||||||
|
|
||||||
|
languageToCode*: array[Language, cstring] = [
|
||||||
|
Language.afZA: cstring"af-ZA",
|
||||||
|
Language.amET: cstring"am-ET",
|
||||||
|
Language.arAE: cstring"ar-AE",
|
||||||
|
Language.arBH: cstring"ar-BH",
|
||||||
|
Language.arDZ: cstring"ar-DZ",
|
||||||
|
Language.arEG: cstring"ar-EG",
|
||||||
|
Language.arIQ: cstring"ar-IQ",
|
||||||
|
Language.arJO: cstring"ar-JO",
|
||||||
|
Language.arKW: cstring"ar-KW",
|
||||||
|
Language.arLB: cstring"ar-LB",
|
||||||
|
Language.arLY: cstring"ar-LY",
|
||||||
|
Language.arMA: cstring"ar-MA",
|
||||||
|
Language.arOM: cstring"ar-OM",
|
||||||
|
Language.arQA: cstring"ar-QA",
|
||||||
|
Language.arSA: cstring"ar-SA",
|
||||||
|
Language.arSY: cstring"ar-SY",
|
||||||
|
Language.arTN: cstring"ar-TN",
|
||||||
|
Language.arYE: cstring"ar-YE",
|
||||||
|
Language.arnCL: cstring"arn-CL",
|
||||||
|
Language.asIN: cstring"as-IN",
|
||||||
|
Language.azAZ: cstring"az-AZ",
|
||||||
|
Language.baRU: cstring"ba-RU",
|
||||||
|
Language.beBY: cstring"be-BY",
|
||||||
|
Language.bgBG: cstring"bg-BG",
|
||||||
|
Language.bnBD: cstring"bn-BD",
|
||||||
|
Language.bnIN: cstring"bn-IN",
|
||||||
|
Language.boCN: cstring"bo-CN",
|
||||||
|
Language.brFR: cstring"br-FR",
|
||||||
|
Language.bsBA: cstring"bs-BA",
|
||||||
|
Language.caES: cstring"ca-ES",
|
||||||
|
Language.coFR: cstring"co-FR",
|
||||||
|
Language.csCZ: cstring"cs-CZ",
|
||||||
|
Language.cyGB: cstring"cy-GB",
|
||||||
|
Language.daDK: cstring"da-DK",
|
||||||
|
Language.deAT: cstring"de-AT",
|
||||||
|
Language.deCH: cstring"de-CH",
|
||||||
|
Language.deDE: cstring"de-DE",
|
||||||
|
Language.deLI: cstring"de-LI",
|
||||||
|
Language.deLU: cstring"de-LU",
|
||||||
|
Language.dsbDE: cstring"dsb-DE",
|
||||||
|
Language.dvMV: cstring"dv-MV",
|
||||||
|
Language.elGR: cstring"el-GR",
|
||||||
|
Language.en029: cstring"en-029",
|
||||||
|
Language.enAU: cstring"en-AU",
|
||||||
|
Language.enBZ: cstring"en-BZ",
|
||||||
|
Language.enCA: cstring"en-CA",
|
||||||
|
Language.enGB: cstring"en-GB",
|
||||||
|
Language.enIE: cstring"en-IE",
|
||||||
|
Language.enIN: cstring"en-IN",
|
||||||
|
Language.enJM: cstring"en-JM",
|
||||||
|
Language.enMY: cstring"en-MY",
|
||||||
|
Language.enNZ: cstring"en-NZ",
|
||||||
|
Language.enPH: cstring"en-PH",
|
||||||
|
Language.enSG: cstring"en-SG",
|
||||||
|
Language.enTT: cstring"en-TT",
|
||||||
|
Language.enUS: cstring"en-US",
|
||||||
|
Language.enZA: cstring"en-ZA",
|
||||||
|
Language.enZW: cstring"en-ZW",
|
||||||
|
Language.esAR: cstring"es-AR",
|
||||||
|
Language.esBO: cstring"es-BO",
|
||||||
|
Language.esCL: cstring"es-CL",
|
||||||
|
Language.esCO: cstring"es-CO",
|
||||||
|
Language.esCR: cstring"es-CR",
|
||||||
|
Language.esDO: cstring"es-DO",
|
||||||
|
Language.esEC: cstring"es-EC",
|
||||||
|
Language.esES: cstring"es-ES",
|
||||||
|
Language.esGT: cstring"es-GT",
|
||||||
|
Language.esHN: cstring"es-HN",
|
||||||
|
Language.esMX: cstring"es-MX",
|
||||||
|
Language.esNI: cstring"es-NI",
|
||||||
|
Language.esPA: cstring"es-PA",
|
||||||
|
Language.esPE: cstring"es-PE",
|
||||||
|
Language.esPR: cstring"es-PR",
|
||||||
|
Language.esPY: cstring"es-PY",
|
||||||
|
Language.esSV: cstring"es-SV",
|
||||||
|
Language.esUS: cstring"es-US",
|
||||||
|
Language.esUY: cstring"es-UY",
|
||||||
|
Language.esVE: cstring"es-VE",
|
||||||
|
Language.etEE: cstring"et-EE",
|
||||||
|
Language.euES: cstring"eu-ES",
|
||||||
|
Language.faIR: cstring"fa-IR",
|
||||||
|
Language.fiFI: cstring"fi-FI",
|
||||||
|
Language.filPH: cstring"fil-PH",
|
||||||
|
Language.foFO: cstring"fo-FO",
|
||||||
|
Language.frBE: cstring"fr-BE",
|
||||||
|
Language.frCA: cstring"fr-CA",
|
||||||
|
Language.frCH: cstring"fr-CH",
|
||||||
|
Language.frFR: cstring"fr-FR",
|
||||||
|
Language.frLU: cstring"fr-LU",
|
||||||
|
Language.frMC: cstring"fr-MC",
|
||||||
|
Language.fyNL: cstring"fy-NL",
|
||||||
|
Language.gaIE: cstring"ga-IE",
|
||||||
|
Language.gdGB: cstring"gd-GB",
|
||||||
|
Language.glES: cstring"gl-ES",
|
||||||
|
Language.gswFR: cstring"gsw-FR",
|
||||||
|
Language.guIN: cstring"gu-IN",
|
||||||
|
Language.haNG: cstring"ha-NG",
|
||||||
|
Language.heIL: cstring"he-IL",
|
||||||
|
Language.hiIN: cstring"hi-IN",
|
||||||
|
Language.hrBA: cstring"hr-BA",
|
||||||
|
Language.hrHR: cstring"hr-HR",
|
||||||
|
Language.hsbDE: cstring"hsb-DE",
|
||||||
|
Language.huHU: cstring"hu-HU",
|
||||||
|
Language.hyAM: cstring"hy-AM",
|
||||||
|
Language.idID: cstring"id-ID",
|
||||||
|
Language.igNG: cstring"ig-NG",
|
||||||
|
Language.iiCN: cstring"ii-CN",
|
||||||
|
Language.isIS: cstring"is-IS",
|
||||||
|
Language.itCH: cstring"it-CH",
|
||||||
|
Language.itIT: cstring"it-IT",
|
||||||
|
Language.iuCA: cstring"iu-CA",
|
||||||
|
Language.jaJP: cstring"ja-JP",
|
||||||
|
Language.kaGE: cstring"ka-GE",
|
||||||
|
Language.kkKZ: cstring"kk-KZ",
|
||||||
|
Language.klGL: cstring"kl-GL",
|
||||||
|
Language.kmKH: cstring"km-KH",
|
||||||
|
Language.knIN: cstring"kn-IN",
|
||||||
|
Language.koKR: cstring"ko-KR",
|
||||||
|
Language.kokIN: cstring"kok-IN",
|
||||||
|
Language.kyKG: cstring"ky-KG",
|
||||||
|
Language.lbLU: cstring"lb-LU",
|
||||||
|
Language.loLA: cstring"lo-LA",
|
||||||
|
Language.ltLT: cstring"lt-LT",
|
||||||
|
Language.lvLV: cstring"lv-LV",
|
||||||
|
Language.miNZ: cstring"mi-NZ",
|
||||||
|
Language.mkMK: cstring"mk-MK",
|
||||||
|
Language.mlIN: cstring"ml-IN",
|
||||||
|
Language.mnCN: cstring"mn-CN",
|
||||||
|
Language.mnMN: cstring"mn-MN",
|
||||||
|
Language.mohCA: cstring"moh-CA",
|
||||||
|
Language.mrIN: cstring"mr-IN",
|
||||||
|
Language.msBN: cstring"ms-BN",
|
||||||
|
Language.msMY: cstring"ms-MY",
|
||||||
|
Language.mtMT: cstring"mt-MT",
|
||||||
|
Language.nbNO: cstring"nb-NO",
|
||||||
|
Language.neNP: cstring"ne-NP",
|
||||||
|
Language.nlBE: cstring"nl-BE",
|
||||||
|
Language.nlNL: cstring"nl-NL",
|
||||||
|
Language.nnNO: cstring"nn-NO",
|
||||||
|
Language.nsoZA: cstring"nso-ZA",
|
||||||
|
Language.ocFR: cstring"oc-FR",
|
||||||
|
Language.orIN: cstring"or-IN",
|
||||||
|
Language.paIN: cstring"pa-IN",
|
||||||
|
Language.plPL: cstring"pl-PL",
|
||||||
|
Language.prsAF: cstring"prs-AF",
|
||||||
|
Language.psAF: cstring"ps-AF",
|
||||||
|
Language.ptBR: cstring"pt-BR",
|
||||||
|
Language.ptPT: cstring"pt-PT",
|
||||||
|
Language.qutGT: cstring"qut-GT",
|
||||||
|
Language.quzBO: cstring"quz-BO",
|
||||||
|
Language.quzEC: cstring"quz-EC",
|
||||||
|
Language.quzPE: cstring"quz-PE",
|
||||||
|
Language.rmCH: cstring"rm-CH",
|
||||||
|
Language.roRO: cstring"ro-RO",
|
||||||
|
Language.ruRU: cstring"ru-RU",
|
||||||
|
Language.rwRW: cstring"rw-RW",
|
||||||
|
Language.saIN: cstring"sa-IN",
|
||||||
|
Language.sahRU: cstring"sah-RU",
|
||||||
|
Language.seFI: cstring"se-FI",
|
||||||
|
Language.seNO: cstring"se-NO",
|
||||||
|
Language.seSE: cstring"se-SE",
|
||||||
|
Language.siLK: cstring"si-LK",
|
||||||
|
Language.skSK: cstring"sk-SK",
|
||||||
|
Language.slSI: cstring"sl-SI",
|
||||||
|
Language.smaNO: cstring"sma-NO",
|
||||||
|
Language.smaSE: cstring"sma-SE",
|
||||||
|
Language.smjNO: cstring"smj-NO",
|
||||||
|
Language.smjSE: cstring"smj-SE",
|
||||||
|
Language.smnFI: cstring"smn-FI",
|
||||||
|
Language.smsFI: cstring"sms-FI",
|
||||||
|
Language.sqAL: cstring"sq-AL",
|
||||||
|
Language.srBA: cstring"sr-BA",
|
||||||
|
Language.srCS: cstring"sr-CS",
|
||||||
|
Language.srME: cstring"sr-ME",
|
||||||
|
Language.srRS: cstring"sr-RS",
|
||||||
|
Language.svFI: cstring"sv-FI",
|
||||||
|
Language.svSE: cstring"sv-SE",
|
||||||
|
Language.swKE: cstring"sw-KE",
|
||||||
|
Language.syrSY: cstring"syr-SY",
|
||||||
|
Language.taIN: cstring"ta-IN",
|
||||||
|
Language.teIN: cstring"te-IN",
|
||||||
|
Language.tgTJ: cstring"tg-TJ",
|
||||||
|
Language.thTH: cstring"th-TH",
|
||||||
|
Language.tkTM: cstring"tk-TM",
|
||||||
|
Language.tnZA: cstring"tn-ZA",
|
||||||
|
Language.trTR: cstring"tr-TR",
|
||||||
|
Language.ttRU: cstring"tt-RU",
|
||||||
|
Language.tzmDZ: cstring"tzm-DZ",
|
||||||
|
Language.ugCN: cstring"ug-CN",
|
||||||
|
Language.ukUA: cstring"uk-UA",
|
||||||
|
Language.urPK: cstring"ur-PK",
|
||||||
|
Language.uzUZ: cstring"uz-UZ",
|
||||||
|
Language.viVN: cstring"vi-VN",
|
||||||
|
Language.woSN: cstring"wo-SN",
|
||||||
|
Language.xhZA: cstring"xh-ZA",
|
||||||
|
Language.yoNG: cstring"yo-NG",
|
||||||
|
Language.zhCN: cstring"zh-CN",
|
||||||
|
Language.zhHK: cstring"zh-HK",
|
||||||
|
Language.zhMO: cstring"zh-MO",
|
||||||
|
Language.zhSG: cstring"zh-SG",
|
||||||
|
Language.zhTW: cstring"zh-TW",
|
||||||
|
Language.zuZA: cstring"zu-ZA"]
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
## This module contains wrappers for the HTML 5 local storage.
|
## This module contains wrappers for the HTML 5 local storage.
|
||||||
|
|
||||||
proc getItem*(key: cstring): cstring {.importc: "localStorage.getItem"}
|
proc getItem*(key: cstring): cstring {.importc: "localStorage.getItem".}
|
||||||
proc setItem*(key, value: cstring) {.importc: "localStorage.setItem"}
|
proc setItem*(key, value: cstring) {.importc: "localStorage.setItem".}
|
||||||
proc hasItem*(key: cstring): bool {.importcpp: "(localStorage.getItem(#) !== null)".}
|
proc hasItem*(key: cstring): bool {.importcpp: "(localStorage.getItem(#) !== null)".}
|
||||||
proc clear*() {.importc: "localStorage.clear"}
|
proc clear*() {.importc: "localStorage.clear".}
|
||||||
|
|
||||||
proc removeItem*(key: cstring) {.importc: "localStorage.removeItem".}
|
proc removeItem*(key: cstring) {.importc: "localStorage.removeItem".}
|
||||||
|
|
|
||||||
380
src/reactive.nim
Normal file
380
src/reactive.nim
Normal file
|
|
@ -0,0 +1,380 @@
|
||||||
|
|
||||||
|
import jdict
|
||||||
|
|
||||||
|
type
|
||||||
|
Message = enum
|
||||||
|
Unchanged
|
||||||
|
Changed
|
||||||
|
Mark
|
||||||
|
Inserted
|
||||||
|
Deleted
|
||||||
|
Replaced
|
||||||
|
|
||||||
|
State = object
|
||||||
|
stale: int
|
||||||
|
outdated: bool
|
||||||
|
phantom: bool
|
||||||
|
|
||||||
|
type
|
||||||
|
SinkSeq = seq[proc(msg: Message, pos: int)]
|
||||||
|
ReactiveBase* = ref object of RootObj ## everything that is a "reactive"
|
||||||
|
## value derives from that
|
||||||
|
sinks*: SinkSeq
|
||||||
|
dups: JDict[cstring, int]
|
||||||
|
id: int
|
||||||
|
Reactive*[T] = ref object of ReactiveBase
|
||||||
|
value*: T
|
||||||
|
|
||||||
|
RString* = Reactive[cstring]
|
||||||
|
# RTime* = Reactive[Time]
|
||||||
|
RInt* = Reactive[int]
|
||||||
|
|
||||||
|
RBool* = Reactive[bool]
|
||||||
|
|
||||||
|
RSeq*[T] = ref object of ReactiveBase
|
||||||
|
s: seq[T]
|
||||||
|
L: RInt
|
||||||
|
|
||||||
|
var rid: int
|
||||||
|
|
||||||
|
proc addSink(x: ReactiveBase; key: cstring; sink: proc(msg: Message; pos: int)) =
|
||||||
|
if x.dups == nil: x.dups = newJDict[cstring, int]()
|
||||||
|
if not x.dups.contains(key):
|
||||||
|
x.dups[key] = x.sinks.len
|
||||||
|
x.sinks.add sink
|
||||||
|
if x.id == 0:
|
||||||
|
inc rid
|
||||||
|
x.id = rid
|
||||||
|
else:
|
||||||
|
# update existing entry:
|
||||||
|
x.sinks[x.dups[key]] = sink
|
||||||
|
|
||||||
|
proc addSink(x: ReactiveBase; sink: proc(msg: Message; pos: int)) =
|
||||||
|
x.sinks.add sink
|
||||||
|
if x.id == 0:
|
||||||
|
inc rid
|
||||||
|
x.id = rid
|
||||||
|
|
||||||
|
var inhibited: int
|
||||||
|
|
||||||
|
proc broadcast(x: ReactiveBase, msg: Message; pos = 0) =
|
||||||
|
if inhibited == 0:
|
||||||
|
for s in x.sinks: s(msg, pos)
|
||||||
|
|
||||||
|
var toTrack: seq[(cstring, proc (msg: Message; pos: int))] = @[]
|
||||||
|
|
||||||
|
template withTrack(key, t, body) =
|
||||||
|
toTrack.add((key, t))
|
||||||
|
body
|
||||||
|
discard toTrack.pop()
|
||||||
|
|
||||||
|
proc trackDependency*(r: ReactiveBase) =
|
||||||
|
for t in toTrack:
|
||||||
|
r.addSink t[0], t[1]
|
||||||
|
|
||||||
|
template wrapObserver(f: untyped) =
|
||||||
|
var state: State
|
||||||
|
proc helper(msg: Message; pos: int) =
|
||||||
|
case msg.kind:
|
||||||
|
of Changed, Unchanged:
|
||||||
|
state.outdated = state.outdated or (msg.kind == Changed)
|
||||||
|
dec state.stale
|
||||||
|
if state.stale == 0:
|
||||||
|
if state.outdated:
|
||||||
|
var t = f()
|
||||||
|
let thisChanged = if x.value != t: Changed else: Unchanged
|
||||||
|
x.value = t
|
||||||
|
x.broadcast(thisChanged, pos)
|
||||||
|
state.outdated = false
|
||||||
|
else:
|
||||||
|
x.broadcast(Unchanged, pos)
|
||||||
|
of Mark:
|
||||||
|
if state.stale == 0: x.broadcast(msg, pos)
|
||||||
|
inc state.stale
|
||||||
|
of Inserted, Deleted, Replaced:
|
||||||
|
dec state.stale
|
||||||
|
if state.stale == 0:
|
||||||
|
x.broadcast(msg, pos)
|
||||||
|
#state.phantom = true
|
||||||
|
helper
|
||||||
|
|
||||||
|
template glitchFree(f: untyped) =
|
||||||
|
var state: State
|
||||||
|
proc helper(msg: Message; pos: int) =
|
||||||
|
case msg.kind:
|
||||||
|
of Changed, Unchanged:
|
||||||
|
state.outdated = state.outdated or (msg.kind == Changed)
|
||||||
|
dec state.stale
|
||||||
|
if state.stale == 0:
|
||||||
|
if state.outdated:
|
||||||
|
x.value = f
|
||||||
|
x.broadcast(msg.kind, pos)
|
||||||
|
state.outdated = false
|
||||||
|
else:
|
||||||
|
x.broadcast(Unchanged, pos)
|
||||||
|
of Mark:
|
||||||
|
if state.stale == 0: x.broadcast(msg, pos)
|
||||||
|
inc state.stale
|
||||||
|
of Inserted, Deleted, Replaced:
|
||||||
|
dec state.stale
|
||||||
|
if state.stale == 0:
|
||||||
|
x.broadcast(msg, pos)
|
||||||
|
helper
|
||||||
|
|
||||||
|
proc `<-`*[T](x: Reactive[T], val: T) =
|
||||||
|
#if x.value != val:
|
||||||
|
x.value = val
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Changed)
|
||||||
|
|
||||||
|
proc notifyObservers*(x: ReactiveBase) =
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Changed)
|
||||||
|
|
||||||
|
proc subscribe*[T](x: Reactive[T], f: proc(x: T)) =
|
||||||
|
let reactor = proc (msg: Message, pos: int) =
|
||||||
|
case msg:
|
||||||
|
of Mark: discard
|
||||||
|
of Changed, Unchanged: f(x.value)
|
||||||
|
of Inserted, Deleted, Replaced: discard
|
||||||
|
x.addSink reactor
|
||||||
|
|
||||||
|
proc subscribeSelf*[T: ReactiveBase](x: T, f: proc()) =
|
||||||
|
let reactor = proc (msg: Message, pos: int) =
|
||||||
|
case msg:
|
||||||
|
of Mark: discard
|
||||||
|
of Changed, Unchanged: f()
|
||||||
|
of Inserted, Deleted, Replaced: discard
|
||||||
|
x.addSink reactor
|
||||||
|
|
||||||
|
template lift1(op: untyped) =
|
||||||
|
proc op[T](a: Reactive[T]): (proc(): T) =
|
||||||
|
result = proc(): T = op(a.now)
|
||||||
|
|
||||||
|
template lift2(op: untyped) =
|
||||||
|
proc op[T](a, b: Reactive[T]): (proc(): T) =
|
||||||
|
result = proc(): T = op(a.now, b.now)
|
||||||
|
|
||||||
|
lift1 `not`
|
||||||
|
lift2 `&`
|
||||||
|
|
||||||
|
proc newReactive*[T](x: T): Reactive[T] =
|
||||||
|
result = Reactive[T](value: x)
|
||||||
|
|
||||||
|
proc rstr*(x: cstring): RString =
|
||||||
|
result = RString(value: x)
|
||||||
|
|
||||||
|
proc newRSeq*[T](len: int = 0): RSeq[T] =
|
||||||
|
result = RSeq[T](s: newSeq[T](len), L: newReactive[int](0))
|
||||||
|
|
||||||
|
proc newRSeq*[T](data: seq[T]): RSeq[T] =
|
||||||
|
result = RSeq[T](s: newSeq[T](data.len), L: newReactive[int](0))
|
||||||
|
for i in 0..high(data):
|
||||||
|
result.s[i] = data[i]
|
||||||
|
|
||||||
|
proc `[]=`*[T](x: RSeq[T]; index: int; v: T) =
|
||||||
|
x.s[index] = v
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Replaced, index)
|
||||||
|
|
||||||
|
proc `[]`*[T](x: RSeq[T]; index: int): T = x.s[index]
|
||||||
|
proc len*[T](x: RSeq[T]): int = x.s.len
|
||||||
|
|
||||||
|
proc add*[T](x: RSeq[T]; y: T) =
|
||||||
|
let position = x.s.len
|
||||||
|
x.s.add(y)
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Inserted, position)
|
||||||
|
|
||||||
|
proc insert*[T](x: RSeq[T]; y: T; position = 0) =
|
||||||
|
x.s.insert(y, position)
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Inserted, position)
|
||||||
|
|
||||||
|
proc delete*[T](x: RSeq[T]; position = 0) =
|
||||||
|
x.s.delete(position)
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Deleted, position)
|
||||||
|
|
||||||
|
proc deleteElem*[T](x: RSeq[T]; y: T) =
|
||||||
|
var position = -1
|
||||||
|
for i in 0..<x.len:
|
||||||
|
if x[i] == y:
|
||||||
|
position = i; break
|
||||||
|
if position >= 0:
|
||||||
|
x.s.delete(position)
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Deleted, position)
|
||||||
|
|
||||||
|
when false:
|
||||||
|
proc `:=`[T](x: Reactive[T], f: proc(): T) =
|
||||||
|
toTrack = wrapObserver(f())
|
||||||
|
x.value = f()
|
||||||
|
toTrack = nil
|
||||||
|
|
||||||
|
proc map*[T, U](x: RSeq[T], f: proc(x: T): U): RSeq[U] =
|
||||||
|
let xl = x.L.value
|
||||||
|
let res = newRSeq[U](xl)
|
||||||
|
for i in 0..<xl:
|
||||||
|
res.s[i] := proc(): U = f(x[i].now)
|
||||||
|
|
||||||
|
let reactor = proc (msg: Message, pos: int) =
|
||||||
|
case msg:
|
||||||
|
of Mark, Changed, Unchanged: discard "nothing to do"
|
||||||
|
of Inserted:
|
||||||
|
res.insert(f(x[pos]), pos)
|
||||||
|
of Deleted:
|
||||||
|
res.delete(pos)
|
||||||
|
x.addSink reactor
|
||||||
|
result = res
|
||||||
|
|
||||||
|
import macros
|
||||||
|
|
||||||
|
template protect(body: untyped) =
|
||||||
|
#var tmp: seq[proc(msg: Message, pos: int)]
|
||||||
|
#swap(r.sinks, tmp)
|
||||||
|
inc inhibited
|
||||||
|
body
|
||||||
|
dec inhibited
|
||||||
|
#swap(r.sinks, tmp)
|
||||||
|
|
||||||
|
template trackStart(key: cstring; a, b: untyped) =
|
||||||
|
proc differ(m: Message; pos: int) =
|
||||||
|
if m == Changed:
|
||||||
|
protect:
|
||||||
|
karax.runDiff(kxi, a, b)
|
||||||
|
toTrack.add((key, differ))
|
||||||
|
|
||||||
|
template trackEnd() =
|
||||||
|
discard toTrack.pop()
|
||||||
|
|
||||||
|
template doTrack(r: ReactiveBase; a, b: untyped) {.dirty.} =
|
||||||
|
bind addSink, Message, RSeq, Changed, Deleted, Inserted
|
||||||
|
addSink r, proc(m: Message; pos: int) =
|
||||||
|
#when r is RSeq:
|
||||||
|
#echo "Message: ", m, " ", pos
|
||||||
|
if m == Changed:
|
||||||
|
protect r:
|
||||||
|
karax.runDiff(kxi, a, b)
|
||||||
|
|
||||||
|
template doTrackResize(r: ReactiveBase; a, b: untyped) {.dirty.} =
|
||||||
|
bind addSink, Message, RSeq, Changed, Deleted, Inserted
|
||||||
|
addSink r, proc(m: Message; pos: int) =
|
||||||
|
#when r is RSeq:
|
||||||
|
#echo "Message: ", m, " ", pos
|
||||||
|
case m
|
||||||
|
of Deleted: karax.runDel(kxi, a, pos)
|
||||||
|
of Inserted:
|
||||||
|
let it {.used.} = r[pos]
|
||||||
|
karax.runIns(kxi, a, b, pos)
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
macro track*(procDef: untyped): untyped =
|
||||||
|
let params = params(procDef)
|
||||||
|
var trackings = newStmtList()
|
||||||
|
var inner = copyNimTree(procDef)
|
||||||
|
inner[0] = ident($procDef.name & "Inner")
|
||||||
|
var call = newCall(inner[0])
|
||||||
|
|
||||||
|
for j in 1..<params.len:
|
||||||
|
let x = params[j]
|
||||||
|
expectKind x, nnkIdentDefs
|
||||||
|
for i in 0..x.len-3:
|
||||||
|
let param = x[i]
|
||||||
|
call.add(param)
|
||||||
|
|
||||||
|
trackings.add inner
|
||||||
|
let key = newCall("cstring", newLit lineInfo(procDef))
|
||||||
|
trackings.add getAst(trackStart(key, ident"result", call))
|
||||||
|
trackings.add newAssignment(ident"result", call)
|
||||||
|
trackings.add getAst(trackEnd())
|
||||||
|
|
||||||
|
result = copyNimTree(procDef)
|
||||||
|
result.body = trackings
|
||||||
|
when defined(debugKaraxDsl):
|
||||||
|
echo repr result
|
||||||
|
|
||||||
|
proc generatePrivateAccessors(name, hidden, typ, fieldTyp: NimNode): NimNode =
|
||||||
|
template helper(name, hidden, typ, fieldTyp) {.dirty.} =
|
||||||
|
proc name(self: typ): fieldTyp =
|
||||||
|
trackDependency(self)
|
||||||
|
result = self.hidden
|
||||||
|
proc `name=`(self: typ; val: fieldTyp) =
|
||||||
|
self.hidden = val
|
||||||
|
notifyObservers(self)
|
||||||
|
result = getAst(helper(name, hidden, typ, fieldTyp))
|
||||||
|
|
||||||
|
proc generatePublicAccessors(name, hidden, typ, fieldTyp: NimNode): NimNode =
|
||||||
|
template helper(name, hidden, typ, fieldTyp) {.dirty.} =
|
||||||
|
proc name*(self: typ): fieldTyp =
|
||||||
|
trackDependency(self)
|
||||||
|
result = self.hidden
|
||||||
|
proc `name=`*(self: typ; val: fieldTyp) =
|
||||||
|
self.hidden = val
|
||||||
|
notifyObservers(self)
|
||||||
|
result = getAst(helper(name, hidden, typ, fieldTyp))
|
||||||
|
|
||||||
|
proc transform(n: NimNode; stmts, obj: NimNode): NimNode =
|
||||||
|
if n.kind == nnkIdentDefs and obj.kind == nnkIdent:
|
||||||
|
for i in 0..n.len-3:
|
||||||
|
let it = n[i]
|
||||||
|
let itB = if it.kind == nnkPostFix: it[1] else: it
|
||||||
|
let hidden = newIdentNode("raw" & $itB)
|
||||||
|
if it.kind == nnkPostFix:
|
||||||
|
stmts.add generatePublicAccessors(itB, hidden, obj, n[n.len-2])
|
||||||
|
else:
|
||||||
|
stmts.add generatePrivateAccessors(itB, hidden, obj, n[n.len-2])
|
||||||
|
n[i] = hidden
|
||||||
|
result = n
|
||||||
|
else:
|
||||||
|
var objB = if n.kind == nnkTypeDef: n[0] else: obj
|
||||||
|
result = copyNimNode(n)
|
||||||
|
for i in 0..<n.len:
|
||||||
|
if n.kind == nnkObjectTy and i == 1 and n[1].kind == nnkEmpty:
|
||||||
|
result.add newTree(nnkOfInherit, ident("ReactiveBase"))
|
||||||
|
else:
|
||||||
|
result.add transform(n[i], stmts, objB)
|
||||||
|
|
||||||
|
macro makeReactive*(n: untyped): untyped =
|
||||||
|
var a = newStmtList()
|
||||||
|
a.add newEmptyNode()
|
||||||
|
let t = transform(n, a, newEmptyNode())
|
||||||
|
a[0] = t
|
||||||
|
result = a
|
||||||
|
when defined(debugKaraxDsl):
|
||||||
|
echo repr result
|
||||||
|
|
||||||
|
import vdom
|
||||||
|
|
||||||
|
template vmap*(x: RSeq; elem, f: untyped): VNode =
|
||||||
|
let tmp = buildHtml(elem):
|
||||||
|
for i in 0..<len(x):
|
||||||
|
f(x[i])
|
||||||
|
doTrackResize(x, tmp, f(x[pos]))
|
||||||
|
tmp
|
||||||
|
|
||||||
|
template vmapIt*(x: RSeq; elem, call: untyped): VNode =
|
||||||
|
var it {.inject.}: type(x[0])
|
||||||
|
let tmp = buildHtml(elem):
|
||||||
|
for i in 0..<len(x):
|
||||||
|
it = x[i]
|
||||||
|
call
|
||||||
|
doTrackResize(x, tmp, call)
|
||||||
|
tmp
|
||||||
|
|
||||||
|
proc text*(s: RString): VNode =
|
||||||
|
result = text(s.value)
|
||||||
|
s.subscribe proc(v: cstring) =
|
||||||
|
if result.dom != nil: result.dom.nodeValue = v
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
makeReactive:
|
||||||
|
type
|
||||||
|
Foo = ref object of ReactiveBase
|
||||||
|
x, y: int
|
||||||
|
Bar = ref object of ReactiveBase
|
||||||
|
z: string
|
||||||
|
exported*: int
|
||||||
|
Another = ref object
|
||||||
|
a, b: cstring
|
||||||
|
c: bool
|
||||||
69
src/vdom.nim
69
src/vdom.nim
|
|
@ -105,16 +105,19 @@ buildLookupTables()
|
||||||
type
|
type
|
||||||
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
|
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
|
||||||
NativeEventHandler* = proc (ev: Event) {.closure.}
|
NativeEventHandler* = proc (ev: Event) {.closure.}
|
||||||
VKey* = int
|
|
||||||
|
EventHandlers* = seq[(EventKind, EventHandler, NativeEventHandler)]
|
||||||
|
|
||||||
|
VKey* = cstring
|
||||||
|
|
||||||
VNode* = ref object of RootObj
|
VNode* = ref object of RootObj
|
||||||
kind*: VNodeKind
|
kind*: VNodeKind
|
||||||
key*: VKey
|
index*: int ## a generally useful 'index'
|
||||||
id*, class*, text*: cstring
|
id*, class*, text*: cstring
|
||||||
kids: seq[VNode]
|
kids: seq[VNode]
|
||||||
# even index: key, odd index: value; done this way for memory efficiency:
|
# even index: key, odd index: value; done this way for memory efficiency:
|
||||||
attrs: seq[cstring]
|
attrs: seq[cstring]
|
||||||
events*: seq[(EventKind, EventHandler, NativeEventHandler)]
|
events*: EventHandlers
|
||||||
when false:
|
when false:
|
||||||
hash*: Hash
|
hash*: Hash
|
||||||
validHash*: bool
|
validHash*: bool
|
||||||
|
|
@ -123,11 +126,14 @@ type
|
||||||
## is not part of the virtual DOM anymore.
|
## is not part of the virtual DOM anymore.
|
||||||
|
|
||||||
VComponent* = ref object of VNode ## The abstract class for every karax component.
|
VComponent* = ref object of VNode ## The abstract class for every karax component.
|
||||||
|
key*: VKey ## key that determines if two components are
|
||||||
|
## identical.
|
||||||
renderImpl*: proc(self: VComponent): VNode
|
renderImpl*: proc(self: VComponent): VNode
|
||||||
changedImpl*: proc(self, newInstance: VComponent): bool
|
changedImpl*: proc(self, newInstance: VComponent): bool
|
||||||
updatedImpl*: proc(self, newInstance: VComponent)
|
updatedImpl*: proc(self, newInstance: VComponent)
|
||||||
onAttachImpl*: proc(self: VComponent)
|
onAttachImpl*: proc(self: VComponent)
|
||||||
onDetachImpl*: proc(self: VComponent)
|
onDetachImpl*: proc(self: VComponent)
|
||||||
|
realDomImpl*: proc(self: VComponent): kdom.Node
|
||||||
version*: int ## Update this to trigger a redraw by karax. Usually you
|
version*: int ## Update this to trigger a redraw by karax. Usually you
|
||||||
## should call 'markDirty' instead which is an alias for
|
## should call 'markDirty' instead which is an alias for
|
||||||
## 'inc version'.
|
## 'inc version'.
|
||||||
|
|
@ -140,24 +146,36 @@ type
|
||||||
proc value*(n: VNode): cstring = n.text
|
proc value*(n: VNode): cstring = n.text
|
||||||
proc `value=`*(n: VNode; v: cstring) = n.text = v
|
proc `value=`*(n: VNode; v: cstring) = n.text = v
|
||||||
|
|
||||||
proc intValue*(n: VNode): int = n.key
|
proc intValue*(n: VNode): int = n.index
|
||||||
proc vn*(i: int): VNode = VNode(kind: VNodeKind.int, key: i)
|
proc vn*(i: int): VNode = VNode(kind: VNodeKind.int, index: i)
|
||||||
proc vn*(b: bool): VNode = VNode(kind: VNodeKind.int, key: ord(b))
|
proc vn*(b: bool): VNode = VNode(kind: VNodeKind.int, index: ord(b))
|
||||||
proc vn*(x: cstring): VNode = VNode(kind: VNodeKind.text, key: -1, text: x)
|
proc vn*(x: cstring): VNode = VNode(kind: VNodeKind.text, index: -1, text: x)
|
||||||
|
|
||||||
template callThunk*(fn: typed; n: VNode): untyped =
|
template callThunk*(fn: typed; n: VNode): untyped =
|
||||||
## for internal usage only.
|
## for internal usage only.
|
||||||
fn(n.kids)
|
fn(n.kids)
|
||||||
|
|
||||||
proc vthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
proc vthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||||
VNode(kind: VNodeKind.vthunk, text: name, key: -1, kids: @args)
|
VNode(kind: VNodeKind.vthunk, text: name, index: -1, kids: @args)
|
||||||
|
|
||||||
proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||||
VNode(kind: VNodeKind.dthunk, text: name, key: -1, kids: @args)
|
VNode(kind: VNodeKind.dthunk, text: name, index: -1, kids: @args)
|
||||||
|
|
||||||
|
proc setEventIfNoConflict(v: VNode; kind: EventKind; handler: EventHandler) =
|
||||||
|
assert handler != nil
|
||||||
|
for i in 0..<v.events.len:
|
||||||
|
if v.events[i][0] == kind:
|
||||||
|
#v.events[i][1] = handler
|
||||||
|
return
|
||||||
|
v.events.add((kind, handler, nil))
|
||||||
|
|
||||||
|
proc mergeEvents*(v: VNode; handlers: EventHandlers) =
|
||||||
|
## Overrides or adds the event handlers to `v`'s internal event handler list.
|
||||||
|
for h in handlers: v.setEventIfNoConflict(h[0], h[1])
|
||||||
|
|
||||||
proc defaultChangedImpl*(v, newInstance: VComponent): bool =
|
proc defaultChangedImpl*(v, newInstance: VComponent): bool =
|
||||||
## The default implementation of 'changed'.
|
## The default implementation of 'changed'.
|
||||||
result = v.version != v.renderedVersion
|
result = v.key != newInstance.key or v.version != v.renderedVersion
|
||||||
|
|
||||||
proc defaultUpdatedImpl*(v, newInstance: VComponent) =
|
proc defaultUpdatedImpl*(v, newInstance: VComponent) =
|
||||||
discard
|
discard
|
||||||
|
|
@ -168,13 +186,13 @@ proc getDebugId(): int =
|
||||||
gid
|
gid
|
||||||
|
|
||||||
template newComponent*[T](t: typeDesc[T];
|
template newComponent*[T](t: typeDesc[T];
|
||||||
render: (proc(self: VComponent): VNode) not nil,
|
render: (proc(self: VComponent): VNode) = nil,
|
||||||
onAttach: proc(self: VComponent) = nil,
|
onAttach: proc(self: VComponent) = nil,
|
||||||
onDetach: proc(self: VComponent) = nil,
|
onDetach: proc(self: VComponent) = nil,
|
||||||
changed: (proc(self, newInstance: VComponent): bool) = defaultChangedImpl,
|
changed: (proc(self, newInstance: VComponent): bool) = defaultChangedImpl,
|
||||||
updated: proc(self, newInstance: VComponent) = defaultUpdatedImpl): T =
|
updated: proc(self, newInstance: VComponent) = defaultUpdatedImpl): T =
|
||||||
## Use this template to create new components.
|
## Use this template to create new components.
|
||||||
T(kind: VNodeKind.component, key: -1,
|
T(kind: VNodeKind.component, index: -1,
|
||||||
text: cstring(astToStr(t)), renderImpl: render,
|
text: cstring(astToStr(t)), renderImpl: render,
|
||||||
changedImpl: changed, updatedImpl: updated,
|
changedImpl: changed, updatedImpl: updated,
|
||||||
onAttachImpl: onAttach, onDetachImpl: onDetach,
|
onAttachImpl: onAttach, onDetachImpl: onDetach,
|
||||||
|
|
@ -199,11 +217,32 @@ proc getAttr*(n: VNode; key: cstring): cstring =
|
||||||
for i in countup(0, n.attrs.len-2, 2):
|
for i in countup(0, n.attrs.len-2, 2):
|
||||||
if n.attrs[i] == key: return n.attrs[i+1]
|
if n.attrs[i] == key: return n.attrs[i+1]
|
||||||
|
|
||||||
|
proc takeOverAttr*(newNode, oldNode: VNode) =
|
||||||
|
shallowCopy oldNode.attrs, newNode.attrs
|
||||||
|
|
||||||
|
proc takeOverFields*(newNode, oldNode: VNode) =
|
||||||
|
template take(field) =
|
||||||
|
shallowCopy oldNode.field, newNode.field
|
||||||
|
take kind
|
||||||
|
take index
|
||||||
|
take id
|
||||||
|
take class
|
||||||
|
take text
|
||||||
|
take kids
|
||||||
|
take attrs
|
||||||
|
take events
|
||||||
|
take style
|
||||||
|
take dom
|
||||||
|
|
||||||
proc len*(x: VNode): int = x.kids.len
|
proc len*(x: VNode): int = x.kids.len
|
||||||
proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx]
|
proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx]
|
||||||
proc `[]=`*(x: VNode; idx: int; y: VNode) = x.kids[idx] = y
|
proc `[]=`*(x: VNode; idx: int; y: VNode) = x.kids[idx] = y
|
||||||
proc add*(parent, kid: VNode) = parent.kids.add kid
|
proc add*(parent, kid: VNode) = parent.kids.add kid
|
||||||
proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1)
|
proc delete*(parent: VNode; position: int) =
|
||||||
|
parent.kids.delete(position)
|
||||||
|
proc insert*(parent, kid: VNode; position: int) =
|
||||||
|
parent.kids.insert(kid, position)
|
||||||
|
proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, index: -1)
|
||||||
|
|
||||||
proc tree*(kind: VNodeKind; kids: varargs[VNode]): VNode =
|
proc tree*(kind: VNodeKind; kids: varargs[VNode]): VNode =
|
||||||
result = newVNode(kind)
|
result = newVNode(kind)
|
||||||
|
|
@ -214,8 +253,8 @@ proc tree*(kind: VNodeKind; attrs: openarray[(cstring, cstring)];
|
||||||
result = tree(kind, kids)
|
result = tree(kind, kids)
|
||||||
for a in attrs: result.setAttr(a[0], a[1])
|
for a in attrs: result.setAttr(a[0], a[1])
|
||||||
|
|
||||||
proc text*(s: string): VNode = VNode(kind: VNodeKind.text, text: cstring(s), key: -1)
|
proc text*(s: string): VNode = VNode(kind: VNodeKind.text, text: cstring(s), index: -1)
|
||||||
proc text*(s: cstring): VNode = VNode(kind: VNodeKind.text, text: s, key: -1)
|
proc text*(s: cstring): VNode = VNode(kind: VNodeKind.text, text: s, index: -1)
|
||||||
|
|
||||||
iterator items*(n: VNode): VNode =
|
iterator items*(n: VNode): VNode =
|
||||||
for i in 0..<n.kids.len: yield n.kids[i]
|
for i in 0..<n.kids.len: yield n.kids[i]
|
||||||
|
|
|
||||||
128
tests/components.nim
Normal file
128
tests/components.nim
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils
|
||||||
|
|
||||||
|
type TextInput* = ref object of VComponent
|
||||||
|
value: cstring
|
||||||
|
isActive: bool
|
||||||
|
|
||||||
|
proc render(x: VComponent): VNode =
|
||||||
|
let self = TextInput(x)
|
||||||
|
|
||||||
|
let style = style(
|
||||||
|
(StyleAttr.position, cstring"relative"),
|
||||||
|
(StyleAttr.paddingLeft, cstring"10px"),
|
||||||
|
(StyleAttr.paddingRight, cstring"5px"),
|
||||||
|
(StyleAttr.height, cstring"30px"),
|
||||||
|
(StyleAttr.lineHeight, cstring"30px"),
|
||||||
|
(StyleAttr.border, cstring"solid 1px " & (if self.isActive: cstring"red" else: cstring"black")),
|
||||||
|
(StyleAttr.fontSize, cstring"12px"),
|
||||||
|
(StyleAttr.fontWeight, cstring"600")
|
||||||
|
).merge(self.style)
|
||||||
|
|
||||||
|
let inputStyle = style.merge(style(
|
||||||
|
(StyleAttr.color, cstring"inherit"),
|
||||||
|
(StyleAttr.fontSize, cstring"inherit"),
|
||||||
|
(StyleAttr.fontWeight, cstring"inherit"),
|
||||||
|
(StyleAttr.fontFamily, cstring"inherit"),
|
||||||
|
(StyleAttr.position, cstring"absolute"),
|
||||||
|
(StyleAttr.top, cstring"0"),
|
||||||
|
(StyleAttr.left, cstring"0"),
|
||||||
|
(StyleAttr.height, cstring"100%"),
|
||||||
|
(StyleAttr.width, cstring"100%"),
|
||||||
|
(StyleAttr.border, cstring"none"),
|
||||||
|
(StyleAttr.backgroundColor, cstring"transparent"),
|
||||||
|
))
|
||||||
|
|
||||||
|
proc flip(ev: Event; n: VNode) =
|
||||||
|
self.isActive = not self.isActive
|
||||||
|
echo "flip! ", self.isActive, " id: ", self.debugId, " version ", self.version
|
||||||
|
markDirty(self)
|
||||||
|
|
||||||
|
result = buildHtml(tdiv(style=style)):
|
||||||
|
input(style=inputStyle, value=self.value, onblur=flip, onfocus=flip,
|
||||||
|
events=self.events)
|
||||||
|
|
||||||
|
proc update(current, next: VComponent) =
|
||||||
|
let current = TextInput(current)
|
||||||
|
let next = TextInput(next)
|
||||||
|
current.value = next.value
|
||||||
|
current.key = next.key
|
||||||
|
|
||||||
|
proc changed(current, next: VComponent): bool = true
|
||||||
|
|
||||||
|
proc newTextInput*(style: VStyle = VStyle(); key: cstring;
|
||||||
|
value = cstring""): TextInput =
|
||||||
|
result = newComponent(TextInput, render, changed=changed, updated=update)
|
||||||
|
result.style = style
|
||||||
|
result.value = value
|
||||||
|
#result.key = key
|
||||||
|
|
||||||
|
when false:
|
||||||
|
type
|
||||||
|
Combined = ref object of VComponent
|
||||||
|
a, b: TextInput
|
||||||
|
|
||||||
|
proc renderComb(self: VComponent): VNode =
|
||||||
|
let self = Combined(self)
|
||||||
|
|
||||||
|
proc bu(ev: Event; n: VNode) =
|
||||||
|
self.a.value = ""
|
||||||
|
self.b.value = ""
|
||||||
|
markDirty(self.a)
|
||||||
|
markDirty(self.b)
|
||||||
|
|
||||||
|
result = buildHtml(tdiv(style=self.style)):
|
||||||
|
self.a
|
||||||
|
self.b
|
||||||
|
button(onclick=bu):
|
||||||
|
text "reset"
|
||||||
|
|
||||||
|
proc changed(self: VComponent): bool =
|
||||||
|
let self = Combined(self)
|
||||||
|
result = self.a.changedImpl(self.a) or self.b.changedImpl(self.b)
|
||||||
|
|
||||||
|
proc newCombined*(style: VStyle = VStyle()): Combined =
|
||||||
|
result = newComponent(Combined, renderComb, changed=changed)
|
||||||
|
result.a = newTextInput(style, "AAA")
|
||||||
|
result.b = newTextInput(style, "BBB")
|
||||||
|
|
||||||
|
|
||||||
|
var
|
||||||
|
persons: seq[cstring] = @[cstring"Karax", "Abathur", "Fenix"]
|
||||||
|
errmsg = cstring""
|
||||||
|
|
||||||
|
proc renderPerson(text: cstring, index: int): VNode =
|
||||||
|
result = buildHtml(tdiv):
|
||||||
|
newTextInput(VStyle(), &index, text):
|
||||||
|
proc onkeyuplater(ev: Event; n: VNode) =
|
||||||
|
let v = n.value
|
||||||
|
if v.len > 0:
|
||||||
|
persons[index] = v
|
||||||
|
errmsg = ""
|
||||||
|
else:
|
||||||
|
errmsg = "name must not be empty"
|
||||||
|
button:
|
||||||
|
proc onclick(ev: Event; n: VNode) =
|
||||||
|
persons.delete(index)
|
||||||
|
errmsg = ""
|
||||||
|
echo persons
|
||||||
|
text "(x)"
|
||||||
|
|
||||||
|
|
||||||
|
proc createDom(): VNode =
|
||||||
|
result = buildHtml(tdiv):
|
||||||
|
tdiv:
|
||||||
|
for index, text in persons.pairs:
|
||||||
|
renderPerson(text, index)
|
||||||
|
tdiv:
|
||||||
|
newTextInput(VStyle(), &persons.len, ""):
|
||||||
|
proc onkeyupenter(ev: Event; n: VNode) =
|
||||||
|
let v = n.value
|
||||||
|
if v.len > 0:
|
||||||
|
persons.add v
|
||||||
|
errmsg = ""
|
||||||
|
else:
|
||||||
|
errmsg = "name must not be empty"
|
||||||
|
tdiv:
|
||||||
|
text errmsg
|
||||||
|
|
||||||
|
setRenderer createDom
|
||||||
|
|
@ -52,12 +52,16 @@ proc testInsert() =
|
||||||
let a = buildHtml(tdiv):
|
let a = buildHtml(tdiv):
|
||||||
ul:
|
ul:
|
||||||
li: text "A"
|
li: text "A"
|
||||||
li: text "C"
|
li:
|
||||||
|
button:
|
||||||
|
text "C"
|
||||||
let b = buildHtml(tdiv):
|
let b = buildHtml(tdiv):
|
||||||
ul:
|
ul:
|
||||||
li: text "A"
|
li: text "A"
|
||||||
li: text "B"
|
li: text "B"
|
||||||
li: text "C"
|
li:
|
||||||
|
button:
|
||||||
|
text "C"
|
||||||
doDiff(a, b, "pkInsertBefore li B")
|
doDiff(a, b, "pkInsertBefore li B")
|
||||||
|
|
||||||
proc testInsert2() =
|
proc testInsert2() =
|
||||||
|
|
@ -66,7 +70,9 @@ proc testInsert2() =
|
||||||
tdiv:
|
tdiv:
|
||||||
ul:
|
ul:
|
||||||
li: text "A"
|
li: text "A"
|
||||||
li: text "D"
|
li:
|
||||||
|
button:
|
||||||
|
text "D"
|
||||||
let b = buildHtml(tdiv):
|
let b = buildHtml(tdiv):
|
||||||
tdiv:
|
tdiv:
|
||||||
tdiv:
|
tdiv:
|
||||||
|
|
@ -74,7 +80,9 @@ proc testInsert2() =
|
||||||
li: text "A"
|
li: text "A"
|
||||||
li: text "B"
|
li: text "B"
|
||||||
li: text "C"
|
li: text "C"
|
||||||
li: text "D"
|
li:
|
||||||
|
button:
|
||||||
|
text "D"
|
||||||
doDiff(a, b, "pkInsertBefore li B", "pkInsertBefore li C")
|
doDiff(a, b, "pkInsertBefore li B", "pkInsertBefore li C")
|
||||||
|
|
||||||
proc testDelete() =
|
proc testDelete() =
|
||||||
|
|
@ -108,7 +116,8 @@ proc testDeleteMiddle() =
|
||||||
li:
|
li:
|
||||||
tdiv: text "G"
|
tdiv: text "G"
|
||||||
li:
|
li:
|
||||||
tdiv: text "H"
|
button:
|
||||||
|
tdiv: text "H"
|
||||||
let b = buildHtml(tdiv):
|
let b = buildHtml(tdiv):
|
||||||
ul:
|
ul:
|
||||||
li:
|
li:
|
||||||
|
|
@ -124,7 +133,8 @@ proc testDeleteMiddle() =
|
||||||
li:
|
li:
|
||||||
tdiv: text "F"
|
tdiv: text "F"
|
||||||
li:
|
li:
|
||||||
tdiv: text "H"
|
button:
|
||||||
|
tdiv: text "H"
|
||||||
doDiff(a, b, "pkDetach li div G", "pkRemove nil")
|
doDiff(a, b, "pkDetach li div G", "pkRemove nil")
|
||||||
|
|
||||||
proc createEntry(id: cstring): VNode =
|
proc createEntry(id: cstring): VNode =
|
||||||
|
|
@ -146,14 +156,14 @@ proc testWild() =
|
||||||
let a = createEntries(entries)
|
let a = createEntries(entries)
|
||||||
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")]
|
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")]
|
||||||
let b = createEntries(entries)
|
let b = createEntries(entries)
|
||||||
doDiff(a, b, "pkDetach button 7", "pkRemove nil", "pkDetach div 7", "pkRemove nil")
|
doDiff(a, b, "pkDetach button 7", "pkRemove nil", "pkDetach div 5", "pkRemove nil")
|
||||||
|
|
||||||
proc testWildInsert() =
|
proc testWildInsert() =
|
||||||
var entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")]
|
var entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")]
|
||||||
let a = createEntries(entries)
|
let a = createEntries(entries)
|
||||||
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring"7", cstring("5")]
|
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring"7", cstring("5")]
|
||||||
let b = createEntries(entries)
|
let b = createEntries(entries)
|
||||||
doDiff(a, b, "pkInsertBefore button 7", "pkInsertBefore div 7")
|
doDiff(a, b, "pkInsertBefore button 7", "pkAppend div 5")
|
||||||
|
|
||||||
kxi = KaraxInstance(rootId: cstring"ROOT", renderer: proc (): VNode = discard)
|
kxi = KaraxInstance(rootId: cstring"ROOT", renderer: proc (): VNode = discard)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue