diff --git a/examples/carousel/carousel.nims b/examples/carousel/carousel.nims index 1451fc0..cef2dec 100644 --- a/examples/carousel/carousel.nims +++ b/examples/carousel/carousel.nims @@ -1 +1 @@ ---path: "../../src" +--path: "../../karax" diff --git a/examples/mediaplayer/mediaplayer.nim b/examples/mediaplayer/mediaplayer.nim index 7bc9c65..c84b68b 100644 --- a/examples/mediaplayer/mediaplayer.nim +++ b/examples/mediaplayer/mediaplayer.nim @@ -1,5 +1,5 @@ -import karax, karaxdsl, vdom, kdom, compact +import karax / [karax, karaxdsl, vdom, kdom, compact] const Play = 0 diff --git a/examples/mediaplayer/playerapp.nim b/examples/mediaplayer/playerapp.nim index e611eec..acb2e68 100644 --- a/examples/mediaplayer/playerapp.nim +++ b/examples/mediaplayer/playerapp.nim @@ -1,6 +1,6 @@ ## Example app that shows how to write and embed a custom component. -include karaxprelude +include karax/prelude import mediaplayer const url = "https://www.w3schools.com/html/mov_bbb.mp4" diff --git a/examples/mediaplayer/playerapp.nims b/examples/mediaplayer/playerapp.nims index 9028ff1..cf2eb9e 100644 --- a/examples/mediaplayer/playerapp.nims +++ b/examples/mediaplayer/playerapp.nims @@ -1 +1 @@ ---path: "../../src" +--path: "../.." diff --git a/examples/scrollapp/scrollapp.nim b/examples/scrollapp/scrollapp.nim index 8ead611..4490447 100644 --- a/examples/scrollapp/scrollapp.nim +++ b/examples/scrollapp/scrollapp.nim @@ -1,7 +1,7 @@ ## Example that shows how to accomplish an "infinitely scrolling" app. -include karaxprelude -import jstrutils, kdom, vstyles +include karax/prelude +import karax / [jstrutils, kdom, vstyles] var entries: seq[cstring] = @[] for i in 1..500: diff --git a/examples/scrollapp/scrollapp.nims b/examples/scrollapp/scrollapp.nims index 9028ff1..cf2eb9e 100644 --- a/examples/scrollapp/scrollapp.nims +++ b/examples/scrollapp/scrollapp.nims @@ -1 +1 @@ ---path: "../../src" +--path: "../.." diff --git a/examples/todoapp/todoapp.nim b/examples/todoapp/todoapp.nim index 47ddeae..99f70ea 100644 --- a/examples/todoapp/todoapp.nim +++ b/examples/todoapp/todoapp.nim @@ -1,5 +1,5 @@ -import vdom, karax, karaxdsl, jstrutils, compact, localstorage +import karax / [vdom, karax, karaxdsl, jstrutils, compact, localstorage] type Filter = enum diff --git a/examples/todoapp/todoapp.nims b/examples/todoapp/todoapp.nims index 9028ff1..cf2eb9e 100644 --- a/examples/todoapp/todoapp.nims +++ b/examples/todoapp/todoapp.nims @@ -1 +1 @@ ---path: "../../src" +--path: "../.." diff --git a/karax.nimble b/karax.nimble index 103da81..65e9d1f 100644 --- a/karax.nimble +++ b/karax.nimble @@ -1,13 +1,12 @@ # Package -version = "0.1.0" +version = "0.2.0" author = "Xored Software, Inc." description = "Karax is a framework for developing single page applications in Nim." license = "MIT" -srcDir = "src" # Dependencies requires "nim >= 0.16.1" -skipDirs = @["examples/todoapp"] \ No newline at end of file +skipDirs = @["examples"] diff --git a/src/autocomplete.nim b/karax/autocomplete.nim similarity index 100% rename from src/autocomplete.nim rename to karax/autocomplete.nim diff --git a/karax/compact.nim b/karax/compact.nim new file mode 100644 index 0000000..f5f21a8 --- /dev/null +++ b/karax/compact.nim @@ -0,0 +1,130 @@ +## Components in Karax are built by the ``.component`` macro annotation. + +when defined(js): + import jdict, kdom + +import macros, vdom, tables, strutils, kbase + +when defined(js): + var + vcomponents* = newJDict[cstring, proc(args: seq[VNode]): VNode]() + dcomponents* = newJDict[cstring, proc(args: seq[VNode]): Node]() +else: + var + vcomponents* = newTable[kstring, proc(args: seq[VNode]): VNode]() + dcomponents* = newTable[kstring, proc(args: seq[VNode]): Node]() + +type + ComponentKind* {.pure.} = enum + None, + Tag, + VNode, + Node + +var + allcomponents {.compileTime.} = initTable[string, ComponentKind]() + +proc isComponent*(x: string): ComponentKind {.compileTime.} = + allcomponents.getOrDefault(x) + +proc addTags() {.compileTime.} = + let x = (bindSym"VNodeKind").getTypeImpl + expectKind(x, nnkEnumTy) + for i in ord(VNodeKind.html)..ord(VNodeKind.high): + # +1 because of empty node at the start of the enum AST: + let tag = $x[i+1] + allcomponents[tag] = ComponentKind.Tag + +static: + addTags() + +proc unpack(symbolicType: NimNode; index: int): NimNode {.compileTime.} = + #let t = symbolicType.getTypeImpl + let t = repr(symbolicType) + case t + of "cstring": + result = quote do: + args[`index`].text + of "int", "VKey": + result = quote do: + args[`index`].intValue + of "bool": + result = quote do: + args[`index`].intValue != 0 + elif t.endsWith"Kind": + result = quote do: + `symbolicType`(args[`index`].intValue) + else: + # just pass it along, maybe there is some conversion for it: + result = quote do: + args[`index`] + +proc newname*(n: NimNode): NimNode = + if n.kind == nnkPostfix: + n[1] = newname(n[1]) + result = n + elif n.kind == nnkSym: + result = ident($n.symbol) + else: + result = n + +when defined(js): + macro compact*(prc: untyped): untyped = + ## A 'compact' tree generation proc is one that only depends on its + ## inputs and should be stored as a compact virtual DOM tree and + ## only expanded on demand (when its inputs changed). + var n = prc.copyNimNode + for i in 0..6: n.add prc[i].copyNimTree + expectKind(n, nnkProcDef) + if n[0].kind == nnkEmpty: + error("please pass a non anonymous proc", n[0]) + let name = n[0] + let params = params(n) + let rettype = repr params[0] + var isvirtual = ComponentKind.None + if rettype == "VNode": + isvirtual = ComponentKind.VNode + elif rettype == "Node": + isvirtual = ComponentKind.Node + else: + error "component must return VNode or Node", params[0] + let realName = if name.kind == nnkPostfix: name[1] else: name + let nn = $realName + n[0] = ident("inner" & nn) + var unpackCall = newCall(n[0]) + var counter = 0 + for i in 1.. ': result.add(">") + of '&': result.add("&") + of '"': result.add(""") + else: result.add(c) + + proc addEscaped(result: var string, s: kstring) = + ## same as ``result.add(escape(s))``, but more efficient. + for c in items(s): + case c + of '<': result.add("<") + of '>': result.add(">") + of '&': result.add("&") + of '"': result.add(""") + of '\'': result.add("'") + of '/': result.add("/") + else: result.add(c) + + proc addIndent(result: var string, indent: int) = + result.add("\n") + for i in 1..indent: result.add(' ') + + if n.kind == VNodeKind.text: + result.addEscaped(n.text) + else: + let kind = $n.kind + result.add('<') + result.add(kind) + if n.id != nil: + result.add " id=\"" + result.addEscapedAttr(n.id) + result.add('"') + if n.class != nil: + result.add " class=\"" + result.addEscapedAttr(n.class) + result.add('"') + for k, v in attrs(n): + result.add(' ') + result.add(k) + result.add("=\"") + result.addEscapedAttr(v) + result.add('"') + # XXX add style to string + if n.len > 0: + result.add('>') + if n.len > 1: + var noWhitespace = false + for i in 0..b`` is + # different from ``a b``. + for i in 0..") + else: + result.add(" />") + + +proc `$`*(n: VNode): kstring = + when defined(js): + var res = "" + toString(n, res, 0) + result = kstring(res) + else: + result = "" + add(result, n) diff --git a/src/vstyles.nim b/karax/vstyles.nim similarity index 76% rename from src/vstyles.nim rename to karax/vstyles.nim index 2f854e1..2b7e48f 100644 --- a/src/vstyles.nim +++ b/karax/vstyles.nim @@ -1,5 +1,8 @@ -import kdom, macros, jdict +import macros, kbase + +when defined(js): + import kdom, jdict type StyleAttr* {.pure.} = enum @@ -200,10 +203,10 @@ type macro buildLookupTables(): untyped = var e = newTree(nnkBracket) for i in low(StyleAttr)..high(StyleAttr): - e.add(newCall("cstring", newLit($i))) + e.add(newCall("kstring", newLit($i))) template tmpl(e) {.dirty.} = const - toStyleAttrName: array[StyleAttr, cstring] = e + toStyleAttrName: array[StyleAttr, kstring] = e result = getAst tmpl(e) buildLookupTables() @@ -212,8 +215,14 @@ buildLookupTables() # it's relevant. # even index: key, odd index: value; done this way for memory efficiency: -type - VStyle* = JSeq[cstring] +when defined(js): + type + VStyle* = JSeq[cstring] +else: + type + VStyle* = ref seq[string] + proc len*(a: VStyle): int = len(a[]) + proc add*(a: VStyle; x: string) = add(a[], x) proc eq*(a, b: VStyle): bool = if a.isNil: @@ -225,9 +234,7 @@ proc eq*(a, b: VStyle): bool = if a[i] != b[i]: return false return true -proc kout[T](x: T) {.importc: "console.log", varargs.} - -proc setAttr(s: VStyle; a, value: cstring) {.noSideEffect.} = +proc setAttr(s: VStyle; a, value: kstring) {.noSideEffect.} = var i = 0 while i < s.len: if s[i] == a: @@ -247,11 +254,11 @@ proc setAttr(s: VStyle; a, value: cstring) {.noSideEffect.} = s.add a s.add value -proc setAttr*(s: VStyle; attr: StyleAttr, value: cstring) {.noSideEffect.} = +proc setAttr*(s: VStyle; attr: StyleAttr, value: kstring) {.noSideEffect.} = assert value != nil, "value must not be nil" setAttr(s, toStyleAttrName[attr], value) -proc getAttr*(s: VStyle; attr: StyleAttr): cstring {.noSideEffect.} = +proc getAttr*(s: VStyle; attr: StyleAttr): kstring {.noSideEffect.} = ## returns 'nil' if the attribute has not been set. var i = 0 let a = toStyleAttrName[attr] @@ -262,44 +269,60 @@ proc getAttr*(s: VStyle; attr: StyleAttr): cstring {.noSideEffect.} = return nil inc i, 2 -proc style*(pairs: varargs[(StyleAttr, cstring)]): VStyle {.noSideEffect.} = +proc style*(pairs: varargs[(StyleAttr, kstring)]): VStyle {.noSideEffect.} = ## constructs a VStyle object from a list of (attribute, value)-pairs. - result = newJSeq[cstring]() + when defined(js): + result = newJSeq[cstring]() + else: + new(result) + result[] = @[] for x in pairs: result.setAttr x[0], x[1] -proc style*(a: StyleAttr; val: cstring): VStyle {.noSideEffect.} = +proc style*(a: StyleAttr; val: kstring): VStyle {.noSideEffect.} = ## constructs a VStyle object from a single (attribute, value)-pair. - result = newJSeq[cstring]() + when defined(js): + result = newJSeq[cstring]() + else: + new(result) + result[] = @[] result.setAttr a, val -proc setStyle(d: Style; key, val: cstring) {.importcpp: "#[#] = #", noSideEffect.} +when defined(js): + proc setStyle(d: Style; key, val: cstring) {.importcpp: "#[#] = #", noSideEffect.} + + proc applyStyle*(n: Node; s: VStyle) {.noSideEffect.} = + ## apply the style to the real DOM node ``n``. + + #n.style = Style() # optimized, this is a hotspot: + {.emit: "`n`.style = {};".} + for i in countup(0, s.len-1, 2): + n.style.setStyle(s[i], s[i+1]) proc merge*(a, b: VStyle): VStyle {.noSideEffect.} = ## merges two styles. ``b`` takes precedence over ``a``. - result = newJSeq[cstring]() + when defined(js): + result = newJSeq[cstring]() + else: + new(result) + result[] = @[] for i in 0..