Add todoapp benchmark
This commit is contained in:
parent
479db8c2cc
commit
02e7b40c07
218 changed files with 63168 additions and 0 deletions
58
bench/implementations/nim-karax/docs/docs.rst
Normal file
58
bench/implementations/nim-karax/docs/docs.rst
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
Virtual DOM
|
||||
===========
|
||||
|
||||
The virtual dom is in the ``vdom`` module.
|
||||
|
||||
The ``VNodeKind`` enum describes every tag that the HTML 5 spec includes
|
||||
and some extensions that allow for an efficient component system.
|
||||
|
||||
These extensions are:
|
||||
|
||||
``VNodeKind.int``
|
||||
The node havs a single integer field.
|
||||
|
||||
``VNodeKind.bool``
|
||||
The node has a single boolean field.
|
||||
|
||||
``VNodeKint.vthunk``
|
||||
The node is a `virtual thunk`:idx:. This means there is a
|
||||
function attached to it that produces the ``VNode`` structure
|
||||
on demand.
|
||||
|
||||
``VNodeKint.dthunk``
|
||||
The node is a `DOM thunk`:idx:. This means there is a
|
||||
function attached to it that produces the ``Node`` DOM structure
|
||||
on demand.
|
||||
|
||||
For convenience of the resulting Nim DSL these tags have enum names
|
||||
that differ from their HTML equivalent:
|
||||
|
||||
================= =======================================================
|
||||
Enum value HTML
|
||||
================= =======================================================
|
||||
``tdiv`` ``div`` (changed because ``div`` is a keyword in Nim)
|
||||
``italic`` ``i``
|
||||
``bold`` ``b``
|
||||
``strikethrough`` ``s``
|
||||
``underlined`` ``u``
|
||||
================= =======================================================
|
||||
|
||||
|
||||
A virtual dom node also has a special field set called ``key``, an integer
|
||||
that can be used as a data model specific key/id. It can be accessed in event
|
||||
handlers to change the data model. See the todoapp for an example of how to
|
||||
use it.
|
||||
|
||||
**Note**: This is not a hint for the DOM diff algorithm, multipe nodes can
|
||||
all have the same key value.
|
||||
|
||||
|
||||
Event system
|
||||
============
|
||||
|
||||
Karax does not abstract over the event system the DOM offers much: The same
|
||||
``Event`` structure is used. Every callback has the
|
||||
signature ``proc (ev: Event; n: VNode)``.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
import karax, karaxdsl, vdom, dom, components
|
||||
|
||||
const
|
||||
Play = 0
|
||||
Big = 1
|
||||
Small = 2
|
||||
Normal = 3
|
||||
|
||||
proc paused(n: Node): bool {.importcpp: "#.paused".}
|
||||
proc play(n: Node) {.importcpp.}
|
||||
proc pause(n: Node) {.importcpp.}
|
||||
proc `width=`(n: Node, w: int) {.importcpp: "#.width = #".}
|
||||
|
||||
proc mplayer*(id, resource: cstring): VNode {.component.} =
|
||||
proc handler(ev: Event; n: VNode) =
|
||||
let myVideo = document.getElementById(id)
|
||||
case n.key
|
||||
of Play:
|
||||
if myVideo.paused:
|
||||
myVideo.play()
|
||||
else:
|
||||
myVideo.pause()
|
||||
of Big: myVideo.width = 560
|
||||
of Small: myVideo.width = 320
|
||||
of Normal: myVideo.width = 420
|
||||
else: discard
|
||||
|
||||
result = buildHtml(tdiv):
|
||||
button(onclick=handler, key=Play):
|
||||
text "Play/Pause"
|
||||
button(onclick=handler, key=Big):
|
||||
text "Big"
|
||||
button(onclick=handler, key=Small):
|
||||
text "Small"
|
||||
button(onclick=handler, key=Normal):
|
||||
text "Normal"
|
||||
br()
|
||||
br()
|
||||
video(id=id, width="420"):
|
||||
source(src=resource, `type`="video/mp4"):
|
||||
text "Your browser does not support HTML5 video."
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Player app</title>
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
|
||||
<div id="ROOT" />
|
||||
|
||||
<script type="text/javascript" src="nimcache/playerapp.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
## Example app that shows how to write and embed a custom component.
|
||||
|
||||
include karaxprelude
|
||||
import mediaplayer
|
||||
|
||||
const url = "https://www.w3schools.com/html/mov_bbb.mp4"
|
||||
|
||||
proc createDom(): VNode =
|
||||
result = buildHtml(table):
|
||||
tr:
|
||||
td:
|
||||
mplayer("vid1", url)
|
||||
td:
|
||||
mplayer("vid2", url)
|
||||
tr:
|
||||
td:
|
||||
mplayer("vid3", url)
|
||||
td:
|
||||
mplayer("vid4", url)
|
||||
|
||||
setRenderer createDom
|
||||
|
|
@ -0,0 +1 @@
|
|||
--path: "../../src"
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Todo app</title>
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
|
||||
<div id="ROOT" />
|
||||
|
||||
<script type="text/javascript" src="nimcache/scrollapp.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
## Example that shows how to accomplish an "infinitely scrolling" app.
|
||||
|
||||
include karaxprelude
|
||||
import jstrutils, dom
|
||||
|
||||
var entries: seq[cstring] = @[]
|
||||
for i in 1..500:
|
||||
entries.add(cstring("Entry ") & &i)
|
||||
|
||||
proc scrollEvent(ev: Event; n: VNode) =
|
||||
let d = n.dom
|
||||
if d != nil and inViewport(d.lastChild):
|
||||
# "load" more data:
|
||||
for i in 1..50:
|
||||
entries.add(cstring("Loaded Entry ") & &i)
|
||||
|
||||
proc createDom(): VNode =
|
||||
result = buildHtml():
|
||||
tdiv(onscroll=scrollEvent, style="height: 400px; overflow: scroll"):
|
||||
for x in entries:
|
||||
tdiv:
|
||||
text x
|
||||
|
||||
setRenderer createDom
|
||||
|
|
@ -0,0 +1 @@
|
|||
--path: "../../src"
|
||||
16
bench/implementations/nim-karax/examples/todoapp/index.html
Normal file
16
bench/implementations/nim-karax/examples/todoapp/index.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Todo app</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
|
||||
<div id="ROOT" />
|
||||
|
||||
<script type="text/javascript" src="nimcache/todoapp.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
378
bench/implementations/nim-karax/examples/todoapp/style.css
Normal file
378
bench/implementations/nim-karax/examples/todoapp/style.css
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.todomvc-wrapper {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4em;
|
||||
background: #f5f5f5;
|
||||
color: #4d4d4d;
|
||||
min-width: 230px;
|
||||
max-width: 550px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
font-smoothing: antialiased;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="checkbox"] {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todoapp {
|
||||
background: #fff;
|
||||
margin: 130px 0 40px 0;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
||||
0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.todoapp input::-webkit-input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp input::-moz-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp input::input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp h1 {
|
||||
position: absolute;
|
||||
top: -155px;
|
||||
width: 100%;
|
||||
font-size: 100px;
|
||||
font-weight: 100;
|
||||
text-align: center;
|
||||
color: rgba(175, 47, 47, 0.15);
|
||||
-webkit-text-rendering: optimizeLegibility;
|
||||
-moz-text-rendering: optimizeLegibility;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.new-todo,
|
||||
.edit {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: 1.4em;
|
||||
border: 0;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
padding: 6px;
|
||||
border: 1px solid #999;
|
||||
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.new-todo {
|
||||
padding: 16px 16px 16px 60px;
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.003);
|
||||
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
label[for='toggle-all'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
position: absolute;
|
||||
top: -55px;
|
||||
left: -12px;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
text-align: center;
|
||||
border: none; /* Mobile Safari */
|
||||
}
|
||||
|
||||
.toggle-all:before {
|
||||
content: '>';
|
||||
font-size: 22px;
|
||||
color: #e6e6e6;
|
||||
padding: 10px 27px 10px 27px;
|
||||
}
|
||||
|
||||
.toggle-all:checked:before {
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.todo-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.todo-list li {
|
||||
position: relative;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.todo-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing {
|
||||
border-bottom: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.todo-list li.editing .edit {
|
||||
display: block;
|
||||
width: 506px;
|
||||
padding: 13px 17px 12px 17px;
|
||||
margin: 0 0 0 43px;
|
||||
}
|
||||
|
||||
.todo-list li.editing .view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
border: none; /* Mobile Safari */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:after {
|
||||
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
|
||||
}
|
||||
|
||||
.todo-list li .toggle:checked:after {
|
||||
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
|
||||
}
|
||||
|
||||
.todo-list li label {
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
padding: 15px 60px 15px 15px;
|
||||
margin-left: 45px;
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
transition: color 0.4s;
|
||||
}
|
||||
|
||||
.todo-list li.completed label {
|
||||
color: #d9d9d9;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.todo-list li .destroy {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: auto 0;
|
||||
font-size: 30px;
|
||||
color: #cc9a9a;
|
||||
margin-bottom: 11px;
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:hover {
|
||||
color: #af5b5e;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:after {
|
||||
content: 'x';
|
||||
}
|
||||
|
||||
.todo-list li:hover .destroy {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.todo-list li.editing:last-child {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: #777;
|
||||
padding: 10px 15px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.footer:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
|
||||
0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2),
|
||||
0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.todo-count {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.todo-count strong {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.filters li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.filters li a {
|
||||
color: inherit;
|
||||
margin: 3px;
|
||||
padding: 3px 7px;
|
||||
text-decoration: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.filters li a.selected,
|
||||
.filters li a:hover {
|
||||
border-color: rgba(175, 47, 47, 0.1);
|
||||
}
|
||||
|
||||
.filters li a.selected {
|
||||
border-color: rgba(175, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
.clear-completed,
|
||||
html .clear-completed:active {
|
||||
float: right;
|
||||
position: relative;
|
||||
line-height: 20px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.clear-completed:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin: 65px auto 0;
|
||||
color: #bfbfbf;
|
||||
/* font-size: 10px; */
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info p {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.info a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/*
|
||||
Hack to remove background from Mobile Safari.
|
||||
Can't use it globally since it destroys checkboxes in Firefox
|
||||
*/
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
.toggle-all,
|
||||
.todo-list li .toggle {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.footer {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
119
bench/implementations/nim-karax/examples/todoapp/todoapp.nim
Normal file
119
bench/implementations/nim-karax/examples/todoapp/todoapp.nim
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
|
||||
import vdom, karax, karaxdsl, jstrutils, components
|
||||
|
||||
type
|
||||
Filter = enum
|
||||
all, active, completed
|
||||
|
||||
var
|
||||
entries: seq[(cstring, bool)]
|
||||
selectedEntry = -1
|
||||
filter: Filter
|
||||
|
||||
proc onTodoEnter(ev: Event; n: VNode) =
|
||||
entries.add((n.value, false))
|
||||
n.value = ""
|
||||
|
||||
proc removeHandler(ev: Event; n: VNode) =
|
||||
entries[n.key] = (cstring(nil), false)
|
||||
|
||||
proc editHandler(ev: Event; n: VNode) =
|
||||
selectedEntry = n.key
|
||||
|
||||
proc focusLost(ev: Event; n: VNode) = selectedEntry = -1
|
||||
|
||||
proc editEntry(ev: Event; n: VNode) =
|
||||
entries[n.key][0] = n.value
|
||||
selectedEntry = -1
|
||||
|
||||
proc toggleEntry(ev: Event; n: VNode) =
|
||||
let id = n.key
|
||||
entries[id][1] = not entries[id][1]
|
||||
|
||||
proc onAllDone(ev: Event; n: VNode) =
|
||||
entries = @[]
|
||||
selectedEntry = -1
|
||||
|
||||
proc clearCompleted(ev: Event, n: VNode) =
|
||||
for i in 0..<entries.len:
|
||||
if entries[i][1]: entries[i][0] = nil
|
||||
|
||||
proc toClass(completed: bool): cstring =
|
||||
(if completed: cstring"completed" else: cstring(nil))
|
||||
|
||||
proc toChecked(checked: bool): cstring =
|
||||
(if checked: cstring"checked" else: cstring(nil))
|
||||
|
||||
proc selected(v: Filter): cstring =
|
||||
(if filter == v: cstring"selected" else: cstring(nil))
|
||||
|
||||
proc createEntry(id: int; d: cstring; completed, selected: bool): VNode {.component.} =
|
||||
result = buildHtml(tr):
|
||||
li(class=toClass(completed)):
|
||||
if not selected:
|
||||
tdiv(class = "view"):
|
||||
input(class = "toggle", `type` = "checkbox", checked = toChecked(completed),
|
||||
onclick=toggleEntry, key=id)
|
||||
label(onDblClick=editHandler, key=id):
|
||||
text d
|
||||
button(class = "destroy", key=id, onclick=removeHandler)
|
||||
else:
|
||||
input(class = "edit", name = "title", key=id,
|
||||
onblur = focusLost,
|
||||
onkeyupenter = editEntry, value = d, setFocus)
|
||||
|
||||
proc makeFooter(entriesCount, completedCount: int): VNode {.component.} =
|
||||
result = buildHtml(footer(class = "footer")):
|
||||
span(class = "todo-count"):
|
||||
strong:
|
||||
text(&entriesCount)
|
||||
text cstring" item" & &(if entriesCount != 1: "s left" else: " left")
|
||||
ul(class = "filters"):
|
||||
li:
|
||||
a(class = selected(all), href = "#/"):
|
||||
text "All"
|
||||
li:
|
||||
a(class = selected(active), href = "#/active"):
|
||||
text "Active"
|
||||
li:
|
||||
a(class = selected(completed), href = "#/completed"):
|
||||
text "Completed"
|
||||
button(class = "clear-completed", onclick = clearCompleted):
|
||||
text "Clear completed (" & &completedCount & ")"
|
||||
|
||||
proc makeHeader(): VNode {.component.} =
|
||||
result = buildHtml(header(class = "header")):
|
||||
h1:
|
||||
text "todos"
|
||||
input(class = "new-todo", placeholder="What needs to be done?", name = "newTodo",
|
||||
onkeyupenter = onTodoEnter, setFocus)
|
||||
|
||||
proc createDom(): VNode =
|
||||
result = buildHtml(tdiv(class="todomvc-wrapper")):
|
||||
section(class = "todoapp"):
|
||||
makeHeader()
|
||||
section(class = "main"):
|
||||
input(class = "toggle-all", `type` = "checkbox", name = "toggle")
|
||||
label(`for` = "toggle-all", onclick = onAllDone):
|
||||
text "Mark all as complete"
|
||||
var entriesCount = 0
|
||||
var completedCount = 0
|
||||
ul(class = "todo-list"):
|
||||
for i, d in pairs(entries):
|
||||
if d[0] != nil:
|
||||
let b = case filter
|
||||
of all: true
|
||||
of active: not d[1]
|
||||
of completed: d[1]
|
||||
if b:
|
||||
createEntry(i, d[0], d[1], i == selectedEntry)
|
||||
inc completedCount, ord(d[1])
|
||||
inc entriesCount
|
||||
makeFooter(entriesCount, completedCount)
|
||||
|
||||
setOnHashChange(proc(hash: cstring) =
|
||||
if hash == "#/": filter = all
|
||||
elif hash == "#/completed": filter = completed
|
||||
elif hash == "#/active": filter = active
|
||||
)
|
||||
setRenderer createDom
|
||||
|
|
@ -0,0 +1 @@
|
|||
--path: "../../src"
|
||||
13
bench/implementations/nim-karax/experiments/dyn.nim
Normal file
13
bench/implementations/nim-karax/experiments/dyn.nim
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
type
|
||||
EntryPoint = proc()
|
||||
|
||||
|
||||
proc dynmain =
|
||||
echo "dynamically loaded"
|
||||
|
||||
|
||||
var plugins {.importc.}: seq[(string, EntryPoint)]
|
||||
|
||||
plugins.add(("dyn", EntryPoint dynmain))
|
||||
15
bench/implementations/nim-karax/experiments/example.html
Normal file
15
bench/implementations/nim-karax/experiments/example.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Todo app</title>
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
|
||||
<div id="ROOT" />
|
||||
|
||||
<script type="text/javascript" src="nimcache/example.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
32
bench/implementations/nim-karax/experiments/example.nim
Normal file
32
bench/implementations/nim-karax/experiments/example.nim
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
import vdom, components, karax, karaxdsl, jdict, jstrutils
|
||||
|
||||
var
|
||||
images: seq[cstring] = @[cstring"a", "b", "c", "d"]
|
||||
|
||||
proc carousel*(key: VKey): VNode {.component.} =
|
||||
state:
|
||||
var counter: int = 0
|
||||
|
||||
proc next(ev: Event; n: VNode) =
|
||||
counter = (counter + 1) mod images.len
|
||||
|
||||
result = buildHtml(tdiv(key=key)):
|
||||
text images[counter]
|
||||
button(onclick = next):
|
||||
text "Next"
|
||||
|
||||
proc createDom(): VNode =
|
||||
result = buildHtml(table):
|
||||
tr:
|
||||
td:
|
||||
carousel(0)
|
||||
td:
|
||||
carousel(1)
|
||||
tr:
|
||||
td:
|
||||
carousel(2)
|
||||
td:
|
||||
carousel(3)
|
||||
|
||||
setRenderer createDom
|
||||
1
bench/implementations/nim-karax/experiments/example.nims
Normal file
1
bench/implementations/nim-karax/experiments/example.nims
Normal file
|
|
@ -0,0 +1 @@
|
|||
--path: "../src"
|
||||
251
bench/implementations/nim-karax/experiments/oldwidgets.nim
Normal file
251
bench/implementations/nim-karax/experiments/oldwidgets.nim
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
var
|
||||
linkCounter: int
|
||||
|
||||
proc link*(id: int): VNode =
|
||||
result = newVNode(VNodeKind.anchor)
|
||||
result.setAttr("href", "#")
|
||||
inc linkCounter
|
||||
result.setAttr("id", $linkCounter & ":" & $id)
|
||||
|
||||
proc link*(action: EventHandler): VNode =
|
||||
result = newVNode(VNodeKind.anchor)
|
||||
result.setAttr("href", "#")
|
||||
result.setOnclick action
|
||||
|
||||
when false:
|
||||
proc button*(caption: cstring; action: EventHandler; disabled=false): VNode =
|
||||
result = newVNode(VNodeKind.button)
|
||||
result.add text(caption)
|
||||
if action != nil:
|
||||
result.setOnClick action
|
||||
if disabled:
|
||||
result.setAttr("disabled", "true")
|
||||
|
||||
proc select*(choices: openarray[cstring]): VNode =
|
||||
result = newVNode(VNodeKind.select)
|
||||
var i = 0
|
||||
for c in choices:
|
||||
result.add tree(VNodeKind.option, [(cstring"value", toCstr(i))], text(c))
|
||||
inc i
|
||||
|
||||
proc select*(choices: openarray[(int, cstring)]): VNode =
|
||||
result = newVNode(VNodeKind.select)
|
||||
for c in choices:
|
||||
result.add tree(VNodeKind.option, [(cstring"value", toCstr(c[0]))], text(c[1]))
|
||||
|
||||
var radioCounter: int
|
||||
|
||||
proc radio*(choices: openarray[(int, cstring)]): VNode =
|
||||
result = newVNode(VNodeKind.fieldset)
|
||||
var i = 0
|
||||
inc radioCounter
|
||||
for c in choices:
|
||||
let id = cstring"radio_" & c[1] & toCstr(i)
|
||||
var kid = tree(VNodeKind.input, [(cstring"type", cstring"radio"),
|
||||
(cstring"id", id), (cstring"name", cstring"radio" & toCStr(radioCounter)),
|
||||
(cstring"value", toCStr(c[0]))])
|
||||
if i == 0:
|
||||
kid.setAttr(cstring"checked", cstring"checked")
|
||||
var lab = tree(VNodeKind.label, [(cstring"for", id)], text(c[1]))
|
||||
kid.add lab
|
||||
result.add kid
|
||||
inc i
|
||||
|
||||
proc tag*(kind: VNodeKind; id=cstring(nil), class=cstring(nil)): VNode =
|
||||
result = newVNode(kind)
|
||||
result.id = id
|
||||
result.class = class
|
||||
|
||||
proc tdiv*(id=cstring(nil), class=cstring(nil)): VNode = tag(VNodeKind.tdiv, id, class)
|
||||
proc span*(id=cstring(nil), class=cstring(nil)): VNode = tag(VNodeKind.span, id, class)
|
||||
|
||||
proc valueAsInt*(e: Node): int = parseInt(e.value)
|
||||
|
||||
proc th*(s: cstring): VNode =
|
||||
result = newVNode(VNodeKind.th)
|
||||
result.add text(s)
|
||||
|
||||
proc td*(s: string): VNode =
|
||||
result = newVNode(VNodeKind.td)
|
||||
result.add text(s)
|
||||
|
||||
proc td*(s: VNode): VNode =
|
||||
result = newVNode(VNodeKind.td)
|
||||
result.add s
|
||||
|
||||
proc td*(class: cstring; s: VNode): VNode =
|
||||
result = newVNode(VNodeKind.td)
|
||||
result.add s
|
||||
result.class = class
|
||||
|
||||
proc table*(class=cstring(nil), kids: varargs[VNode]): VNode =
|
||||
result = tag(VNodeKind.table, nil, class)
|
||||
for k in kids: result.add k
|
||||
|
||||
proc tr*(kids: varargs[VNode]): VNode =
|
||||
result = newVNode(VNodeKind.tr)
|
||||
for k in kids:
|
||||
if k.kind in {VNodeKind.td, VNodeKind.th}:
|
||||
result.add k
|
||||
else:
|
||||
result.add td(k)
|
||||
|
||||
proc suffix*(s, prefix: cstring): cstring =
|
||||
if s.startsWith(prefix):
|
||||
result = s.substr(prefix.len)
|
||||
else:
|
||||
kout(cstring"bug! " & s & cstring" does not start with " & prefix)
|
||||
|
||||
proc suffixAsInt*(s, prefix: cstring): int = parseInt(suffix(s, prefix))
|
||||
|
||||
#proc ceil(f: float): int {.importc: "Math.ceil", nodecl.}
|
||||
|
||||
proc realtimeInput*(val: cstring; action: EventHandler): VNode =
|
||||
var timer: Timeout
|
||||
proc onkeyup(ev: Event; n: VNode) =
|
||||
proc wrapper() = keyeventBody()
|
||||
|
||||
if timer != nil: clearTimeout(timer)
|
||||
timer = setTimeout(wrapper, 400)
|
||||
result = tree(VNodeKind.input, [(cstring"type", cstring"text")])
|
||||
result.value = val
|
||||
result.addEventListener(EventKind.onkeyup, onkeyup)
|
||||
|
||||
proc enterInput*(id, val: cstring; action: EventHandler): VNode =
|
||||
proc onkeyup(ev: Event; n: VNode) =
|
||||
if ev.keyCode == 13: keyeventBody()
|
||||
|
||||
result = tree(VNodeKind.input, [(cstring"type", cstring"text")])
|
||||
result.id = id
|
||||
result.value = val
|
||||
result.addEventListener(EventKind.onkeyup, onkeyup)
|
||||
|
||||
proc setOnEnter*(n: VNode; action: EventHandler) =
|
||||
proc onkeyup(ev: Event; n: VNode) =
|
||||
if ev.keyCode == 13: keyeventBody()
|
||||
n.addEventListener(EventKind.onkeyup, onkeyup)
|
||||
|
||||
proc setOnscroll*(action: proc(min, max: VKey; diff: int)) =
|
||||
var oldY = window.pageYOffset
|
||||
|
||||
proc wrapper(ev: Event) =
|
||||
let dir = window.pageYOffset - oldY
|
||||
if dir == 0: return
|
||||
|
||||
var a = VKey high(int)
|
||||
var b = VKey 0
|
||||
var h, count: int
|
||||
document.visibleKeys(a, b, h, count)
|
||||
let avgh = h / count
|
||||
let diff = toInt(dir.float / avgh)
|
||||
if diff != 0:
|
||||
oldY = window.pageYOffset
|
||||
action(a, b, diff)
|
||||
redraw()
|
||||
|
||||
document.addEventListener("scroll", wrapper)
|
||||
|
||||
when false:
|
||||
var plugins {.exportc.}: seq[(string, proc())] = @[]
|
||||
|
||||
proc onInput(val: cstring) =
|
||||
kout val
|
||||
if val == "dyn":
|
||||
kout(plugins.len)
|
||||
if plugins.len > 0:
|
||||
plugins[0][1]()
|
||||
|
||||
var
|
||||
images: seq[cstring] = @[cstring"a", "b", "c", "d"]
|
||||
|
||||
proc carousel*(): VNode =
|
||||
var currentIndex = 0
|
||||
|
||||
proc next(ev: Event; n: VNode) =
|
||||
currentIndex = (currentIndex + 1) mod images.len
|
||||
|
||||
proc prev(ev: Event; n: VNode) =
|
||||
currentIndex = (currentIndex - 1) mod images.len
|
||||
|
||||
result = buildHtml(tdiv):
|
||||
text images[currentIndex]
|
||||
button(onclick = next):
|
||||
text "Next"
|
||||
button(onclick = prev):
|
||||
text "Previous"
|
||||
|
||||
#proc targetElem*(e: Event): Element = cast[Element](e.target)
|
||||
|
||||
#proc getElementsByClassName*(cls: cstring): seq[Element] {.importc:
|
||||
# "document.getElementsByClassName", nodecl.}
|
||||
#proc textContent(e: Node): cstring {.
|
||||
# importcpp: "#.textContent", nodecl.}
|
||||
|
||||
proc isElementInViewport(el: Node; h: var int): bool =
|
||||
let rect = el.getBoundingClientRect()
|
||||
h = rect.bottom - rect.top
|
||||
result = rect.top >= 0 and rect.left >= 0 and
|
||||
rect.bottom <= clientHeight() and
|
||||
rect.right <= clientWidth()
|
||||
|
||||
proc visibleKeys(e: Node; a, b: var VKey; h, count: var int) =
|
||||
# we only care about nodes that have a key:
|
||||
var hh = 0
|
||||
# do not recurse if there is a 'key' field already:
|
||||
if e.key >= 0:
|
||||
if isElementInViewport(e, hh):
|
||||
inc count
|
||||
inc h, hh
|
||||
a = min(a, e.key)
|
||||
b = max(b, e.key)
|
||||
else:
|
||||
for i in 0..<e.len:
|
||||
visibleKeys(e[i], a, b, h, count)
|
||||
|
||||
template toState(x): untyped = "state" & x
|
||||
proc accessState(sv: string): NimNode {.compileTime.} =
|
||||
newTree(nnkBracketExpr, newIdentNode(sv), newIdentNode("key"))
|
||||
|
||||
proc stateDecl(n: NimNode; names: TableRef[string, bool]; decl, init: NimNode) =
|
||||
case n.kind
|
||||
of nnkVarSection, nnkLetSection:
|
||||
for c in n:
|
||||
expectKind c, nnkIdentDefs
|
||||
let typ = c[^2]
|
||||
let val = c[^1]
|
||||
let usedType = if typ.kind != nnkEmpty: typ else: val
|
||||
if usedType.kind == nnkEmpty:
|
||||
error("cannot determine the variable's type", c)
|
||||
for i in 0 .. c.len-3:
|
||||
let v = $c[i]
|
||||
let sv = toState v
|
||||
decl.add quote do:
|
||||
var `sv` = newJDict[VKey, `usedType`]()
|
||||
if val.kind != nnkEmpty:
|
||||
init.add newTree(nnkAsgn, accessState(sv), val)
|
||||
names[v] = true
|
||||
of nnkStmtList, nnkStmtListExpr:
|
||||
for x in n: stateDecl(x, names, decl, init)
|
||||
of nnkDo:
|
||||
stateDecl(n.body, names, decl, init)
|
||||
of nnkCommentStmt: discard
|
||||
else:
|
||||
error("invalid 'state' declaration", n)
|
||||
|
||||
proc doState(n: NimNode; names: TableRef[string, bool];
|
||||
decl, init: NimNode): NimNode =
|
||||
result = n
|
||||
case n.kind
|
||||
of nnkCallKinds:
|
||||
# handle 'state' declaration and remove it from the AST:
|
||||
if n.len == 2 and repr(n[0]) == "state":
|
||||
stateDecl(n[1], names, decl, init)
|
||||
result = newTree(nnkEmpty)
|
||||
of nnkSym, nnkIdent:
|
||||
let v = $n
|
||||
if v in names:
|
||||
let sv = toState v
|
||||
result = accessState(sv)
|
||||
else:
|
||||
for i in 0..<n.len:
|
||||
result[i] = doState(n[i], names, decl, init)
|
||||
19
bench/implementations/nim-karax/experiments/scrollapp.html
Normal file
19
bench/implementations/nim-karax/experiments/scrollapp.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Todo app</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
|
||||
<div id="ROOT" />
|
||||
|
||||
<script type="text/javascript" src="nimcache/scrollapp.js"></script>
|
||||
<script type="text/javascript">
|
||||
onload(window.location.pathname.split('/')[1])
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
142
bench/implementations/nim-karax/experiments/scrollapp.nim
Normal file
142
bench/implementations/nim-karax/experiments/scrollapp.nim
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
|
||||
import vdom, karax, karaxdsl, jdict, jstrutils, components
|
||||
|
||||
type
|
||||
Filter = enum
|
||||
all, active, completed
|
||||
|
||||
var
|
||||
entries: seq[(cstring, bool)]
|
||||
visibleA = 0
|
||||
visibleB = 10
|
||||
selectedEntry = -1
|
||||
filter: Filter
|
||||
|
||||
proc onTodoEnter(ev: Event; n: VNode) =
|
||||
entries.add((n.value, false))
|
||||
n.value = ""
|
||||
|
||||
proc removeHandler(ev: Event; n: VNode) =
|
||||
entries[n.key] = (cstring(nil), false)
|
||||
|
||||
proc editHandler(ev: Event; n: VNode) =
|
||||
selectedEntry = n.key
|
||||
|
||||
proc focusLost(ev: Event; n: VNode) = selectedEntry = -1
|
||||
|
||||
proc editEntry(ev: Event; n: VNode) =
|
||||
entries[n.key][0] = n.value
|
||||
selectedEntry = -1
|
||||
|
||||
proc toggleEntry(ev: Event; n: VNode) =
|
||||
let id = n.key
|
||||
entries[id][1] = not entries[id][1]
|
||||
|
||||
proc onAllDone(ev: Event; n: VNode) =
|
||||
entries = @[]
|
||||
selectedEntry = -1
|
||||
|
||||
proc clearCompleted(ev: Event, n: VNode) =
|
||||
for i in 0..<entries.len:
|
||||
if entries[i][1]: entries[i][0] = nil
|
||||
|
||||
proc toClass(completed: bool): cstring =
|
||||
(if completed: cstring"completed" else: cstring(nil))
|
||||
|
||||
proc toChecked(checked: bool): cstring =
|
||||
(if checked: cstring"checked" else: cstring(nil))
|
||||
|
||||
proc selected(v: Filter): cstring =
|
||||
(if filter == v: cstring"selected" else: cstring(nil))
|
||||
|
||||
proc createEntry(id: int; d: cstring;
|
||||
completed: bool, selected: bool): VNode {.component.} =
|
||||
result = buildHtml(tr):
|
||||
li(class=toClass(completed), key = id):
|
||||
if not selected:
|
||||
tdiv(class = "view"):
|
||||
input(class = "toggle", `type` = "checkbox", checked = toChecked(completed),
|
||||
onclick=toggleEntry, key = id)
|
||||
label(onclick=editHandler, key = id):
|
||||
text d
|
||||
button(class = "destroy", key = id, onclick = removeHandler)
|
||||
else:
|
||||
input(class = "edit", name = "title", key = id,
|
||||
onfocusLost = focusLost,
|
||||
onenter = editEntry, value = d, setFocus)
|
||||
|
||||
proc myscroll(ev: Event; n: VNode) =
|
||||
kout cstring"scrolling event here"
|
||||
|
||||
proc createDom(): VNode =
|
||||
result = buildHtml(tdiv(class="todomvc-wrapper")):
|
||||
section(class = "todoapp"):
|
||||
header(class = "header"):
|
||||
h1:
|
||||
text "todos"
|
||||
input(class = "new-todo", placeholder="What needs to be done?", name = "newTodo",
|
||||
onenter = onTodoEnter, setFocus)
|
||||
section(class = "main"):
|
||||
input(class = "toggle-all", `type` = "checkbox", name = "toggle")
|
||||
label(`for` = "toggle-all", onclick = onAllDone):
|
||||
text "Mark all as complete"
|
||||
|
||||
var entriesCount = 0
|
||||
var completedCount = 0
|
||||
ul(class = "todo-list"):
|
||||
for i in visibleA..visibleB:
|
||||
let d = entries[i]
|
||||
if d[0] != nil:
|
||||
let b = case filter
|
||||
of all: true
|
||||
of active: not d[1]
|
||||
of completed: d[1]
|
||||
if b:
|
||||
createEntry(i, d[0], d[1], i == selectedEntry)
|
||||
inc completedCount, ord(d[1])
|
||||
inc entriesCount
|
||||
footer(class = "footer"):
|
||||
span(class = "todo-count"):
|
||||
strong:
|
||||
text(&entriesCount)
|
||||
text cstring" item" & &(if entriesCount != 1: "s left" else: " left")
|
||||
ul(class = "filters"):
|
||||
li:
|
||||
anchor(class = selected(all), href = "#/"):
|
||||
text "All"
|
||||
li:
|
||||
anchor(class = selected(active), href = "#/active"):
|
||||
text "Active"
|
||||
li:
|
||||
anchor(class = selected(completed), href = "#/completed"):
|
||||
text "Completed"
|
||||
button(class = "clear-completed", onclick = clearCompleted):
|
||||
text "Clear completed (" & &completedCount & ")"
|
||||
|
||||
|
||||
when false:
|
||||
setOnscroll(proc(a, b: VKey; diff: int) =
|
||||
const
|
||||
overshoot1 = 50
|
||||
overshoot2 = 5
|
||||
if diff > 0:
|
||||
# scroll downwards:
|
||||
visibleA = max(a-overshoot2, 0)
|
||||
visibleB = min(b+diff+overshoot1, entries.len-1)
|
||||
elif diff < 0:
|
||||
# scroll upwards:
|
||||
visibleA = max(a+diff-overshoot1, 0)
|
||||
visibleB = min(b+overshoot2, entries.len-1)
|
||||
)
|
||||
|
||||
setOnHashChange(proc(hash: cstring) =
|
||||
if hash == cstring"#/": filter = all
|
||||
elif hash == cstring"#/completed": filter = completed
|
||||
elif hash == cstring"#/active": filter = active
|
||||
)
|
||||
setRenderer createDom
|
||||
|
||||
proc onload(session: cstring) {.exportc.} =
|
||||
for i in 0..visibleB:
|
||||
entries.add((cstring"Entry " & &i, false))
|
||||
init()
|
||||
|
|
@ -0,0 +1 @@
|
|||
--path: "../src"
|
||||
378
bench/implementations/nim-karax/experiments/style.css
Normal file
378
bench/implementations/nim-karax/experiments/style.css
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.todomvc-wrapper {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4em;
|
||||
background: #f5f5f5;
|
||||
color: #4d4d4d;
|
||||
min-width: 230px;
|
||||
max-width: 550px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
font-smoothing: antialiased;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="checkbox"] {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todoapp {
|
||||
background: #fff;
|
||||
margin: 130px 0 40px 0;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
||||
0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.todoapp input::-webkit-input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp input::-moz-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp input::input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
.todoapp h1 {
|
||||
position: absolute;
|
||||
top: -155px;
|
||||
width: 100%;
|
||||
font-size: 100px;
|
||||
font-weight: 100;
|
||||
text-align: center;
|
||||
color: rgba(175, 47, 47, 0.15);
|
||||
-webkit-text-rendering: optimizeLegibility;
|
||||
-moz-text-rendering: optimizeLegibility;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.new-todo,
|
||||
.edit {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: 1.4em;
|
||||
border: 0;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
padding: 6px;
|
||||
border: 1px solid #999;
|
||||
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.new-todo {
|
||||
padding: 16px 16px 16px 60px;
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.003);
|
||||
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
label[for='toggle-all'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
position: absolute;
|
||||
top: -55px;
|
||||
left: -12px;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
text-align: center;
|
||||
border: none; /* Mobile Safari */
|
||||
}
|
||||
|
||||
.toggle-all:before {
|
||||
content: '>';
|
||||
font-size: 22px;
|
||||
color: #e6e6e6;
|
||||
padding: 10px 27px 10px 27px;
|
||||
}
|
||||
|
||||
.toggle-all:checked:before {
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.todo-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.todo-list li {
|
||||
position: relative;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.todo-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing {
|
||||
border-bottom: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.todo-list li.editing .edit {
|
||||
display: block;
|
||||
width: 506px;
|
||||
padding: 13px 17px 12px 17px;
|
||||
margin: 0 0 0 43px;
|
||||
}
|
||||
|
||||
.todo-list li.editing .view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
border: none; /* Mobile Safari */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:after {
|
||||
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
|
||||
}
|
||||
|
||||
.todo-list li .toggle:checked:after {
|
||||
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
|
||||
}
|
||||
|
||||
.todo-list li label {
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
padding: 15px 60px 15px 15px;
|
||||
margin-left: 45px;
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
transition: color 0.4s;
|
||||
}
|
||||
|
||||
.todo-list li.completed label {
|
||||
color: #d9d9d9;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.todo-list li .destroy {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: auto 0;
|
||||
font-size: 30px;
|
||||
color: #cc9a9a;
|
||||
margin-bottom: 11px;
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:hover {
|
||||
color: #af5b5e;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:after {
|
||||
content: 'x';
|
||||
}
|
||||
|
||||
.todo-list li:hover .destroy {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.todo-list li.editing:last-child {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: #777;
|
||||
padding: 10px 15px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.footer:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
|
||||
0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2),
|
||||
0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.todo-count {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.todo-count strong {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.filters li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.filters li a {
|
||||
color: inherit;
|
||||
margin: 3px;
|
||||
padding: 3px 7px;
|
||||
text-decoration: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.filters li a.selected,
|
||||
.filters li a:hover {
|
||||
border-color: rgba(175, 47, 47, 0.1);
|
||||
}
|
||||
|
||||
.filters li a.selected {
|
||||
border-color: rgba(175, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
.clear-completed,
|
||||
html .clear-completed:active {
|
||||
float: right;
|
||||
position: relative;
|
||||
line-height: 20px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.clear-completed:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin: 65px auto 0;
|
||||
color: #bfbfbf;
|
||||
/* font-size: 10px; */
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info p {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.info a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/*
|
||||
Hack to remove background from Mobile Safari.
|
||||
Can't use it globally since it destroys checkboxes in Firefox
|
||||
*/
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
.toggle-all,
|
||||
.todo-list li .toggle {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.footer {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
12
bench/implementations/nim-karax/karax.nimble
Normal file
12
bench/implementations/nim-karax/karax.nimble
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Package
|
||||
|
||||
version = "0.1.0"
|
||||
author = "Araq"
|
||||
description = "Karax is a framework for developing single page applications in Nim."
|
||||
license = "MIT"
|
||||
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 0.16.1"
|
||||
|
||||
skipDirs = @["examples/todoapp"]
|
||||
38
bench/implementations/nim-karax/readme.rst
Normal file
38
bench/implementations/nim-karax/readme.rst
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
Karax -- Single page applications in Nim
|
||||
========================================
|
||||
|
||||
Karax is a framework for developing single page applications in Nim.
|
||||
It's still in heavy development, so keep in mind that the API is subject
|
||||
to change.
|
||||
|
||||
To try it out, run::
|
||||
|
||||
nimble install # For first time
|
||||
|
||||
cd examples/todoapp
|
||||
nim js todoapp.nim
|
||||
open todoapp.html
|
||||
cd ../..
|
||||
|
||||
cd examples/mediaplayer
|
||||
nim js playerapp.nim
|
||||
open playerapp.html
|
||||
|
||||
It uses a virtual DOM like React, but is much smaller than the existing
|
||||
frameworks plus of course it's written in Nim for Nim. No external
|
||||
dependencies! And thanks to Nim's whole program optimization only what
|
||||
is used ends up in the generated JavaScript code.
|
||||
|
||||
|
||||
Goals
|
||||
=====
|
||||
|
||||
- Leverage Nim's macro system to produce a framework that allows
|
||||
for the development of applications that are boilerplate free.
|
||||
- Keep it small, keep it fast, keep it flexible.
|
||||
|
||||
|
||||
Todo
|
||||
====
|
||||
|
||||
- Expose a nice API to do Ajax requests.
|
||||
225
bench/implementations/nim-karax/src/components.nim
Normal file
225
bench/implementations/nim-karax/src/components.nim
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
## Components in Karax are built by the ``.component`` macro annotation.
|
||||
|
||||
import macros, jdict, dom, vdom, tables, strutils
|
||||
|
||||
type
|
||||
StateDict*[V] = ref object
|
||||
defaultValue*: V
|
||||
|
||||
proc get[V](d: StateDict[V], k: VKey): V {.importcpp: "#[#]".}
|
||||
proc put[V](d: StateDict[V], k: VKey, v: V) {.importcpp: "#[#] = #".}
|
||||
|
||||
proc contains*[V](d: StateDict[V], k: VKey): bool {.importcpp: "#.hasOwnProperty(#)".}
|
||||
proc del*[V](d: StateDict[V], k: VKey) {.importcpp: "delete #[#]".}
|
||||
|
||||
proc newStateDict*[V](): StateDict[V] {.importcpp: "{@}".}
|
||||
|
||||
var
|
||||
dirty = newStateDict[bool]()
|
||||
someDirty*: bool
|
||||
|
||||
proc markDirty*(key: VKey) =
|
||||
dirty.put(key, true)
|
||||
someDirty = true
|
||||
|
||||
proc unmarkDirty*(key: VKey) = dirty.del key
|
||||
proc isDirty*(key: VKey): bool = dirty.contains(key)
|
||||
|
||||
proc `[]`*[V](d: StateDict[V], k: VKey): V =
|
||||
if d.contains(k): result = d.get(k)
|
||||
else: result = d.defaultValue
|
||||
|
||||
proc `[]=`*[V](d: StateDict[V], k: VKey, v: V) =
|
||||
d.put(k, v)
|
||||
markDirty(k)
|
||||
|
||||
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()
|
||||
|
||||
|
||||
template toState(x): untyped = newIdentNode("state" & x)
|
||||
proc accessState(sv: NimNode): NimNode {.compileTime.} =
|
||||
newTree(nnkBracketExpr, sv, newIdentNode("key"))
|
||||
|
||||
proc stateDecl(n: NimNode; names: TableRef[string, bool]; decl: NimNode) =
|
||||
case n.kind
|
||||
of nnkVarSection, nnkLetSection:
|
||||
for c in n:
|
||||
expectKind c, nnkIdentDefs
|
||||
let typ = c[^2]
|
||||
let val = c[^1]
|
||||
let usedType = if typ.kind != nnkEmpty: typ else: newCall("type", val)
|
||||
if usedType.kind == nnkEmpty:
|
||||
error("cannot determine the variable's type", c)
|
||||
for i in 0 .. c.len-3:
|
||||
let v = $c[i]
|
||||
let sv = toState v
|
||||
decl.add quote do:
|
||||
var `sv` = newStateDict[`usedType`]()
|
||||
if val.kind != nnkEmpty:
|
||||
decl.add newTree(nnkAsgn, newDotExpr(sv, newIdentNode"defaultValue"),
|
||||
val)
|
||||
else:
|
||||
error("component state must have a primitive initializer")
|
||||
names[v] = true
|
||||
of nnkStmtList, nnkStmtListExpr:
|
||||
for x in n: stateDecl(x, names, decl)
|
||||
of nnkDo:
|
||||
stateDecl(n.body, names, decl)
|
||||
of nnkCommentStmt: discard
|
||||
else:
|
||||
error("invalid 'state' declaration", n)
|
||||
|
||||
proc doState(n: NimNode; names: TableRef[string, bool];
|
||||
decl: NimNode): NimNode =
|
||||
case n.kind
|
||||
of nnkCallKinds:
|
||||
# handle 'state' declaration and remove it from the AST:
|
||||
if n.len == 2 and repr(n[0]) == "state":
|
||||
stateDecl(n[1], names, decl)
|
||||
return nil #newTree(nnkEmpty)
|
||||
of nnkSym, nnkIdent:
|
||||
let v = $n
|
||||
if v in names:
|
||||
let sv = toState v
|
||||
return accessState(sv)
|
||||
else: discard
|
||||
result = copyNimNode(n)
|
||||
for i in 0..<n.len:
|
||||
let x = doState(n[i], names, decl)
|
||||
if x != nil: result.add x
|
||||
|
||||
proc compBody(body, decl: NimNode): NimNode =
|
||||
var names = newTable[string, bool]()
|
||||
result = doState(body, names, decl)
|
||||
|
||||
proc unpack(symbolicType: NimNode; index: int): NimNode {.compileTime.} =
|
||||
#let t = symbolicType.getTypeImpl
|
||||
let t = repr(symbolicType)
|
||||
case t
|
||||
of "cstring":
|
||||
result = quote do:
|
||||
args[`index`].text
|
||||
of "int", "VKey":
|
||||
result = quote do:
|
||||
args[`index`].intValue
|
||||
of "bool":
|
||||
result = quote do:
|
||||
args[`index`].intValue != 0
|
||||
elif t.endsWith"Kind":
|
||||
result = quote do:
|
||||
`symbolicType`(args[`index`].intValue)
|
||||
else:
|
||||
# just pass it along, maybe there is some conversion for it:
|
||||
result = quote do:
|
||||
args[`index`]
|
||||
|
||||
proc newname*(n: NimNode): NimNode =
|
||||
if n.kind == nnkPostfix:
|
||||
n[1] = newname(n[1])
|
||||
result = n
|
||||
elif n.kind == nnkSym:
|
||||
result = ident($n.symbol)
|
||||
else:
|
||||
result = n
|
||||
|
||||
macro component*(prc: untyped): untyped =
|
||||
## A component takes an proc body and registers it as a component to the
|
||||
## virtual dom.
|
||||
var n = prc.copyNimNode
|
||||
for i in 0..6: n.add prc[i].copyNimTree
|
||||
expectKind(n, nnkProcDef)
|
||||
if n[0].kind == nnkEmpty:
|
||||
error("please pass a non anonymous proc", 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
|
||||
|
||||
let decl = newTree(nnkStmtList)
|
||||
let newBody = compBody(n, decl)
|
||||
|
||||
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, decl, newBody)
|
||||
|
||||
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
|
||||
|
||||
when isMainModule:
|
||||
proc public*(key: VKey; x, y: int, b: bool; s: cstring): VNode {.component.} =
|
||||
state:
|
||||
var foo = 89
|
||||
|
||||
proc callback() =
|
||||
foo = 78
|
||||
|
||||
let cc = callback
|
||||
|
||||
when false:
|
||||
proc private(x, y: int, b: bool; s: cstring): VNode {.component.} =
|
||||
discard
|
||||
586
bench/implementations/nim-karax/src/dom.nim
Normal file
586
bench/implementations/nim-karax/src/dom.nim
Normal file
|
|
@ -0,0 +1,586 @@
|
|||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2012 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Declaration of the Document Object Model for the `JavaScript backend
|
||||
## <backends.html#the-javascript-target>`_.
|
||||
|
||||
when not defined(js) and not defined(Nimdoc):
|
||||
{.error: "This module only works on the JavaScript platform".}
|
||||
|
||||
type
|
||||
EventTarget* = ref EventTargetObj
|
||||
EventTargetObj {.importc.} = object of RootObj
|
||||
onabort*: proc (event: Event) {.nimcall.}
|
||||
onblur*: proc (event: Event) {.nimcall.}
|
||||
onchange*: proc (event: Event) {.nimcall.}
|
||||
onclick*: proc (event: Event) {.nimcall.}
|
||||
ondblclick*: proc (event: Event) {.nimcall.}
|
||||
onerror*: proc (event: Event) {.nimcall.}
|
||||
onfocus*: proc (event: Event) {.nimcall.}
|
||||
onkeydown*: proc (event: Event) {.nimcall.}
|
||||
onkeypress*: proc (event: Event) {.nimcall.}
|
||||
onkeyup*: proc (event: Event) {.nimcall.}
|
||||
onload*: proc (event: Event) {.nimcall.}
|
||||
onmousedown*: proc (event: Event) {.nimcall.}
|
||||
onmousemove*: proc (event: Event) {.nimcall.}
|
||||
onmouseout*: proc (event: Event) {.nimcall.}
|
||||
onmouseover*: proc (event: Event) {.nimcall.}
|
||||
onmouseup*: proc (event: Event) {.nimcall.}
|
||||
onreset*: proc (event: Event) {.nimcall.}
|
||||
onselect*: proc (event: Event) {.nimcall.}
|
||||
onsubmit*: proc (event: Event) {.nimcall.}
|
||||
onunload*: proc (event: Event) {.nimcall.}
|
||||
|
||||
Window* = ref WindowObj
|
||||
WindowObj {.importc.} = object of EventTargetObj
|
||||
document*: Document
|
||||
event*: Event
|
||||
history*: History
|
||||
location*: Location
|
||||
closed*: bool
|
||||
defaultStatus*: cstring
|
||||
innerHeight*, innerWidth*: int
|
||||
locationbar*: ref LocationBar
|
||||
menubar*: ref MenuBar
|
||||
name*: cstring
|
||||
outerHeight*, outerWidth*: int
|
||||
pageXOffset*, pageYOffset*: int
|
||||
personalbar*: ref PersonalBar
|
||||
scrollbars*: ref ScrollBars
|
||||
statusbar*: ref StatusBar
|
||||
status*: cstring
|
||||
toolbar*: ref ToolBar
|
||||
frames*: seq[Frame]
|
||||
|
||||
Frame* = ref FrameObj
|
||||
FrameObj {.importc.} = object of WindowObj
|
||||
|
||||
ClassList* = ref ClassListObj
|
||||
ClassListObj {.importc.} = object of RootObj
|
||||
|
||||
NodeType* = enum
|
||||
ElementNode = 1,
|
||||
AttributeNode,
|
||||
TextNode,
|
||||
CDATANode,
|
||||
EntityRefNode,
|
||||
EntityNode,
|
||||
ProcessingInstructionNode,
|
||||
CommentNode,
|
||||
DocumentNode,
|
||||
DocumentTypeNode,
|
||||
DocumentFragmentNode,
|
||||
NotationNode
|
||||
|
||||
Node* = ref NodeObj
|
||||
NodeObj {.importc.} = object of EventTargetObj
|
||||
attributes*: seq[Node]
|
||||
childNodes*: seq[Node]
|
||||
children*: seq[Node]
|
||||
data*: cstring
|
||||
firstChild*: Node
|
||||
lastChild*: Node
|
||||
nextSibling*: Node
|
||||
nodeName*: cstring
|
||||
nodeType*: NodeType
|
||||
nodeValue*: cstring
|
||||
parentNode*: Node
|
||||
previousSibling*: Node
|
||||
innerHTML*: cstring
|
||||
style*: Style
|
||||
|
||||
Document* = ref DocumentObj
|
||||
DocumentObj {.importc.} = object of NodeObj
|
||||
alinkColor*: cstring
|
||||
bgColor*: cstring
|
||||
body*: Element
|
||||
charset*: cstring
|
||||
cookie*: cstring
|
||||
defaultCharset*: cstring
|
||||
fgColor*: cstring
|
||||
head*: Element
|
||||
lastModified*: cstring
|
||||
linkColor*: cstring
|
||||
referrer*: cstring
|
||||
title*: cstring
|
||||
URL*: cstring
|
||||
vlinkColor*: cstring
|
||||
anchors*: seq[AnchorElement]
|
||||
forms*: seq[FormElement]
|
||||
images*: seq[ImageElement]
|
||||
applets*: seq[Element]
|
||||
embeds*: seq[EmbedElement]
|
||||
links*: seq[LinkElement]
|
||||
|
||||
Element* = ref ElementObj
|
||||
ElementObj {.importc.} = object of NodeObj
|
||||
classList*: Classlist
|
||||
checked*: bool
|
||||
defaultChecked*: bool
|
||||
defaultValue*: cstring
|
||||
disabled*: bool
|
||||
form*: FormElement
|
||||
name*: cstring
|
||||
readOnly*: bool
|
||||
options*: seq[OptionElement]
|
||||
|
||||
# https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
||||
HtmlElement* = ref object of Element
|
||||
contentEditable*: string
|
||||
isContentEditable*: bool
|
||||
dir*: string
|
||||
offsetHeight*: int
|
||||
offsetWidth*: int
|
||||
offsetLeft*: int
|
||||
offsetTop*: int
|
||||
|
||||
LinkElement* = ref LinkObj
|
||||
LinkObj {.importc.} = object of ElementObj
|
||||
target*: cstring
|
||||
text*: cstring
|
||||
x*: int
|
||||
y*: int
|
||||
|
||||
EmbedElement* = ref EmbedObj
|
||||
EmbedObj {.importc.} = object of ElementObj
|
||||
height*: int
|
||||
hspace*: int
|
||||
src*: cstring
|
||||
width*: int
|
||||
`type`*: cstring
|
||||
vspace*: int
|
||||
|
||||
AnchorElement* = ref AnchorObj
|
||||
AnchorObj {.importc.} = object of ElementObj
|
||||
text*: cstring
|
||||
x*, y*: int
|
||||
|
||||
OptionElement* = ref OptionObj
|
||||
OptionObj {.importc.} = object of ElementObj
|
||||
defaultSelected*: bool
|
||||
selected*: bool
|
||||
selectedIndex*: int
|
||||
text*: cstring
|
||||
value*: cstring
|
||||
|
||||
FormElement* = ref FormObj
|
||||
FormObj {.importc.} = object of ElementObj
|
||||
action*: cstring
|
||||
encoding*: cstring
|
||||
`method`*: cstring
|
||||
target*: cstring
|
||||
elements*: seq[Element]
|
||||
|
||||
ImageElement* = ref ImageObj
|
||||
ImageObj {.importc.} = object of ElementObj
|
||||
border*: int
|
||||
complete*: bool
|
||||
height*: int
|
||||
hspace*: int
|
||||
lowsrc*: cstring
|
||||
src*: cstring
|
||||
vspace*: int
|
||||
width*: int
|
||||
|
||||
Style = ref StyleObj
|
||||
StyleObj {.importc.} = object of RootObj
|
||||
background*: cstring
|
||||
backgroundAttachment*: cstring
|
||||
backgroundColor*: cstring
|
||||
backgroundImage*: cstring
|
||||
backgroundPosition*: cstring
|
||||
backgroundRepeat*: cstring
|
||||
border*: cstring
|
||||
borderBottom*: cstring
|
||||
borderBottomColor*: cstring
|
||||
borderBottomStyle*: cstring
|
||||
borderBottomWidth*: cstring
|
||||
borderColor*: cstring
|
||||
borderLeft*: cstring
|
||||
borderLeftColor*: cstring
|
||||
borderLeftStyle*: cstring
|
||||
borderLeftWidth*: cstring
|
||||
borderRight*: cstring
|
||||
borderRightColor*: cstring
|
||||
borderRightStyle*: cstring
|
||||
borderRightWidth*: cstring
|
||||
borderStyle*: cstring
|
||||
borderTop*: cstring
|
||||
borderTopColor*: cstring
|
||||
borderTopStyle*: cstring
|
||||
borderTopWidth*: cstring
|
||||
borderWidth*: cstring
|
||||
bottom*: cstring
|
||||
captionSide*: cstring
|
||||
clear*: cstring
|
||||
clip*: cstring
|
||||
color*: cstring
|
||||
cursor*: cstring
|
||||
direction*: cstring
|
||||
display*: cstring
|
||||
emptyCells*: cstring
|
||||
cssFloat*: cstring
|
||||
font*: cstring
|
||||
fontFamily*: cstring
|
||||
fontSize*: cstring
|
||||
fontStretch*: cstring
|
||||
fontStyle*: cstring
|
||||
fontVariant*: cstring
|
||||
fontWeight*: cstring
|
||||
height*: cstring
|
||||
left*: cstring
|
||||
letterSpacing*: cstring
|
||||
lineHeight*: cstring
|
||||
listStyle*: cstring
|
||||
listStyleImage*: cstring
|
||||
listStylePosition*: cstring
|
||||
listStyleType*: cstring
|
||||
margin*: cstring
|
||||
marginBottom*: cstring
|
||||
marginLeft*: cstring
|
||||
marginRight*: cstring
|
||||
marginTop*: cstring
|
||||
maxHeight*: cstring
|
||||
maxWidth*: cstring
|
||||
minHeight*: cstring
|
||||
minWidth*: cstring
|
||||
overflow*: cstring
|
||||
padding*: cstring
|
||||
paddingBottom*: cstring
|
||||
paddingLeft*: cstring
|
||||
paddingRight*: cstring
|
||||
paddingTop*: cstring
|
||||
pageBreakAfter*: cstring
|
||||
pageBreakBefore*: cstring
|
||||
position*: cstring
|
||||
right*: cstring
|
||||
scrollbar3dLightColor*: cstring
|
||||
scrollbarArrowColor*: cstring
|
||||
scrollbarBaseColor*: cstring
|
||||
scrollbarDarkshadowColor*: cstring
|
||||
scrollbarFaceColor*: cstring
|
||||
scrollbarHighlightColor*: cstring
|
||||
scrollbarShadowColor*: cstring
|
||||
scrollbarTrackColor*: cstring
|
||||
tableLayout*: cstring
|
||||
textAlign*: cstring
|
||||
textDecoration*: cstring
|
||||
textIndent*: cstring
|
||||
textTransform*: cstring
|
||||
top*: cstring
|
||||
verticalAlign*: cstring
|
||||
visibility*: cstring
|
||||
width*: cstring
|
||||
wordSpacing*: cstring
|
||||
zIndex*: int
|
||||
|
||||
# TODO: A lot of the fields in Event belong to a more specific type of event.
|
||||
# TODO: Should we clean this up?
|
||||
Event* = ref EventObj
|
||||
EventObj {.importc.} = object of RootObj
|
||||
target*: Node
|
||||
altKey*, ctrlKey*, shiftKey*: bool
|
||||
button*: int
|
||||
clientX*, clientY*: int
|
||||
keyCode*: int
|
||||
layerX*, layerY*: int
|
||||
modifiers*: int
|
||||
ALT_MASK*, CONTROL_MASK*, SHIFT_MASK*, META_MASK*: int
|
||||
offsetX*, offsetY*: int
|
||||
pageX*, pageY*: int
|
||||
screenX*, screenY*: int
|
||||
which*: int
|
||||
`type`*: cstring
|
||||
x*, y*: int
|
||||
ABORT*: int
|
||||
BLUR*: int
|
||||
CHANGE*: int
|
||||
CLICK*: int
|
||||
DBLCLICK*: int
|
||||
DRAGDROP*: int
|
||||
ERROR*: int
|
||||
FOCUS*: int
|
||||
KEYDOWN*: int
|
||||
KEYPRESS*: int
|
||||
KEYUP*: int
|
||||
LOAD*: int
|
||||
MOUSEDOWN*: int
|
||||
MOUSEMOVE*: int
|
||||
MOUSEOUT*: int
|
||||
MOUSEOVER*: int
|
||||
MOUSEUP*: int
|
||||
MOVE*: int
|
||||
RESET*: int
|
||||
RESIZE*: int
|
||||
SELECT*: int
|
||||
SUBMIT*: int
|
||||
UNLOAD*: int
|
||||
|
||||
TouchList* {.importc.} = ref object of RootObj
|
||||
length*: int
|
||||
|
||||
TouchEvent* {.importc.} = ref object of Event
|
||||
changedTouches*, targetTouches*, touches*: TouchList
|
||||
|
||||
Touch* {.importc.} = ref object of RootObj
|
||||
identifier*: int
|
||||
screenX*, screenY*, clientX*, clientY*, pageX*, pageY*: int
|
||||
target*: Element
|
||||
radiusX*, radiusY*: int
|
||||
rotationAngle*: int
|
||||
force*: float
|
||||
|
||||
Location* = ref LocationObj
|
||||
LocationObj {.importc.} = object of RootObj
|
||||
hash*: cstring
|
||||
host*: cstring
|
||||
hostname*: cstring
|
||||
href*: cstring
|
||||
pathname*: cstring
|
||||
port*: cstring
|
||||
protocol*: cstring
|
||||
search*: cstring
|
||||
|
||||
History* = ref HistoryObj
|
||||
HistoryObj {.importc.} = object of RootObj
|
||||
length*: int
|
||||
|
||||
Navigator* = ref NavigatorObj
|
||||
NavigatorObj {.importc.} = object of RootObj
|
||||
appCodeName*: cstring
|
||||
appName*: cstring
|
||||
appVersion*: cstring
|
||||
cookieEnabled*: bool
|
||||
language*: cstring
|
||||
platform*: cstring
|
||||
userAgent*: cstring
|
||||
mimeTypes*: seq[ref MimeType]
|
||||
|
||||
Plugin* {.importc.} = object of RootObj
|
||||
description*: cstring
|
||||
filename*: cstring
|
||||
name*: cstring
|
||||
|
||||
MimeType* {.importc.} = object of RootObj
|
||||
description*: cstring
|
||||
enabledPlugin*: ref Plugin
|
||||
suffixes*: seq[cstring]
|
||||
`type`*: cstring
|
||||
|
||||
LocationBar* {.importc.} = object of RootObj
|
||||
visible*: bool
|
||||
MenuBar* = LocationBar
|
||||
PersonalBar* = LocationBar
|
||||
ScrollBars* = LocationBar
|
||||
ToolBar* = LocationBar
|
||||
StatusBar* = LocationBar
|
||||
|
||||
Screen = ref ScreenObj
|
||||
ScreenObj {.importc.} = object of RootObj
|
||||
availHeight*: int
|
||||
availWidth*: int
|
||||
colorDepth*: int
|
||||
height*: int
|
||||
pixelDepth*: int
|
||||
width*: int
|
||||
|
||||
TimeOut* {.importc.} = ref object of RootObj
|
||||
Interval* {.importc.} = object of RootObj
|
||||
|
||||
proc len*(x: Node): int {.importcpp: "#.childNodes.length".}
|
||||
proc `[]`*(x: Node; idx: int): Element {.importcpp: "#.childNodes[#]".}
|
||||
|
||||
proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.}
|
||||
proc clearTimeout*(t: Timeout) {.importc, nodecl.}
|
||||
proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.}
|
||||
|
||||
{.push importcpp.}
|
||||
|
||||
# EventTarget "methods"
|
||||
proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false)
|
||||
|
||||
# Window "methods"
|
||||
proc alert*(w: Window, msg: cstring)
|
||||
proc back*(w: Window)
|
||||
proc blur*(w: Window)
|
||||
proc captureEvents*(w: Window, eventMask: int) {.deprecated.}
|
||||
proc clearInterval*(w: Window, interval: ref Interval)
|
||||
proc clearTimeout*(w: Window, timeout: TimeOut)
|
||||
proc close*(w: Window)
|
||||
proc confirm*(w: Window, msg: cstring): bool
|
||||
proc disableExternalCapture*(w: Window)
|
||||
proc enableExternalCapture*(w: Window)
|
||||
proc find*(w: Window, text: cstring, caseSensitive = false,
|
||||
backwards = false)
|
||||
proc focus*(w: Window)
|
||||
proc forward*(w: Window)
|
||||
proc handleEvent*(w: Window, e: Event)
|
||||
proc home*(w: Window)
|
||||
proc moveBy*(w: Window, x, y: int)
|
||||
proc moveTo*(w: Window, x, y: int)
|
||||
proc open*(w: Window, uri, windowname: cstring,
|
||||
properties: cstring = nil): Window
|
||||
proc print*(w: Window)
|
||||
proc prompt*(w: Window, text, default: cstring): cstring
|
||||
proc releaseEvents*(w: Window, eventMask: int) {.deprecated.}
|
||||
proc resizeBy*(w: Window, x, y: int)
|
||||
proc resizeTo*(w: Window, x, y: int)
|
||||
proc routeEvent*(w: Window, event: Event)
|
||||
proc scrollBy*(w: Window, x, y: int)
|
||||
proc scrollTo*(w: Window, x, y: int)
|
||||
proc setInterval*(w: Window, code: cstring, pause: int): ref Interval
|
||||
proc setInterval*(w: Window, function: proc (), pause: int): ref Interval
|
||||
proc setTimeout*(w: Window, code: cstring, pause: int): TimeOut
|
||||
proc setTimeout*(w: Window, function: proc (), pause: int): ref Interval
|
||||
proc stop*(w: Window)
|
||||
proc requestAnimationFrame*(w: Window, function: proc (time: float)): int
|
||||
proc cancelAnimationFrame*(w: Window, id: int)
|
||||
|
||||
# Node "methods"
|
||||
proc appendChild*(n, child: Node)
|
||||
proc appendData*(n: Node, data: cstring)
|
||||
proc cloneNode*(n: Node, copyContent: bool): Node
|
||||
proc deleteData*(n: Node, start, len: int)
|
||||
proc getAttribute*(n: Node, attr: cstring): cstring
|
||||
proc getAttributeNode*(n: Node, attr: cstring): Node
|
||||
proc hasChildNodes*(n: Node): bool
|
||||
proc insertBefore*(n, newNode, before: Node)
|
||||
proc insertData*(n: Node, position: int, data: cstring)
|
||||
proc removeAttribute*(n: Node, attr: cstring)
|
||||
proc removeAttributeNode*(n, attr: Node)
|
||||
proc removeChild*(n, child: Node)
|
||||
proc replaceChild*(n, newNode, oldNode: Node)
|
||||
proc replaceData*(n: Node, start, len: int, text: cstring)
|
||||
proc scrollIntoView*(n: Node)
|
||||
proc setAttribute*(n: Node, name, value: cstring)
|
||||
proc setAttributeNode*(n: Node, attr: Node)
|
||||
|
||||
# Document "methods"
|
||||
proc captureEvents*(d: Document, eventMask: int) {.deprecated.}
|
||||
proc createAttribute*(d: Document, identifier: cstring): Node
|
||||
proc createElement*(d: Document, identifier: cstring): Element
|
||||
proc createTextNode*(d: Document, identifier: cstring): Node
|
||||
proc getElementById*(d: Document, id: cstring): Element
|
||||
proc getElementsByName*(d: Document, name: cstring): seq[Element]
|
||||
proc getElementsByTagName*(d: Document, name: cstring): seq[Element]
|
||||
proc getElementsByClassName*(d: Document, name: cstring): seq[Element]
|
||||
proc getSelection*(d: Document): cstring
|
||||
proc handleEvent*(d: Document, event: Event)
|
||||
proc open*(d: Document)
|
||||
proc releaseEvents*(d: Document, eventMask: int) {.deprecated.}
|
||||
proc routeEvent*(d: Document, event: Event)
|
||||
proc write*(d: Document, text: cstring)
|
||||
proc writeln*(d: Document, text: cstring)
|
||||
|
||||
# Element "methods"
|
||||
proc blur*(e: Element)
|
||||
proc click*(e: Element)
|
||||
proc focus*(e: Node)
|
||||
proc handleEvent*(e: Element, event: Event)
|
||||
proc select*(e: Element)
|
||||
proc getElementsByTagName*(e: Element, name: cstring): seq[Element]
|
||||
proc getElementsByClassName*(e: Element, name: cstring): seq[Element]
|
||||
|
||||
# FormElement "methods"
|
||||
proc reset*(f: FormElement)
|
||||
proc submit*(f: FormElement)
|
||||
|
||||
# EmbedElement "methods"
|
||||
proc play*(e: EmbedElement)
|
||||
proc stop*(e: EmbedElement)
|
||||
|
||||
# Location "methods"
|
||||
proc reload*(loc: Location)
|
||||
proc replace*(loc: Location, s: cstring)
|
||||
|
||||
# History "methods"
|
||||
proc back*(h: History)
|
||||
proc forward*(h: History)
|
||||
proc go*(h: History, pagesToJump: int)
|
||||
|
||||
# Navigator "methods"
|
||||
proc javaEnabled*(h: Navigator): bool
|
||||
|
||||
# ClassList "methods"
|
||||
proc add*(c: ClassList, class: cstring)
|
||||
proc remove*(c: ClassList, class: cstring)
|
||||
proc contains*(c: ClassList, class: cstring):bool
|
||||
proc toggle*(c: ClassList, class: cstring)
|
||||
|
||||
# Style "methods"
|
||||
proc getAttribute*(s: Style, attr: cstring, caseSensitive=false): cstring
|
||||
proc removeAttribute*(s: Style, attr: cstring, caseSensitive=false)
|
||||
proc setAttribute*(s: Style, attr, value: cstring, caseSensitive=false)
|
||||
|
||||
# Event "methods"
|
||||
proc preventDefault*(ev: Event)
|
||||
|
||||
# TouchEvent "methods"
|
||||
proc identifiedTouch*(list: TouchList): Touch
|
||||
proc item*(list: TouchList, i: int): Touch
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc setAttr*(n: Node; key, val: cstring) {.importcpp: "#.setAttribute(@)".}
|
||||
|
||||
var
|
||||
window* {.importc, nodecl.}: Window
|
||||
document* {.importc, nodecl.}: Document
|
||||
navigator* {.importc, nodecl.}: Navigator
|
||||
screen* {.importc, nodecl.}: Screen
|
||||
|
||||
proc decodeURI*(uri: cstring): cstring {.importc, nodecl.}
|
||||
proc encodeURI*(uri: cstring): cstring {.importc, nodecl.}
|
||||
|
||||
proc escape*(uri: cstring): cstring {.importc, nodecl.}
|
||||
proc unescape*(uri: cstring): cstring {.importc, nodecl.}
|
||||
|
||||
proc decodeURIComponent*(uri: cstring): cstring {.importc, nodecl.}
|
||||
proc encodeURIComponent*(uri: cstring): cstring {.importc, nodecl.}
|
||||
proc isFinite*(x: BiggestFloat): bool {.importc, nodecl.}
|
||||
proc isNaN*(x: BiggestFloat): bool {.importc, nodecl.}
|
||||
proc parseFloat*(s: cstring): BiggestFloat {.importc, nodecl.}
|
||||
proc parseInt*(s: cstring): int {.importc, nodecl.}
|
||||
proc parseInt*(s: cstring, radix: int):int {.importc, nodecl.}
|
||||
|
||||
|
||||
proc id*(n: Node): cstring {.importcpp: "#.id", nodecl.}
|
||||
proc `id=`*(n: Node; x: cstring) {.importcpp: "#.id = #", nodecl.}
|
||||
proc class*(n: Node): cstring {.importcpp: "#.className", nodecl.}
|
||||
proc `class=`*(n: Node; v: cstring) {.importcpp: "#.className = #", nodecl.}
|
||||
|
||||
proc value*(n: Node): cstring {.importcpp: "#.value", nodecl.}
|
||||
proc `value=`*(n: Node; v: cstring) {.importcpp: "#.value = #", nodecl.}
|
||||
|
||||
proc `disabled=`*(n: Node; v: bool) {.importcpp: "#.disabled = #", nodecl.}
|
||||
|
||||
proc getElementsByClass*(n: Node; name: cstring): seq[Node] {.
|
||||
importcpp: "#.getElementsByClassName(#)", nodecl.}
|
||||
|
||||
|
||||
type
|
||||
BoundingRect* {.importc.} = object
|
||||
top*, bottom*, left*, right*: int
|
||||
|
||||
proc getBoundingClientRect*(e: Node): BoundingRect {.
|
||||
importcpp: "getBoundingClientRect", nodecl.}
|
||||
proc clientHeight*(): int {.
|
||||
importcpp: "(window.innerHeight || document.documentElement.clientHeight)@", nodecl}
|
||||
proc clientWidth*(): int {.
|
||||
importcpp: "(window.innerWidth || document.documentElement.clientWidth)@", nodecl}
|
||||
|
||||
proc inViewport*(el: Node): bool =
|
||||
let rect = el.getBoundingClientRect()
|
||||
result = rect.top >= 0 and rect.left >= 0 and
|
||||
rect.bottom <= clientHeight() and
|
||||
rect.right <= clientWidth()
|
||||
|
||||
proc scrollTop*(e: Node): int {.importcpp: "#.scrollTop", nodecl.}
|
||||
proc offsetHeight*(e: Node): int {.importcpp: "#.offsetHeight", nodecl.}
|
||||
proc offsetTop*(e: Node): int {.importcpp: "#.offsetTop", nodecl.}
|
||||
13
bench/implementations/nim-karax/src/jdict.nim
Normal file
13
bench/implementations/nim-karax/src/jdict.nim
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
type
|
||||
JDict*[K, V] = ref object
|
||||
|
||||
proc `[]`*[K, V](d: JDict[K, V], k: K): V {.importcpp: "#[#]".}
|
||||
proc `[]=`*[K, V](d: JDict[K, V], k: K, v: V) {.importcpp: "#[#] = #".}
|
||||
|
||||
proc newJDict*[K, V](): JDict[K, V] {.importcpp: "{@}".}
|
||||
|
||||
proc contains*[K, V](d: JDict[K, V], k: K): bool {.importcpp: "#.hasOwnProperty(#)".}
|
||||
|
||||
proc del*[K, V](d: JDict[K, V], k: K) {.importcpp: "delete #[#]".}
|
||||
|
||||
23
bench/implementations/nim-karax/src/jstrutils.nim
Normal file
23
bench/implementations/nim-karax/src/jstrutils.nim
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# cstring handling utilities for performance
|
||||
|
||||
proc split*(s, sep: cstring): seq[cstring] {.importcpp, nodecl.}
|
||||
|
||||
proc split*(s, sep: cstring; max: int): seq[cstring] {.importcpp, nodecl.}
|
||||
|
||||
proc startsWith*(a, b: cstring): bool {.importcpp: "startsWith", nodecl.}
|
||||
proc contains*(a, b: cstring): bool {.importcpp: "(#.indexOf(#)>=0)", nodecl.}
|
||||
proc substr*(s: cstring; start: int): cstring {.importcpp: "substr", nodecl.}
|
||||
proc substr*(s: cstring; start, length: int): cstring {.importcpp: "substr", nodecl.}
|
||||
|
||||
#proc len*(s: cstring): int {.importcpp: "#.length", nodecl.}
|
||||
proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)", nodecl.}
|
||||
proc toCstr*(s: int): cstring {.importcpp: "((#)+'')", nodecl.}
|
||||
proc `&`*(s: int): cstring {.importcpp: "((#)+'')", nodecl.}
|
||||
proc `&`*(s: bool): cstring {.importcpp: "((#)+'')", nodecl.}
|
||||
|
||||
proc `&`*(s: cstring): cstring {.importcpp: "(#)", nodecl.}
|
||||
|
||||
proc isInt*(s: cstring): bool {.asmNoStackFrame.} =
|
||||
asm """
|
||||
return s.match(/^[0-9]+$/);
|
||||
"""
|
||||
345
bench/implementations/nim-karax/src/karax.nim
Normal file
345
bench/implementations/nim-karax/src/karax.nim
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
## Karax -- Single page applications for Nim.
|
||||
|
||||
import dom, vdom, jstrutils, components, jdict
|
||||
|
||||
export dom.Event #, dom.cloneNode, dom
|
||||
|
||||
proc kout*[T](x: T) {.importc: "console.log", varargs.}
|
||||
## 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.}
|
||||
|
||||
var
|
||||
toFocus: Node
|
||||
toFocusV: VNode
|
||||
|
||||
proc setFocus*(n: VNode) =
|
||||
toFocusV = n
|
||||
|
||||
# ----------------- event wrapping ---------------------------------------
|
||||
|
||||
template nativeValue(ev): cstring = cast[Element](ev.target).value
|
||||
template setNativeValue(ev, val) = cast[Element](ev.target).value = val
|
||||
|
||||
template keyeventBody() =
|
||||
n.value = nativeValue(ev)
|
||||
action(ev, n)
|
||||
setNativeValue(ev, n.value)
|
||||
# Do not call redraw() here! That is already done
|
||||
# by ``karax.addEventHandler``.
|
||||
|
||||
proc wrapEvent(d: Node; n: VNode; k: EventKind; action: EventHandler) =
|
||||
proc stdWrapper(): (proc (ev: Event)) =
|
||||
let action = action
|
||||
let n = n
|
||||
result = proc (ev: Event) =
|
||||
action(ev, n)
|
||||
|
||||
proc enterWrapper(): (proc (ev: Event)) =
|
||||
let action = action
|
||||
let n = n
|
||||
result = proc (ev: Event) =
|
||||
if ev.keyCode == 13: keyeventBody()
|
||||
|
||||
proc laterWrapper(): (proc (ev: Event)) =
|
||||
let action = action
|
||||
let n = n
|
||||
var timer: Timeout
|
||||
result = proc (ev: Event) =
|
||||
proc wrapper() = keyeventBody()
|
||||
if timer != nil: clearTimeout(timer)
|
||||
timer = setTimeout(wrapper, 400)
|
||||
|
||||
case k
|
||||
of EventKind.onkeyuplater:
|
||||
d.addEventListener("keyup", laterWrapper())
|
||||
of EventKind.onkeyupenter:
|
||||
d.addEventListener("keyup", enterWrapper())
|
||||
else:
|
||||
d.addEventListener(toEventName[k], stdWrapper())
|
||||
|
||||
# --------------------- DOM diff -----------------------------------------
|
||||
|
||||
template detach(n: VNode) = n.dom = nil
|
||||
template attach(n: Vnode) = n.dom = result
|
||||
|
||||
proc vnodeToDom(n: VNode): Node =
|
||||
if n.kind == VNodeKind.text:
|
||||
result = document.createTextNode(n.text)
|
||||
attach n
|
||||
elif n.kind == VNodeKind.vthunk:
|
||||
let x = callThunk(vcomponents[n.text], n)
|
||||
result = vnodeToDom(x)
|
||||
n.key = result.key
|
||||
attach n
|
||||
return result
|
||||
elif n.kind == VNodeKind.dthunk:
|
||||
result = callThunk(dcomponents[n.text], n)
|
||||
n.key = result.key
|
||||
attach n
|
||||
return result
|
||||
else:
|
||||
result = document.createElement(toTag[n.kind])
|
||||
attach n
|
||||
for k in n:
|
||||
appendChild(result, vnodeToDom(k))
|
||||
# text is mapped to 'value':
|
||||
if n.text != nil:
|
||||
result.value = n.text
|
||||
if n.id != nil:
|
||||
result.id = n.id
|
||||
if n.class != nil:
|
||||
result.class = n.class
|
||||
if n.key >= 0:
|
||||
result.key = n.key
|
||||
for k, v in attrs(n):
|
||||
if v != nil:
|
||||
result.setAttr(k, v)
|
||||
for e, h in items(n.events):
|
||||
wrapEvent(result, n, e, h)
|
||||
if n == toFocusV and toFocus.isNil:
|
||||
toFocus = result
|
||||
|
||||
proc same(n: VNode, e: Node): bool =
|
||||
if toTag[n.kind] == e.nodename:
|
||||
result = true
|
||||
if n.kind != VNodeKind.text:
|
||||
if e.len != n.len: return false
|
||||
for i in 0 ..< n.len:
|
||||
if not same(n[i], e[i]): return false
|
||||
|
||||
var
|
||||
dorender: proc (): VNode {.closure.}
|
||||
currentTree: VNode
|
||||
|
||||
proc replaceById(id: cstring; newTree: Node) =
|
||||
let x = document.getElementById(id)
|
||||
x.parentNode.replaceChild(newTree, x)
|
||||
#newTree.id = id
|
||||
|
||||
proc equalsShallow(a, b: VNode): bool =
|
||||
if a.kind != b.kind: return false
|
||||
if a.id != b.id: return false
|
||||
if a.key != b.key: return false
|
||||
if a.kind == VNodeKind.text:
|
||||
if a.text != b.text: return false
|
||||
elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk:
|
||||
if a.text != b.text: return false
|
||||
if a.len != b.len: return false
|
||||
for i in 0..<a.len:
|
||||
if not equalsShallow(a[i], b[i]): return false
|
||||
if not sameAttrs(a, b): return false
|
||||
if a.class != b.class: return false
|
||||
# XXX test event listeners here?
|
||||
return true
|
||||
|
||||
proc equalsTree(a, b: VNode): bool =
|
||||
when false:
|
||||
# hashing is too fragile now with component support:
|
||||
if not a.validHash:
|
||||
a.calcHash()
|
||||
if not b.validHash:
|
||||
b.calcHash()
|
||||
return a.hash == b.hash
|
||||
else:
|
||||
result = eq(a, b)
|
||||
|
||||
proc updateDirtyElements(parent, current: Node, newNode: VNode) =
|
||||
if newNode.key >= 0 and isDirty(newNode.key):
|
||||
unmarkDirty(newNode.key)
|
||||
let n = vnodeToDom(newNode)
|
||||
if parent == nil:
|
||||
replaceById("ROOT", n)
|
||||
else:
|
||||
parent.replaceChild(n, current)
|
||||
elif newNode.kind != VNodeKind.text and newNode.kind != VNodeKind.vthunk and
|
||||
newNode.kind != VNodeKind.dthunk:
|
||||
for i in 0..newNode.len-1:
|
||||
updateDirtyElements(current, current[i], newNode[i])
|
||||
# leave early if we know there cannot be anything left to do:
|
||||
#if dirtyCount <= 0: return
|
||||
|
||||
proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
|
||||
if not equalsShallow(newNode, oldNode):
|
||||
detach(oldNode)
|
||||
let n = vnodeToDom(newNode)
|
||||
if parent == nil:
|
||||
replaceById("ROOT", n)
|
||||
else:
|
||||
parent.replaceChild(n, current)
|
||||
elif newNode.kind != VNodeKind.text:
|
||||
let newLength = newNode.len
|
||||
var oldLength = oldNode.len
|
||||
let minLength = min(newLength, oldLength)
|
||||
assert oldNode.kind == newNode.kind
|
||||
when defined(simpleDiff):
|
||||
for i in 0..min(newLength, oldLength)-1:
|
||||
updateElement(current, current[i], newNode[i], oldNode[i])
|
||||
if newLength > oldLength:
|
||||
for i in oldLength..newLength-1:
|
||||
current.appendChild(vnodeToDom(newNode[i]))
|
||||
elif oldLength > newLength:
|
||||
for i in countdown(oldLength-1, newLength):
|
||||
detach(oldNode[i])
|
||||
current.removeChild(current.lastChild)
|
||||
else:
|
||||
var commonPrefix = 0
|
||||
while commonPrefix < minLength and
|
||||
equalsTree(newNode[commonPrefix], oldNode[commonPrefix]):
|
||||
inc commonPrefix
|
||||
|
||||
var oldPos = oldLength - 1
|
||||
var newPos = newLength - 1
|
||||
while oldPos >= commonPrefix and newPos >= commonPrefix and
|
||||
equalsTree(newNode[newPos], oldNode[oldPos]):
|
||||
dec oldPos
|
||||
dec newPos
|
||||
|
||||
var pos = min(oldPos, newPos) + 1
|
||||
for i in commonPrefix..pos-1:
|
||||
updateElement(current, current.childNodes[i], newNode[i], oldNode[i])
|
||||
|
||||
var nextChildPos = oldPos + 1
|
||||
while pos <= newPos:
|
||||
if nextChildPos == oldLength:
|
||||
current.appendChild(vnodeToDom(newNode[pos]))
|
||||
else:
|
||||
current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos])
|
||||
# added new Node, so old state of VDOM have one more Node
|
||||
inc oldLength
|
||||
inc pos
|
||||
inc nextChildPos
|
||||
|
||||
for i in pos..oldPos:
|
||||
detach(oldNode[i])
|
||||
current.removeChild(current.childNodes[pos])
|
||||
|
||||
when false:
|
||||
var drawTimeout: Timeout
|
||||
|
||||
proc dodraw() =
|
||||
let newtree = dorender()
|
||||
newtree.id = "ROOT"
|
||||
toFocus = nil
|
||||
if currentTree == nil:
|
||||
currentTree = newtree
|
||||
let asdom = vnodeToDom currentTree
|
||||
replaceById("ROOT", asdom)
|
||||
else:
|
||||
let olddom = document.getElementById("ROOT")
|
||||
updateElement(nil, olddom, newtree, currentTree)
|
||||
#assert same(newtree, document.getElementById("ROOT"))
|
||||
if someDirty:
|
||||
updateDirtyElements(nil, olddom, newtree)
|
||||
someDirty = false
|
||||
currentTree = newtree
|
||||
# now that it's part of the DOM, give it the focus:
|
||||
if toFocus != nil:
|
||||
toFocus.focus()
|
||||
|
||||
proc reqFrame(callback: proc()) {.importc: "window.requestAnimationFrame".}
|
||||
|
||||
proc redraw*() =
|
||||
# we buffer redraw requests:
|
||||
when false:
|
||||
if drawTimeout != nil:
|
||||
clearTimeout(drawTimeout)
|
||||
drawTimeout = setTimeout(dodraw, 30)
|
||||
elif true:
|
||||
reqFrame(dodraw)
|
||||
else:
|
||||
dodraw()
|
||||
|
||||
proc init(ev: Event) =
|
||||
reqFrame(dodraw)
|
||||
|
||||
proc setRenderer*(renderer: proc (): VNode) =
|
||||
dorender = renderer
|
||||
window.onload = init
|
||||
|
||||
proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler) =
|
||||
## 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(ev, n)
|
||||
redraw()
|
||||
addEventListener(n, k, wrapper)
|
||||
|
||||
proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
||||
var onhashChange {.importc: "window.onhashchange".}: proc()
|
||||
var hashPart {.importc: "window.location.hash".}: cstring
|
||||
proc wrapper() =
|
||||
action(hashPart)
|
||||
redraw()
|
||||
onhashchange = wrapper
|
||||
|
||||
proc ajax(meth, url: cstring; headers: openarray[(cstring, cstring)];
|
||||
data: cstring;
|
||||
cont: proc (httpStatus: int; response: cstring)) =
|
||||
proc setRequestHeader(a, b: cstring) {.importc: "ajax.setRequestHeader".}
|
||||
{.emit: """
|
||||
var ajax = new XMLHttpRequest();
|
||||
ajax.open(`meth`,`url`,true);""".}
|
||||
for a, b in items(headers):
|
||||
setRequestHeader(a, b)
|
||||
{.emit: """
|
||||
ajax.onreadystatechange = function(){
|
||||
if(this.readyState == 4){
|
||||
if(this.status == 200){
|
||||
`cont`(this.status, this.responseText);
|
||||
} else {
|
||||
`cont`(this.status, this.statusText);
|
||||
}
|
||||
}
|
||||
}
|
||||
ajax.send(`data`);
|
||||
""".}
|
||||
|
||||
proc ajaxPut*(url: string; headers: openarray[(cstring, cstring)];
|
||||
data: cstring;
|
||||
cont: proc (httpStatus: int, response: cstring)) =
|
||||
ajax("PUT", url, headers, data, cont)
|
||||
|
||||
proc ajaxGet*(url: string; headers: openarray[(cstring, cstring)];
|
||||
cont: proc (httpStatus: int, response: cstring)) =
|
||||
ajax("GET", url, headers, nil, cont)
|
||||
|
||||
{.push stackTrace:off.}
|
||||
|
||||
proc setupErrorHandler*(useAlert=false) =
|
||||
## Installs an error handler that transforms native JS unhandled
|
||||
## exceptions into Nim based stack traces. If `useAlert` is false,
|
||||
## the error message is put into the console, otherwise `alert`
|
||||
## is called.
|
||||
proc stackTraceAsCstring(): cstring = cstring(getStackTrace())
|
||||
{.emit: """
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
var x = "Error: " + msg + "\n" + `stackTraceAsCstring`()
|
||||
if (`useAlert`)
|
||||
alert(x);
|
||||
else
|
||||
console.log(x);
|
||||
var suppressErrorAlert = true;
|
||||
return suppressErrorAlert;
|
||||
};""".}
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc prepend(parent, kid: Element) =
|
||||
parent.insertBefore(kid, parent.firstChild)
|
||||
|
||||
proc loadScript*(jsfilename: cstring) =
|
||||
let body = getElementById("body")
|
||||
let s = document.createElement("script")
|
||||
s.setAttr "type", "text/javascript"
|
||||
s.setAttr "src", jsfilename
|
||||
body.prepend(s)
|
||||
redraw()
|
||||
119
bench/implementations/nim-karax/src/karaxdsl.nim
Normal file
119
bench/implementations/nim-karax/src/karaxdsl.nim
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
|
||||
import macros, karax, vdom, components
|
||||
from strutils import startsWith, toLowerAscii
|
||||
|
||||
const
|
||||
StmtContext = ["kout", "inc", "echo", "dec", "!"]
|
||||
SpecialAttrs = ["id", "class", "value", "key"]
|
||||
|
||||
proc getName(n: NimNode): string =
|
||||
case n.kind
|
||||
of nnkIdent:
|
||||
result = $n.ident
|
||||
of nnkAccQuoted:
|
||||
result = ""
|
||||
for i in 0..<n.len:
|
||||
result.add getName(n[i])
|
||||
of nnkStrLit..nnkTripleStrLit:
|
||||
result = n.strVal
|
||||
else:
|
||||
#echo repr n
|
||||
expectKind(n, nnkIdent)
|
||||
|
||||
proc newDotAsgn(tmp: NimNode, key: string, x: NimNode): NimNode =
|
||||
result = newTree(nnkAsgn, newDotExpr(tmp, newIdentNode key), x)
|
||||
|
||||
proc tcall2(n, tmpContext: NimNode): NimNode =
|
||||
# we need to distinguish statement and expression contexts:
|
||||
# every call statement 's' needs to be transformed to 'dest.add s'.
|
||||
# If expressions need to be distinguished from if statements. Since
|
||||
# we know we start in a statement context, it's pretty simple to
|
||||
# figure out expression contexts: In calls everything is an expression
|
||||
# (except for the last child of the macros we consider here),
|
||||
# lets, consts, types can be considered as expressions
|
||||
# case is complex, calls are assumed to produce a value.
|
||||
case n.kind
|
||||
of nnkLiterals, nnkIdent, nnkSym, nnkDotExpr, nnkBracketExpr:
|
||||
if tmpContext != nil:
|
||||
result = newCall(bindSym"add", tmpContext, n)
|
||||
else:
|
||||
result = n
|
||||
of nnkForStmt, nnkIfExpr, nnkElifExpr, nnkElseExpr,
|
||||
nnkOfBranch, nnkElifBranch, nnkExceptBranch, nnkElse,
|
||||
nnkConstDef, nnkWhileStmt, nnkIdentDefs, nnkVarTuple:
|
||||
# recurse for the last son:
|
||||
result = copyNimTree(n)
|
||||
let L = n.len
|
||||
assert n.len == result.len
|
||||
if L > 0:
|
||||
result[L-1] = tcall2(result[L-1], tmpContext)
|
||||
of nnkStmtList, nnkStmtListExpr, nnkWhenStmt, nnkIfStmt, nnkCaseStmt,
|
||||
nnkTryStmt, nnkFinally:
|
||||
# recurse for every child:
|
||||
result = copyNimNode(n)
|
||||
for x in n:
|
||||
result.add tcall2(x, tmpContext)
|
||||
of nnkVarSection, nnkLetSection, nnkConstSection:
|
||||
result = n
|
||||
of nnkCallKinds:
|
||||
let op = getName(n[0])
|
||||
let ck = isComponent(op)
|
||||
if ck != ComponentKind.None:
|
||||
let tmp = genSym(nskLet, "tmp")
|
||||
let call = if ck == ComponentKind.Tag:
|
||||
newCall(bindSym"tree", newDotExpr(bindSym"VNodeKind", n[0]))
|
||||
elif ck == ComponentKind.VNode:
|
||||
newCall(bindSym"vthunk", newLit(op))
|
||||
else:
|
||||
newCall(bindSym"dthunk", newLit(op))
|
||||
result = newTree(
|
||||
if tmpContext == nil: nnkStmtListExpr else: nnkStmtList,
|
||||
newLetStmt(tmp, call))
|
||||
for i in 1 ..< n.len:
|
||||
# named parameters are transformed into attributes or events:
|
||||
let x = n[i]
|
||||
if x.kind == nnkExprEqExpr:
|
||||
let key = getName x[0]
|
||||
if key.startsWith("on"):
|
||||
result.add newCall(bindSym"addEventHandler",
|
||||
tmp, newDotExpr(bindSym"EventKind", x[0]), x[1])
|
||||
elif key in SpecialAttrs:
|
||||
result.add newDotAsgn(tmp, key, x[1])
|
||||
else:
|
||||
result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1])
|
||||
elif ck != ComponentKind.Tag:
|
||||
call.add x
|
||||
elif eqIdent(x, "setFocus"):
|
||||
result.add newCall(x, tmp)
|
||||
else:
|
||||
result.add tcall2(x, tmp)
|
||||
if tmpContext == nil:
|
||||
result.add tmp
|
||||
else:
|
||||
result.add newCall(bindSym"add", tmpContext, tmp)
|
||||
elif tmpContext != nil and op notin StmtContext:
|
||||
result = newCall(bindSym"add", tmpContext, n)
|
||||
elif op == "!" and n.len == 2:
|
||||
result = n[1]
|
||||
else:
|
||||
result = n
|
||||
else:
|
||||
result = n
|
||||
|
||||
macro buildHtml*(tag, children: untyped): VNode =
|
||||
expectKind children, nnkDo
|
||||
var call: NimNode
|
||||
if tag.kind in nnkCallKinds:
|
||||
call = tag
|
||||
else:
|
||||
call = newCall(tag)
|
||||
call.add body(children)
|
||||
result = tcall2(call, nil)
|
||||
when defined(debugKaraxDsl):
|
||||
echo repr result
|
||||
|
||||
macro buildHtml*(children: untyped): VNode =
|
||||
expectKind children, nnkDo
|
||||
result = tcall2(body(children), nil)
|
||||
when defined(debugKaraxDsl):
|
||||
echo repr result
|
||||
3
bench/implementations/nim-karax/src/karaxprelude.nim
Normal file
3
bench/implementations/nim-karax/src/karaxprelude.nim
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## Include file that contains the common imports for the Karax framework.
|
||||
|
||||
import karax, karaxdsl, vdom, components
|
||||
17
bench/implementations/nim-karax/src/shash.nim
Normal file
17
bench/implementations/nim-karax/src/shash.nim
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Simple hashing for the JS target.
|
||||
|
||||
type
|
||||
Hash* = int
|
||||
|
||||
{.push checks: off.}
|
||||
proc sdbmHash(hash: Hash, c: int): Hash {.inline.} =
|
||||
return Hash(c) + (hash shl 6) + (hash shl 16) - hash
|
||||
{.pop.}
|
||||
|
||||
proc charCodeAt(s: cstring; i: int): int {.importcpp: "#.charCodeAt(#)".}
|
||||
|
||||
template `&=`*(x: var Hash, c: int) = x = sdbmHash(x, c)
|
||||
template `&=`*(x: var Hash, s: cstring) =
|
||||
for i in 0..<s.len:
|
||||
let c = charCodeAt(s, i)
|
||||
x = sdbmHash(x, c)
|
||||
249
bench/implementations/nim-karax/src/vdom.nim
Normal file
249
bench/implementations/nim-karax/src/vdom.nim
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
## Virtual DOM implementation for Karax.
|
||||
|
||||
from dom import Event, Node
|
||||
import shash, macros
|
||||
from strutils import toUpperAscii
|
||||
|
||||
type
|
||||
VNodeKind* {.pure.} = enum
|
||||
text = "#text", int = "#int", bool = "#bool",
|
||||
vthunk = "#vthunk", dthunk = "#dthunk",
|
||||
|
||||
html, head, title, base, link, meta, style,
|
||||
script, noscript,
|
||||
body, section, nav, article, aside,
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
header, footer, address, main
|
||||
|
||||
p, hr, pre, blockquote, ol, ul, li,
|
||||
dl, dt, dd,
|
||||
figure, figcaption,
|
||||
|
||||
tdiv = "div",
|
||||
|
||||
a, em, strong, small,
|
||||
strikethrough = "s", cite, quote,
|
||||
dfn, abbr, data, time, code, `var` = "var", samp,
|
||||
kdb, sub, sup, italic = "i", bold = "b", underlined = "u",
|
||||
mark, ruby, rt, rp, bdi, dbo, span, br, wbr,
|
||||
ins, del, img, iframe, embed, `object` = "object",
|
||||
param, video, audio, source, track, canvas, map,
|
||||
area, svg, math,
|
||||
|
||||
table, caption, colgroup, col, tbody, thead,
|
||||
tfoot, tr, td, th,
|
||||
|
||||
form, fieldset, legend, label, input, button,
|
||||
select, datalist, optgroup, option, textarea,
|
||||
keygen, output, progress, meter,
|
||||
details, summary, command, menu
|
||||
|
||||
type
|
||||
EventKind* {.pure.} = enum ## The events supported by the virtual DOM.
|
||||
onclick, ## An element is clicked.
|
||||
ondblclick, ## An element is double clicked.
|
||||
onkeyup, ## A key was released.
|
||||
onkeydown, ## A key is pressed.
|
||||
onkeypressed, # A key was pressed.
|
||||
onblur, ## An element lost the focus.
|
||||
onchange, ## The selected value of an element was changed.
|
||||
onscroll, ## The user scrolled within an element.
|
||||
|
||||
onmousedown, ## A pointing device button (usually a mouse) is pressed
|
||||
## on an element.
|
||||
onmouseenter, ## A pointing device is moved onto the element that
|
||||
## has the listener attached.
|
||||
onmouseleave, ## A pointing device is moved off the element that
|
||||
## has the listener attached.
|
||||
onmousemove, ## A pointing device is moved over an element.
|
||||
onmouseout, ## A pointing device is moved off the element that
|
||||
## has the listener attached or off one of its children.
|
||||
onmouseover, ## A pointing device is moved onto the element that has
|
||||
## the listener attached or onto one of its children.
|
||||
onmouseup, ## A pointing device button is released over an element.
|
||||
|
||||
ondrag, ## An element or text selection is being dragged (every 350ms).
|
||||
ondragend, ## A drag operation is being ended (by releasing a mouse button
|
||||
## or hitting the escape key).
|
||||
ondragenter, ## A dragged element or text selection enters a valid drop target.
|
||||
ondragleave, ## A dragged element or text selection leaves a valid drop target.
|
||||
ondragover, ## An element or text selection is being dragged over a valid
|
||||
## drop target (every 350ms).
|
||||
ondragstart, ## The user starts dragging an element or text selection.
|
||||
ondrop, ## An element is dropped on a valid drop target.
|
||||
|
||||
onkeyupenter, ## vdom extension: an input field received the ENTER key press
|
||||
onkeyuplater ## vdom extension: a key was pressed and some time
|
||||
## passed (useful for on-the-fly text completions)
|
||||
|
||||
macro buildLookupTables(): untyped =
|
||||
var a = newTree(nnkBracket)
|
||||
for i in low(VNodeKind)..high(VNodeKind):
|
||||
let x = $i
|
||||
let y = if x[0] == '#': x else: toUpperAscii(x)
|
||||
a.add(newCall("cstring", newLit(y)))
|
||||
var e = newTree(nnkBracket)
|
||||
for i in low(EventKind)..high(EventKind):
|
||||
e.add(newCall("cstring", newLit(substr($i, 2))))
|
||||
|
||||
template tmpl(a, e) {.dirty.} =
|
||||
const
|
||||
toTag*: array[VNodeKind, cstring] = a
|
||||
toEventName*: array[EventKind, cstring] = e
|
||||
|
||||
result = getAst tmpl(a, e)
|
||||
|
||||
buildLookupTables()
|
||||
|
||||
type
|
||||
EventHandler* = proc (ev: Event; target: VNode) {.closure.}
|
||||
VKey* = int
|
||||
VNode* = ref object
|
||||
kind*: VNodeKind
|
||||
key*: VKey
|
||||
id*, class*, text*: cstring
|
||||
kids: seq[VNode]
|
||||
# even index: key, odd index: value; done this way for memory efficiency:
|
||||
attrs: seq[cstring]
|
||||
events*: seq[(EventKind, EventHandler)]
|
||||
hash*: Hash
|
||||
validHash*: bool
|
||||
dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node
|
||||
## is not part of the virtual DOM anymore.
|
||||
|
||||
proc value*(n: VNode): cstring = n.text
|
||||
proc `value=`*(n: VNode; v: cstring) = n.text = v
|
||||
|
||||
proc intValue*(n: VNode): int = n.key
|
||||
proc vn*(i: int): VNode = VNode(kind: VNodeKind.int, key: i)
|
||||
proc vn*(b: bool): VNode = VNode(kind: VNodeKind.int, key: ord(b))
|
||||
proc vn*(x: cstring): VNode = VNode(kind: VNodeKind.text, key: -1, text: x)
|
||||
|
||||
template callThunk*(fn: typed; n: VNode): untyped =
|
||||
## for internal usage only.
|
||||
fn(n.kids)
|
||||
|
||||
proc vthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||
VNode(kind: VNodeKind.vthunk, text: name, key: -1, kids: @args)
|
||||
|
||||
proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
|
||||
VNode(kind: VNodeKind.dthunk, text: name, key: -1, kids: @args)
|
||||
|
||||
proc eq*(a, b: VNode): bool =
|
||||
if a.kind != b.kind: return false
|
||||
if a.id != b.id: return false
|
||||
if a.class != b.class: return false
|
||||
if a.key != b.key: return false
|
||||
if a.kind != VNodeKind.text:
|
||||
if a.kids.len != b.kids.len: return false
|
||||
for i in 0..<a.kids.len:
|
||||
if not eq(a.kids[i], b.kids[i]): return false
|
||||
if a.text != b.text: return false
|
||||
if a.attrs.len != b.attrs.len: return false
|
||||
for i in 0..<a.attrs.len:
|
||||
if a.attrs[i] != b.attrs[i]: return false
|
||||
result = true
|
||||
|
||||
proc setAttr*(n: VNode; key: cstring; val: cstring = "") =
|
||||
if n.attrs.isNil:
|
||||
n.attrs = @[key, val]
|
||||
else:
|
||||
for i in countup(0, n.attrs.len-2, 2):
|
||||
if n.attrs[i] == key:
|
||||
n.attrs[i+1] = val
|
||||
return
|
||||
n.attrs.add key
|
||||
n.attrs.add val
|
||||
|
||||
proc getAttr*(n: VNode; key: cstring): cstring =
|
||||
for i in countup(0, n.attrs.len-2, 2):
|
||||
if n.attrs[i] == key: return n.attrs[i+1]
|
||||
|
||||
proc len*(x: VNode): int = x.kids.len
|
||||
proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx]
|
||||
proc add*(parent, kid: VNode) = parent.kids.add kid
|
||||
proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1)
|
||||
|
||||
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)];
|
||||
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), key: -1)
|
||||
proc text*(s: cstring): VNode = VNode(kind: VNodeKind.text, text: s, key: -1)
|
||||
|
||||
iterator items*(n: VNode): VNode =
|
||||
for i in 0..<n.kids.len: yield n.kids[i]
|
||||
|
||||
iterator attrs*(n: VNode): (cstring, cstring) =
|
||||
for i in countup(0, n.attrs.len-2, 2):
|
||||
yield (n.attrs[i], n.attrs[i+1])
|
||||
|
||||
proc sameAttrs*(a, b: VNode): bool =
|
||||
if a.attrs.len == b.attrs.len:
|
||||
result = true
|
||||
for i in 0 ..< a.attrs.len:
|
||||
if a.attrs[i] != b.attrs[i]: return false
|
||||
|
||||
proc addEventListener*(n: VNode; event: EventKind; handler: EventHandler) =
|
||||
n.events.add((event, handler))
|
||||
|
||||
template toStringAttr(field) =
|
||||
if n.field != nil:
|
||||
result.add " " & astToStr(field) & " = " & $n.field
|
||||
|
||||
proc toString*(n: VNode; result: var string; indent: int) =
|
||||
for i in 1..indent: result.add ' '
|
||||
if result.len > 0: result.add '\L'
|
||||
result.add "<" & $n.kind
|
||||
toStringAttr(id)
|
||||
toStringAttr(class)
|
||||
for k, v in attrs(n):
|
||||
result.add " " & $k & " = " & $v
|
||||
result.add ">\L"
|
||||
if n.kind == VNodeKind.text:
|
||||
result.add n.text
|
||||
else:
|
||||
if n.text != nil:
|
||||
result.add " value = "
|
||||
result.add n.text
|
||||
for child in items(n):
|
||||
toString(child, result, indent+2)
|
||||
for i in 1..indent: result.add ' '
|
||||
result.add "\L</" & $n.kind & ">"
|
||||
|
||||
proc calcHash*(n: VNode) =
|
||||
if n.validHash: return
|
||||
n.validHash = true
|
||||
var h: Hash = ord n.kind
|
||||
if n.id != nil:
|
||||
h &= "id"
|
||||
h &= n.id
|
||||
if n.class != nil:
|
||||
h &= "class"
|
||||
h &= n.class
|
||||
if n.key >= 0:
|
||||
h &= "k"
|
||||
h &= n.key
|
||||
for k, v in attrs(n):
|
||||
h &= " "
|
||||
h &= k
|
||||
h &= "="
|
||||
h &= v
|
||||
if n.kind == VNodeKind.text or n.text != nil:
|
||||
h &= "t"
|
||||
h &= n.text
|
||||
else:
|
||||
for child in items(n):
|
||||
calcHash(child)
|
||||
h &= child.hash
|
||||
n.hash = h
|
||||
|
||||
proc `$`*(n: VNode): cstring =
|
||||
var res = ""
|
||||
toString(n, res, 0)
|
||||
result = cstring(res)
|
||||
19
bench/implementations/nim-karax/tests/diffDomTests.html
Normal file
19
bench/implementations/nim-karax/tests/diffDomTests.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Diff dom tests</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
|
||||
<div id="ROOT" />
|
||||
|
||||
<script type="text/javascript" src="nimcache/diffDomTests.js"></script>
|
||||
<script type="text/javascript">
|
||||
onload(window.location.pathname.split('/')[1])
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
183
bench/implementations/nim-karax/tests/diffDomTests.nim
Normal file
183
bench/implementations/nim-karax/tests/diffDomTests.nim
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
|
||||
import dom, vdom, times, karax, karaxdsl, jdict, jstrutils, parseutils, sequtils
|
||||
|
||||
var
|
||||
entries: seq[cstring]
|
||||
results: seq[cstring]
|
||||
timeout: Timeout
|
||||
|
||||
proc reset() =
|
||||
results.add cstring"reset started"
|
||||
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
||||
redraw()
|
||||
results.add cstring"reset finished"
|
||||
|
||||
proc checkOrder(order : seq[int]): bool =
|
||||
var ul = getElementById("ul")
|
||||
if ul == nil or len(ul.children) != len(order):
|
||||
return false
|
||||
var pos = 0
|
||||
for child in ul.children:
|
||||
if child.id != $order[pos]:
|
||||
return false
|
||||
inc pos
|
||||
return true
|
||||
|
||||
proc check1() =
|
||||
let result = checkOrder(@[0, 1, 2, 3, 4, 7, 5])
|
||||
if result:
|
||||
results.add cstring"test1 - OK"
|
||||
else:
|
||||
results.add cstring"test1 - FAIL"
|
||||
|
||||
# result: 0 1 2 3 4 7 5
|
||||
proc test1() =
|
||||
results.add cstring"test1 started"
|
||||
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
||||
entries.insert(cstring("7"), 5)
|
||||
redraw()
|
||||
timeout = setTimeout(check1, 20)
|
||||
|
||||
proc check2() =
|
||||
let result = checkOrder(@[8, 0, 1, 2, 3, 4, 7, 5])
|
||||
if result:
|
||||
results.add cstring"test1 - OK"
|
||||
else:
|
||||
results.add cstring"test2 - FAIL"
|
||||
|
||||
# result: 8 0 1 2 3 4 7 5
|
||||
proc test2() =
|
||||
results.add cstring"test2 started"
|
||||
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
||||
entries.insert(cstring("7"), 5)
|
||||
entries.insert(cstring("8"), 0)
|
||||
redraw()
|
||||
timeout = setTimeout(check2, 20)
|
||||
|
||||
proc check3() =
|
||||
let result = checkOrder(@[2, 3, 4, 1])
|
||||
if result:
|
||||
results.add cstring"test3 - OK"
|
||||
else:
|
||||
results.add cstring"test3 - FAIL"
|
||||
|
||||
# result: 2 3 4 1
|
||||
proc test3() =
|
||||
results.add cstring"test3 started"
|
||||
entries = @[cstring("2"), cstring("3"), cstring("4"), cstring("1") ]
|
||||
redraw()
|
||||
timeout = setTimeout(check3, 20)
|
||||
|
||||
proc check4() =
|
||||
let result = checkOrder(@[5, 6, 7, 8])
|
||||
if result:
|
||||
results.add cstring"test4 - OK"
|
||||
else:
|
||||
results.add cstring"test4 - FAIL"
|
||||
|
||||
# result: 5 6 7 8
|
||||
proc test4() =
|
||||
results.add cstring"test4 started"
|
||||
entries = @[cstring("5"), cstring("6"), cstring("7"), cstring("8") ]
|
||||
redraw()
|
||||
timeout = setTimeout(check4, 20)
|
||||
|
||||
proc check5() =
|
||||
let result = checkOrder(@[0, 1, 3, 5, 4, 5])
|
||||
if result:
|
||||
results.add cstring"test5 - OK"
|
||||
else:
|
||||
results.add cstring"test5 - FAIL"
|
||||
|
||||
# result: 0 1 3 5 4 5
|
||||
proc test5() =
|
||||
results.add cstring"test5 started"
|
||||
entries = @[cstring("0"), cstring("1"), cstring("3"), cstring("5"), cstring("4"), cstring("5") ]
|
||||
redraw()
|
||||
timeout = setTimeout(check5, 20)
|
||||
|
||||
proc check6() =
|
||||
let result = checkOrder(@[])
|
||||
if result:
|
||||
results.add cstring"test6 - OK"
|
||||
else:
|
||||
results.add cstring"test6 - FAIL"
|
||||
|
||||
# result: empty
|
||||
proc test6() =
|
||||
results.add cstring"test6 started"
|
||||
entries = @[]
|
||||
redraw()
|
||||
timeout = setTimeout(check6, 20)
|
||||
|
||||
proc check7() =
|
||||
let result = checkOrder(@[2])
|
||||
if result:
|
||||
results.add cstring"test7 - OK"
|
||||
else:
|
||||
results.add cstring"test7 - FAIL"
|
||||
redraw()
|
||||
|
||||
# result: 2
|
||||
proc test7() =
|
||||
results.add cstring"test7 started"
|
||||
entries = @[cstring("2")]
|
||||
redraw()
|
||||
timeout = setTimeout(check7, 20)
|
||||
|
||||
proc createEntry(id: int): VNode =
|
||||
result = buildHtml():
|
||||
button(id="" & $id):
|
||||
text $id
|
||||
|
||||
proc createDom(): VNode =
|
||||
result = buildHtml(tdiv()):
|
||||
ul(id="ul"):
|
||||
for e in entries:
|
||||
createEntry(parseInt(e))
|
||||
for r in results:
|
||||
tdiv:
|
||||
text r
|
||||
|
||||
proc onload() =
|
||||
for i in 0..5: # 0_000:
|
||||
entries.add(cstring($i))
|
||||
|
||||
var dtReset = 100
|
||||
var dtTest = 500
|
||||
|
||||
var t = dtReset
|
||||
timeout = setTimeout(test1, t)
|
||||
t += dtTest
|
||||
timeout = setTimeout(reset, t)
|
||||
t += dtReset
|
||||
|
||||
timeout = setTimeout(test2, t)
|
||||
t += dtTest
|
||||
timeout = setTimeout(reset, t)
|
||||
t += dtReset
|
||||
|
||||
timeout = setTimeout(test3, t)
|
||||
t += dtTest
|
||||
timeout = setTimeout(reset, t)
|
||||
t += dtReset
|
||||
|
||||
timeout = setTimeout(test4, t)
|
||||
t += dtTest
|
||||
timeout = setTimeout(reset, t)
|
||||
t += dtReset
|
||||
|
||||
timeout = setTimeout(test5, t)
|
||||
t += dtTest
|
||||
timeout = setTimeout(reset, t)
|
||||
t += dtReset
|
||||
|
||||
timeout = setTimeout(test6, t)
|
||||
t += dtTest
|
||||
timeout = setTimeout(reset, t)
|
||||
t += dtReset
|
||||
|
||||
timeout = setTimeout(test7, t)
|
||||
|
||||
onload()
|
||||
setRenderer createDom
|
||||
1
bench/implementations/nim-karax/tests/diffDomTests.nims
Normal file
1
bench/implementations/nim-karax/tests/diffDomTests.nims
Normal file
|
|
@ -0,0 +1 @@
|
|||
--path: "../src"
|
||||
Loading…
Add table
Add a link
Reference in a new issue