Browse code
Add PoC, with date selection and elapsed time
fiddlerwoaroof authored on 04/12/2016 09:17:47
Showing 7 changed files
Showing 7 changed files
0 | 5 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,42 @@ |
1 |
+# history_app |
|
2 |
+ |
|
3 |
+A [Hoplon][3] project designed to...well, that part is up to you. |
|
4 |
+ |
|
5 |
+## Dependencies |
|
6 |
+ |
|
7 |
+- java 1.7+ |
|
8 |
+- [boot][1] |
|
9 |
+ |
|
10 |
+## Usage |
|
11 |
+### Development |
|
12 |
+1. Start the `dev` task. In a terminal run: |
|
13 |
+ ```bash |
|
14 |
+ $ boot dev |
|
15 |
+ ``` |
|
16 |
+ This will give you a Hoplon development setup with: |
|
17 |
+ - auto compilation on file changes |
|
18 |
+ - audible warning for compilation success or failures |
|
19 |
+ - auto reload the html page on changes |
|
20 |
+ - Clojurescript REPL |
|
21 |
+ |
|
22 |
+2. Go to [http://localhost:8000][2] in your browser. You should see "Hello, Hoplon!". |
|
23 |
+ |
|
24 |
+3. If you edit and save a file, the task will recompile the code and reload the |
|
25 |
+ browser to show the updated version. |
|
26 |
+ |
|
27 |
+### Production |
|
28 |
+1. Run the `prod` task. In a terminal run: |
|
29 |
+ ```bash |
|
30 |
+ $ boot prod |
|
31 |
+ ``` |
|
32 |
+ |
|
33 |
+2. The compiled files will be on the `target/` directory. This will use |
|
34 |
+ advanced compilation and prerender the html. |
|
35 |
+ |
|
36 |
+## License |
|
37 |
+ |
|
38 |
+Copyright © 2016, **Your Name Goes Here** |
|
39 |
+ |
|
40 |
+[1]: http://boot-clj.com |
|
41 |
+[2]: http://localhost:8000 |
|
42 |
+[3]: http://hoplon.io |
0 | 43 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,25 @@ |
1 |
+* { |
|
2 |
+ box-sizing: border-box; |
|
3 |
+} |
|
4 |
+ |
|
5 |
+body { |
|
6 |
+ margin: 0; |
|
7 |
+ padding: 0; |
|
8 |
+} |
|
9 |
+ |
|
10 |
+h2 { |
|
11 |
+ color: blue; |
|
12 |
+ width: 100%; |
|
13 |
+ text-align: center; |
|
14 |
+ margin: 2em; |
|
15 |
+} |
|
16 |
+ |
|
17 |
+#container { |
|
18 |
+ width: 100vw; |
|
19 |
+ height: 100vh; |
|
20 |
+ border: thin solid black; |
|
21 |
+} |
|
22 |
+ |
|
23 |
+#line { |
|
24 |
+ height: 3em; |
|
25 |
+} |
0 | 5 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,45 @@ |
1 |
+(set-env! |
|
2 |
+ :dependencies '[[adzerk/boot-cljs "1.7.228-2"] |
|
3 |
+ [adzerk/boot-reload "0.4.13"] |
|
4 |
+ [secretary "1.2.3"] |
|
5 |
+ [com.cemerick/url "0.1.1"] |
|
6 |
+ [funcool/cuerdas "2.0.1"] |
|
7 |
+ [hoplon/hoplon "6.0.0-alpha17"] |
|
8 |
+ [org.clojure/clojure "1.8.0"] |
|
9 |
+ [org.clojure/clojurescript "1.9.293"] |
|
10 |
+ [tailrecursion/boot-jetty "0.1.3"] |
|
11 |
+ [adzerk/boot-cljs-repl "0.3.3"] |
|
12 |
+ [com.cemerick/piggieback "0.2.1" :scope "test"] |
|
13 |
+ [weasel "0.7.0" :scope "test"] |
|
14 |
+ [org.clojure/tools.nrepl "0.2.12" :scope "test"] |
|
15 |
+ ] |
|
16 |
+ |
|
17 |
+ :source-paths #{"src"} |
|
18 |
+ :asset-paths #{"assets"}) |
|
19 |
+ |
|
20 |
+(require |
|
21 |
+ '[adzerk.boot-cljs :refer [cljs]] |
|
22 |
+ '[adzerk.boot-cljs-repl :refer [cljs-repl-env start-repl]] |
|
23 |
+ '[adzerk.boot-reload :refer [reload]] |
|
24 |
+ '[hoplon.boot-hoplon :refer [hoplon prerender]] |
|
25 |
+ '[tailrecursion.boot-jetty :refer [serve]]) |
|
26 |
+ |
|
27 |
+(deftask dev |
|
28 |
+ "Build history_app for local development." |
|
29 |
+ [] |
|
30 |
+ (comp |
|
31 |
+ (watch) |
|
32 |
+ (speak) |
|
33 |
+ (hoplon) |
|
34 |
+ (reload) |
|
35 |
+ (cljs-repl-env) |
|
36 |
+ (cljs) |
|
37 |
+ (serve :port 8123))) |
|
38 |
+ |
|
39 |
+(deftask prod |
|
40 |
+ "Build history_app for production deployment." |
|
41 |
+ [] |
|
42 |
+ (comp |
|
43 |
+ (hoplon) |
|
44 |
+ (cljs :optimizations :advanced) |
|
45 |
+ (target :dir #{"target"}))) |
0 | 46 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,38 @@ |
1 |
+(ns app.route |
|
2 |
+ (:require [clojure.string] |
|
3 |
+ [javelin.core :as j] |
|
4 |
+ [hoplon.core :as h] |
|
5 |
+ [cuerdas.core] |
|
6 |
+ [cemerick.url] |
|
7 |
+ [secretary.core])) |
|
8 |
+ |
|
9 |
+(def r (h/route-cell)) |
|
10 |
+ |
|
11 |
+(def path |
|
12 |
+ (j/cell= (-> r |
|
13 |
+ (cuerdas.core/strip-prefix "#") |
|
14 |
+ (cuerdas.core/strip-prefix "/") |
|
15 |
+ (#(str "/" %)) |
|
16 |
+ (#(do (print "The url is: " %) |
|
17 |
+ %))))) |
|
18 |
+ |
|
19 |
+ |
|
20 |
+(defn generate-route |
|
21 |
+ ([path] (generate-route path nil)) |
|
22 |
+ ([path query] |
|
23 |
+ {:pre [(sequential? path) (or (nil? query) (map? query))]} |
|
24 |
+ (str "#/" |
|
25 |
+ (clojure.string/join "/" path) |
|
26 |
+ (if query |
|
27 |
+ (str "?" |
|
28 |
+ (cemerick.url/map->query query)))))) |
|
29 |
+ |
|
30 |
+(defn set-route! |
|
31 |
+ "Set the URL hash to work fo the given route." |
|
32 |
+ ([route] |
|
33 |
+ (if (string? route) |
|
34 |
+ (set! (.-hash (.-location js/window)) |
|
35 |
+ route) |
|
36 |
+ (set-route! (generate-route route)))) |
|
37 |
+ ([path query] |
|
38 |
+ (set-route! (generate-route path query)))) |
0 | 39 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,140 @@ |
1 |
+(page "index.html" |
|
2 |
+ (:require [cuerdas.core :as str] |
|
3 |
+ [hoplon.svg :as svg] |
|
4 |
+ [clojure.pprint :refer [cl-format]])) |
|
5 |
+ |
|
6 |
+(defc state |
|
7 |
+ {:elapsed-time 0 |
|
8 |
+ :total-time 300 |
|
9 |
+ :date-range [1776 2076] |
|
10 |
+ :selected-date 1926 |
|
11 |
+ :topics ["American History" "European History"]}) |
|
12 |
+ |
|
13 |
+(defn fraction-to-date [fraction start-date end-date] |
|
14 |
+ (.floor js/Math |
|
15 |
+ (+ start-date |
|
16 |
+ (* fraction |
|
17 |
+ (- end-date |
|
18 |
+ start-date))))) |
|
19 |
+ |
|
20 |
+ |
|
21 |
+(defc= start-date (get-in state [:date-range 0])) |
|
22 |
+(defc= end-date (get-in state [:date-range 1])) |
|
23 |
+(defc= date-range (- end-date start-date)) |
|
24 |
+ |
|
25 |
+(defc hovering nil) |
|
26 |
+(defc hovered-fraction 0) |
|
27 |
+(defc= hovered-date |
|
28 |
+ (fraction-to-date hovered-fraction start-date end-date)) |
|
29 |
+(defc= hovered-date-as-fraction |
|
30 |
+ (/ (- hovered-date start-date) |
|
31 |
+ date-range)) |
|
32 |
+ |
|
33 |
+(defc= selected-date |
|
34 |
+ (:selected-date state) |
|
35 |
+ (partial swap! state assoc-in [:selected-date])) |
|
36 |
+ |
|
37 |
+(defc= selected-fraction (/ (- selected-date |
|
38 |
+ start-date) |
|
39 |
+ date-range)) |
|
40 |
+ |
|
41 |
+(defc= topics |
|
42 |
+ (:topics state)) |
|
43 |
+ |
|
44 |
+(defc= elapsed-time |
|
45 |
+ (:elapsed-time state) |
|
46 |
+ (partial swap! state assoc-in [:elapsed-time])) |
|
47 |
+ |
|
48 |
+;;; Update total-time with the _difference_ between current and desired; |
|
49 |
+(defc= total-time |
|
50 |
+ (:total-time state) |
|
51 |
+ (partial swap! state update-in [:total-time] +)) |
|
52 |
+ |
|
53 |
+(defc= remaining-time |
|
54 |
+ (- total-time elapsed-time)) |
|
55 |
+ |
|
56 |
+(defc= time-percentage |
|
57 |
+ (* 100 (/ elapsed-time total-time))) |
|
58 |
+ |
|
59 |
+(defn num-to-time [num] |
|
60 |
+ (cl-format nil "~2d:~2,'0d" (quot num 60) (mod num 60))) |
|
61 |
+ |
|
62 |
+(defelem time-display [_ _ _] |
|
63 |
+ (div (div :class "remaining-time" :style "display: inline-block; margin: 0 1em;" |
|
64 |
+ (cell= (str "Remaining Time: " (num-to-time remaining-time)))) |
|
65 |
+ (div :class "elapsed-time" :style "display: inline-block; margin: 0 1em;" |
|
66 |
+ (cell= (str "Time Spent:" (num-to-time elapsed-time)))))) |
|
67 |
+ |
|
68 |
+(defelem progress-bar [_ _ _] |
|
69 |
+ (div {:style (cell= (str "background: hsl(" (- 117 (* 115 (/ time-percentage 100))) ", 75%, 50%); " |
|
70 |
+ "height: 0.2em; position: fixed;top:0;left:0;width: " |
|
71 |
+ (.floor js/Math (+ 1 (/ (* 99 time-percentage) 100))) "vw;"))})) |
|
72 |
+ |
|
73 |
+(defmethod do! :viewBox |
|
74 |
+ [elem _ value] |
|
75 |
+ (if (= false value) |
|
76 |
+ (.removeAttribute elem "viewBox") |
|
77 |
+ (.setAttribute elem "viewBox" value))) |
|
78 |
+ |
|
79 |
+(defmethod do! :preserveAspectRatio |
|
80 |
+ [elem _ value] |
|
81 |
+ (if (= false value) |
|
82 |
+ (.removeAttribute elem "preserveAspectRatio") |
|
83 |
+ (.setAttribute elem "preserveAspectRatio" value))) |
|
84 |
+ |
|
85 |
+(definterval elapsed-update-interval-id 1000 |
|
86 |
+ (swap! elapsed-time inc)) |
|
87 |
+ |
|
88 |
+(cell= (print "The state is:" state)) |
|
89 |
+ |
|
90 |
+(html |
|
91 |
+ (head |
|
92 |
+ (link :href "app.css" :rel "stylesheet") |
|
93 |
+ (style "#line { width: 100%; display: block;}")) |
|
94 |
+ |
|
95 |
+ (body |
|
96 |
+ (progress-bar) |
|
97 |
+ (div :id "container" |
|
98 |
+ (h1 "You're bad at history") |
|
99 |
+ (time-display) |
|
100 |
+ (h2 "When did World War 1 start?") |
|
101 |
+ (div :class "timeline" |
|
102 |
+ :style "position: relative;" |
|
103 |
+ (svg/svg :id "line" :height "20" |
|
104 |
+ :click #(this-as this |
|
105 |
+ (let* [bbox-width (.-width (.getBBox this)) |
|
106 |
+ bbox-left (.-x (.getBBox this)) |
|
107 |
+ click-x (.-offsetX %)] |
|
108 |
+ (reset! selected-date |
|
109 |
+ (fraction-to-date |
|
110 |
+ (/ (- click-x bbox-left) |
|
111 |
+ bbox-width) |
|
112 |
+ @start-date |
|
113 |
+ @end-date)))) |
|
114 |
+ :mousemove #(this-as this |
|
115 |
+ (reset! hovering true) |
|
116 |
+ (let* [bbox-width (.-width (.getBBox this)) |
|
117 |
+ bbox-left (.-x (.getBBox this)) |
|
118 |
+ click-x (.-offsetX %)] |
|
119 |
+ (reset! hovered-fraction |
|
120 |
+ (/ (- click-x bbox-left) |
|
121 |
+ bbox-width)))) |
|
122 |
+ (svg/line :x1 "0" :y1 "12" :x2 "100%" :y2 "12" :stroke "blue") |
|
123 |
+ (when-tpl hovering |
|
124 |
+ (let [hovered-percentage (cell= (cl-format nil "~d%" (* 100 hovered-date-as-fraction)))] |
|
125 |
+ (svg/g (svg/circle :id "date-cursor" |
|
126 |
+ {:cx hovered-percentage} |
|
127 |
+ :cy "12" :r "10" :fill "transparent" :stroke-width 2 :stroke "red") |
|
128 |
+ (svg/text :id "cursor-text" {:x hovered-percentage} :y "32" :dx "-0.75em" |
|
129 |
+ hovered-date )))) |
|
130 |
+ (svg/circle :id "date-sel" {:cx (cell= (str (* 100 selected-fraction) "%"))} |
|
131 |
+ :cy "12" :r "9" :fill "#88f") |
|
132 |
+ (svg/text :id "cursor-text" {:x (cell= (cl-format nil "~d%" (* 100 selected-fraction)))} :y "32" :dx "-0.75em" |
|
133 |
+ selected-date)) |
|
134 |
+ |
|
135 |
+ (div :style "position: absolute; display:inline-block; margin-top: 1em;left: 0%" 1776) |
|
136 |
+ (div :style "position: absolute; display:inline-block; margin-top: 1em; left: 33%" 1876) |
|
137 |
+ (div :style "position: absolute; display:inline-block; margin-top: 1em; right: 33%" 1976) |
|
138 |
+ (div :style "position: absolute; display:inline-block; margin-top: 1em; right: 0%" 2076) |
|
139 |
+ |
|
140 |
+ )))) |