move karax source code from 'src' to 'karax'; make karaxdsl work with native Nim; added jjson and jwebsockets modules
This commit is contained in:
parent
bb1732c834
commit
8e8ada7cd2
34 changed files with 407 additions and 203 deletions
|
|
@ -1 +1 @@
|
|||
--path: "../../src"
|
||||
--path: "../../karax"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import karax, karaxdsl, vdom, kdom, compact
|
||||
import karax / [karax, karaxdsl, vdom, kdom, compact]
|
||||
|
||||
const
|
||||
Play = 0
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
--path: "../../src"
|
||||
--path: "../.."
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
--path: "../../src"
|
||||
--path: "../.."
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import vdom, karax, karaxdsl, jstrutils, compact, localstorage
|
||||
import karax / [vdom, karax, karaxdsl, jstrutils, compact, localstorage]
|
||||
|
||||
type
|
||||
Filter = enum
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
--path: "../../src"
|
||||
--path: "../.."
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
skipDirs = @["examples"]
|
||||
|
|
|
|||
130
karax/compact.nim
Normal file
130
karax/compact.nim
Normal file
|
|
@ -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.. <params.len:
|
||||
let param = params[i]
|
||||
let L = param.len
|
||||
let typ = param[L-2]
|
||||
for j in 0 .. L-3:
|
||||
unpackCall.add unpack(typ, counter)
|
||||
inc counter
|
||||
|
||||
template vwrapper(pname, unpackCall) {.dirty.} =
|
||||
proc pname(args: seq[VNode]): VNode =
|
||||
unpackCall
|
||||
|
||||
template dwrapper(pname, unpackCall) {.dirty.} =
|
||||
proc pname(args: seq[VNode]): Node =
|
||||
unpackCall
|
||||
|
||||
template vregister(key, val) =
|
||||
bind jdict.`[]=`
|
||||
`[]=`(vcomponents, kstring(key), val)
|
||||
|
||||
template dregister(key, val) =
|
||||
bind jdict.`[]=`
|
||||
`[]=`(dcomponents, kstring(key), val)
|
||||
|
||||
result = newTree(nnkStmtList, n)
|
||||
|
||||
if isvirtual == ComponentKind.VNode:
|
||||
result.add getAst(vwrapper(newname name, unpackCall))
|
||||
result.add getAst(vregister(newLit(nn), realName))
|
||||
else:
|
||||
result.add getAst(dwrapper(newname name, unpackCall))
|
||||
result.add getAst(dregister(newLit(nn), realName))
|
||||
allcomponents[nn] = isvirtual
|
||||
when defined(debugKaraxDsl):
|
||||
echo repr result
|
||||
33
karax/jjson.nim
Normal file
33
karax/jjson.nim
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
## This module implements some small zero-overhead 'JsonNode' type
|
||||
## and helper that maps directly to JavaScript objects.
|
||||
|
||||
type
|
||||
JsonNode* {.importc.} = ref object
|
||||
|
||||
proc `[]`*(obj: JsonNode; fieldname: cstring): JsonNode {.importcpp: "#[#]".}
|
||||
proc `[]`*(obj: JsonNode; index: int): JsonNode {.importcpp: "#[#]".}
|
||||
proc `[]=`*[T](obj: JsonNode; fieldname: cstring; value: T)
|
||||
{.importcpp: "#[#] = #".}
|
||||
proc length(x: JsonNode): int {.importcpp: "#.length".}
|
||||
proc len*(x: JsonNode): int = (if x.isNil: 0 else: x.length)
|
||||
|
||||
proc newJsonNode*(fields: varargs[(cstring, JsonNode)]): JsonNode =
|
||||
result = JsonNode()
|
||||
for f in fields:
|
||||
result[f[0]] = f[1]
|
||||
|
||||
proc newJObject*(): JsonNode =
|
||||
result = JsonNode()
|
||||
|
||||
proc newJNull*(): JsonNode = nil
|
||||
|
||||
template `%`*(x: typed): JsonNode = cast[JsonNode](x)
|
||||
|
||||
proc getNum*(x: JsonNode): int {.importcpp: "#".}
|
||||
|
||||
proc getInt*(x: JsonNode): int {.importcpp: "#".}
|
||||
proc getStr*(x: JsonNode): cstring {.importcpp: "#".}
|
||||
proc getFNum*(x: JsonNode): cstring {.importcpp: "#".}
|
||||
|
||||
iterator items*(x: JsonNode): JsonNode =
|
||||
for i in 0..<len(x): yield x[i]
|
||||
13
karax/jwebsockets.nim
Normal file
13
karax/jwebsockets.nim
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
## Websockets support for the JSON backend.
|
||||
|
||||
type
|
||||
MessageEvent* {.importc.} = ref object
|
||||
data*: cstring
|
||||
|
||||
WebSocket* {.importc.} = ref object
|
||||
onmessage*: proc (e: MessageEvent)
|
||||
|
||||
proc newWebSocket*(url, key: cstring): WebSocket
|
||||
{.importcpp: "new WebSocket(@)".}
|
||||
|
||||
proc send*(w: WebSocket; data: cstring) {.importcpp.}
|
||||
|
|
@ -143,7 +143,6 @@ proc vnodeToDom*(n: VNode; kxi: KaraxInstance): Node =
|
|||
return result
|
||||
elif n.kind == VNodeKind.component:
|
||||
let x = VComponent(n)
|
||||
if x.realDomImpl != nil: return x.realDomImpl(x)
|
||||
if x.onAttachImpl != nil: x.onAttachImpl(x)
|
||||
assert x.renderImpl != nil
|
||||
if x.expanded == nil:
|
||||
|
|
@ -479,11 +478,7 @@ proc applyComponents(kxi: KaraxInstance) =
|
|||
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):
|
||||
if x.changedImpl != nil and x.changedImpl(x, newNode):
|
||||
when defined(karaxDebug):
|
||||
echo "Component ", newNode.text, " did change"
|
||||
let current = kxi.components[i].current
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
|
||||
import macros, karax, vdom, compact
|
||||
import macros, vdom, compact
|
||||
from strutils import startsWith, toLowerAscii
|
||||
|
||||
when defined(js):
|
||||
import karax
|
||||
|
||||
const
|
||||
StmtContext = ["kout", "inc", "echo", "dec", "!"]
|
||||
SpecialAttrs = ["id", "class", "value", "index", "style"]
|
||||
|
|
@ -32,6 +35,11 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
|||
# (except for the last child of the macros we consider here),
|
||||
# lets, consts, types can be considered as expressions
|
||||
# case is complex, calls are assumed to produce a value.
|
||||
when defined(js):
|
||||
template evHandler(): untyped = bindSym"addEventHandler"
|
||||
else:
|
||||
template evHandler(): untyped = ident"addEventHandler"
|
||||
|
||||
case n.kind
|
||||
of nnkLiterals, nnkIdent, nnkSym, nnkDotExpr, nnkBracketExpr:
|
||||
if tmpContext != nil:
|
||||
|
|
@ -68,7 +76,7 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
|||
if tmpContext == nil:
|
||||
error "no VNode to attach the event handler to"
|
||||
else:
|
||||
result = newCall(bindSym"addEventHandler", tmpContext,
|
||||
result = newCall(evHandler(), tmpContext,
|
||||
newDotExpr(bindSym"EventKind", n[0]), anon, ident("kxi"))
|
||||
else:
|
||||
result = n
|
||||
|
|
@ -94,7 +102,7 @@ proc tcall2(n, tmpContext: NimNode): NimNode =
|
|||
if x.kind == nnkExprEqExpr:
|
||||
let key = getName x[0]
|
||||
if key.startsWith("on"):
|
||||
result.add newCall(bindSym"addEventHandler",
|
||||
result.add newCall(evHandler(),
|
||||
tmp, newDotExpr(bindSym"EventKind", x[0]), x[1], ident("kxi"))
|
||||
elif key in SpecialAttrs:
|
||||
result.add newDotAsgn(tmp, key, x[1])
|
||||
8
karax/kbase.nim
Normal file
8
karax/kbase.nim
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
## Karax -- Single page applications for Nim.
|
||||
|
||||
## Basic type definitions for Karax.
|
||||
when defined(js):
|
||||
type kstring* = cstring
|
||||
else:
|
||||
type kstring* = string
|
||||
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
## Virtual DOM implementation for Karax.
|
||||
|
||||
from kdom import Event, Node
|
||||
import shash, macros, vstyles
|
||||
when defined(js):
|
||||
from kdom import Event, Node
|
||||
else:
|
||||
type
|
||||
Event* = ref object
|
||||
Node* = ref object
|
||||
|
||||
import macros, vstyles, kbase
|
||||
from strutils import toUpperAscii
|
||||
|
||||
type
|
||||
|
|
@ -88,15 +94,15 @@ macro buildLookupTables(): untyped =
|
|||
for i in low(VNodeKind)..high(VNodeKind):
|
||||
let x = $i
|
||||
let y = if x[0] == '#': x else: toUpperAscii(x)
|
||||
a.add(newCall("cstring", newLit(y)))
|
||||
a.add(newCall("kstring", newLit(y)))
|
||||
var e = newTree(nnkBracket)
|
||||
for i in low(EventKind)..high(EventKind):
|
||||
e.add(newCall("cstring", newLit(substr($i, 2))))
|
||||
e.add(newCall("kstring", newLit(substr($i, 2))))
|
||||
|
||||
template tmpl(a, e) {.dirty.} =
|
||||
const
|
||||
toTag*: array[VNodeKind, cstring] = a
|
||||
toEventName*: array[EventKind, cstring] = e
|
||||
toTag*: array[VNodeKind, kstring] = a
|
||||
toEventName*: array[EventKind, kstring] = e
|
||||
|
||||
result = getAst tmpl(a, e)
|
||||
|
||||
|
|
@ -108,15 +114,15 @@ type
|
|||
|
||||
EventHandlers* = seq[(EventKind, EventHandler, NativeEventHandler)]
|
||||
|
||||
VKey* = cstring
|
||||
VKey* = kstring
|
||||
|
||||
VNode* = ref object of RootObj
|
||||
kind*: VNodeKind
|
||||
index*: int ## a generally useful 'index'
|
||||
id*, class*, text*: cstring
|
||||
id*, class*, text*: kstring
|
||||
kids: seq[VNode]
|
||||
# even index: key, odd index: value; done this way for memory efficiency:
|
||||
attrs: seq[cstring]
|
||||
attrs: seq[kstring]
|
||||
events*: EventHandlers
|
||||
when false:
|
||||
hash*: Hash
|
||||
|
|
@ -133,7 +139,6 @@ type
|
|||
updatedImpl*: proc(self, newInstance: VComponent)
|
||||
onAttachImpl*: 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
|
||||
## should call 'markDirty' instead which is an alias for
|
||||
## 'inc version'.
|
||||
|
|
@ -143,22 +148,22 @@ type
|
|||
## expanded to.
|
||||
debugId*: int
|
||||
|
||||
proc value*(n: VNode): cstring = n.text
|
||||
proc `value=`*(n: VNode; v: cstring) = n.text = v
|
||||
proc value*(n: VNode): kstring = n.text
|
||||
proc `value=`*(n: VNode; v: kstring) = n.text = v
|
||||
|
||||
proc intValue*(n: VNode): int = n.index
|
||||
proc vn*(i: int): VNode = VNode(kind: VNodeKind.int, index: i)
|
||||
proc vn*(b: bool): VNode = VNode(kind: VNodeKind.int, index: ord(b))
|
||||
proc vn*(x: cstring): VNode = VNode(kind: VNodeKind.text, index: -1, text: x)
|
||||
proc vn*(x: kstring): VNode = VNode(kind: VNodeKind.text, index: -1, text: x)
|
||||
|
||||
template callThunk*(fn: typed; n: VNode): untyped =
|
||||
## for internal usage only.
|
||||
fn(n.kids)
|
||||
|
||||
proc vthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||
proc vthunk*(name: kstring; args: varargs[VNode, vn]): VNode =
|
||||
VNode(kind: VNodeKind.vthunk, text: name, index: -1, kids: @args)
|
||||
|
||||
proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||
proc dthunk*(name: kstring; args: varargs[VNode, vn]): VNode =
|
||||
VNode(kind: VNodeKind.dthunk, text: name, index: -1, kids: @args)
|
||||
|
||||
proc setEventIfNoConflict(v: VNode; kind: EventKind; handler: EventHandler) =
|
||||
|
|
@ -193,7 +198,7 @@ template newComponent*[T](t: typeDesc[T];
|
|||
updated: proc(self, newInstance: VComponent) = defaultUpdatedImpl): T =
|
||||
## Use this template to create new components.
|
||||
T(kind: VNodeKind.component, index: -1,
|
||||
text: cstring(astToStr(t)), renderImpl: render,
|
||||
text: kstring(astToStr(t)), renderImpl: render,
|
||||
changedImpl: changed, updatedImpl: updated,
|
||||
onAttachImpl: onAttach, onDetachImpl: onDetach,
|
||||
debugId: getDebugId())
|
||||
|
|
@ -202,7 +207,7 @@ template markDirty*(c: VComponent) =
|
|||
## mark the component as dirty so that it is re-rendered.
|
||||
inc c.version
|
||||
|
||||
proc setAttr*(n: VNode; key: cstring; val: cstring = "") =
|
||||
proc setAttr*(n: VNode; key: kstring; val: kstring = "") =
|
||||
if n.attrs.isNil:
|
||||
n.attrs = @[key, val]
|
||||
else:
|
||||
|
|
@ -213,7 +218,7 @@ proc setAttr*(n: VNode; key: cstring; val: cstring = "") =
|
|||
n.attrs.add key
|
||||
n.attrs.add val
|
||||
|
||||
proc getAttr*(n: VNode; key: cstring): cstring =
|
||||
proc getAttr*(n: VNode; key: kstring): kstring =
|
||||
for i in countup(0, n.attrs.len-2, 2):
|
||||
if n.attrs[i] == key: return n.attrs[i+1]
|
||||
|
||||
|
|
@ -237,7 +242,12 @@ proc takeOverFields*(newNode, oldNode: VNode) =
|
|||
proc len*(x: VNode): int = x.kids.len
|
||||
proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx]
|
||||
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) =
|
||||
when not defined(js) and not defined(nimNoNil):
|
||||
if parent.kids.isNil: parent.kids = @[]
|
||||
parent.kids.add kid
|
||||
|
||||
proc delete*(parent: VNode; position: int) =
|
||||
parent.kids.delete(position)
|
||||
proc insert*(parent, kid: VNode; position: int) =
|
||||
|
|
@ -248,18 +258,19 @@ proc tree*(kind: VNodeKind; kids: varargs[VNode]): VNode =
|
|||
result = newVNode(kind)
|
||||
for k in kids: result.add k
|
||||
|
||||
proc tree*(kind: VNodeKind; attrs: openarray[(cstring, cstring)];
|
||||
proc tree*(kind: VNodeKind; attrs: openarray[(kstring, kstring)];
|
||||
kids: varargs[VNode]): VNode =
|
||||
result = tree(kind, kids)
|
||||
for a in attrs: result.setAttr(a[0], a[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, index: -1)
|
||||
when defined(js):
|
||||
proc text*(s: string): VNode = VNode(kind: VNodeKind.text, text: kstring(s), index: -1)
|
||||
proc text*(s: kstring): VNode = VNode(kind: VNodeKind.text, text: s, index: -1)
|
||||
|
||||
iterator items*(n: VNode): VNode =
|
||||
for i in 0..<n.kids.len: yield n.kids[i]
|
||||
|
||||
iterator attrs*(n: VNode): (cstring, cstring) =
|
||||
iterator attrs*(n: VNode): (kstring, kstring) =
|
||||
for i in countup(0, n.attrs.len-2, 2):
|
||||
yield (n.attrs[i], n.attrs[i+1])
|
||||
|
||||
|
|
@ -324,7 +335,91 @@ when false:
|
|||
h &= child.hash
|
||||
n.hash = h
|
||||
|
||||
proc `$`*(n: VNode): cstring =
|
||||
var res = ""
|
||||
toString(n, res, 0)
|
||||
result = cstring(res)
|
||||
|
||||
proc add*(result: var string, n: VNode, indent = 0, indWidth = 2) =
|
||||
## adds the textual representation of `n` to `result`.
|
||||
|
||||
proc addEscapedAttr(result: var string, s: kstring) =
|
||||
# `addEscaped` alternative with less escaped characters.
|
||||
# Only to be used for escaping attribute values enclosed in double quotes!
|
||||
for c in items(s):
|
||||
case c
|
||||
of '<': result.add("<")
|
||||
of '>': 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..<n.len:
|
||||
if n[i].kind == VNodeKind.text:
|
||||
noWhitespace = true
|
||||
break
|
||||
|
||||
if noWhitespace:
|
||||
# for mixed leaves, we cannot output whitespace for readability,
|
||||
# because this would be wrong. For example: ``a<b>b</b>`` is
|
||||
# different from ``a <b>b</b>``.
|
||||
for i in 0..<n.len: result.add(n[i], indent+indWidth, indWidth)
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
result.addIndent(indent+indWidth)
|
||||
result.add(n[i], indent+indWidth, indWidth)
|
||||
result.addIndent(indent)
|
||||
else:
|
||||
result.add(n[0], indent+indWidth, indWidth)
|
||||
result.add("</")
|
||||
result.add(kind)
|
||||
result.add(">")
|
||||
else:
|
||||
result.add(" />")
|
||||
|
||||
|
||||
proc `$`*(n: VNode): kstring =
|
||||
when defined(js):
|
||||
var res = ""
|
||||
toString(n, res, 0)
|
||||
result = kstring(res)
|
||||
else:
|
||||
result = ""
|
||||
add(result, n)
|
||||
|
|
@ -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..<a.len:
|
||||
result.add a[i]
|
||||
for i in countup(0, b.len-1, 2):
|
||||
setAttr(result, b[i], b[i+1])
|
||||
|
||||
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])
|
||||
|
||||
iterator pairs*(s: VStyle): (cstring, cstring) {.noSideEffect.} =
|
||||
iterator pairs*(s: VStyle): (kstring, kstring) {.noSideEffect.} =
|
||||
if s != nil:
|
||||
for i in countup(0, s.len-1, 2):
|
||||
yield (s[i], s[i+1])
|
||||
|
||||
import jstrutils
|
||||
when defined(js):
|
||||
import jstrutils
|
||||
else:
|
||||
template `&`(x: untyped): untyped = $x
|
||||
|
||||
proc rgb*(r, g, b: range[0..255]): cstring =
|
||||
cstring"rgb(" & &r & cstring", " & &g & cstring", " & &b & cstring")"
|
||||
proc rgb*(r, g, b: range[0..255]): kstring =
|
||||
kstring"rgb(" & &r & kstring", " & &g & kstring", " & &b & kstring")"
|
||||
|
||||
proc rgba*(r, g, b: range[0..255], alpha: float): cstring =
|
||||
cstring"rgba(" & &r & cstring", " & &g & cstring", " & &b & cstring", " & &alpha & cstring")"
|
||||
proc rgba*(r, g, b: range[0..255], alpha: float): kstring =
|
||||
kstring"rgba(" & &r & kstring", " & &g & kstring", " & &b & kstring", " & &alpha & kstring")"
|
||||
121
src/compact.nim
121
src/compact.nim
|
|
@ -1,121 +0,0 @@
|
|||
## Components in Karax are built by the ``.component`` macro annotation.
|
||||
|
||||
import macros, jdict, kdom, vdom, tables, strutils
|
||||
|
||||
var
|
||||
vcomponents* = newJDict[cstring, proc(args: seq[VNode]): VNode]()
|
||||
dcomponents* = newJDict[cstring, proc(args: seq[VNode]): Node]()
|
||||
|
||||
type
|
||||
ComponentKind* {.pure.} = enum
|
||||
None,
|
||||
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
|
||||
|
||||
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.. <params.len:
|
||||
let param = params[i]
|
||||
let L = param.len
|
||||
let typ = param[L-2]
|
||||
for j in 0 .. L-3:
|
||||
unpackCall.add unpack(typ, counter)
|
||||
inc counter
|
||||
|
||||
template vwrapper(pname, unpackCall) {.dirty.} =
|
||||
proc pname(args: seq[VNode]): VNode =
|
||||
unpackCall
|
||||
|
||||
template dwrapper(pname, unpackCall) {.dirty.} =
|
||||
proc pname(args: seq[VNode]): Node =
|
||||
unpackCall
|
||||
|
||||
template vregister(key, val) =
|
||||
bind jdict.`[]=`
|
||||
`[]=`(vcomponents, cstring(key), val)
|
||||
|
||||
template dregister(key, val) =
|
||||
bind jdict.`[]=`
|
||||
`[]=`(dcomponents, cstring(key), val)
|
||||
|
||||
result = newTree(nnkStmtList, n)
|
||||
|
||||
if isvirtual == ComponentKind.VNode:
|
||||
result.add getAst(vwrapper(newname name, unpackCall))
|
||||
result.add getAst(vregister(newLit(nn), realName))
|
||||
else:
|
||||
result.add getAst(dwrapper(newname name, unpackCall))
|
||||
result.add getAst(dregister(newLit(nn), realName))
|
||||
allcomponents[nn] = isvirtual
|
||||
when defined(debugKaraxDsl):
|
||||
echo repr result
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
include karaxprelude
|
||||
include ../karax/prelude
|
||||
|
||||
block:
|
||||
type
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
include "../src/karax"
|
||||
import "../src/karaxdsl"
|
||||
include "../karax/karax"
|
||||
import "../karax/karaxdsl"
|
||||
|
||||
proc hasDom(n: Vnode) =
|
||||
if n.kind in {VNodeKind.component, VNodeKind.vthunk, VNodeKind.dthunk}:
|
||||
|
|
|
|||
20
tests/nativehtmlgen.nim
Normal file
20
tests/nativehtmlgen.nim
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
## Simple test that shows Karax can also do client-side HTML rendering.
|
||||
|
||||
import "../karax" / [karaxdsl, vdom]
|
||||
|
||||
when defined(js):
|
||||
{.error: "Use 'nim c' to compile this example".}
|
||||
|
||||
let tab = buildHtml(table):
|
||||
tr:
|
||||
td:
|
||||
text "Cell A"
|
||||
td:
|
||||
text "Cell B"
|
||||
tr:
|
||||
td:
|
||||
text "Cell C"
|
||||
td:
|
||||
text "Cell D"
|
||||
|
||||
echo tab
|
||||
|
|
@ -1 +1 @@
|
|||
--path: "../src"
|
||||
--path: "../karax"
|
||||
|
|
|
|||
|
|
@ -14,5 +14,6 @@ proc main =
|
|||
exec("nim js examples/mediaplayer/playerapp.nim")
|
||||
exec("nim js examples/carousel/carousel.nim")
|
||||
exec("nim js -d:nodejs -r tests/difftest.nim")
|
||||
exec("nim c tests/nativehtmlgen.nim")
|
||||
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue