├── nim.cfg ├── UnitTesting ├── .npmrc ├── README.md ├── jasmine.nim ├── test.nim ├── package.json └── karma.conf.js ├── .gitignore ├── .gifs ├── demo_elmlike1.gif ├── demo_elmlike2.gif └── demo_transitiongroups.gif ├── JsIntegration ├── README.md ├── demo.js ├── demo.nim └── demo.html ├── VueLike1 ├── README.md ├── demo.html ├── counter_component.nim └── demo.nim ├── ElmLike1 ├── README.md ├── demo.html └── demo.nim ├── ElmLike2 ├── README.md ├── demo.html └── demo.nim ├── Refs ├── demo.html └── demo.nim ├── demo.css ├── DemoTransitionGroups ├── demo.html ├── README.md └── demo.nim ├── README.md ├── karax_utils.nim └── .travis.yml /nim.cfg: -------------------------------------------------------------------------------- 1 | --path: "../karax/src" -------------------------------------------------------------------------------- /UnitTesting/.npmrc: -------------------------------------------------------------------------------- 1 | loglevel=silent 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache 2 | debug.nim 3 | node_modules 4 | /UnitTesting/test.js -------------------------------------------------------------------------------- /.gifs/demo_elmlike1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluenote10/KaraxExamples/HEAD/.gifs/demo_elmlike1.gif -------------------------------------------------------------------------------- /.gifs/demo_elmlike2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluenote10/KaraxExamples/HEAD/.gifs/demo_elmlike2.gif -------------------------------------------------------------------------------- /.gifs/demo_transitiongroups.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluenote10/KaraxExamples/HEAD/.gifs/demo_transitiongroups.gif -------------------------------------------------------------------------------- /JsIntegration/README.md: -------------------------------------------------------------------------------- 1 | ## JS Integration 2 | 3 | Just a quick experiment how to embed existing JS code at compile time. 4 | -------------------------------------------------------------------------------- /JsIntegration/demo.js: -------------------------------------------------------------------------------- 1 | 2 | function jsHelloWorld(x) { 3 | console.log("Running jsHelloWorld"); 4 | console.log(x); 5 | return 42; 6 | } -------------------------------------------------------------------------------- /VueLike1/README.md: -------------------------------------------------------------------------------- 1 | ## Vue-like 1 2 | 3 | Example showing functionality similar to Vue components using a "props down, emit up" architecture. 4 | 5 | -------------------------------------------------------------------------------- /ElmLike1/README.md: -------------------------------------------------------------------------------- 1 | ## Elm-like 1 2 | 3 | Example going in the direction of the Elm architecture. 4 | 5 | ![Demo](../.gifs/demo_elmlike1.gif?raw=true "Demo") 6 | -------------------------------------------------------------------------------- /JsIntegration/demo.nim: -------------------------------------------------------------------------------- 1 | 2 | # Embed JS code at compile time 3 | const s = staticRead("demo.js") 4 | {.emit: s.} 5 | 6 | # Write corresponding function signatures 7 | proc jsHelloWorld(x: int): int {.importc, nodecl.} 8 | 9 | discard jsHelloWorld(1) 10 | -------------------------------------------------------------------------------- /ElmLike2/README.md: -------------------------------------------------------------------------------- 1 | ## Elm-like 2 2 | 3 | Example going in the direction of the Elm architecture. 4 | 5 | Instead of currying this example uses local message handlers, 6 | which send a message to a global update function. 7 | 8 | ![Demo](../.gifs/demo_elmlike2.gif?raw=true "Demo") 9 | -------------------------------------------------------------------------------- /Refs/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Elm-like 1 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /VueLike1/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | VueLike 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ElmLike1/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Elm-like 1 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ElmLike2/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Elm-like 2 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | width: 800px; 4 | margin: 20px auto; 5 | padding: 20px; 6 | border: 1px solid #EEE; 7 | } 8 | 9 | .button { 10 | margin: 5px; 11 | } 12 | 13 | .word { 14 | margin: 5px; 15 | padding: 5px; 16 | border: 1px solid #EEE; 17 | display: inline-block; 18 | } 19 | 20 | .animate-on-transform { 21 | transition: transform 1s; 22 | } -------------------------------------------------------------------------------- /JsIntegration/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Demo Transition Groups 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DemoTransitionGroups/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Demo Transition Groups 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Karax Examples 2 | 3 | [![Build Status](https://travis-ci.org/bluenote10/KaraxExamples.svg?branch=master)](https://travis-ci.org/bluenote10/KaraxExamples) 4 | 5 | Some Karax example projects: 6 | 7 | * [Elm-like 1](ElmLike1) 8 | * [Elm-like 2](ElmLike2) 9 | * [Transition Groups](DemoTransitionGroups) 10 | * [Js Integration](JsIntegration) 11 | * [Unit Testing](UnitTesting) 12 | -------------------------------------------------------------------------------- /UnitTesting/README.md: -------------------------------------------------------------------------------- 1 | ## Unit Testing 2 | 3 | Experiment to get headless-browser testing to work. 4 | 5 | ### Usage 6 | 7 | Install dependencies: 8 | 9 | $ npm install 10 | 11 | To run Karma tests: 12 | 13 | $ npm test 14 | 15 | This will internally run the npm scripts `test:build` (which runs the Nim compiler) 16 | and `test:karma` (which runs Karma on the compiler output). 17 | 18 | For a continous watch + build + test cycle run: 19 | 20 | $ npm run test:watch 21 | -------------------------------------------------------------------------------- /DemoTransitionGroups/README.md: -------------------------------------------------------------------------------- 1 | ## Transitions Groups 2 | 3 | Small example of how to implement transition groups in Karax. 4 | 5 | ![Demo](../.gifs/demo_transitiongroups.gif?raw=true "Demo") 6 | 7 | Currently requires a modified version of Karax, see my PR's for Karax. 8 | 9 | Inspired by: 10 | 11 | * Paul Lewis's [FLIP transitions](https://aerotwist.com/blog/flip-your-animations/) 12 | * Vue's [list move transitions](https://vuejs.org/v2/guide/transitions.html#List-Move-Transitions) 13 | -------------------------------------------------------------------------------- /UnitTesting/jasmine.nim: -------------------------------------------------------------------------------- 1 | import future 2 | 3 | proc describe*(description: cstring, body: () -> void) {.importc.} 4 | 5 | proc it*(description: cstring, body: () -> void) {.importc.} 6 | 7 | type 8 | JasmineRequireObj* {.importc.} = ref object 9 | `not`* {.importc: "not".}: JasmineRequireObj 10 | 11 | proc expect*[T](x: T): JasmineRequireObj {.importc.} 12 | 13 | proc toBe*[T](e: JasmineRequireObj, x: T) {.importcpp.} 14 | 15 | #proc `not`*(e: JasmineRequireObj): JasmineRequireObj {.importc.} 16 | 17 | -------------------------------------------------------------------------------- /UnitTesting/test.nim: -------------------------------------------------------------------------------- 1 | 2 | import dom, vdom, times, karax, karaxdsl, jdict, jstrutils, parseutils, sequtils 3 | import future 4 | import jasmine 5 | 6 | static: 7 | echo: "Compiling..." 8 | 9 | # To clear console manually you can use: 10 | # {.emit: "console.log('\u001b[2J\u001b[0;0H');".} 11 | 12 | describe("A test suite"): 13 | 14 | it("should work"): 15 | var a = 1 16 | expect(true).toBe(true) 17 | expect(a).toBe(1) 18 | expect(a).`not`.toBe(2) 19 | 20 | let name = "asdf" 21 | 22 | it(name): 23 | discard 24 | -------------------------------------------------------------------------------- /UnitTesting/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "karax-unit-test-tests", 3 | "version": "0.1.0", 4 | "description": "Testing unit tests", 5 | "scripts": { 6 | "test": "npm run test:build --silent && npm run test:karma --silent", 7 | "test:build": "nim js test.nim", 8 | "test:karma": "karma start karma.conf.js --singleRun", 9 | "test:watch": "watch 'npm run test' . --interval=0.1" 10 | }, 11 | "devDependencies": { 12 | "jasmine": "^2.6.0", 13 | "karma": "^1.7.0", 14 | "karma-jasmine": "^1.1.0", 15 | "karma-phantomjs-launcher": "^1.0.4", 16 | "karma-spec-reporter": "0.0.31", 17 | "watch": "^1.0.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /VueLike1/counter_component.nim: -------------------------------------------------------------------------------- 1 | include karaxprelude 2 | import karaxdsl 3 | import future 4 | 5 | 6 | type 7 | CounterComponent* = ref object 8 | # holds internal state like `data` in Vue 9 | counter*: int 10 | emitInc*: () -> void 11 | emitDec*: () -> void 12 | 13 | 14 | proc render*(self: CounterComponent): VNode = 15 | 16 | proc onInc(ev: Event, n: VNode) = 17 | self.counter += 1 18 | self.emitInc() 19 | 20 | proc onDec(ev: Event, n: VNode) = 21 | self.counter -= 1 22 | self.emitDec() 23 | 24 | result = buildHtml(): 25 | tdiv(): 26 | tdiv(): 27 | text "Counter value: " & $self.counter 28 | button(onclick=onInc): 29 | text "inc" 30 | button(onclick=onDec): 31 | text "dec" 32 | -------------------------------------------------------------------------------- /ElmLike1/demo.nim: -------------------------------------------------------------------------------- 1 | 2 | include karaxprelude 3 | import jstrutils, jdict, kdom 4 | import ../karax_utils 5 | 6 | type 7 | # Elm's `model` 8 | Model = ref object 9 | toggle: bool 10 | 11 | 12 | proc init(): Model = 13 | # Elm's `init` 14 | Model(toggle: true) 15 | 16 | 17 | proc onClick(model: Model, ev: Event, n: VNode) = 18 | # Elm's `update`. Note that we could have 19 | # a global `update` like Elm as well. In this case 20 | # the event handlers would construct a message type 21 | # and pass it to a model update function. 22 | # See second ElmLike demo for an example 23 | kout(ev) 24 | kout(model) 25 | model.toggle = model.toggle xor true 26 | 27 | 28 | proc view(model: Model): VNode = 29 | # Elm's `view`. 30 | result = buildHtml(): 31 | tdiv: 32 | # message handler close over the model using the curry macro 33 | button(onclick=curry(onClick, model)): 34 | text "click me" 35 | tdiv: 36 | text "Toggle state:" 37 | tdiv: 38 | if model.toggle: 39 | text "true" 40 | else: 41 | text "false" 42 | 43 | # Putting it all together 44 | proc runMain() = 45 | var model = init() 46 | 47 | proc renderer(): VNode = 48 | view(model) 49 | 50 | setRenderer renderer 51 | 52 | runMain() 53 | -------------------------------------------------------------------------------- /VueLike1/demo.nim: -------------------------------------------------------------------------------- 1 | include karaxprelude 2 | import karaxdsl, ../karax_utils 3 | import future, sequtils 4 | 5 | import counter_component 6 | 7 | type 8 | MainComponent = ref object 9 | # holds internal state like `data` in Vue 10 | children: seq[CounterComponent] 11 | sumOfCounters: int 12 | 13 | proc render(self: MainComponent): VNode = 14 | result = buildHtml(): 15 | tdiv(): 16 | for child in self.children: 17 | child.render 18 | tdiv(): 19 | text "Sum of counters: " & $self.sumOfCounters 20 | 21 | 22 | proc registerCounterComponent(self: MainComponent, counterInit: int) = 23 | 24 | proc onInc() = 25 | self.sumOfCounters += 1 26 | 27 | proc onDec() = 28 | self.sumOfCounters -= 1 29 | 30 | self.children.add(CounterComponent( 31 | counter: counterInit, 32 | emitInc: onInc, 33 | emitDec: onDec, 34 | )) 35 | 36 | 37 | proc runMain() = 38 | 39 | let initValues = @[2, 5, 3] 40 | let initSumOfCounters = initValues.reduce((a, b) => a + b) 41 | var mainComponent = MainComponent( 42 | sumOfCounters: initSumOfCounters, 43 | children: @[], 44 | ) 45 | 46 | for counterInit in initValues: 47 | mainComponent.registerCounterComponent(counterInit) 48 | 49 | proc renderer(): VNode = 50 | mainComponent.render() 51 | 52 | setRenderer renderer 53 | 54 | runMain() 55 | -------------------------------------------------------------------------------- /ElmLike2/demo.nim: -------------------------------------------------------------------------------- 1 | 2 | include karaxprelude 3 | import jstrutils, jdict, kdom 4 | import ../karax_utils 5 | import future 6 | 7 | type 8 | # Elm's `model` 9 | Model = ref object 10 | items: seq[int] not nil 11 | counter: int 12 | 13 | Message = enum 14 | AddItem, 15 | RemoveItem 16 | 17 | 18 | proc init(): Model = 19 | # Elm's `init` 20 | Model( 21 | items: @[], 22 | counter: 1, 23 | ) 24 | 25 | 26 | proc update(model: Model, msg: Message) = 27 | # Elm's `update` (but in-place) 28 | case msg 29 | of AddItem: 30 | let toAdd = model.counter 31 | model.counter += 1 32 | model.items.add(toAdd) 33 | of RemoveItem: 34 | if model.items.len > 0: 35 | model.items.setLen(model.items.len - 1) 36 | 37 | 38 | proc view(model: Model): VNode = 39 | # Elm's `view`. 40 | 41 | proc onAdd(ev: Event, n: VNode) = 42 | update(model, Message.AddItem) 43 | 44 | proc onRemove(ev: Event, n: VNode) = 45 | update(model, Message.RemoveItem) 46 | 47 | result = buildHtml(): 48 | tdiv: 49 | button(onclick=onAdd): 50 | text "Add item" 51 | button(onclick=onRemove): 52 | text "Remove item" 53 | tdiv: 54 | for item in model.items: 55 | tdiv: 56 | text "Item: " & $item 57 | 58 | # Alternatively, the message handler could be written without 59 | # explicitly creating procs, i.e.: 60 | # button(onclick=(ev: Event, n: VNode) => update(model, Message.AddItem)) 61 | # button(onclick=(ev: Event, n: VNode) => update(model, Message.RemoveItem)) 62 | 63 | 64 | # Putting it all together 65 | proc runMain() = 66 | var model = init() 67 | 68 | proc renderer(): VNode = 69 | view(model) 70 | 71 | setRenderer renderer 72 | 73 | runMain() 74 | -------------------------------------------------------------------------------- /karax_utils.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | import future 3 | 4 | 5 | macro curry*(f: typed, args: varargs[untyped]): untyped = # , 6 | 7 | # echo f.treeRepr 8 | # echo args.treeRepr 9 | 10 | # echo f.getType.treeRepr 11 | # echo f.getTypeInst.treeRepr 12 | # echo f.getTypeImpl.treeRepr 13 | 14 | var tTypeImpl = f.getTypeImpl 15 | # echo tTypeImpl.len 16 | # echo tTypeImpl.kind 17 | # echo tTypeImpl.typeKind 18 | # echo tTypeImpl.treeRepr 19 | 20 | if tTypeImpl.typeKind != ntyProc: 21 | error "curry requires a proc as its first argument, but received: " & f.repr & 22 | " which has typeKind " & $tTypeImpl.typeKind 23 | 24 | # we need the FormalParams which are the first child 25 | let formalParams = tTypeImpl[0] 26 | 27 | # and we have to iterate its childred starting at 1 28 | # because child 0 is the return type 29 | let returnType = formalParams[0] 30 | # echo returnType.treeRepr 31 | 32 | var params = @[ 33 | returnType, 34 | ] 35 | 36 | var call = newCall(f) 37 | for arg in args: 38 | call.add(arg) 39 | 40 | for i in (1 + args.len) ..< formalParams.len: 41 | let child = formalParams[i] 42 | # echo child.treeRepr 43 | if child.kind != nnkIdentDefs: 44 | error "Function parameters are expected to be IdentDefs, but received: " & child.repr & 45 | " which has typeKind " & $child.kind 46 | else: 47 | let nameIdent = newIdentNode($child[0]) 48 | let typeIdent = newIdentNode($child[1]) 49 | params.add(newIdentDefs(name = nameIdent, kind = typeIdent)) 50 | call.add(newIdentNode($child[0])) 51 | 52 | let body = newStmtList(call) 53 | 54 | result = newProc(params=params, body=body, procType=nnkLambda) 55 | # echo result.treeRepr 56 | 57 | 58 | proc reduce*[T](s: seq[T], f: (T, T) -> T): T = 59 | result = T(0) 60 | for x in s: 61 | result = f(result, x) 62 | -------------------------------------------------------------------------------- /UnitTesting/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | 4 | // base path that will be used to resolve all patterns (eg. files, exclude) 5 | basePath: '', 6 | 7 | // frameworks to use 8 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 9 | frameworks: ['jasmine'], 10 | 11 | // list of files / patterns to load in the browser 12 | files: [ 13 | // the main test file is served by Karma 14 | 'nimcache/test.js' 15 | ], 16 | 17 | // list of files to exclude 18 | exclude: [ 19 | ], 20 | 21 | // preprocess matching files before serving them to the browser 22 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 23 | preprocessors: { 24 | }, 25 | 26 | // test results reporter to use 27 | // possible values: 'dots', 'progress' 28 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 29 | reporters: ['spec'], 30 | 31 | // web server port 32 | port: 9876, 33 | 34 | // enable / disable colors in the output (reporters and logs) 35 | colors: true, 36 | 37 | // level of logging 38 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 39 | logLevel: config.LOG_INFO, 40 | 41 | // enable / disable watching file and executing tests whenever any file changes 42 | autoWatch: true, 43 | 44 | // start these browsers 45 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 46 | browsers: ['PhantomJS'], // 'Firefox' 47 | 48 | // Continuous Integration mode 49 | // if true, Karma captures browsers, runs the tests and exits 50 | singleRun: false, 51 | 52 | // Concurrency level 53 | // how many browser should be started simultaneous 54 | concurrency: Infinity 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /Refs/demo.nim: -------------------------------------------------------------------------------- 1 | 2 | include karaxprelude 3 | import jstrutils, jdict, kdom 4 | import ../karax_utils 5 | 6 | type 7 | Model = ref object 8 | counter: int 9 | textA: cstring 10 | textB: cstring 11 | messages: seq[cstring] 12 | 13 | 14 | var vnodeMap = newJDict[cstring, (VNode, VNode)]() 15 | 16 | 17 | proc registerAs(n: VNode, name: cstring): VNode = 18 | if name in vnodeMap: 19 | # store new candidate node 20 | vnodeMap[name] = (vnodeMap[name][0], n) 21 | else: 22 | vnodeMap[name] = (n, n) 23 | result = n 24 | 25 | 26 | proc onClick(model: Model, ev: Event, n: VNode) = 27 | model.counter += 1 28 | 29 | if model.counter mod 5 == 0: 30 | swap(model.textA, model.textB) 31 | 32 | let idsToCheck = [cstring"textA", cstring"textB"] 33 | for id in idsToCheck: 34 | let (vnodeOld, vnodeNew) = vnodeMap[id] 35 | var vnode: VNode 36 | # If the new VNode candidate has a DOM we can store it 37 | if not vnodeNew.dom.isNil: 38 | vnodeMap[id] = (vnodeNew, vnodeNew) 39 | vnode = vnodeNew 40 | # Otherwise the old VNode must still be valid 41 | else: 42 | vnode = vnodeOld 43 | let node = vnode.dom 44 | if not node.isNil: 45 | let bb = node.getBoundingClientRect() 46 | model.messages.add( 47 | cstring"Element: " & id & " has width " & $bb.width 48 | ) 49 | 50 | 51 | proc wordSpan(spanText: cstring): VNode = 52 | result = buildHtml(): 53 | span(class="word"): 54 | text spanText 55 | 56 | proc view(model: Model): VNode = 57 | 58 | let spanA = wordSpan(model.textA).registerAs("textA") 59 | let spanB = wordSpan(model.textB).registerAs("textB") 60 | 61 | result = buildHtml(): 62 | tdiv: 63 | button(onclick=curry(onClick, model)): 64 | text "click me" 65 | spanA 66 | spanB 67 | for message in model.messages: 68 | tdiv: 69 | text message 70 | 71 | 72 | proc runMain() = 73 | var model = Model( 74 | counter: 1, 75 | textA: cstring"Text A", 76 | textB: cstring"Text B (longer than text A)", 77 | messages: @[] 78 | ) 79 | 80 | proc renderer(): VNode = 81 | view(model) 82 | 83 | setRenderer renderer 84 | 85 | runMain() 86 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/nim-lang/Nim/wiki/TravisCI 2 | language: node_js 3 | env: 4 | # Build and test against the master and devel branches of Nim 5 | - BRANCH=master 6 | - BRANCH=devel 7 | matrix: 8 | allow_failures: 9 | # Ignore failures when building against the devel Nim branch 10 | - env: BRANCH=devel 11 | fast_finish: true 12 | install: 13 | - | 14 | if [ ! -x nim-$BRANCH/bin/nim ]; then 15 | git clone -b $BRANCH --depth 1 git://github.com/nim-lang/nim nim-$BRANCH/ 16 | cd nim-$BRANCH 17 | git clone --depth 1 git://github.com/nim-lang/csources csources/ 18 | cd csources 19 | sh build.sh 20 | cd .. 21 | rm -rf csources 22 | bin/nim c koch 23 | ./koch boot -d:release 24 | ./koch nimble 25 | else 26 | cd nim-$BRANCH 27 | git fetch origin 28 | if ! git merge FETCH_HEAD | grep "Already up-to-date"; then 29 | bin/nim c koch 30 | ./koch boot -d:release 31 | ./koch nimble 32 | fi 33 | fi 34 | cd .. 35 | before_script: 36 | - | 37 | echo "Running with Nim from: nim-$BRANCH" 38 | ls -l "nim-$BRANCH/bin" 39 | NIMABSPATH=`readlink -f nim-$BRANCH/bin` 40 | export PATH="${NIMABSPATH}${PATH:+:$PATH}" 41 | echo "Path: $PATH" 42 | cd "$TRAVIS_BUILD_DIR/.." 43 | git clone git://github.com/pragmagic/karax.git 44 | cd karax 45 | nimble install 46 | script: 47 | - | 48 | cd "$TRAVIS_BUILD_DIR/ElmLike1" 49 | nim js demo.nim 50 | - | 51 | cd "$TRAVIS_BUILD_DIR/ElmLike2" 52 | nim js demo.nim 53 | - | 54 | cd "$TRAVIS_BUILD_DIR/VueLike1" 55 | nim js demo.nim 56 | - | 57 | cd "$TRAVIS_BUILD_DIR/DemoTransitionGroups" 58 | nim js demo.nim 59 | echo "Ignoring for now until PR is merged" 60 | - | 61 | cd "$TRAVIS_BUILD_DIR/JsIntegration" 62 | nim js demo.nim 63 | - | 64 | cd "$TRAVIS_BUILD_DIR/UnitTesting" 65 | npm install 66 | npm test 67 | # Optional: build docs. 68 | # - nim doc --docSeeSrcUrl:https://github.com/AUTHOR/MYPROJECT/blob/master --project MYFILE.nim 69 | cache: 70 | directories: 71 | - nim-master 72 | - nim-devel 73 | notifications: 74 | email: false -------------------------------------------------------------------------------- /DemoTransitionGroups/demo.nim: -------------------------------------------------------------------------------- 1 | 2 | include karaxprelude 3 | import jstrutils, jdict, dom 4 | import future, sequtils, random 5 | import ../karax_utils 6 | 7 | type 8 | IdentifyableString = object 9 | s: string 10 | id: int 11 | 12 | Position = object 13 | x: float 14 | y: float 15 | 16 | Model = ref object 17 | text: seq[IdentifyableString] 18 | 19 | positions: JDict[string, Position] 20 | 21 | 22 | proc onClick(model: Model, ev: Event, n: VNode) = 23 | 24 | for word in model.text: 25 | let id = "word-" & $word.id 26 | let element = getElementById(id) 27 | if not element.isNil: 28 | let boundingBox = element.getBoundingClientRect() 29 | model.positions[id] = Position( 30 | x: boundingBox.left, 31 | y: boundingBox.top, 32 | ) 33 | 34 | shuffle(model.text) 35 | 36 | 37 | proc startTransform(elements: seq[Element]): () -> void = 38 | result = proc() = 39 | for element in elements: 40 | element.classList.add("animate-on-transform") 41 | element.style.transform = cstring"" 42 | 43 | 44 | proc postRenderCallback(model: Model) = 45 | kout("post render".cstring) 46 | 47 | var deltas = newSeq[(Element, float, float)]() 48 | 49 | # using a two pass approach to avoid layout thrashing 50 | for word in model.text: 51 | var id = "word-" & $word.id 52 | var element = getElementById(id) 53 | if not element.isNil: 54 | 55 | if model.positions.contains(id): 56 | let boundingBox = element.getBoundingClientRect() 57 | let oldPos = model.positions[id] 58 | let newPos = Position( 59 | x: boundingBox.left, 60 | y: boundingBox.top, 61 | ) 62 | let dx = oldPos.x - newPos.x 63 | let dy = oldPos.y - newPos.y 64 | deltas &= (element, dx, dy) 65 | 66 | # second pass: apply transforms 67 | for element, dx, dy in deltas.items(): 68 | let transform = "translateX(" & $dx & "px) translateY(" & $dy & "px)" 69 | # kout(oldPos, newPos) 70 | element.style.transform = transform.cstring 71 | 72 | # request start of animation for affected elements 73 | let elements = deltas.map(x => x[0]) 74 | reqFrame(startTransform(elements)) 75 | 76 | 77 | proc view(model: Model): VNode = 78 | result = buildHtml(): 79 | tdiv: 80 | button(class="button", onclick=curry(onClick, model)): 81 | text "Shuffle" 82 | tdiv: 83 | for word in model.text: 84 | let id = "word-" & $word.id 85 | span(class="word", id=id): 86 | text word.s.cstring 87 | 88 | 89 | proc runMain() = 90 | 91 | # A pity that `pairs` can't be chained. Would be nice to write: 92 | # let text = textOrig.cycle(10) 93 | # .pairs() 94 | # .map((i, w) => IdentifyableString(s: w, id: i)) 95 | let textOrig = @["Entropy", "isn’t", "what", "it", "used", "to", "be."] 96 | let text = toSeq(pairs(textOrig.cycle(10))).map( 97 | t => IdentifyableString(s: t.val, id: t.key) 98 | ) 99 | 100 | var model = Model( 101 | text: text, 102 | positions: newJDict[string, Position]() 103 | ) 104 | 105 | proc renderer(): VNode = 106 | view(model) 107 | 108 | setRenderer renderer, curry(postRenderCallback, model) 109 | 110 | runMain() 111 | 112 | --------------------------------------------------------------------------------