├── .gitignore ├── LICENSE ├── README.md ├── preview.gif ├── project.clj ├── publish.sh ├── resources └── public │ ├── cljs.svg │ ├── css │ ├── keys.css │ └── main.css │ ├── index.html │ ├── index_release.html │ └── js.svg ├── sketch.jpg ├── src-dev └── solar_system_of_js │ └── dev.cljs ├── src └── solar_system_of_js │ ├── actions.cljs │ ├── animate.cljs │ ├── canvas.cljs │ ├── caption.cljs │ ├── control.cljs │ ├── core.cljs │ ├── draw.cljs │ ├── math.cljs │ ├── nav.cljs │ └── state.cljs └── title.png /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .lein-deps-sum 8 | .lein-repl-history 9 | .lein-plugins/ 10 | figwheel_server.log 11 | 12 | resources/public/js/out 13 | resources/public/js/out-adv 14 | resources/public/js/*.js 15 | 16 | *.swp 17 | 18 | hosted/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2015 Shaun Williams 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![title](title.png)](http://shaunlebron.github.io/solar-system-of-js/) 2 | [![preview](preview.gif)](http://shaunlebron.github.io/solar-system-of-js/) 3 | 4 | __[>> View the "Solar System of JS"](http://shaunlebron.github.io/solar-system-of-js/)__ 5 | 6 | I made this to visualize the __current state of languages on the JS platform__, 7 | because I am frankly overwhelmed by all the different activity happening in 8 | this space. It was originally motivated by a desire to bring ClojureScript 9 | into this view, as a language that I think has simple, fundamental solutions to 10 | a lot of the problems that are fragmenting JS. 11 | 12 | ## Implementation 13 | 14 | The code is written in \* 16 | ClojureScript. It implements its own animated presentation architecture that 17 | hopefully serves as an example for interesting things that can be done with the 18 | language. 19 | 20 | ###### Some features: 21 | 22 | - frames are drawn to a Canvas using the 2D api 23 | - all animations tap a `core.async` channel to receive a delta-time `dt` value every frame. 24 | - slide transitions are driven by an [actions spec] to animate [state data] 25 | - the incremental actions spec allows us to jump ahead to specific slides (for URL routing) 26 | - the [drawing] is a pure function of the application state (basically) 27 | 28 | ###### File Descriptions: 29 | 30 | - [`core.cljs`](src/solar_system_of_js/core.cljs)- entry point 31 | - [`actions.cljs`](src/solar_system_of_js/actions.cljs)- slide transition state actions 32 | - [`animate.cljs`](src/solar_system_of_js/animate.cljs)- animation utility (using core.async) 33 | - [`canvas.cljs`](src/solar_system_of_js/canvas.cljs)- canvas initialization and api wrapper 34 | - [`caption.cljs`](src/solar_system_of_js/caption.cljs)- caption setter in markdown 35 | - [`control.cljs`](src/solar_system_of_js/control.cljs)- touch/key controls 36 | - [`draw.cljs`](src/solar_system_of_js/draw.cljs)- all drawing functions 37 | - [`math.cljs`](src/solar_system_of_js/math.cljs)- math aliases 38 | - [`nav.cljs`](src/solar_system_of_js/nav.cljs)- slide navigation and routing 39 | - [`state.cljs`](src/solar_system_of_js/state.cljs)- state of the application 40 | 41 | _\* The [logo] is part of a branding effort for a proper ClojureScript 42 | website that we are building this year, with docs pages and tutorials._ 43 | 44 | ## Development Setup 45 | 46 | 1. Install [leiningen] 47 | 2. Run this to get a live-reload compiler, http server, and REPL: 48 | 49 | ``` 50 | $ lein figwheel dev 51 | ``` 52 | 53 | 3. Open 54 | 55 | ## Contact 56 | 57 | - email: 58 | - twitter: [@shaunlebron](http://twitter.com/shaunlebron) 59 | 60 | ## License 61 | 62 | MIT 63 | 64 | ---- 65 | 66 | [![sketch](sketch.jpg)](https://raw.githubusercontent.com/shaunlebron/solar-system-of-js/master/sketch.jpg) 67 | 68 | [actions spec]:https://github.com/shaunlebron/solar-system-of-js/blob/master/src/solar_system_of_js/actions.cljs#L160 69 | [state data]:https://github.com/shaunlebron/solar-system-of-js/blob/master/src/solar_system_of_js/state.cljs#L5 70 | [drawing]:https://github.com/shaunlebron/solar-system-of-js/blob/master/src/solar_system_of_js/draw.cljs#L459 71 | [leiningen]:http://leiningen.org/ 72 | [logo]:https://github.com/oakmac/cljs.info/blob/master/00-scrap/cljs_logo-01.svg 73 | 74 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunlebron/solar-system-of-js/a5c53c9727622ac023963766323b6eb5d03f5226/preview.gif -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject solar-system-of-js "0.1.0-SNAPSHOT" 2 | :description "Visualizing the state of JS as a solar system" 3 | :url "https://github.com/shaunlebron/solar-system-of-js" 4 | 5 | :dependencies [[org.clojure/clojure "1.6.0"] 6 | [org.clojure/clojurescript "0.0-3058"] 7 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 8 | [figwheel "0.2.5"] 9 | [cljsjs/hammer "2.0.4-4"] 10 | [markdown-clj "0.9.65"] 11 | [hiccups "0.3.0"]] 12 | 13 | :node-dependencies [[source-map-support "0.2.8"]] 14 | 15 | :plugins [[lein-cljsbuild "1.0.4"] 16 | [lein-npm "0.4.0"] 17 | [lein-figwheel "0.2.5"]] 18 | 19 | :source-paths ["src" "target/classes"] 20 | 21 | :clean-targets ["resources/public/js/out" 22 | "resources/public/js/out-adv"] 23 | 24 | :figwheel {:http-server-root "public" ;; this will be in resources/ 25 | :server-port 3449 ;; default 26 | :css-dirs ["resources/public/css"] 27 | } 28 | 29 | :cljsbuild { 30 | :builds [{:id "dev" 31 | :source-paths ["src" "src-dev"] 32 | :compiler { 33 | :output-to "resources/public/js/solar_system_of_js.js" 34 | :output-dir "resources/public/js/out" 35 | :optimizations :none 36 | :cache-analysis true 37 | :source-map true}} 38 | {:id "release" 39 | :source-paths ["src"] 40 | :compiler { 41 | :output-to "resources/public/js/solar_system_of_js.min.js" 42 | :output-dir "resources/public/js/out-adv" 43 | :optimizations :advanced 44 | :pretty-print false}}]}) 45 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd `dirname $0` 6 | 7 | if [ ! -d hosted ]; then 8 | git clone git@github.com:shaunlebron/solar-system-of-js.git hosted 9 | fi 10 | 11 | cd hosted 12 | 13 | # checkout gh-pages branch 14 | git checkout gh-pages 15 | 16 | # make sure gh-pages is up-to-date 17 | git pull 18 | 19 | # remove all files 20 | git rm -rf . 21 | 22 | # add new report files 23 | lein cljsbuild once release 24 | cp -r ../resources/public/* . 25 | 26 | # clean out unneeded 27 | rm -rf js/out-adv \ 28 | js/out \ 29 | js/solar_system_of_js.js 30 | 31 | # choose production page 32 | mv index_release.html index.html 33 | 34 | git add . 35 | git commit -m "auto-update" 36 | 37 | # publish to website 38 | git push origin gh-pages 39 | -------------------------------------------------------------------------------- /resources/public/cljs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 12 | 13 | 17 | 22 | 23 | 25 | 27 | 28 | -------------------------------------------------------------------------------- /resources/public/css/keys.css: -------------------------------------------------------------------------------- 1 | /** 2 | * KEYS.css 3 | * 4 | * A simple stylesheet for rendering beautiful keyboard-style elements. 5 | * 6 | * Author: Michael Hüneburg 7 | * Website: http://michaelhue.com/keyscss 8 | * License: MIT License (see LICENSE.txt) 9 | */ 10 | 11 | /* Base style, essential for every key. */ 12 | kbd, .key { 13 | display: inline; 14 | display: inline-block; 15 | min-width: 1em; 16 | padding: .2em .3em; 17 | font: normal .85em/1 "Lucida Grande", Lucida, Arial, sans-serif; 18 | text-align: center; 19 | text-decoration: none; 20 | -moz-border-radius: .3em; 21 | -webkit-border-radius: .3em; 22 | border-radius: .3em; 23 | border: none; 24 | cursor: default; 25 | -moz-user-select: none; 26 | -webkit-user-select: none; 27 | user-select: none; 28 | } 29 | kbd[title], .key[title] { 30 | cursor: help; 31 | } 32 | 33 | /* Dark style for display on light background. This is the default style. */ 34 | kbd, kbd.dark, .dark-keys kbd, .key, .key.dark, .dark-keys .key { 35 | background: rgb(80, 80, 80); 36 | background: -moz-linear-gradient(top, rgb(60, 60, 60), rgb(80, 80, 80)); 37 | background: -webkit-gradient(linear, left top, left bottom, from(rgb(60, 60, 60)), to(rgb(80, 80, 80))); 38 | color: rgb(250, 250, 250); 39 | text-shadow: -1px -1px 0 rgb(70, 70, 70); 40 | -moz-box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); 41 | -webkit-box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); 42 | box-shadow: inset 0 0 1px rgb(150, 150, 150), inset 0 -.05em .4em rgb(80, 80, 80), 0 .1em 0 rgb(30, 30, 30), 0 .1em .1em rgba(0, 0, 0, .3); 43 | } 44 | 45 | /* Light style for display on dark background. */ 46 | kbd.light, .light-keys kbd, .key.light, .light-keys .key { 47 | background: rgb(250, 250, 250); 48 | background: -moz-linear-gradient(top, rgb(210, 210, 210), rgb(255, 255, 255)); 49 | background: -webkit-gradient(linear, left top, left bottom, from(rgb(210, 210, 210)), to(rgb(255, 255, 255))); 50 | color: rgb(50, 50, 50); 51 | text-shadow: 0 0 2px rgb(255, 255, 255); 52 | -moz-box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); 53 | -webkit-box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); 54 | box-shadow: inset 0 0 1px rgb(255, 255, 255), inset 0 0 .4em rgb(200, 200, 200), 0 .1em 0 rgb(130, 130, 130), 0 .11em 0 rgba(0, 0, 0, .4), 0 .1em .11em rgba(0, 0, 0, .9); 55 | } 56 | -------------------------------------------------------------------------------- /resources/public/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #1a1a1a; 3 | margin: 0; 4 | font: 16px "Open Sans", Helvetica, sans-serif; 5 | } 6 | 7 | body, html { 8 | min-width: 1024px; 9 | min-height: 660px; 10 | height: 100%; 11 | } 12 | 13 | #jsLogo { 14 | display: none; 15 | } 16 | 17 | #cljsLogo { 18 | height: 40px; 19 | opacity: 0.5; 20 | vertical-align: middle; 21 | transition: opacity 0.2s linear; 22 | } 23 | 24 | #container { 25 | display: -webkit-box; 26 | display: -moz-box; 27 | display: -ms-flexbox; 28 | display: -webkit-flex; 29 | display: flex; 30 | 31 | flex-direction: column; 32 | justify-content: center; 33 | align-items: center; 34 | align-content: center; 35 | -webkit-flex-direction: column; 36 | -webkit-justify-content: center; 37 | -webkit-align-items: center; 38 | -webkit-align-content: center; 39 | 40 | width: 100%; 41 | height: 100%; 42 | } 43 | 44 | #canvas-container { 45 | width: 100%; 46 | height: 70%; 47 | text-align: center; 48 | } 49 | 50 | #canvas { 51 | width: auto; 52 | height: auto; 53 | max-width: 100%; 54 | max-height: 100%; 55 | border-radius: 5px; 56 | } 57 | 58 | #caption { 59 | transition: opacity 1s; 60 | opacity: 0; 61 | 62 | margin: 20px auto; 63 | padding: 20px 40px; 64 | max-width: 900px; 65 | color: #AAA; 66 | background: #2a2a2a; 67 | text-align: center; 68 | border-radius: 5px; 69 | } 70 | 71 | #canvas-footer { 72 | position: relative; 73 | height: 80px; 74 | opacity: 0; 75 | transition: opacity 1s; 76 | } 77 | 78 | #keys { 79 | color: #AAA; 80 | position: absolute; 81 | text-align: right; 82 | display: block; 83 | opacity: 0.2; 84 | right: 10px; 85 | top: 10px; 86 | transition: opacity 0.2s; 87 | cursor: default; 88 | } 89 | 90 | #keys:hover { 91 | opacity: 0.5; 92 | } 93 | 94 | #keys tr td:last-child { 95 | text-align: left; 96 | } 97 | 98 | #caption p { 99 | margin: 5px; 100 | } 101 | 102 | #caption a { 103 | color: #EEE; 104 | text-decoration: none; 105 | border-bottom: 2px solid #666; 106 | } 107 | 108 | #caption i.fa { 109 | font-size: 1.2em; 110 | vertical-align: middle; 111 | } 112 | 113 | #github { 114 | padding: 20px; 115 | color: #444; 116 | display: block; 117 | width: 260px; 118 | margin: 0 auto; 119 | text-align: center; 120 | } 121 | 122 | a { 123 | color: inherit; 124 | text-decoration: none; 125 | } 126 | 127 | #github:hover #cljsLogo { 128 | opacity: 1; 129 | } 130 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Solar System of JS 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /resources/public/index_release.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Solar System of JS 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/public/js.svg: -------------------------------------------------------------------------------- 1 | 2 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /sketch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunlebron/solar-system-of-js/a5c53c9727622ac023963766323b6eb5d03f5226/sketch.jpg -------------------------------------------------------------------------------- /src-dev/solar_system_of_js/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns solar-system-of-js.dev 2 | (:require 3 | solar-system-of-js.core 4 | [figwheel.client :as fw])) 5 | 6 | ;; start figwheel 7 | (fw/start {}) 8 | 9 | -------------------------------------------------------------------------------- /src/solar_system_of_js/actions.cljs: -------------------------------------------------------------------------------- 1 | (ns solar-system-of-js.actions 2 | (:require-macros 3 | [cljs.core.async.macros :refer [go go-loop]]) 4 | (:require 5 | [cljs.core.async :refer [> (filter immediate? step-map) (skip-step!)) 136 | anim-chan (->> (filter animation? step-map) (multi-animate!)) 137 | go-block-chans (->> (filter custom? step-map) (map second) (map :go-block) (mapv #(%))) 138 | wait-chans (cons anim-chan go-block-chans)] 139 | (go 140 | (doseq [c wait-chans] 141 | (") 419 | [:cam :x] {:a :_ :b 0 :duration 2} 420 | } 421 | 422 | ])) 423 | 424 | ;;---------------------------------------------------------------------- 425 | ;; Action Loops 426 | ;;---------------------------------------------------------------------- 427 | 428 | (defn update-orbit! 429 | [name- dt] 430 | (let [v (* dt (get-in @state [name- :angle-speed]))] 431 | (swap! state update-in [name- :angle] + v))) 432 | 433 | (defn tick-orbits! 434 | [dt] 435 | (when (:enable-orbits? @state) 436 | (update-orbit! :coffeescript dt) 437 | (update-orbit! :dart dt) 438 | (update-orbit! :clojurescript dt))) 439 | 440 | (defn tick-radar! 441 | [dt] 442 | (when (:enable-orbits? @state) 443 | (swap! state update-in [:radar :offset] + (* dt 400)))) 444 | 445 | (defn start-loops! 446 | "These are looping animations that cannot be represented as slide transitions. 447 | (Slide transitions have to end at some point.)" 448 | [] 449 | (let [c (chan)] 450 | (tap tick-tap c) 451 | (go-loop [] 452 | (let [dt ( tween 57 | (keyword? tween) tweens)) 58 | 59 | (defn animate! 60 | "Pass given animation values to the given callback. 61 | Returns a channel that closes when done." 62 | [state-path {:keys [a b duration tween] :or {tween :swing} :as opts}] 63 | (let [tween (resolve-tween tween) 64 | c (chan) 65 | resolve-var #(if (= % :_) (get-in @state state-path) %) 66 | a (resolve-var a) 67 | dv (- b a)] 68 | (tap tick-tap c) 69 | (go-loop [t 0] 70 | (let [dt ( (/ t duration) 73 | (min 1) 74 | (tween)) 75 | v (+ a (* percent dv))] 76 | (swap! state assoc-in state-path v) 77 | (when (< t duration) 78 | (recur t))) 79 | (untap tick-tap c)))) 80 | 81 | (defn multi-animate! 82 | "Helper for concurrent animations with `animate!`. 83 | Returns a channel that closes when all are done." 84 | [pairs] 85 | (let [anims (mapv #(apply animate! %) pairs)] 86 | (go 87 | (doseq [a anims] 88 | (html]])) 5 | 6 | (defn set-caption! 7 | [caption] 8 | (let [el (.getElementById js/document "caption")] 9 | 10 | (let [html (md->html caption)] 11 | (aset el "style" "opacity" "1") 12 | (aset el "innerHTML" html)) 13 | 14 | ;; make all links open in new tab 15 | (let [elements (.querySelectorAll el "a")] 16 | (dotimes [i (aget elements "length")] 17 | (let [el (aget elements i)] 18 | (.setAttribute el "target" "_blank")))))) 19 | 20 | (defn on-caption-change 21 | [_key _atom {old-caption :caption} {new-caption :caption}] 22 | (when (not= old-caption new-caption) 23 | (set-caption! new-caption))) 24 | 25 | (add-watch state :caption-watcher on-caption-change) 26 | -------------------------------------------------------------------------------- /src/solar_system_of_js/control.cljs: -------------------------------------------------------------------------------- 1 | (ns solar-system-of-js.control 2 | (:require 3 | [solar-system-of-js.actions :refer [slide-actions]] 4 | [solar-system-of-js.nav :refer [skip-slide! next-slide! prev-slide!]] 5 | [solar-system-of-js.canvas :refer [canvas]] 6 | cljsjs.hammer)) 7 | 8 | (def key-names 9 | {37 :left 10 | 38 :up 11 | 39 :right 12 | 40 :down 13 | 32 :space}) 14 | 15 | (def key-name #(-> % .-keyCode key-names)) 16 | 17 | (defn key-down [e] 18 | (let [kname (key-name e) 19 | shift (.-shiftKey e)] 20 | (case kname 21 | :space (do 22 | (next-slide!) 23 | (.preventDefault e)) 24 | :left (do 25 | (prev-slide!) 26 | (.preventDefault e)) 27 | :right (do 28 | (if shift 29 | (skip-slide!) 30 | (next-slide!)) 31 | (.preventDefault e)) 32 | nil))) 33 | 34 | (defn on-swipe! 35 | [type-] 36 | (case type- 37 | "tap" (next-slide!) 38 | "swipeleft" (next-slide!) 39 | "swiperight" (prev-slide!) 40 | nil)) 41 | 42 | (defn init-touch! 43 | [] 44 | (doto (js/Hammer. canvas) 45 | (.on "swipeleft swiperight tap" 46 | #(on-swipe! (aget % "type"))))) 47 | 48 | (defn init-controls! 49 | [] 50 | (init-touch!) 51 | (.addEventListener js/window "keydown" key-down)) 52 | 53 | -------------------------------------------------------------------------------- /src/solar_system_of_js/core.cljs: -------------------------------------------------------------------------------- 1 | (ns solar-system-of-js.core 2 | (:require-macros 3 | [cljs.core.async.macros :refer [go go-loop]] 4 | [hiccups.core :refer [html]]) 5 | (:require 6 | hiccups.runtime 7 | [cljs.core.async :refer [js {:google {:families families} :active #(close! c)})) 41 | c)) 42 | 43 | (defn main 44 | [] 45 | 46 | (let [loading-fonts (load-fonts! ["Roboto:100,300,400,700" "Open Sans"])] 47 | (init-page!) 48 | 49 | ;; initialize drawing canvas 50 | (init-canvas!) 51 | 52 | ;; initialize touch and key controls 53 | (init-controls!) 54 | 55 | ;; start animation heartbeat 56 | (start-ticking!) 57 | 58 | ;; do some state setup for the first slide 59 | (init-first-slide!) 60 | 61 | ;; go to slide listed in the url hash 62 | (sync-slide-to-hash!) 63 | 64 | ;; start any animation loops 65 | (start-loops!) 66 | 67 | ;; wait until fonts have loaded to start drawing (self-schedules after first draw) 68 | (go 69 | ( @state :js-face :r) 140 | dr (get-core-dr r) 141 | cr (- (get-core-radius i r) (/ dr 2))] 142 | 143 | ;; highlight the layer 144 | (line-width! dr) 145 | (circle! 0 0 cr) 146 | (stroke-style! "#1EA") 147 | (stroke!)))) 148 | 149 | (defn draw-es-captions! 150 | [{:keys [es3 es5 es6 es7 es8]}] 151 | (save!) 152 | (doseq [[i es] (map-indexed vector [es3 es5 es6 es7 es8])] 153 | (when-not (zero? (:alpha es)) 154 | (save!) 155 | (global-alpha! (:alpha es)) 156 | (let [angle (/ PI 4) 157 | dx (cos angle) 158 | dy (sin angle) 159 | r (-> @state :js-face :r) 160 | dr (get-core-dr r) 161 | cr (- (get-core-radius i r) (/ dr 2)) 162 | y (- (* dy cr)) 163 | x0 (* dx cr) 164 | x1 250 165 | a 10 166 | at (/ a 3)] 167 | 168 | ;; highlight the layer 169 | (draw-highlight-layer! i) 170 | 171 | ;; point at layer 172 | (line-width! 5) 173 | (line-cap! "round") 174 | (stroke-line! x0 y x1 y) 175 | (stroke!) 176 | 177 | ;; draw caption 178 | (let [layer (get core-layers i)] 179 | (text-baseline! "middle") 180 | (text-align! "left") 181 | (fill-style! "#EEE") 182 | (font! "100 40px Roboto") 183 | (fill-text! (:name layer) (+ 10 x1) y) 184 | (fill-style! "#AAA") 185 | (font! "300 14px Roboto") 186 | (fill-text! (:desc layer) (+ 15 x1) (+ 30 y))) 187 | 188 | ) 189 | (restore!) 190 | )) 191 | (restore!)) 192 | 193 | (defn draw-sign! 194 | [{:keys [x y alpha font-alpha highlight]} caption offset] 195 | (when-not (zero? alpha) 196 | (save!) 197 | (translate! x y) 198 | (let [a 10 199 | w 100 200 | h 30] 201 | (translate! (- (* 2 a (* 1.2 offset))) 0) 202 | 203 | (save!) 204 | (global-alpha! alpha) 205 | (begin-path!) 206 | (move-to! (- a w) (- h)) 207 | (line-to! (+ a w) (- h)) 208 | (line-to! (- w a) (+ h)) 209 | (line-to! (- (+ w a)) (+ h)) 210 | (fill-style! (if highlight "#1EA" "#EEE")) 211 | (fill!) 212 | (restore!) 213 | 214 | (global-alpha! font-alpha) 215 | (text-baseline! "middle") 216 | (text-align! "center") 217 | (font! "300 24px Roboto") 218 | (fill-style! "#222") 219 | (fill-style! (if highlight "#222" "#222")) 220 | (fill-text! caption 0 0)) 221 | (restore!))) 222 | 223 | (defn draw-transpiler! 224 | [opts] 225 | (draw-sign! opts "TRANSPILER" 0)) 226 | 227 | (defn draw-linter! 228 | [opts] 229 | (draw-sign! opts "LINTER" 1)) 230 | 231 | (defn draw-modulesys! 232 | [opts] 233 | (draw-sign! opts "MODULE SYS" 2)) 234 | 235 | (defn draw-static-arc! 236 | [start-a da r-percent color] 237 | (save!) 238 | 239 | (let [max-r (- (-> @state :static :sphere :r) 24) 240 | min-r (-> @state :js-face :r) 241 | dr (- max-r min-r) 242 | r (+ min-r (* r-percent dr))] 243 | 244 | (fill-style! color) 245 | (begin-path!) 246 | (move-to! 0 0) 247 | (arc! 0 0 r start-a (+ start-a da) false) 248 | (close-path!) 249 | (fill!) 250 | ) 251 | (restore!)) 252 | 253 | (defn draw-static-text! 254 | [{:keys [alpha]} text y] 255 | (when-not (zero? alpha) 256 | (save!) 257 | (global-alpha! alpha) 258 | (text-baseline! "middle") 259 | (text-align! "left") 260 | (font! "300 100px Roboto") 261 | (fill-style! "#FFF") 262 | (fill-text! text 800 y) 263 | (restore!))) 264 | 265 | (defn draw-typescript! 266 | [opts] 267 | (draw-static-text! opts "TYPESCRIPT" -100)) 268 | 269 | (defn draw-flow! 270 | [opts] 271 | (draw-static-text! opts "FLOW" 0)) 272 | 273 | (defn draw-closure! 274 | [opts] 275 | (draw-static-text! opts "CLOSURE" 100)) 276 | 277 | (defn draw-soundscript! 278 | [opts] 279 | (draw-static-text! opts "SOUNDSCRIPT" 200)) 280 | 281 | 282 | (defn draw-staticsphere! 283 | [{:keys [arcs alpha r]}] 284 | (save!) 285 | (when-not (zero? alpha) 286 | (global-alpha! alpha) 287 | (circle! 0 0 r) 288 | (fill-style! "#BCC") 289 | (fill!) 290 | 291 | (let [max-arcs 4 292 | full-arcs (Math/floor arcs) 293 | partial-arc (- arcs full-arcs) 294 | top-a (/ PI -2) 295 | da (/ (* 2 PI) max-arcs) 296 | num-greyed-arcs (cond-> full-arcs 297 | (and (pos? full-arcs) 298 | (zero? partial-arc)) dec) 299 | ] 300 | 301 | ;; draw full arcs 302 | (doseq [i (range full-arcs)] 303 | (let [color (if (< i num-greyed-arcs) "#CDD" "#FFF")] 304 | (draw-static-arc! (+ top-a (* i da)) da 1 color))) 305 | 306 | ;; draw partial arc 307 | (when (pos? partial-arc) 308 | (draw-static-arc! (+ top-a (* full-arcs da)) da partial-arc "#FFF")) 309 | 310 | (when (= 1 alpha) 311 | (line-width! 24) 312 | (stroke-style! "#BCC") 313 | (let [r (-> @state :static :sphere :r)] 314 | (dotimes [i max-arcs] 315 | (stroke-line! 0 0 0 r) 316 | (rotate! da)))) 317 | ) 318 | ) 319 | (restore!)) 320 | 321 | (defn draw-static! 322 | [{:keys [title sphere angle typescript soundscript flow closure]}] 323 | (when-not (zero? (:alpha sphere)) 324 | (save!) 325 | 326 | (save!) 327 | (global-alpha! (:alpha title)) 328 | (font! "300 90px Roboto") 329 | (text-baseline! "middle") 330 | (text-align! "center") 331 | (fill-style! "#677") 332 | (fill-text! "STATIC TYPING" 0 -600) 333 | (restore!) 334 | 335 | (draw-staticsphere! sphere) 336 | (draw-typescript! typescript) 337 | (draw-soundscript! soundscript) 338 | (draw-flow! flow) 339 | (draw-closure! closure) 340 | 341 | (restore!))) 342 | 343 | (defn draw-orbit! 344 | [{:keys [r]}] 345 | (line-width! 10) 346 | (circle! 0 0 r) 347 | (stroke-style! "#566") 348 | (stroke!)) 349 | 350 | (defn draw-radar! 351 | [{:keys [orbit offset]}] 352 | (when orbit 353 | (save!) 354 | (let [o (-> @state orbit) 355 | r (:r o) 356 | dx (cos (:angle o)) 357 | dy (sin (:angle o)) 358 | gap 40 359 | offset (mod offset (* 2 gap)) 360 | x (* (:r o) dx) 361 | y (* (:r o) dy) 362 | lx (- dy) 363 | ly dx 364 | rx dy 365 | ry (- dx) 366 | ] 367 | (global-alpha! (/ (:alpha o) 2)) 368 | (line-width! 20) 369 | (stroke-style! "#f7df1e") 370 | 371 | (loop [i 0] 372 | (let [r0 (+ offset (* i gap)) 373 | w (* (+ 0.2 (/ r0 r)) gap) 374 | x0 (+ (- x (* dx r0)) (* lx w)) 375 | y0 (+ (- y (* dy r0)) (* ly w)) 376 | x1 (+ (- x (* dx r0)) (* rx w)) 377 | y1 (+ (- y (* dy r0)) (* ry w))] 378 | (when (<= r0 r) 379 | (stroke-line! x0 y0 x1 y1) 380 | (recur (+ i 1))))) 381 | ) 382 | (restore!))) 383 | 384 | (defn draw-planet! 385 | [{:keys [size angle r highlight]}] 386 | (let [x (* r (cos angle)) 387 | y (* r (sin angle))] 388 | 389 | (circle! x y size) 390 | (fill-style! (if highlight "#FFF" "#566")) 391 | (fill!) 392 | )) 393 | 394 | (defn draw-coffeescript! 395 | [{:keys [alpha highlight] :as opts}] 396 | (when-not (zero? alpha) 397 | (save!) 398 | (global-alpha! alpha) 399 | (draw-orbit! opts) 400 | (draw-planet! opts) 401 | (when highlight 402 | (font! "100 200px Roboto") 403 | (text-baseline! "middle") 404 | (text-align! "center") 405 | (fill-style! "#566") 406 | (fill-text! "COFFEESCRIPT" 0 -1400)) 407 | (restore!))) 408 | 409 | (defn draw-dart! 410 | [{:keys [alpha highlight] :as opts}] 411 | (when-not (zero? alpha) 412 | (save!) 413 | (global-alpha! alpha) 414 | (draw-orbit! opts) 415 | (draw-planet! opts) 416 | (when highlight 417 | (font! "100 200px Roboto") 418 | (text-baseline! "middle") 419 | (text-align! "center") 420 | (fill-style! "#566") 421 | (fill-text! "DART" 0 -1700)) 422 | (restore!))) 423 | 424 | (defn draw-clojurescript! 425 | [{:keys [alpha highlight] :as opts}] 426 | (when-not (zero? alpha) 427 | (save!) 428 | (global-alpha! alpha) 429 | (draw-orbit! opts) 430 | (draw-planet! opts) 431 | (when highlight 432 | (font! "100 300px Roboto") 433 | (text-baseline! "middle") 434 | (text-align! "center") 435 | (fill-style! "#566") 436 | (fill-text! "CLOJURESCRIPT" 3800 0)) 437 | (restore!))) 438 | 439 | (defn draw-other-langs! 440 | [opts] 441 | (save!) 442 | (font! "100 400px Roboto") 443 | (fill-style! "#DEE") 444 | (let [names [[:gwt "GWT"] 445 | [:websharper "WebSharper"] 446 | [:objective-j "Objective-J"] 447 | [:scala.js "Scala.js"] 448 | [:elm "Elm"] 449 | [:purescript "PureScript"] 450 | [:js_of_ocaml "Js_of_ocaml"] 451 | [:asm "Emscripten -> asm.js"]] 452 | rs (iterate (partial + 600) 2800) 453 | ys (iterate (partial + 600) -1800)] 454 | (doseq [[[name- text] r y] (map vector names rs ys)] 455 | (save!) 456 | (let [obj (get opts name-) 457 | alpha (:alpha obj)] 458 | (global-alpha! alpha)) 459 | (draw-orbit! {:r r}) 460 | (fill-text! text 7600 y) 461 | (restore!)) 462 | (restore!))) 463 | 464 | ;;-------------------------------------------------------------------------------- 465 | ;; Drawing Dispatch 466 | ;;-------------------------------------------------------------------------------- 467 | 468 | (defn set-cam! 469 | "Set camera's position, zoom, and rotation." 470 | [{:keys [x y zoom angle]}] 471 | (translate! (/ width 2) (/ height 2)) 472 | (translate! (- x) (- y)) 473 | (scale! zoom zoom) 474 | (rotate! angle)) 475 | 476 | (defn draw-progress! 477 | [] 478 | (save!) 479 | (let [i (:slide @state) 480 | end-i (dec num-slides) 481 | len (* width (/ i end-i))] 482 | 483 | (begin-path!) 484 | (move-to! 0 0) 485 | (line-to! len 0) 486 | (line-width! 10) 487 | (stroke-style! "#ACC") 488 | (stroke!)) 489 | (restore!)) 490 | 491 | (def prev-state nil) 492 | 493 | (defn draw! 494 | "Draw the current state of the application." 495 | [] 496 | (when (not= prev-state @state) 497 | (set! prev-state @state) 498 | (save!) 499 | (fill-style! "#222") 500 | (fill-rect! 0 0 width height) 501 | (set-cam! (:cam @state)) 502 | 503 | (draw-title! (:title @state)) 504 | (draw-js-core! (:js-core @state)) 505 | (draw-es-captions! (:es-captions @state)) 506 | (draw-highlight-layer! (:highlight-layer @state)) 507 | 508 | (draw-transpiler! (:transpiler @state)) 509 | (draw-linter! (:linter @state)) 510 | (draw-modulesys! (:modulesys @state)) 511 | 512 | (draw-static! (:static @state)) 513 | 514 | (draw-radar! (:radar @state)) 515 | 516 | (draw-js-face! (:js-face @state)) 517 | 518 | (draw-coffeescript! (:coffeescript @state)) 519 | (draw-dart! (:dart @state)) 520 | (draw-clojurescript! (:clojurescript @state)) 521 | 522 | (draw-other-langs! @state) 523 | 524 | (restore!) 525 | (draw-progress!)) 526 | 527 | ;; self-schedule the next frame to draw 528 | (.requestAnimationFrame js/window draw!)) 529 | 530 | -------------------------------------------------------------------------------- /src/solar_system_of_js/math.cljs: -------------------------------------------------------------------------------- 1 | (ns solar-system-of-js.math) 2 | 3 | (def PI (aget js/Math "PI")) 4 | (def sin (aget js/Math "sin")) 5 | (def cos (aget js/Math "cos")) 6 | -------------------------------------------------------------------------------- /src/solar_system_of_js/nav.cljs: -------------------------------------------------------------------------------- 1 | (ns solar-system-of-js.nav 2 | (:require-macros 3 | [cljs.core.async.macros :refer [go]]) 4 | (:require 5 | [cljs.core.async :refer [ (aget js/location "hash") (subs 1) (js/parseInt))] 26 | (when (<= 0 i (dec num-slides)) 27 | i))) 28 | 29 | (defn init-first-slide! 30 | "Do some setup that the first slide requires." 31 | [] 32 | ;; execute first slide action 33 | (let [action (first slide-actions)] 34 | (skip-action! action)) 35 | 36 | ;; save state of first slide 37 | (save-slide-state!) 38 | 39 | ;; set initial hash 40 | (when-not (hash-slide) 41 | (aset js/location "hash" 0))) 42 | 43 | (def in-transition? 44 | "True if we are in the middle of a slide's actions (i.e. animation)" 45 | (atom false)) 46 | 47 | (defn sync-hash-to-slide! 48 | "Make url hash equal to current slide" 49 | [] 50 | (aset js/location "hash" (:slide @state))) 51 | 52 | (defn next-slide! 53 | "Go to next slide if we can." 54 | [] 55 | (when-not @in-transition? 56 | (when-let [action (get slide-actions (inc (:slide @state)))] 57 | (save-slide-state!) 58 | (swap! state update-in [:slide] inc) 59 | (sync-hash-to-slide!) 60 | (reset! in-transition? true) 61 | (go 62 | ( i cur) 94 | (dotimes [_ (- i cur)] 95 | (skip-slide! false)))) 96 | (sync-hash-to-slide!)))) 97 | 98 | (defn sync-slide-to-hash! 99 | "Make sure the slide reflects the current url hash." 100 | [] 101 | (when-let [i (hash-slide)] 102 | (seek-slide! i))) 103 | 104 | (.addEventListener js/window "hashchange" sync-slide-to-hash!) 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/solar_system_of_js/state.cljs: -------------------------------------------------------------------------------- 1 | (ns solar-system-of-js.state 2 | (:require 3 | [solar-system-of-js.math :refer [PI]])) 4 | 5 | (def initial-state 6 | "Initial state of the application." 7 | {:slide 0 8 | :caption "" 9 | :title {:x 0 10 | :y -100 11 | :alpha 1} 12 | :cam {:x 0 13 | :y 0 14 | :zoom 1 15 | :angle 0} 16 | :js-face {:x 900 17 | :y 0 18 | :r 200 19 | :alpha 1 20 | :angle 0} 21 | :js-core {:x 0 22 | :y 0 23 | :r 200 24 | :alpha 0} 25 | :es-captions {:es3 {:alpha 0} 26 | :es5 {:alpha 0} 27 | :es6 {:alpha 0} 28 | :es7 {:alpha 0} 29 | :es8 {:alpha 0}} 30 | :highlight-layer nil 31 | :transpiler {:x 900 32 | :y 0 33 | :alpha 0 34 | :font-alpha 0} 35 | :linter {:x 900 36 | :y 70 37 | :alpha 0 38 | :font-alpha 0} 39 | :modulesys {:x 900 40 | :y 140 41 | :alpha 0 42 | :font-alpha 0} 43 | :static {:title {:alpha 0} 44 | :sphere {:alpha 0 45 | :r 200 46 | :arcs 0} 47 | :typescript {:alpha 0} 48 | :soundscript {:alpha 0} 49 | :closure {:alpha 0} 50 | :flow {:alpha 0}} 51 | :coffeescript {:alpha 0 52 | :size 50 53 | :highlight false 54 | :angle 0 55 | :angle-speed (/ PI 5) 56 | :r 900 57 | } 58 | :dart {:alpha 0 59 | :size 100 60 | :highlight false 61 | :angle 0 62 | :angle-speed (/ PI 10) 63 | :r 1400 64 | } 65 | :clojurescript {:alpha 0 66 | :size 100 67 | :highlight false 68 | :angle 0 69 | :angle-speed (/ PI 15) 70 | :r 2100 71 | } 72 | :radar {:orbit nil 73 | :offset 0} 74 | :gwt {:alpha 0} 75 | :websharper {:alpha 0} 76 | :objective-j {:alpha 0} 77 | :scala.js {:alpha 0} 78 | :elm {:alpha 0} 79 | :purescript {:alpha 0} 80 | :js_of_ocaml {:alpha 0} 81 | :asm {:alpha 0}}) 82 | 83 | ;; Current state of the application. 84 | (defonce state 85 | (atom initial-state)) 86 | 87 | -------------------------------------------------------------------------------- /title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunlebron/solar-system-of-js/a5c53c9727622ac023963766323b6eb5d03f5226/title.png --------------------------------------------------------------------------------