9 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/04-array-cardio/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/04-array-cardio/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [goog.object])
4 | (:require-macros [app.macros :refer [p pp]]))
5 |
6 |
7 | (p "---------------------------- Array.prototype.filter() ---------------------------------")
8 |
9 | ;; 1. Filter the list of inventors for those who were born in the 1500's)
10 | ;; http://blog.jenkster.com/2013/11/clojure-less-than-greater-than-tip.html
11 | ;; we are asking if the number is larger than 1500 and smaller than 1600
12 | ;; is 1500 less than or equal to 1501?
13 | ;; is 1501 less than or equal to 1600?
14 |
15 | (p "Original Data Structure")
16 | (p js/inventors)
17 |
18 | (def tranformed-ds (clj->js (filter #(<= 1500 (goog.object/get % "year") 1600) js/inventors)))
19 |
20 | (p "Transformed Data Structure")
21 | (p tranformed-ds)
22 |
23 |
24 | (p "---------------------------- Array.prototype.map() ------------------------------------")
25 |
26 | ;; 2. Give us an array of the inventors' first and last names
27 |
28 | (defn full-name [inventor]
29 | (str (goog.object/get inventor "first") " " (goog.object/get inventor "last")))
30 |
31 | (def tranformed-ds-two (clj->js (map full-name js/inventors)))
32 |
33 | (p "Transformed Data Structure")
34 | (p tranformed-ds-two)
35 |
36 |
37 | (p "---------------------------- Array.prototype.sort() ------------------------------------")
38 |
39 | ;; 3. Sort the inventors by birthdate, oldest to youngest
40 |
41 | (def tranformed-ds-three (clj->js (sort-by #(goog.object/get % "year") js/inventors)))
42 |
43 | (p "Transformed Data Structure")
44 | (p tranformed-ds-three)
45 |
46 |
47 | (p "---------------------------- reduce -----------------------------------")
48 |
49 | ;; 4. How many years did all the inventors live?
50 | ;; subtract the year and passed date
51 | ;; add the above to a total tracker
52 |
53 | (def tranformed-ds-four (clj->js (reduce #(+ %1 (- (goog.object/get %2 "passed") (goog.object/get %2 "year")) ) 0 js/inventors)))
54 |
55 | (p "Transformed Data Structure")
56 | (p tranformed-ds-four)
57 |
58 |
59 | (p "-----------------------------sort again-------------------------------------")
60 |
61 | ;; 5. Sort the inventors by years lived
62 |
63 | ;; sorted from youngest to oldest
64 |
65 | (def tranformed-ds-five (clj->js (sort-by #(- (goog.object/get % "passed") (goog.object/get % "year")) js/inventors)))
66 |
67 | ;; sorted from oldest to youngest
68 |
69 | (def tranformed-ds-six (clj->js (sort-by #(- (goog.object/get % "passed") (goog.object/get % "year")) > js/inventors)))
70 |
71 | (p tranformed-ds-five)
72 | (p tranformed-ds-six)
73 |
74 |
75 | (p "---------------------------- filter again ------------------------------------")
76 |
77 | ;; 6. create a list of Boulevards in Paris that contain 'de' anywhere in the name
78 |
79 | (def tranformed-ds-seven (clj->js (filter #(re-find #"de" %) js/people)))
80 |
81 | (p tranformed-ds-seven)
82 |
83 |
84 | (p "---------------------------- sort again ------------------------------------")
85 |
86 | ;; Sort the people alphabetically by last name
87 |
88 | ;; you could also do > (split % #",") which is a little cleaner and more conventional
89 |
90 | (def tranformed-ds-eight (clj->js (sort-by #(get (re-find #", (.*)" %) 1) js/people)))
91 |
92 | (p tranformed-ds-eight)
93 |
94 | (p "-----------------------------reduce again-------------------------------------")
95 |
96 | ;; Sum up the instances of each of these - in other words, creating a word frequency
97 | ;; map
98 |
99 | ;; So this one was fun because clojure actually provides a function that does
100 | ;; exactly this.
101 |
102 | (def tranformed-ds-nine (clj->js (frequencies js/data)))
103 |
104 | ;; Just for fun, I have also done the above using reduce. See below.
105 |
106 | ;; reduce over each, keep some kind of map available and increment the maps number
107 | ;; when a word in the map appears again.
108 |
109 | (def tranformed-ds-ten (clj->js (reduce #(assoc %1 %2 (inc (%1 %2 0))) {} js/data)))
110 |
111 | (p tranformed-ds-nine)
112 | (p tranformed-ds-ten)
113 |
--------------------------------------------------------------------------------
/04-array-cardio/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/05-flex-panels-image-gallery/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/05-flex-panels-image-gallery/README.md:
--------------------------------------------------------------------------------
1 | # 05 Flex Panels Image Gallery
2 |
3 | - [Housekeeping](#housekeepings)
4 | - [Quickstart](#quickstart)
5 | - [Lessons Learned](#lessons-learned)
6 | - [Double Dot](#double-dot)
7 | - [Parens](#parens)
8 |
9 | ## Housekeeping
10 |
11 | Before trying out this repo please ensure you have a cljs environment setup. See the [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started)
12 |
13 | ## Quickstart
14 |
15 | Run the following comamnds from the root of the `07-flex-panels-image-gallery` repo
16 |
17 | **1. Build and watch the project**
18 |
19 | ```bash
20 | clj -M:dev
21 | ```
22 |
23 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
24 |
25 | ```bash
26 | ➜ clj -h
27 | Version: 1.10.3.855 # this is the version your on
28 | ```
29 |
30 |
31 | ## Lessons Learned
32 |
33 | ### Double Dot
34 |
35 | If you review the [Double Dot](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/..) macro's documentation, they even provide the reasoinnig which is `easier to read, write etc`. Why note this? Because the clojure docs are truly a wealth of info, they just sometimes, often times, take a little bit of practice to learn where that treasure lives.
36 |
37 | ### Parens
38 |
39 | Another big reminder from this is always watch your parens. Likely that if the syntax looks correct, your parens are probably fucking with you.
40 |
--------------------------------------------------------------------------------
/05-flex-panels-image-gallery/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/05-flex-panels-image-gallery/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flex Panels 💪
6 |
7 |
8 |
9 |
100 |
101 |
102 |
103 |
104 |
Hey
105 |
Let's
106 |
Dance
107 |
108 |
109 |
Give
110 |
Take
111 |
Receive
112 |
113 |
114 |
Experience
115 |
It
116 |
Today
117 |
118 |
119 |
Give
120 |
All
121 |
You can
122 |
123 |
124 |
Life
125 |
In
126 |
Motion
127 |
128 |
129 |
130 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/05-flex-panels-image-gallery/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/05-flex-panels-image-gallery/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require-macros [app.macros :refer [p pp]]))
4 |
5 |
6 | ;; helpers
7 | (defn toggle-open [e]
8 | (this-as this
9 | (.. this -classList (toggle "open"))))
10 |
11 | (defn toggle-active [e]
12 | (this-as this
13 | (when (.. e -propertyName (includes "flex"))
14 | (.. this -classList (toggle "open-active")))))
15 |
16 |
17 | ;; start
18 |
19 | (let [panels (.. js/document (querySelectorAll ".panel"))]
20 | (doseq [panel (array-seq panels)]
21 | (.addEventListener panel "click" toggle-open)
22 | (.addEventListener panel "transitionend" toggle-active)))
23 |
--------------------------------------------------------------------------------
/05-flex-panels-image-gallery/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/06-ajax-type-ahead/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/06-ajax-type-ahead/README.md:
--------------------------------------------------------------------------------
1 | # 08 Ajax Type Ahead
2 |
3 | - [Housekeeping](#housekeepings)
4 | - [Quickstart](#quickstart)
5 | - [Lessons Learned](#lessons-learned)
6 | - [JS Interop](#js-interop)
7 | - [Fetch](#fetch)
8 | - [Clojure](#clojure)
9 | - [Spread Operator](#spread-operator)
10 | - [Regexes](#regexes)
11 | - [Concat V Into](#concat-v-into)
12 |
13 | ## Housekeeping
14 |
15 | Before trying out this repo please ensure you have a cljs environment setup. See the [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started)
16 |
17 | ## Quickstart
18 |
19 | Run the following comamnds from the root of the `08-ajax-type-ahead` repo
20 |
21 | **1. Build and watch the project**
22 |
23 | ```bash
24 | clj -M:dev
25 | ```
26 |
27 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
28 |
29 | ```bash
30 | ➜ clj -h
31 | Version: 1.10.3.855 # this is the version your on
32 | ```
33 |
34 | ## Lessons Learned
35 |
36 | This lesson brought some new pain. Some of the big challenges were
37 |
38 | 1. JS Interop - fetch
39 | 1. Clojure - regex, lazy-seqs, maps, string formatting
40 | 1. Debugging Tools
41 |
42 | ### JS Interop
43 |
44 | #### Fetch
45 |
46 | What does the fetch `api signature` look like in CLJS?
47 |
48 | ```clojure
49 | (-> (js/fetch "http://10.0.3.2:3000/" (clj->js {:method "POST"}))
50 | (.then #(.json %)) ;; warning: `.json` returns a promise that resolves to the parsed json body
51 | (.then js->clj)
52 | (.then yourhandlerhere)
53 | (.catch #(js/console.error %)))
54 | ```
55 |
56 | One question is whether calling this the `API signature` is correct?
57 |
58 | Lets see what the above looks like when I don't use `->` which is a good examle of how to implement idiomatic cljs
59 |
60 | I was a little stumped on how to think about this one from a clojure perspective. Consider what we are tying to do here:
61 |
62 | 1. get data
63 | 2. store data is some global var (is this an atom in clojure?)
64 | 3. have other functions that can operate on this global data store?
65 |
66 | So what would this look like?
67 |
68 | ```clojure
69 | (def cities (atom nil))
70 |
71 | (-> (js/fetch endpoint)
72 | (.then (fn [res] (reset! cities (seq (.json res))))))
73 | ```
74 |
75 | A great piece of advice from `noisesmith` was
76 |
77 | > in cljs you can use google closure, goog.object has things that make dealing with values a lot easier
78 |
79 | I think the nicer way would be to use `swap`
80 |
81 | ### Clojure
82 |
83 | #### Spread Operator
84 |
85 | In JS we do this:
86 |
87 | ```
88 | a1 = [1, 2, 3]
89 | a2 = [4]
90 | a3 = [...a1, ...a2] // [1, 2, 3, 4]
91 | ```
92 |
93 | Would be good to understand how the above works in CLJS
94 |
95 | #### Regexes
96 |
97 | If we want to dynamically look for a word, we also have to use the `re-pattern` which lets us create a pattern
98 |
99 | `(re-pattern (str "" some-string))`
100 |
101 | Something to remember here is that regexes seemed a lot more difficult when performing this excercise then they ever have and for me, I felt it was because of the syntax. It make it confusing to follow and the whole thing is confusing anyways.
102 |
103 | 2. How regex's actually work
104 | 3. debugging a filter to see what it is doing
105 | 4. lazySeq not allowing me to see WTF is in the array-seq - how to realize them. in our case, using join does the trick, but maybe explore other options
106 | 5. Not being able to iterate over a map - obvi
107 | 6. Adding html in pur strings is wicked hard - maybe a look at how to add an HTML helper like hiccup
108 | 7. What if the cities object has no vals cause the the promise has not resolved? add a timeout to illustrate
109 |
110 | ## Concat v Into
111 |
112 | > noisesmith
113 |
114 | in clojure, you could do this:
115 |
116 | if a1 is a clojure type
117 | or `(into [] cat [a1 a2])` if not
118 |
119 | or just `(concat a1 a2)` - depending on what you are trying to do
120 |
121 | concat is lazy, into is strict
122 |
123 | > val_waeselynck
124 |
125 | Either `(concat a1 a2)` - yields a lazy seq - or `(into a1 a2)` (yields a collection of the same type as `a1`)
126 |
--------------------------------------------------------------------------------
/06-ajax-type-ahead/README.org:
--------------------------------------------------------------------------------
1 | * Fetch
2 |
3 | What does the fetch `api signature` look like in cljs?
4 |
5 | #+BEGIN_SRC clojure
6 | (defn "This is clojure")
7 | #+END_SRC
8 |
9 | What's the deal with this? Lets start by understanding `->`in cljs
10 |
11 | * Concurrency v. Parallelism
12 |
13 |
14 |
15 | * Concurrency
16 |
17 | Concurrency is about correctly, safely and efficiently sharing resources. First person to get coffee, would make a pot of coffee. This can be seen as a policy we setup and we use primitives to represent this.
18 |
19 | We have 5 employees. Each of the employees can be considered a thread. Each person wants to get some coffee and there is only one coffee pot.
20 |
21 | ```
22 | (dotimes [x 5]
23 | (.start (Thread (fn []
24 | (Thread/sleep (rand-int 10000)
25 | (println x "Im here")
26 | (println x "I'm Grabbing coffee")
27 | @coffee
28 | (println x "sip"))))))
29 | ```
30 |
31 | @coffee === (deref coffee)
32 |
33 | * Regexs
34 |
35 | These are a little fucked syntatically. After a lot of searching, this resource - https://cljs.github.io/api/syntax/regex - was the first to actually explain, just a little, that in CLJ there is a special syntax to make this work.
36 |
37 | Learn more about Atoms with https://purelyfunctional.tv/lesson/keeping-state-consistent-with-atoms/ video
38 |
39 | I asked the question about how to debug inside of *filter* and realized I am a fool. Here is a good answer from eggsyntax -
40 |
41 | *I’d start by calling the predicate function on some individual values, from the REPL. If you need to dig deeper than that (eg if the predicate is really complicated), it’s a great candidate for running in a debugger.
42 |
43 | eg if I’m doing `(filter even? (range 5))`, I’d start in the repl by calling `(even? 0)`, `(even? 1)`, etc, until the behavior is totally clear. (edited)
44 |
45 | Also you can definitely cause side effects from within `filter`. eg you could do `(filter #(do (println %) (even? %)) (range 5))`*
46 |
47 | If you run into an error like this: Uncaught (in promise) Error: [object Object] is not ISeqable --> consider that it means you are trying to loop over an object that - can't do that!
48 |
49 | The BIG takeaway is that you can use *do*. This would be a great time in the docs to illustrate how to debug this in different ways!
50 |
51 | There is not global regex concept in clojure or clojurescript so you should use *re-seq* over *re-matches* if this is the desired effect...kind of odd
52 |
53 | How to get a specific item from a list
54 | ()
55 |
56 | How to access a property in a map (object)
57 |
58 |
--------------------------------------------------------------------------------
/06-ajax-type-ahead/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/06-ajax-type-ahead/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Type Ahead 👀
6 |
7 |
8 |
9 |
10 |
17 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/06-ajax-type-ahead/resources/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | background: #ffc600;
4 | font-family: 'helvetica neue';
5 | font-size: 20px;
6 | font-weight: 200;
7 | }
8 | *,
9 | *:before,
10 | *:after {
11 | box-sizing: inherit;
12 | }
13 | input {
14 | width: 100%;
15 | padding: 20px;
16 | }
17 |
18 | .search-form {
19 | max-width: 400px;
20 | margin: 50px auto;
21 | }
22 |
23 | input.search {
24 | margin: 0;
25 | text-align: center;
26 | outline: 0;
27 | border: 10px solid #f7f7f7;
28 | width: 120%;
29 | left: -10%;
30 | position: relative;
31 | top: 10px;
32 | z-index: 2;
33 | border-radius: 5px;
34 | font-size: 40px;
35 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.19);
36 | }
37 |
38 | .suggestions {
39 | margin: 0;
40 | padding: 0;
41 | position: relative;
42 | /*perspective:20px;*/
43 | }
44 | .suggestions li {
45 | background: white;
46 | list-style: none;
47 | border-bottom: 1px solid #d8d8d8;
48 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.14);
49 | margin: 0;
50 | padding: 20px;
51 | transition: background 0.2s;
52 | display: flex;
53 | justify-content: space-between;
54 | text-transform: capitalize;
55 | }
56 |
57 | .suggestions li:nth-child(even) {
58 | transform: perspective(100px) rotateX(3deg) translateY(2px) scale(1.001);
59 | background: linear-gradient(to bottom, #ffffff 0%, #efefef 100%);
60 | }
61 | .suggestions li:nth-child(odd) {
62 | transform: perspective(100px) rotateX(-3deg) translateY(3px);
63 | background: linear-gradient(to top, #ffffff 0%, #efefef 100%);
64 | }
65 |
66 | span.population {
67 | font-size: 15px;
68 | }
69 |
70 | .hl {
71 | background: #ffc600;
72 | }
73 |
74 | a {
75 | color: black;
76 | background: rgba(0, 0, 0, 0.1);
77 | text-decoration: none;
78 | }
79 |
--------------------------------------------------------------------------------
/06-ajax-type-ahead/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/06-ajax-type-ahead/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require-macros [app.macros :refer [p pp]]))
4 |
5 |
6 | ;; Globals
7 |
8 | (def cities (atom nil))
9 |
10 | (def endpoint "https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json")
11 |
12 | (def suggestions (.querySelector js/document ".suggestions"))
13 |
14 |
15 | ;; Helpers
16 |
17 | (defn find-matches
18 | [word-to-match, cities]
19 | (filter #(re-seq (re-pattern (str "(?i)" word-to-match)) (.. % -city)) (.. cities -state)))
20 |
21 |
22 | ;; Event Handlers
23 |
24 | (defn display-matches!
25 | [e]
26 | (this-as this
27 | (let [matchArray (find-matches (.. this -value) cities)
28 | html (clojure.string/join "" (map #(str "
9 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/07-array-cardio-2/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/07-array-cardio-2/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [goog.string :as gstr] goog.string.format)
4 | (:require-macros [app.macros :refer [p pp]]))
5 |
6 |
7 | (p "---------------------------- some ---------------------------------")
8 |
9 | ;; is at least one person 19 or older?
10 |
11 | (p js/people)
12 | (p js/comments)
13 |
14 | (p (some #(> 19 (- (.. % -year) 2017))
15 | js/people))
16 |
17 | (p "---------------------------- every ---------------------------------")
18 |
19 | ;; is everyone 19 or older
20 | (p (every? #(> 19 (- (.. % -year) 2017))
21 | js/people))
22 |
23 | (p "---------------------------- find ---------------------------------")
24 |
25 | ;; find the comment with the ID of 823423
26 |
27 | ;; another way to write the below (first (filter (fn [v] (= (:id v) ??)) book-list))
28 |
29 | (p (first (filter #(= (.. % -id) 823423)
30 | js/comments)))
31 |
32 | (p "---------------------------- findIndex ---------------------------------")
33 |
34 | ;; Find the comment with this ID - 823423
35 |
36 | (def indexOfComments (keep-indexed
37 | (fn [index item]
38 | (when (= (.. item -id) 823423)
39 | index)) js/comments))
40 |
41 | (p (first indexOfComments))
42 |
43 | ;; took me about two hours to find a good way to do this :)
44 |
--------------------------------------------------------------------------------
/07-array-cardio-2/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/08-html5-canvas/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/08-html5-canvas/README.md:
--------------------------------------------------------------------------------
1 | # 08 HTML5 Canvas
2 |
3 | - [Housekeeping](#housekeepings)
4 | - [Quickstart](#quickstart)
5 | - [Lessons Learned](#lessons-learned)
6 | - [Functions](#functions)
7 | - [Atoms](#atoms)
8 | - [State](#state)
9 |
10 | # Housekeeping
11 |
12 | Before trying out this repo please ensure you have a cljs environment setup. See the [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started)
13 |
14 | ## Quickstart
15 |
16 | Run the following comamnds from the root of the `10-html-canvas` repo
17 |
18 | **1. Build and watch the project**
19 |
20 | ```bash
21 | clj -M:dev
22 | ```
23 |
24 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
25 |
26 | ```bash
27 | ➜ clj -h
28 | Version: 1.10.3.855 # this is the version your on
29 | ```
30 |
31 |
32 | # Lessons Learned
33 |
34 | ## Functions
35 |
36 | I was playing with the idea that I could avoid storing DOM elements with `vars` and instead use functions which, when called, would return the required DOM elements. At this stage in my learning, this felt a little more idiomatic.
37 |
38 | For example:
39 |
40 | ```
41 | (defn get-canvas []
42 | (.querySelector js/document "#draw"))
43 |
44 | (get-canvas)
45 | ```
46 |
47 | would be better than this:
48 |
49 | ```
50 | (def canvas (.querySelector js/document "#draw"))
51 |
52 | canvas
53 | ```
54 |
55 | Furthering to this point, how exactly to invoke a function was not clear. I believe one of the challenges was that in a language like jsvascript, you have a tail of parens `myFunction()` but in clojure, its the first item in the list. And most times, you are working with examples which does noy give a lot of time to focus on what a function invocation looks like. Example:
56 |
57 | ```clojure
58 | # works
59 | (fn [] (setDrawing true))
60 |
61 | # does not work
62 | (fn [] setDrawing true)
63 | ```
64 |
65 | ## Atoms
66 |
67 | This course was also the first time I used atoms.
68 |
69 | ```clojure
70 | (get @appState :isDrawing)
71 | ```
72 |
73 | When first coming across this I did not realize that in order to access the contents of an Atom you would have to use `@` symbol. This threw me off quite a bit. I ended up figuring it out by looking at the [atom problem](http://www.lispcast.com/atom-problem)
74 |
75 | ## State
76 |
77 | It was also at this point where I realized how wonderful `Atoms` are and how we can think of them as appState - which is nice because we are building a small program, one that is often not considered stateful. This is a light intro to state.
78 |
--------------------------------------------------------------------------------
/08-html5-canvas/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/08-html5-canvas/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HTML5 Canvas
6 |
7 |
8 |
9 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/08-html5-canvas/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/08-html5-canvas/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | (ns app.core
2 | (:require [goog.string :as gstr] goog.string.format)
3 | (:require-macros [app.macros :refer [p pp]]))
4 |
5 |
6 | ;; App State
7 |
8 | (def appState (atom { :isDrawing false :lastX 0 :lastY 0 :hue 0}))
9 |
10 |
11 | ;; DOM Helpers
12 |
13 | (defn get-canvas []
14 | (.querySelector js/document "#draw"))
15 |
16 | (defn get-ctx []
17 | (.getContext (get-canvas) "2d"))
18 |
19 | (defn get-window-innerWidth []
20 | (.. js/window -innerWidth))
21 |
22 | (defn get-window-innerHeight []
23 | (.. js/window -innerHeight))
24 |
25 | (defn toggle-drawing [b]
26 | (swap! appState assoc :isDrawing b))
27 |
28 |
29 | ;; Event Handlers
30 |
31 | (defn draw [e]
32 | (when (get @appState :isDrawing)
33 | (do
34 | (.beginPath (get-ctx))
35 | ;; this is interesting because if I remove this line the color does not
36 | ;; rainbow, however, when I have it hear it does - does an atom not update
37 | ;; unless it is being accessed again?
38 | (p (get @appState :hue))
39 | (set! (.. (get-ctx) -strokeStyle) (str "hsl(" (get @appState :hue) ", 100%, 50%"))
40 | (.moveTo (get-ctx) (get @appState :lastX) (get @appState :lastY))
41 | (.lineTo (get-ctx) (.. e -offsetX) (.. e -offsetY))
42 | (.stroke (get-ctx))
43 | (swap! appState assoc :lastX (.. e -offsetX))
44 | (swap! appState assoc :lastY (.. e -offsetY))
45 | (swap! appState update-in [:hue] inc))))
46 |
47 |
48 | ;; Start
49 |
50 | ;; set canvas to be width and height of window
51 | (set! (.. (get-canvas) -width) (get-window-innerWidth))
52 | (set! (.. (get-canvas) -height) (get-window-innerHeight))
53 |
54 | ;; set drawing style of canvas
55 | (set! (.. (get-ctx) -lineJoin) "round")
56 | (set! (.. (get-ctx) -lineCap) "round")
57 | (set! (.. (get-ctx) -lineWidth) 50)
58 |
59 |
60 | (.addEventListener (get-canvas) "mousemove" draw)
61 | (.addEventListener (get-canvas) "mousedown" (fn [e] (do
62 | (toggle-drawing true)
63 | (swap! appState assoc :lastX (.. e -offsetX))
64 | (swap! appState assoc :lastY (.. e -offsetY)))))
65 |
66 |
67 | (.addEventListener (get-canvas) "mouseup" (fn [] (toggle-drawing false)))
68 |
--------------------------------------------------------------------------------
/08-html5-canvas/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/09-dev-tool-tricks/README.md:
--------------------------------------------------------------------------------
1 | # Dev Tool Tricks
2 |
3 | I will dive into this one, but it feels that the lessons learned here are more about converting the JS logs to CLJS. So instead of running though this exercise right now, here are some resources for debugging ClojureScript:
4 |
5 | - [The Power of Clojure Debugging](https://cambium.consulting/articles/2018/2/8/the-power-of-clojure-debugging)
6 | - [Debugging ClojureScript](https://medium.com/@roman01la/debugging-clojurescript-6e4d903e831)
7 |
--------------------------------------------------------------------------------
/10-check-multiple-checkboxes/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/10-check-multiple-checkboxes/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/10-check-multiple-checkboxes/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hold Shift to Check Multiple Checkboxes
6 |
7 |
8 |
56 |
62 |
63 |
64 |
65 |
This is an inbox layout.
66 |
67 |
68 |
69 |
Check one item
70 |
71 |
72 |
73 |
Hold down your Shift key
74 |
75 |
76 |
77 |
Check a lower item
78 |
79 |
80 |
81 |
Everything inbetween should also be set to checked
82 |
83 |
84 |
85 |
Try do it with out any libraries
86 |
87 |
88 |
89 |
Just regular JavaScript
90 |
91 |
92 |
93 |
Good Luck!
94 |
95 |
96 |
97 |
Don't forget to tweet your result!
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/10-check-multiple-checkboxes/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/10-check-multiple-checkboxes/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | (ns app.core
2 | (:require-macros [app.macros :refer [p pp]]))
3 |
4 |
5 | (def app-state (atom { :last-checked nil}))
6 |
7 |
8 | ;; DOM helper
9 |
10 | (defn get-checkboxes []
11 | (.. js/document (querySelectorAll ".inbox input[type=\"checkbox\"]")))
12 |
13 |
14 | ;; Event Handler
15 |
16 | (defn handle-check [e]
17 | (this-as this
18 | (when (and (.. e -shiftKey) (.. this -checked))
19 | (let [select-checkbox? (fn [in-between? checkbox]
20 | (cond
21 | ;; 1. checkbox user shift-clicked
22 | (identical? checkbox this)
23 | (do
24 | (set! (.. checkbox -checked) true)
25 | (not in-between?))
26 |
27 | ;; 2. checkbox last checked
28 | (identical? (get @app-state :last-checked) checkbox)
29 | (do
30 | (set! (.. checkbox -checked) true)
31 | (not in-between?))
32 |
33 | ;; 3. in between the last checked and shift-clicked checkbox
34 | (true? in-between?)
35 | (do
36 | (set! (.. checkbox -checked) true)
37 | true)
38 |
39 | ;; 4. outside of last checked and shift-clicked checkbox
40 | :else false))]
41 | (reduce select-checkbox? false (array-seq (get-checkboxes)))))
42 | (swap! app-state assoc :last-checked this)))
43 |
44 | ;; start
45 |
46 | (doseq [checkbox (array-seq (get-checkboxes))]
47 | (.addEventListener checkbox "click" handle-check))
48 |
--------------------------------------------------------------------------------
/10-check-multiple-checkboxes/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/11-html5-video-player/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/11-html5-video-player/README.md:
--------------------------------------------------------------------------------
1 | # 11 Custom HTML5 Video Player
2 |
3 | Customize the HTML5 Video Player with CLJS.
4 |
5 | - [Quickstart](#quickstart)
6 | - [Learnings](#Learnings)
7 | - [Ternary v. If](#ternary-v-if)
8 | - [Imperative v. Functional](#imperative-v-functional)
9 | - [JS Hot Reloading](#js-hot-reloading)
10 |
11 | ## Quickstart
12 |
13 | ```bash
14 | clj -M:dev
15 | ```
16 |
17 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
18 |
19 | ```bash
20 | ➜ clj -h
21 | Version: 1.10.3.855 # this is the version your on
22 | ```
23 |
24 | Visit site at `http://localhost:3000`
25 |
26 | ## Learnings
27 |
28 | ### Ternary V If
29 |
30 | In Clojure, the idiomatic way of getting this functionality is an If statement.
31 |
32 | ## Imperative v Functional
33 |
34 | I was required to write this:
35 |
36 | ```clojure
37 | (defn toggle-play []
38 | (if (.-paused video)
39 | (.play video)
40 | (.pause video)))
41 | ```
42 |
43 | And as I was looking at it I could not help but think that this can't be the best way. The issue I see is that this function:
44 |
45 | - Accesses stuff from the global namespace - is there a way to not do this, or is it okay? Not everything has to be reusable, but this just does not feel functional.
46 |
47 | This brings up some bigger questions like:
48 |
49 | - When should I be using side effects and how can I know if I am using them too often or not enough?
50 | - When is something just not functional?
51 | - When should something be pure?
52 | - Are we supposed to be separating functions in some way that have side effects?
53 |
54 | Part of the answer to this is knowing that not everything has to be reusable and that you are building programs so there are going to be domain specific things as part of this. If this is the case, we can start to make it data structure oriented and then the functions are acting on these data structures
55 |
56 | One better way for the above might be to just
57 |
58 | ```clojure
59 | (map paused? ("play", "pause")) ;;returns true or false and than based on this we toggle the play or not
60 | ```
61 |
62 | The above seems nicer, aside from the fact that map is not correct, because we now have a function that only cares if it finds a true or false in our domain of the video player app. I was inspired to do the above in the [predicates](https://github.com/mynomoto/lt-clojure-tutorial/blob/master/conditionals.clj) section of the above
63 |
64 | ### JS Hot Reloading
65 |
66 | If you are running this app and live coding it you are going to see that hot reloading is not really working. This is because to make something hot reloadable you have to write your code to be hot reloadable.
67 |
68 | # Musings
69 |
70 | I am keeping these around because I had issues with these so others might as well and they could be good topics to discuss in the future.
71 |
72 | - What do people like when it comes to `this-as`? From a style convention or a when to use perspective?
73 | - At which point do you make the anonymous function its own named function?
74 | - when grabbing DOM elements, which one would be preferred `defn` or `def`
75 |
--------------------------------------------------------------------------------
/11-html5-video-player/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/11-html5-video-player/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HTML Video Player
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/11-html5-video-player/resources/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | *,
6 | *:before,
7 | *:after {
8 | box-sizing: inherit;
9 | }
10 |
11 | body {
12 | margin: 0;
13 | padding: 0;
14 | display: flex;
15 | background: #7a419b;
16 | min-height: 100vh;
17 | background: linear-gradient(135deg, #7c1599 0%, #921099 48%, #7e4ae8 100%);
18 | background-size: cover;
19 | align-items: center;
20 | justify-content: center;
21 | }
22 |
23 | .player {
24 | max-width: 750px;
25 | border: 5px solid rgba(0, 0, 0, 0.2);
26 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
27 | position: relative;
28 | font-size: 0;
29 | overflow: hidden;
30 | }
31 |
32 | /* This css is only applied when fullscreen is active. */
33 | .player:fullscreen {
34 | max-width: none;
35 | width: 100%;
36 | }
37 |
38 | .player:-webkit-full-screen {
39 | max-width: none;
40 | width: 100%;
41 | }
42 |
43 | .player__video {
44 | width: 100%;
45 | }
46 |
47 | .player__button {
48 | background: none;
49 | border: 0;
50 | line-height: 1;
51 | color: white;
52 | text-align: center;
53 | outline: 0;
54 | padding: 0;
55 | cursor: pointer;
56 | max-width: 50px;
57 | }
58 |
59 | .player__button:focus {
60 | border-color: #ffc600;
61 | }
62 |
63 | .player__slider {
64 | width: 10px;
65 | height: 30px;
66 | }
67 |
68 | .player__controls {
69 | display: flex;
70 | position: absolute;
71 | bottom: 0;
72 | width: 100%;
73 | transform: translateY(100%) translateY(-5px);
74 | transition: all 0.3s;
75 | flex-wrap: wrap;
76 | background: rgba(0, 0, 0, 0.1);
77 | }
78 |
79 | .player:hover .player__controls {
80 | transform: translateY(0);
81 | }
82 |
83 | .player:hover .progress {
84 | height: 15px;
85 | }
86 |
87 | .player__controls > * {
88 | flex: 1;
89 | }
90 |
91 | .progress {
92 | flex: 10;
93 | position: relative;
94 | display: flex;
95 | flex-basis: 100%;
96 | height: 5px;
97 | transition: height 0.3s;
98 | background: rgba(0, 0, 0, 0.5);
99 | cursor: ew-resize;
100 | }
101 |
102 | .progress__filled {
103 | width: 50%;
104 | background: #ffc600;
105 | flex: 0;
106 | flex-basis: 50%;
107 | }
108 |
109 | /* unholy css to style input type="range" */
110 |
111 | input[type='range'] {
112 | -webkit-appearance: none;
113 | background: transparent;
114 | width: 100%;
115 | margin: 0 5px;
116 | }
117 | input[type='range']:focus {
118 | outline: none;
119 | }
120 | input[type='range']::-webkit-slider-runnable-track {
121 | width: 100%;
122 | height: 8.4px;
123 | cursor: pointer;
124 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0);
125 | background: rgba(255, 255, 255, 0.8);
126 | border-radius: 1.3px;
127 | border: 0.2px solid rgba(1, 1, 1, 0);
128 | }
129 | input[type='range']::-webkit-slider-thumb {
130 | height: 15px;
131 | width: 15px;
132 | border-radius: 50px;
133 | background: #ffc600;
134 | cursor: pointer;
135 | -webkit-appearance: none;
136 | margin-top: -3.5px;
137 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
138 | }
139 | input[type='range']:focus::-webkit-slider-runnable-track {
140 | background: #bada55;
141 | }
142 | input[type='range']::-moz-range-track {
143 | width: 100%;
144 | height: 8.4px;
145 | cursor: pointer;
146 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0);
147 | background: #ffffff;
148 | border-radius: 1.3px;
149 | border: 0.2px solid rgba(1, 1, 1, 0);
150 | }
151 | input[type='range']::-moz-range-thumb {
152 | box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0);
153 | height: 15px;
154 | width: 15px;
155 | border-radius: 50px;
156 | background: #ffc600;
157 | cursor: pointer;
158 | }
159 |
--------------------------------------------------------------------------------
/11-html5-video-player/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/11-html5-video-player/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [goog.string :as gstr] goog.string.format)
4 | (:require-macros [app.macros :refer [p pp]]))
5 |
6 | ;; constants
7 |
8 | (def player (.querySelector js/document ".player"))
9 |
10 | (def video (.querySelector player ".viewer"))
11 |
12 | (def toggle (.querySelector player ".toggle"))
13 |
14 | (def progress (.querySelector player ".progress"))
15 |
16 | (def progress-bar (.querySelector player ".progress__filled"))
17 |
18 | (def skip-buttons (.querySelectorAll player "[data-skip]"))
19 |
20 | (def ranges (.querySelectorAll player ".player__slider"))
21 |
22 | ;; state
23 |
24 | (def app-state (atom {:mouse-down? false}))
25 |
26 | ;; protocols
27 |
28 | (extend-type js/NodeList
29 | ISeqable
30 | (-seq [node-list] (array-seq node-list)))
31 |
32 |
33 | ;; event functions
34 |
35 | (defn toggle-mouse-down []
36 | (let [current-mouse-down (get @app-state :mouse-down?)]
37 | (swap! app-state assoc :mouse-down? (not current-mouse-down))))
38 |
39 |
40 | (defn toggle-play [e]
41 | (if (.-paused video)
42 | (.play video)
43 | (.pause video)))
44 |
45 |
46 | (defn toggle-play-btn-icon [e]
47 | (this-as this
48 | (let [icon (if (.-paused this) "►" "❚ ❚")]
49 | (set! (.-textContent toggle) icon))))
50 |
51 |
52 | (defn skip [e]
53 | (this-as this
54 | (let [skip-count (.. this -dataset -skip)
55 | skip-count-str (js/parseFloat skip-count)
56 | next-skip-count (+ (.-currentTime video) skip-count-str)]
57 | (set! (.-currentTime video) next-skip-count))))
58 |
59 |
60 | (defn handle-range-update [e]
61 | (this-as this
62 | (let [volume-slider? (= (.-name this) "volume")
63 | playback-rate? (= (.-name this) "playbackRate")]
64 | (when volume-slider?
65 | (set! (.-volume video) (.-value this)))
66 | (when playback-rate?
67 | (set! (.-playbackRate this) (.-value this))))))
68 |
69 |
70 | (defn handle-progress [e]
71 | (let [percent (* (/ (.-currentTime video) (.-duration video)) 100)
72 | next-flex-basis (str percent "%")]
73 | (p next-flex-basis)
74 | (set! (.. progress-bar -style -flexBasis) next-flex-basis)))
75 |
76 |
77 | (defn scrub [e]
78 | (let [scrub-time (* (/ (.-offsetX e) (.-offsetWidth progress)) (.-duration video))]
79 | (set! (.-currentTime video) scrub-time)))
80 |
81 |
82 | ;; event listeners
83 |
84 | (.addEventListener video "click" toggle-play)
85 |
86 | (.addEventListener video "play" toggle-play-btn-icon)
87 |
88 | (.addEventListener video "pause" toggle-play-btn-icon)
89 |
90 | (.addEventListener video "timeupdate" handle-progress)
91 |
92 | (.addEventListener progress "click" scrub)
93 |
94 | (.addEventListener progress "mousemove" #(and (get @app-state :mouse-down?) scrub))
95 |
96 | (.addEventListener progress "mousedown" toggle-mouse-down)
97 |
98 | (.addEventListener progress "mouseup" toggle-mouse-down)
99 |
100 | (.addEventListener toggle "click" toggle-play)
101 |
102 | (doseq [btn skip-buttons]
103 | (.addEventListener btn "click" skip))
104 |
105 | (doseq [range ranges]
106 | (.addEventListener range "change" handle-range-update))
107 |
108 | (doseq [range ranges]
109 | (.addEventListener range "mousemove" handle-range-update))
110 |
--------------------------------------------------------------------------------
/11-html5-video-player/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/12-key-sequence-detection/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/12-key-sequence-detection/README.md:
--------------------------------------------------------------------------------
1 | # 12 Key Sequence Detection
2 |
3 | In this excercise you will review the following concepts
4 |
5 | - [Lessons](#lessons)
6 | - [Slice](#slice)
7 | - [Queue](#queue)
8 | - [Code Cleanup](#code-cleanup)
9 |
10 | # Requirements
11 |
12 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up.
13 |
14 | # Quickstart
15 |
16 | Run the following comamnds from the root of the `14-key-sequence-detection`
17 |
18 | **1. Run the projcet**
19 |
20 | ```bash
21 | clj -M:dev
22 | ```
23 |
24 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
25 |
26 | ```bash
27 | ➜ clj -h
28 | Version: 1.10.3.855 # this is the version your on
29 | ```
30 |
31 | **2. Visit the app**
32 |
33 | http://localhost:3000/
34 |
35 | You are going to see a blank screen. Try typing in the word `secret` :)
36 |
37 | # Lessons
38 |
39 | Its all about the Data with clojure. As a result though, you will be asked to think more about the data structures you choose in clojure as opposed to JS which until recently, only really had the `Object` and `Array` as options. Thus, Figuring out how you are going to interact with the data is important.
40 |
41 | This leads to the lesson which is to really question everything you write when you write clojure/script. Especially if coming from the JS world. It really is a different way of thinking.
42 |
43 | ## Slice
44 |
45 | The primary goal of this excerise it do the following:
46 |
47 | > When the user types in the word `secret` we trigger some event
48 |
49 | The solution provided by wes in JavaScript 30 is to use `splice`. However, when you try to find the equivalent of `splice` you will notice it does not exist. One reason is because splice is going to mutate the original data structure, so your not going to find an equivalent of `splice`. What we would be looking for is `slice`. In Clojure you are going to find some gnarly solutions to do a `slice`, unless you are using a vector, which is meant to have `slice` like operations done on it, and in this case the method you are looking for is `subvec` as seen below
50 |
51 | ```clojure
52 | (swap! keys-pressed subvec keys (- (count keys) 6) (count keys))
53 | ```
54 |
55 | This is totally fine and works. However, when we consider what we are trying to do, which is leave off the left most item when our data structure has more than 6 items, we might actually be more interested in a `queue`.
56 |
57 | ## Queue
58 |
59 | In my solution I opted to use a `queue` instead of a `vector` or a `list`. The reason I chose a `queue` is because we are leaving off the left most items after a minimum of 6 key strokes. Thus, the above `subvec` is replaced with the following:
60 |
61 | ```clojure
62 | (swap! keys-pressed pop keys-pressed))
63 | ```
64 |
65 | The fact that this code is less verbose should not be the big take away, the big takeaway is that this is the _ideal_ data structure for the job.
66 |
67 | This does not mean that using a vector is wrong, especially given that in this situation, your vector will never really contain a performance hindering number of items.
68 |
69 | For more details on this, please see [Joy of Clojure](https://www.manning.com/books/the-joy-of-clojure-second-edition) section 5.2.7
70 |
71 | ## Code Cleanup
72 |
73 | After I finished the first draft of the code, I took a few steps to make this cleaner:
74 |
75 | **Atom**
76 |
77 | Instead of doing this:
78 |
79 | ```clojure
80 | (def app-state (atom {:keys-pressed #queue []}))
81 |
82 | ;; get
83 | (get @app-state :keys-pressed))
84 | ```
85 |
86 | I changed it to this:
87 |
88 | ```clojure
89 | (def keys-pressed (atom #queue []))
90 |
91 | ;; get
92 | @app-state
93 | ```
94 |
95 | The benefit was that getting the value of `keys-pressed` becomes less code.
96 |
97 | **Higher Order Function**
98 |
99 | My next question was whether I could make my `keyup-handler` function align to more of the principles of functional programming. A route that I chose was to turn `keyup-handler` into a `higher order function`. In this case, it is a function that returns a function. This is a very simple HOF.
100 |
101 | ```clojure
102 | (defn create-hof
103 | [name]
104 | (fn []
105 | (p name)))
106 | ```
107 |
108 | In this example, calling `(create-hof "Terry")` would return another function that when called would console log "terry". In the case of our function, we created a function that would return the event handler, looking like this:
109 |
110 | ```clojure
111 | (create-keyup-handler
112 | [secret f]
113 | (fn [e]))
114 | ```
115 |
116 | This helps because now we have an event handler where the secret and the event are customizable. We are still working with an atom, which could be affected by other functions, so this is not a truly pure function.
117 |
118 | **naming conventions**
119 |
120 | You could use `set` or `create` to name the function, from conversations on clojurians, one approach is to reserve `set` or `register` for functions which call the `addEventListener`. In our case, we are creating a function, so we can prefix with the word `create`.
121 |
122 | # Resources
123 |
124 | - [Atom v. Refs](http://tarynsauer.tumblr.com/post/77631451200/clojure-should-i-use-atoms-or-refs)
125 | https://learnxinyminutes.com/docs/clojure/
126 | - [Understanding persistent vectors](http://hypirion.com/musings/understanding-persistent-vector-pt-1)
127 | - [Why clojure goes fast](http://clojure-goes-fast.com/)
128 |
--------------------------------------------------------------------------------
/12-key-sequence-detection/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/12-key-sequence-detection/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Key Detection
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/12-key-sequence-detection/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/12-key-sequence-detection/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [goog.string :as gstr] goog.string.format)
4 | (:require-macros [app.macros :refer [p pp]]))
5 |
6 | ;; App state
7 |
8 | (def keys-pressed (atom #queue []))
9 |
10 |
11 | ;; Event handlers
12 |
13 | (defn create-keyup-handler
14 | [secret f]
15 | (fn
16 | [e]
17 | ;; add recently pressed key to our queue
18 | (swap! keys-pressed conj (.-key e))
19 |
20 | ;; store as many items as the length of the secret
21 | (when (> (count @keys-pressed) (count secret))
22 | (swap! keys-pressed pop keys-pressed))
23 |
24 | ;; check if user typed secret
25 | (when (and (= (count @keys-pressed) (count secret))
26 | (= (apply str @keys-pressed) secret))
27 | (f))))
28 |
29 |
30 | ;; Event listeners
31 |
32 | (.addEventListener js/window "keyup" (create-keyup-handler "secret" (.-cornify_add js/window)))
33 |
--------------------------------------------------------------------------------
/12-key-sequence-detection/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/13-slide-in-on-scroll/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/13-slide-in-on-scroll/README.md:
--------------------------------------------------------------------------------
1 | # 13 Slide in on scroll
2 |
3 | I found that this lesson did not contain anything new, but it did build upon concepts explored earlier
4 |
5 | - [Higher Order Functions](#higher-order-function)
6 | - [doseq](doseq)
7 |
8 | # Requirements
9 |
10 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up.
11 |
12 | # Quickstart
13 |
14 | Run the following comamnds from the root of the `15-slide-in-on-scroll`
15 |
16 | **1. Run the projcet**
17 |
18 | ```bash
19 | clj -M:dev
20 | ```
21 |
22 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
23 |
24 | ```bash
25 | ➜ clj -h
26 | Version: 1.10.3.855 # this is the version your on
27 | ```
28 |
29 |
30 | **2. Visit the app**
31 |
32 | http://localhost:3000/
33 |
34 | ## Higher Order Functions
35 |
36 | In this case we are using the global function `debounce` and passing our `check-slide` function to it.
37 |
38 | ## doseq
39 |
40 | We are not modifying the collection, we are only getting side effects. `for` can also be used for side effects, but it is going to return another list and this is not what we want to happen, so we use `doseq`. `for` is what's known as a `list comprehension`.
41 |
--------------------------------------------------------------------------------
/13-slide-in-on-scroll/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/13-slide-in-on-scroll/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/13-slide-in-on-scroll/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [goog.string :as gstr] goog.string.format)
4 | (:require-macros [app.macros :refer [p pp]]))
5 |
6 |
7 | ;; Constants
8 |
9 | (def slide-images (.querySelectorAll js/document ".slide-in"))
10 |
11 |
12 | ;; Protocols
13 |
14 | (extend-type js/NodeList
15 | ISeqable
16 | (-seq [node-list] (array-seq node-list)))
17 |
18 |
19 | ;; Event handlers
20 |
21 | (defn check-slide
22 | [e]
23 | (doseq [image slide-images]
24 | (let [scroll-y (.-scrollY js/window)
25 | win-inner-height (.-innerHeight js/window)
26 | image-height (.-height image)
27 | image-offsetTop (.-offsetTop image)
28 | slide-in-at (- (+ scroll-y win-inner-height) (/ image-height 2))
29 | image-bottom (+ image-offsetTop image-height)
30 | half-shown? (> slide-in-at image-offsetTop)
31 | not-scrolled-past? (< scroll-y image-bottom)]
32 | (if (and half-shown? not-scrolled-past?)
33 | (.. image -classList (add "active"))
34 | (.. image -classList (remove "active"))))))
35 |
36 |
37 | ;; Event Listeners
38 |
39 | (.addEventListener js/window "scroll" (.debounce js/window check-slide))
40 |
--------------------------------------------------------------------------------
/13-slide-in-on-scroll/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/14-object-and-arrays/README.md:
--------------------------------------------------------------------------------
1 | # Object and Array
2 |
3 | Wes took a look at `assignment v. reference` in JavaScript for this lesson. I used this lesson to explore the equivalent concept in CLJ/S:
4 |
5 | 0. [Terminology](#terminology)
6 | 1. [Symbols](#symbols)
7 | 2. [Vars](#vars)
8 | 3. [Assignment vs. Binding](#assignment-vs-binding)
9 |
10 | # Lesson
11 |
12 | I work as a developer professionally and I found this lesson refreshing because it can be easy to forget how these seemingly "basic" concepts are difficult for professional and new developers alike to fully grasp. This being said, these are notes that helped me sort this out. Consider the challenge that is explaining the difference between a **key**, **Symbol** and **Var**. I believe a core challenge here is that someone new is looking at them they would immediatley think they are all just **Strings** With this said, this lesson is not done and I will likely revisit this several more times to improve and clarify where possible, but for now, I feel it is a good start.
13 |
14 |
15 | # Resources
16 |
17 | Getting into the nitty-gritty's of all this can be very challenging. What I have provided below is a summation of my understanding of these things. For stronger technical explainations see the following resources:
18 |
19 | * https://clojure.org/reference/vars
20 | * http://fasttrackclojure.blogspot.ca/2010/10/what-no-variables-in-clojure.html
21 | * https://8thlight.com/blog/aaron-lahey/2016/07/20/relationship-between-clojure-functions-symbols-vars-namespaces.html
22 |
23 | # Terminology
24 |
25 | These are the terms used in Clojure. They are not interchangeable, rather they each refer to one clearly defined idea. The spelling is important. If I get the spelling wrong in this document, I likely messed up.
26 |
27 | * Var
28 | * Symbol
29 | * Unqualified Symbol
30 | * Data Type
31 | * resolved Symbol
32 | * value
33 | * root binding
34 | * dynamic thread-local binding
35 | * unbound
36 | * local scope
37 | * global scope
38 | * scope
39 |
40 | # Vars
41 |
42 | This is how you declare a **Var** in Clojure:
43 |
44 | ```clojure
45 | (def my-name "Jerald")
46 |
47 | ;; #'user/my-name
48 | ```
49 |
50 | * A **Var** is a mutable container
51 | * A **Var** declared with `def` is going to always become global in your namespace
52 | * A **Var** references its _binding_ (_value_). In the above, `my-name` (_var_) refernces `"Jerald"` (_binding_)
53 | * A **Var** can have two types of binding: _root binding_ and _dynamic thread-local binding_
54 | * A **Var** can be of several sub-types: _static_, _dynamic_, _free_
55 |
56 | > The root binding
57 |
58 | All **Vars** and their subtypes have a **root binding**. A root binding is the initial value associated with your variable. For example:
59 |
60 | ```clojure
61 | (def name "Thomas")
62 | ```
63 |
64 | In the above, "Thomas" is the **root binding**. Having said this, you do not need to bind a **Var** with a `root value`. Consider;
65 |
66 | ```clojure
67 | (def name)
68 | ```
69 |
70 | In the above example, the **Var** above was not bound to a `value` when it was created, so we consider this **unbound**.
71 |
72 | > Var search order
73 |
74 |
75 | We will look in **local scope** first and than move to the **global scope**
76 |
77 | > Re-bind Vars
78 |
79 | ```clojure
80 | (def name "Thomas")
81 |
82 | (def name "Kate")
83 | ```
84 |
85 | The variable `"name"` is not destroyed and re-created, but rather the **Var** is re-bound to `"Kate"`.
86 |
87 |
88 | # Symbols
89 |
90 | A **Symbol** is [Data Type](https://clojure.org/reference/data_structures). Here are some examples of **Symbols**:
91 |
92 | ```clojure
93 | (def my-name "Jerald")
94 |
95 | (+ 1 2)
96 | ```
97 |
98 | In the above, `+` and `my-name` are **Symbols**. Comparatively, `1` and `2` are **Numbers** which are a different kind of **Data Type**. This could be confusing because in above section we said that `my-name` is a **Var**. The truth is that the reaility is a little more complex and for most of your development process, does not really matter, but keep the following in mind:
99 |
100 | * `my-name` is a **Symbol** which references a **Var**
101 | * When we reference a **Symbol** the symbol knows where to find a **Var** and the **Var** is going to return it's binding.
102 |
103 | This distinction is going to help when / if you really need to dive into CLJ/S code. For a great overview of this, please see [this article](https://8thlight.com/blog/aaron-lahey/2016/07/20/relationship-between-clojure-functions-symbols-vars-namespaces.html)
104 |
105 |
106 | # Assignment vs Binding
107 |
108 | When we talk about variables in clojure we do not say **assigning**, we actually say **binding**. I like to think that one of the key differences is how assigning vs. binding looks. In JS, you would do this to create a variable:
109 |
110 | ```javascript
111 | var myName = "Jay"
112 | ```
113 |
114 | In the above example, the `=` is an example of an [assignment operator](https://www.w3schools.com/js/js_assignment.asp). Clojure does not have assignment operators as you can see in the way we declare a **Var**.
115 |
116 | ```clojure
117 | (def my-name "Jay")
118 | ```
119 |
120 | This seems to be the only way to see the differene in syntax. When it comes to Clojure internals, there is likely other differences, but for now, the takeaway is we don't assign in CLJ/S we bind.
121 |
--------------------------------------------------------------------------------
/15-localstorage-and-event-delegation/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/15-localstorage-and-event-delegation/README.md:
--------------------------------------------------------------------------------
1 | # 15 Local Storage
2 |
3 | For this excercise, I focused on the JS interop aspects of CLJS. By this I mean that I did not use immutable datastructures in favor of things like `array.push`. The goal was to see how this would feel. With this said, here are some of the larger topics touched on:
4 |
5 | - [apply](#apply)
6 | - [map-indexed](#map-indexed)
7 | - [how to not do HTML in CLJS](#how-to-not-do-html)
8 |
9 | # Requirements
10 |
11 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up.
12 |
13 | # Quickstart
14 |
15 | Run the following comamnds from the root of the `17-localstorage-and-event-delegation`
16 |
17 | **1. Run the projcet**
18 |
19 | ```bash
20 | clj -M:dev
21 | ```
22 |
23 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
24 |
25 | ```bash
26 | ➜ clj -h
27 | Version: 1.10.3.855 # this is the version your on
28 | ```
29 |
30 |
31 | **2. Visit the app**
32 |
33 | http://localhost:3000/
34 |
35 | # Apply
36 |
37 | Wes used `array.join('')` in the video series. There is nothing quite like this in clojurescript. We can do something similar using reduce or apply. I opted to use apply because I have not had the opportunity thus far. This is what my code looked like
38 |
39 | ```clojure
40 | (apply str (map-indexed #(make-plate-html %1 %2) plates))
41 | ```
42 |
43 | Lets break the above down in the order they are evaluated so its a little easier to grasp:
44 |
45 | ```clojure
46 | ;; map over each item in plates (array) and for each item return an HTML string
47 | (map #(make-plate-html (.-text %1)) plates))
48 |
49 | ;; the above returns something like this - I am writing HTML as a placeholder for
50 | ;; the list item which is actually in there
51 | ["HTML" "HTML" "HTML"]
52 |
53 | ;; think: apply the function str over each element which is the equivalent of
54 | ;; writing ( str "HTML" "HTML" "HTML")
55 | (apply str ["HTML" "HTML" "HTML"])
56 | ```
57 |
58 | # map-indexed
59 |
60 | I used `map-indexed` because I needed to know the current index of the current element I was mapping over. JS provides this natively with the map function, but in CLJ you have to explicitly use `map-indexed`.
61 |
62 | # How to not do HTML
63 |
64 | You will notice in the `make-plate-html` function I have a big-ass string concatenation fiesta going on. I would strongly suggest never doing this like I have in a production app. This code is difficult to maintain, hard to read, and likely a security risk. I did this as I have just complete the excercise without introducing a third party library. Having said this, in a production app I would just use something like [hiccup](https://github.com/weavejester/hiccup) to handle this in a smart way.
65 |
66 | # Resources
67 |
68 | http://www.spacjer.com/blog/2014/09/12/clojurescript-javascript-interop/
69 |
--------------------------------------------------------------------------------
/15-localstorage-and-event-delegation/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/15-localstorage-and-event-delegation/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LocalStorage
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
"))
24 |
25 | (defn make-plate-list
26 | [plates]
27 | (apply str (map-indexed #(make-plate-html %1 %2) plates)))
28 |
29 | (defn save-items
30 | "save a food item in the browsers local storage"
31 | [items]
32 | (.setItem js/localStorage "items" items))
33 |
34 | (defn update-items-list
35 | "Update the innerHTML of the items list"
36 | [prev-list next-list]
37 | (set! (.-innerHTML prev-list) (make-plate-list next-list)))
38 |
39 | (defn update-item-done-state
40 | "Toggle the done state of an item"
41 | [item]
42 | (set! (.-done item) (not (.-done item))))
43 |
44 |
45 | ;; Event handlers
46 |
47 | (defn add-item
48 | [e]
49 | (.preventDefault e)
50 |
51 | (this-as this
52 | (let [text (.. this (querySelector "[name=item]") -value)
53 | item (create-tapa text false)]
54 | (.push js/items item)
55 | (update-items-list js/itemsList js/items)
56 | (save-items (.stringify js/JSON js/items))
57 | (.reset this))))
58 |
59 | (defn toggle-done
60 | [e]
61 | (when (.. e -target (matches "input"))
62 | (let [element (.-target e)
63 | index (.. element -dataset -index)
64 | item (aget js/items index)]
65 | (update-item-done-state item)
66 | (save-items (.stringify js/JSON js/items))
67 | (update-items-list js/itemsList js/items))))
68 |
69 |
70 | ;; Event listeners
71 |
72 | (.addEventListener js/addItems "submit" add-item)
73 |
74 | (.addEventListener js/itemsList "click" toggle-done)
75 |
76 |
77 | ;; Populate the list of items on page load
78 |
79 | (update-items-list js/itemsList js/items)
80 |
--------------------------------------------------------------------------------
/15-localstorage-and-event-delegation/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/16-mousemove-text-shadows/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/16-mousemove-text-shadows/README.md:
--------------------------------------------------------------------------------
1 | # 16 Mouse Move
2 |
3 | This lesson presented a good opportunity to use `Math` and challenge the imperative approach to programming with the functional approach.
4 |
5 | - [Math](#math)
6 | - [Functional Thinking](#functional-thinking)
7 | - [Destructuring Clojurscript](#destructuring-javascript)
8 |
9 | # Requirements
10 |
11 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up.
12 |
13 | # Quickstart
14 |
15 | Run the following comamnds from the root of the `18-mousemove-and-text-shadows`
16 |
17 | **1. Run the projcet**
18 |
19 | ```bash
20 | clj -M:dev
21 | ```
22 |
23 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
24 |
25 | ```bash
26 | ➜ clj -h
27 | Version: 1.10.3.855 # this is the version your on
28 | ```
29 |
30 |
31 | **2. Visit the app**
32 |
33 | http://localhost:9000
34 |
35 | # Math
36 |
37 | `Math` is short for `java.lang.Math` which is one of the Java classes automatically imported into your namespace for you by Clojure. This is the reason you can access `Math` in any of your namespaces by just using something like `(Math/round 8.837372)`. You will use `Math` when you need functions like `round`, `abs`, `exp`. There is a lot more to this, so I recommend taking a look at [Clojure in Action - pg 11](https://www.manning.com/books/clojure-in-action-second-edition).
38 |
39 | If you want to research this more, this falls into the **Java Interop** category.
40 |
41 | # Functional Thinking
42 |
43 | This lesson introduces an opportunity to think like a functional programmer. Wes writes his code like this:
44 |
45 | ```javascript
46 | function moveShadow(e) {
47 | let x = e.target.offsetHeight;
48 | let y = e.target.offsetWidth;
49 |
50 | if (condition) {
51 | x += 10; //update value
52 | y += 15; // update value
53 | }
54 |
55 | doSomethingWithSideEffects(x, y);
56 | }
57 | ```
58 |
59 | In the above we have local mutable state, or state that changes within the function. This is an imperative approach. There is nothing wrong with this for imperative languages and you can do something like this in Clojure, but clojure will fight you. The result will be code that is confusing and unmaintainable. So rather than imperative, we can try a more functional approach.
60 |
61 | A functional approach could be like this:
62 |
63 | ```clojure
64 | (defn get-xy
65 | [e this]
66 | (let [x (.-offsetX e)
67 | y (.-offsetY e)]
68 | (if (not= this (.-target e))
69 | [(+ x (.. e -target -offsetLeft)) (+ y (.. e -target -offsetTop))]
70 | [x y])))
71 | ```
72 |
73 | Here is what the above looks like in JS if it makes it a little clearer
74 |
75 | ```javascript
76 | function getXY (e this) {
77 | const { offsetX: x, offsetY: y } = e;
78 |
79 | if ( this !== e.target ) {
80 | const modx = x + e.target.offsetLeft
81 | const mody = y + e.target.offsetTop
82 | return [modx, mody]
83 | }
84 |
85 | return [x, y]
86 | }
87 | ```
88 |
89 | `get-xy` is the rewrite of the variable re-assignment we see in the JS solution. In this example, we choose to not set variables that are re-assigned, but create a function that returns the original `x and y` or the modified `x and y`. We can now use the above function inside of our `shadow-move` function.
90 |
91 | # Destructuring Clojurscript
92 |
93 | Destructuring is a nice way of writing more concise code. This lesson was my first opporunity to use destructuring as seen here:
94 |
95 | ```clojure
96 | (let [[x y] (get-xy e this)])
97 | ```
98 |
99 | You will also notice that in the Functional Thinking example above, I did not destructure the `offsetX` and `offsetY` in the let:
100 |
101 | ```clojure
102 | (let [x (.-offsetX e)
103 | y (.-offsetY e)])
104 | ```
105 |
106 | We did not do this because under the hood `let` is using `clojure.core/destructure` to create its binding form and does not expand access macros like that.
107 |
108 | If you did want to enable Clojure to do this, you could replacing let with something that uses an expanded destructure, or having a separate macro meant to be used inside a let block. I will likely not take on this challenge at the moment, but its a good example/opportunity to explore writing macros. To start this excercise, take a look at the [destructure macro](<(https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L4355)>)
109 |
110 | For more info on destructuring, check out the [clojure destructuring documentation](https://clojure.org/guides/destructuring) and there is also a handy [destructuring cheatsheet](https://gist.github.com/john2x/e1dca953548bfdfb9844) and [another guide here](http://blog.brunobonacci.com/2014/11/16/clojure-complete-guide-to-destructuring/)
111 |
112 | and here is a great over of [syntax in clojurescript](https://cljs.github.io/api/syntax/)
113 |
--------------------------------------------------------------------------------
/16-mousemove-text-shadows/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/16-mousemove-text-shadows/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Mouse Shadow
6 |
7 |
8 |
9 |
10 |
🔥WOAH!
11 |
12 |
13 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/16-mousemove-text-shadows/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/16-mousemove-text-shadows/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [goog.string :as gstr] goog.string.format)
4 | (:require-macros [app.macros :refer [p pp]]))
5 |
6 | ;; globals
7 |
8 | (def hero (.querySelector js/document ".hero"))
9 |
10 | (def text (.querySelector hero "h1"))
11 |
12 | (def walk 100)
13 |
14 |
15 | ;; Helpers
16 |
17 | (defn calc-x-walk
18 | [x walk width]
19 | (int (- (* (/ x width) walk) (/ walk 2))))
20 |
21 | (defn calc-y-walk
22 | [y walk height]
23 | (int (- (* (/ y height) walk) (/ walk 2))))
24 |
25 | (defn make-text-shadow
26 | [x y]
27 | (str x"px " y"px " "0 red"))
28 |
29 | (defn get-xy
30 | [e this]
31 | (let [x (.-offsetX e)
32 | y (.-offsetY e)]
33 | (if (not= this (.-target e))
34 | [(+ x (.. e -target -offsetLeft)) (+ y (.. e -target -offsetTop))]
35 | [x y])))
36 |
37 |
38 | ;; Event handlers
39 |
40 | (defn move-shadow
41 | [e]
42 | (this-as this
43 | (let [width (.-offsetWidth hero)
44 | height (.-offsetHeight hero)
45 | [x y] (get-xy e this)
46 | x-walk (calc-x-walk x walk width)
47 | y-walk (calc-y-walk y walk height)]
48 | (set! (.. text -style -textShadow) (make-text-shadow x-walk y-walk)))))
49 |
50 |
51 | ;; Event listeners
52 |
53 | (.addEventListener hero "mousemove" move-shadow)
54 |
--------------------------------------------------------------------------------
/16-mousemove-text-shadows/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/17-sort-without-articles/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/17-sort-without-articles/README.md:
--------------------------------------------------------------------------------
1 | # 17 Sort Without Articles
2 |
3 | This lesson goes over things like regexes and comparators
4 |
5 | - [Sorting](#sort)
6 | - [Strip](#strip)
7 | - [Regex](#regex)
8 |
9 | # Requirements
10 |
11 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up.
12 |
13 | # Quickstart
14 |
15 | Run the following comamnds from the root of the `18-mousemove-and-text-shadows`
16 |
17 | **1. Run the projcet**
18 |
19 | ```bash
20 | clj -M:dev
21 | ```
22 |
23 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
24 |
25 | ```bash
26 | ➜ clj -h
27 | Version: 1.10.3.855 # this is the version your on
28 | ```
29 |
30 |
31 | **2. Visit the app**
32 |
33 | http://localhost:9000/
34 |
35 | # Sorting
36 |
37 | Sorting is not difficult in JS or Clojure, but clojure definitely provides a more terse syntax.
38 |
39 | ```clojure
40 | (def bands ["The Plot in You", "The Devil Wears Prada", "Pierce the Veil"])
41 |
42 | (sort bands)
43 | ```
44 |
45 | The above will alphabetically sort things for you. In JS you would have to pass a [comparator](https://clojure.org/guides/comparators) (a function that tells sort how to sort things). Having said this, the task we are trying to execute does require we pass `sort` a `comparator` even in clojure.
46 |
47 | # Regex
48 |
49 | Regex always feels oddly different when I write them in clojure, so I wanted to note how to write case-insensitive regexes here:
50 |
51 | ```clojure
52 | #"(?i)a |the |an "
53 | ```
54 |
55 | As you can see, you prefix with `(?i)`. Most everything else about regexes is usually the same, but you get odd moments like the above where your just left wondering.
56 |
57 | # Strip
58 |
59 | This can also be known as `trimming`, but the idea is the same: Remove a type or series of characters from a string. The most common case is usually `trimming` whitespace. This is likely why clojure actually provides a `trim` function.
60 |
--------------------------------------------------------------------------------
/17-sort-without-articles/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/17-sort-without-articles/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sort Without Articles
6 |
7 |
8 |
9 |
43 |
44 |
"))
16 |
17 | (defn make-band-list
18 | [bands]
19 | (apply str (map make-band-listItem bands)))
20 |
21 |
22 | ;; comparator
23 |
24 | (defn alphabetically
25 | [a b]
26 | (compare (strip-article a)
27 | (strip-article b)))
28 |
29 |
30 | ;; event listener
31 |
32 | (set! (.-innerHTML (.querySelector js/document "#bands")) (make-band-list (sort alphabetically js/bands)))
33 |
--------------------------------------------------------------------------------
/17-sort-without-articles/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/18-string-times/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/18-string-times/README.md:
--------------------------------------------------------------------------------
1 | # String Times
2 |
3 | I liked this lesson because it provided an opportunity to explore how strings and numbers respond to math in Clojure.
4 |
5 | - [Quick Start](#quick-start)
6 | - [Multiplication and Strings](#multiplication-and-strings)
7 |
8 | # Requirements
9 |
10 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up.
11 |
12 | # Quick Start
13 |
14 | Run the following comamnds from the root of `20-string-times`
15 |
16 | **1. Run the projcet**
17 |
18 | ```bash
19 | clj -M:dev
20 | ```
21 |
22 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
23 |
24 | ```bash
25 | ➜ clj -h
26 | Version: 1.10.3.855 # this is the version your on
27 | ```
28 |
29 |
30 | **2. Visit the app**
31 |
32 | http://localhost:9000/
33 |
34 | # Multiplication and Strings
35 |
36 | In Clojure, you cannot multiple a string and a number. Whay even make this a note? Because in JS you can and it is a source of great pain if you are not careful. For example:
37 |
38 | ```clojure
39 | (* "5" 5)
40 | ```
41 |
42 | The above is going to result in a `ClassCastException` error. What would happen in JS?
43 |
44 | ```javascript
45 | var hot = "5" * 5; // 25
46 |
47 | typeof hot; // 'number'
48 | ```
49 |
50 | So in JS, it will actually result in a number. Not so much with Clojure. So what does this mean? Well, we have to be sure that we are always doing math against numbers. So how do we **convert a string to a number**?
51 |
52 | From my research and adivce from **clojurians** the suggested approach is to use `Integer/parseInt` / `Double/parseDouble`. Another approach is to use `cljs.reader/read-string`. Regarding the later, the advice is to not opt for `read-string` first because it does some interesting things under the hood that will not be what you are expecting. There is also the fact that there are no less than three `read-string` functions in clojure, so it is confusing. Here is an article that outlines [some of the differences](https://coderwall.com/p/8krwqg/clojure-script-compatibility-magic).
53 |
--------------------------------------------------------------------------------
/18-string-times/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}
3 |
4 | :aliases {:dev {:main-opts ["-m" "cljs.main"
5 | "-ro" "ro.edn"
6 | "-w" "src"
7 | "-c" "app.core"
8 | "-r"]}}}
9 |
--------------------------------------------------------------------------------
/18-string-times/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Videos
6 |
7 |
8 |
9 |
10 | Video 1
11 |
12 |
13 | Video 2
14 |
15 |
16 | Video 3
17 |
18 |
19 | Video 4
20 |
21 |
22 | Video 5
23 |
24 |
25 | Video 6
26 |
27 |
28 | Video 7
29 |
30 |
31 | Video 8
32 |
33 |
34 | Video 9
35 |
36 |
37 | Video 10
38 |
39 |
40 | Video 11
41 |
42 |
43 | Video 12
44 |
45 |
46 | Video 13
47 |
48 |
49 | Video 14
50 |
51 |
52 | Video 15
53 |
54 |
55 | Video 16
56 |
57 |
58 | Video 17
59 |
60 |
61 | Video 18
62 |
63 |
64 | Video 19
65 |
66 |
67 | Video 20
68 |
69 |
70 | Video 21
71 |
72 |
73 | Video 22
74 |
75 |
76 | Video 23
77 |
78 |
79 | Video 24
80 |
81 |
82 | Video 25
83 |
84 |
85 | Video 26
86 |
87 |
88 | Video 27
89 |
90 |
91 | Video 28
92 |
93 |
94 | Video 29
95 |
96 |
97 | Video 30
98 |
99 |
100 | Video 31
101 |
102 |
103 | Video 32
104 |
105 |
106 | Video 33
107 |
108 |
109 | Video 34
110 |
111 |
112 | Video 35
113 |
114 |
115 | Video 36
116 |
117 |
118 | Video 37
119 |
120 |
121 | Video 38
122 |
123 |
124 | Video 39
125 |
126 |
127 | Video 40
128 |
129 |
130 | Video 41
131 |
132 |
133 | Video 42
134 |
135 |
136 | Video 43
137 |
138 |
139 | Video 44
140 |
141 |
142 | Video 45
143 |
144 |
145 | Video 46
146 |
147 |
148 | Video 47
149 |
150 |
151 | Video 48
152 |
153 |
154 | Video 49
155 |
156 |
157 | Video 50
158 |
159 |
160 | Video 51
161 |
162 |
163 | Video 52
164 |
165 |
166 | Video 53
167 |
168 |
169 | Video 54
170 |
171 |
172 | Video 55
173 |
174 |
175 | Video 56
176 |
177 |
178 | Video 57
179 |
180 |
181 | Video 58
182 |
183 |
184 |
186 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/18-string-times/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/18-string-times/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [goog.string :as gstr] goog.string.format)
4 | (:require-macros [app.macros :refer [p pp]]))
5 |
6 |
7 | ;; globals
8 |
9 | (def node-list (.querySelectorAll js/document "[data-time]"))
10 |
11 |
12 | ;; protocols
13 |
14 | (extend-type js/NodeList
15 | ISeqable
16 | (-seq [node-list] (array-seq node-list)))
17 |
18 |
19 | ;; helpers
20 |
21 | (defn get-dataset-time
22 | [node]
23 | (.. node -dataset -time))
24 |
25 |
26 | (defn split-time-string
27 | "Takes a string like 5:45, splits it, and returns a vector of numbers lie
28 | [5 45]"
29 | [timestring]
30 | (map #(js/parseInt %1) (clojure.string/split timestring #":")))
31 |
32 |
33 | ;; Helpers
34 |
35 | (defn get-seconds-from-node
36 | [acc node]
37 | (let [time-string (get-dataset-time node)
38 | [mins secs] (split-time-string time-string)
39 | total-seconds (+ (* mins 60) secs)]
40 | (+ acc total-seconds)))
41 |
42 |
43 | (defn get-total-seconds-from-nodes
44 | [nodelist]
45 | (reduce get-seconds-from-node 0 node-list))
46 |
47 |
48 | (defn get-hours-min-seconds
49 | [seconds]
50 | (let [hours (Math/floor (/ seconds 3600))
51 | mins (Math/floor (/ (mod seconds 3600) 60))
52 | secondsLeft (mod (mod seconds 3600) 60)]
53 | [hours mins secondsLeft]))
54 |
55 |
56 | (pp (get-hours-min-seconds (get-total-seconds-from-nodes node-list)))
57 |
--------------------------------------------------------------------------------
/18-string-times/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/19-unreal-webcam-fun/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/19-unreal-webcam-fun/README.md:
--------------------------------------------------------------------------------
1 | # Unreal Webcam Fun
2 |
3 | For this one I did not add the rgb effects and only focused on showing the webcam in the video element and taking a picture. The other items have been done before and I spent more time researching more idiomatic clojure code
4 |
5 | - [Quick Start](#quick-start)
6 | - [Working with Native JS Functions](#working-with-native-js-functions)
7 | - [Thread v. Double Dot Macro](#thread-v-dot-dot-macro)
8 | - [Require from Google Closure](#require-from-google-closure)
9 | - [Time Intervals](#time-intervals)
10 | - [Add to JS Global Scope](#add-to-js-global-scope)
11 |
12 | # Quick Start
13 |
14 | Run the following comamnds from the root of `20-string-times`
15 |
16 | **1. Run the projcet**
17 |
18 | ```bash
19 | clj -M:dev
20 | ```
21 |
22 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
23 |
24 | ```bash
25 | ➜ clj -h
26 | Version: 1.10.3.855 # this is the version your on
27 | ```
28 |
29 |
30 | **2. Visit the app**
31 |
32 | http://localhost:9000/
33 |
34 | # Working with Native JS Functions
35 |
36 | This is more of a note, but sometimes you might try to use a JS method and be confused by the fact that passing a CLJS object will not work with it. In cases like this, remember to switch to a JS object instead. For example:
37 |
38 | ```clojure
39 | (-> js/navigator
40 | .-mediaDevices
41 | (.getUserMedia #js {:video true :audio false}) ;; <-- native js object
42 | (.then handle-promise)
43 | (.catch handle-promise-err)))
44 | ```
45 |
46 | By using the `#js` macro, I am creating and passing a JS object to `.getUserMedia`
47 |
48 | # Thread v dot dot macro
49 |
50 | You are going to notice that I use the `->` macro for the first time in this series. I am going to opt for this going forward. It appears to be the preferred option based on conversation I have had with other **clojurians**.
51 |
52 | # Require from Google Closure
53 |
54 | Take a look at [this article](https://www.martinklepsch.org/posts/requiring-closure-namespaces.html) for more information.
55 |
56 | # Time Intervals
57 |
58 | Lets say you want to `console.log` something once every 2 seconds. To do this in JavaScript, you would use something like this:
59 |
60 | ```javascript
61 | setInterval(() => console.log("tick"), 1000);
62 | ```
63 |
64 | How would you achieve the same thing in clojure? Turns out there are no less than 3 approaches to this. Below, I will show examples of all three of the options, but I would say that either **option 1** or **option 2** are your best bets. `core.async` is a beast of a library to use for such a simple task.
65 |
66 | **1. js/setInterval**
67 |
68 | ```clojure
69 | (js/setInterval #(p "tick") 1000)
70 | ```
71 |
72 | **2. goog.timer**
73 |
74 | To use this, you need to require `goog timer` into your namespace like this:
75 |
76 | ```clojure
77 | (:require [goog.events :as events]
78 | [goog.Timer :as timer])
79 | (:import [goog Timer])
80 | ```
81 |
82 | and than you can use it like this
83 |
84 | ```clojure
85 | (def my-timer (Timer.))
86 |
87 | (events/listen my-timer timer/TICK (fn [e] (p "hi")))
88 |
89 | (.start my-timer)
90 | ```
91 |
92 | Working with google closure can be a little confusing, so I recommend reviewing the source code when stuck. For example, when learning how to start the above timer, I reviewed [google closure timer source code](https://github.com/google/closure-library/tree/master/closure/goog/timer). Specifically, checkout `timer_test.js`.
93 |
94 | **3. core.async**
95 |
96 | This is the heavy duty option. To start using this approach, you have to add it to your projects dependencies. See `build.boot` for this line:
97 |
98 | ```clojure
99 | [org.clojure/core.async "0.3.443"]
100 | ```
101 |
102 | > My version might become out-of-date, checkout the [official github repo](https://github.com/clojure/core.async) for the most current version
103 |
104 | You then need to import core.async into your namespace. This would look like this:
105 |
106 | ```clojure
107 | (:require [cljs.core.async :as async])
108 | (:require-macros [cljs.core.async.macros :as m :refer [go]]))
109 | ```
110 |
111 | > Take note that you are importing from `cljs` and not `clj`.
112 |
113 | Now we can make ourselves a little setInterval timer core.async style:
114 |
115 | ```clojure
116 | (go
117 | (loop []
118 | (async/
3 |
4 |
5 |
6 | Get User Media Code Along!
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/19-unreal-webcam-fun/resources/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | *, *:before, *:after {
6 | box-sizing: inherit;
7 | }
8 |
9 | html {
10 | font-size: 10px;
11 | background:#ffc600;
12 | }
13 |
14 | .photobooth {
15 | background:white;
16 | max-width:150rem;
17 | margin: 2rem auto;
18 | border-radius:2px;
19 | }
20 |
21 | /*clearfix*/
22 | .photobooth:after {
23 | content: '';
24 | display: block;
25 | clear: both;
26 | }
27 |
28 | .photo {
29 | width:100%;
30 | float:left;
31 | }
32 |
33 | .player {
34 | position: absolute;
35 | top:20px;
36 | right: 20px;
37 | width:200px;
38 | }
39 |
40 | /*
41 | Strip!
42 | */
43 |
44 | .strip {
45 | padding:2rem;
46 | }
47 | .strip img {
48 | width:100px;
49 | overflow-x: scroll;
50 | padding:0.8rem 0.8rem 2.5rem 0.8rem;
51 | box-shadow:0 0 3px rgba(0,0,0,0.2);
52 | background:white;
53 | }
54 |
55 | .strip a:nth-child(5n+1) img { transform: rotate(10deg); }
56 | .strip a:nth-child(5n+2) img { transform: rotate(-2deg); }
57 | .strip a:nth-child(5n+3) img { transform: rotate(8deg); }
58 | .strip a:nth-child(5n+4) img { transform: rotate(-11deg); }
59 | .strip a:nth-child(5n+5) img { transform: rotate(12deg); }
60 |
--------------------------------------------------------------------------------
/19-unreal-webcam-fun/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/19-unreal-webcam-fun/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [cljs.core.async :as async])
4 | (:require-macros [app.macros :refer [p pp]]
5 | [cljs.core.async.macros :as m :refer [go]]))
6 |
7 |
8 | ;; Globals
9 |
10 | (def video (.querySelector js/document ".player"))
11 |
12 | (def canvas (.querySelector js/document ".photo"))
13 |
14 | (def ctx (.getContext canvas "2d"))
15 |
16 | (def strip (.querySelector js/document ".strip"))
17 |
18 | (def snap (.querySelector js/document ".snap"))
19 |
20 |
21 | ;; Webcam helpers
22 |
23 | (defn handle-promise
24 | [local-media-stream]
25 | (set! (.-src video) (.. js/window -URL (createObjectURL local-media-stream)))
26 | (.play video))
27 |
28 |
29 | (defn handle-promise-err
30 | [err]
31 | (p "You did not give me permission to use the video"))
32 |
33 |
34 | (defn get-video
35 | []
36 | (-> js/navigator
37 | .-mediaDevices
38 | (.getUserMedia #js {:video true :audio false})
39 | (.then handle-promise)
40 | (.catch handle-promise-err)))
41 |
42 |
43 | (defn paint-to-canvas
44 | []
45 | (let [height (.-videoHeight video)
46 | width (.-videoWidth video)]
47 | (set! (.-width canvas) width)
48 | (set! (.-height canvas) height)
49 | (go
50 | (loop []
51 | (async/"))
67 | (.insertBefore strip link (.-firstChild strip)))))
68 |
69 |
70 | (get-video)
71 |
72 | (.addEventListener video "canplay" paint-to-canvas)
73 |
--------------------------------------------------------------------------------
/19-unreal-webcam-fun/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/20-native-speech-recognition/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/20-native-speech-recognition/README.md:
--------------------------------------------------------------------------------
1 | # Native Speech Recognition
2 |
3 | Customize the HTML5 Video Player with CLJS.
4 |
5 | - [Quickstart](#quickstart)
6 | - [Learnings](#learnings)
7 | - [JS Hot Reloading](#js-hot-reloading)
8 | - [Global Functions](#global-functions)
9 |
10 | ## Quickstart
11 |
12 | ```bash
13 | clj -M:dev
14 | ```
15 |
16 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
17 |
18 | ```bash
19 | ➜ clj -h
20 | Version: 1.10.3.855 # this is the version your on
21 | ```
22 |
23 |
24 | Visit site at `http://localhost:3000`
25 |
26 | ## Learnings
27 |
28 | ### JS Hot Reloading
29 |
30 | This would be a good example of how to perform hot reloading with vanilla javascript as you get an error every time you hit save in this project.
31 |
32 | ### Global Functions
33 |
34 | In this lesson, all the global functions had to be in the HTML file vs my CLJS file. I don't actually think this is required, but to keep this exercise focused, I have left it as such. Would like to go back and change this up. The goal is to define `words` and `speech` object in the CLJS file.
35 |
--------------------------------------------------------------------------------
/20-native-speech-recognition/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}
3 | org.clojure/core.async {:mvn/version "1.3.610"}}
4 |
5 | :aliases {:dev {:main-opts ["-m" "cljs.main"
6 | "-ro" "ro.edn"
7 | "-w" "src"
8 | "-c" "app.core"
9 | "-r"]}}}
10 |
--------------------------------------------------------------------------------
/20-native-speech-recognition/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Speech Detection
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
27 |
28 |
29 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/20-native-speech-recognition/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/20-native-speech-recognition/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require-macros [app.macros :refer [p pp]]))
4 |
5 |
6 | ;; Globals
7 |
8 |
9 | ;; Handlers
10 |
11 | (defn write-msg! [msg final?]
12 | ; Add new paragraph
13 | (when final?
14 | (p "NEW PARA")
15 | (set! js/paragraph (.createElement js/document "p"))
16 | (.. js/words (appendChild js/paragraph)))
17 |
18 | ; Add to existing paragraph
19 | (when-not final?
20 | (p "ORIGINAL")
21 | (set! (.-textContent js/paragraph) (clojure.string/trim msg))))
22 |
23 |
24 | (defn- handle-results [results]
25 | (let [index (.-resultIndex results)
26 | final? (.-isFinal (aget (.-results results) 0))
27 | message (.-transcript (aget (.-results results) index 0))
28 | message-o (.-transcript (aget (.-results results) 0 0))]
29 | (if (not (and (= 1 index) (= message-o message)))
30 | (write-msg! (clojure.string/trim message) final?))))
31 |
32 |
33 | ;; Configure speech recognition
34 |
35 | (set! (.-interimResults js/speech) true)
36 |
37 | ;; Start
38 |
39 | (.addEventListener js/speech "result" handle-results)
40 |
41 | (.addEventListener js/speech "end" #(.start js/speech))
42 |
43 | (.start js/speech)
44 |
--------------------------------------------------------------------------------
/20-native-speech-recognition/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | (defmacro p
6 | "Print and return native JavaScript argument."
7 | [x]
8 | `(let [res# ~x]
9 | (.log js/console res#)
10 | res#))
11 |
12 |
13 | (defmacro pp
14 | "Pretty print and return argument (uses `prn-str` internally)."
15 | [x]
16 | `(let [res# ~x]
17 | (.log js/console (prn-str res#))
18 | res#))
19 |
--------------------------------------------------------------------------------
/21-geolocation/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 | /.nrepl-history
11 | /.cpcache
12 | /out
13 | /node_modules
14 |
--------------------------------------------------------------------------------
/21-geolocation/README.md:
--------------------------------------------------------------------------------
1 | # Geolocation
2 |
3 | Have not started this one just yet. Working out HTTPS options for figwheel.
4 |
5 | - [Quickstart](#quickstart)
6 |
7 | ## Quick Start
8 |
9 | ```bash
10 | clojure -m figwheel.main -b dev -r
11 | ```
12 |
--------------------------------------------------------------------------------
/21-geolocation/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "target" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}
3 | com.bhauman/figwheel-main {:mvn/version "0.2.13"}}}
4 |
--------------------------------------------------------------------------------
/21-geolocation/dev.cljs.edn:
--------------------------------------------------------------------------------
1 | {:main app.core}
2 |
--------------------------------------------------------------------------------
/21-geolocation/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/21-geolocation/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | (ns app.core
2 | (:require-macros [app.macros :refer [p pp]]))
3 |
4 | (def last-hole (atom 0))
5 | (def time-up (atom false))
6 | (def score (atom 0))
7 |
8 | (def holes (-> js/document (.querySelectorAll ".hole")))
9 | (def score-board (-> js/document (.querySelector ".score")))
10 | (def moles (-> js/document (.querySelectorAll ".mole")))
11 |
12 |
13 | (defn random-time
14 | "Generate a random time in miliseconds"
15 | [min max]
16 | (as-> (- max min) time
17 | (* time (-> js/Math (.random)))
18 | (+ time min)
19 | (-> js/Math (.round time))))
20 |
21 |
22 | (defn random-hole
23 | "Select a hole at random"
24 | [holes]
25 | (let [index (as-> (count holes) hole-count
26 | (* hole-count (-> js/Math (.random)))
27 | (-> js/Math (.floor hole-count)))
28 | hole (nth holes index)]
29 |
30 | (when (= index last-hole)
31 | (random-hole holes)
32 | (p "same hole"))
33 |
34 | (reset! last-hole index)
35 |
36 | hole))
37 |
38 |
39 | (defn peep
40 | "Show a mole"
41 | [holes]
42 | (let [time (random-time 200, 1000)
43 | hole (random-hole holes)]
44 |
45 | (-> hole .-classList (.add "up"))
46 |
47 | (js/setTimeout
48 | (fn []
49 | (-> hole .-classList (.remove "up"))
50 | (when-not @time-up
51 | (peep holes)))
52 | time)))
53 |
54 |
55 | (defn bonk
56 | "Hide mole when user clicks of them"
57 | [e]
58 | (this-as this
59 | (if-not (-> e .-isTrusted)
60 | nil
61 | (do
62 | (swap! score inc)
63 | (-> this .-classList (.remove "up"))
64 | (set! (-> score-board .-textContent) @score)))))
65 |
66 |
67 |
68 |
69 | (defn startGame
70 | "You know what it is"
71 | []
72 | (set! (-> score-board .-textContent) 0)
73 |
74 | (reset! time-up false)
75 |
76 | (reset! score 0)
77 |
78 | (peep (array-seq holes))
79 |
80 | (js/setTimeout #(reset! time-up true) 10000))
81 |
82 |
83 | (doseq [mole (array-seq moles)]
84 | (-> mole (.addEventListener "click" bonk)))
85 |
--------------------------------------------------------------------------------
/21-geolocation/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | (ns app.macros)
2 |
3 |
4 | (defmacro p
5 | "Print and return native JavaScript argument."
6 | [x]
7 | `(let [res# ~x]
8 | (.log js/console res#)
9 | res#))
10 |
11 |
12 | (defmacro pp
13 | "Pretty print and return argument (uses `prn-str` internally)."
14 | [x]
15 | `(let [res# ~x]
16 | (.log js/console (prn-str res#))
17 | res#))
18 |
--------------------------------------------------------------------------------
/22-follow-along-links/.gitignore:
--------------------------------------------------------------------------------
1 | # clojure
2 | out
3 | .cpcache
4 |
--------------------------------------------------------------------------------
/22-follow-along-links/README.md:
--------------------------------------------------------------------------------
1 | # Follow Along Links
2 |
3 | - [Quickstart](#quickstart)
4 | - [Learnings](#learnings)
5 | - [Macros](#macro)
6 | - [Resources](#resources)
7 |
8 | ## Quickstart
9 |
10 | ```bash
11 | clj -M:dev
12 | ```
13 |
14 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like:
15 |
16 | ```bash
17 | ➜ clj -h
18 | Version: 1.10.3.855 # this is the version your on
19 | ```
20 |
21 |
22 | Visit site at `http://localhost:9000`
23 |
24 | ## Learnings
25 |
26 | ### Macros
27 |
28 | ```clojure
29 | (-> highlight .-style .-width)
30 | ```
31 |
32 | I found myself writing the above a bunch and wondered if I could explore different ways of doing this in Clojure. I found two options that I wanted to explore:
33 |
34 | 1. Macros
35 | 2. Google Closure Library
36 |
37 | Here what each of the solutions looked like:
38 |
39 | **Macro**
40 |
41 | To start, don't jump to macros. I was just trying to understand them better and compare against other solutions, but its a powerful tool that we can reserve for other times. What was interesting with Macros is I found it hard to get it to work initially. So here is an overview:
42 |
43 | _Broken Macro_
44 |
45 | ```clojure
46 | ;; I do not work
47 | (defmacro get-attr
48 | [el attr]
49 | `(let [element# ~el
50 | attribute# ~attr]
51 | (attribute# element# )))
52 | ```
53 |
54 | _Working Macro_
55 |
56 | ```clojure
57 | ;; I work
58 | (defmacro get-attr
59 | [el attr]
60 | `(~attr ~el))
61 |
62 | ;; I work
63 | (defmacro get-attr [i e]
64 | (let [prop-sym i
65 | obj-sym e]
66 | (list prop-sym obj-sym)))
67 | ```
68 |
69 | Lets break it down the first one that works
70 |
71 | 1. The list is **quoted** - essentially telling clojure compiler not to evaluate the list or its items. Just return itself.
72 | 2. At compile time, evaluate `~attr` and `~el` Which means our code now looks like this at the end of compile time:
73 |
74 | ```clojure
75 | (.-style element)
76 | ```
77 |
78 | Okay, now compare to the very first example. So what was happening in the first example? Again, the whole thing is quoted, but the error happens here:
79 |
80 | ```
81 | (let [element# ~el
82 | attribute# ~attr])
83 | ```
84 |
85 | Essentially the above is the equivalent of trying to do
86 |
87 | ```clojure
88 | (let [my-local .-style])
89 |
90 | or
91 |
92 | (def my-global .-style)
93 | ```
94 |
95 | What is the problem with the above? Well, its because we were trying to evaluate an [instanceField](https://clojure.org/reference/java_interop#_the_dot_special_form)
96 |
97 | ```clojure
98 | (def my-global .-style)
99 | ```
100 |
101 | The abvoe is going to break because each of the items is evaluated one at a time. first, the `def`. then the `my-global` and then `.-style`. Normally, the evaluator expects something with `.-` to be at the front of a list and have
102 |
103 | The question is, what is `.-`. The answer is that the `.` dot part of it is special and reserved in clojure for interop. That's why it's not going to work.
104 |
105 | After all the above, I believe the cleanest way might be the `doto`
106 |
107 | ```clojure
108 | (set! (-> highlight .-style .-width) (make-hw-val width))
109 | (set! (-> highlight .-style .-height) (make-hw-val height))
110 | (set! (-> highlight .-style .-transform) (make-trans-val left top)))))
111 | ```
112 |
113 | ```clojure
114 | (doto
115 | (-> highlight .-style)
116 | (set! -width (make-hw-val width))
117 | (set! -height (make-hw-val height))
118 | (set! -transform (make-trans-val left top))))))
119 | ```
120 |
121 | ## Resources
122 |
123 | https://cljs.github.io/api/syntax/dot
124 |
125 | http://clojurescriptmadeeasy.com/blog/js-interop-property-access.html
126 |
127 | https://clojure.org/guides/weird_characters
128 |
--------------------------------------------------------------------------------
/22-follow-along-links/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}
3 | org.clojure/core.async {:mvn/version "1.3.610"}}
4 |
5 | :aliases {:dev {:main-opts ["-m" "cljs.main"
6 | "-ro" "ro.edn"
7 | "-w" "src"
8 | "-c" "app.core"
9 | "-r"]}}}
10 |
--------------------------------------------------------------------------------
/22-follow-along-links/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 👀👀👀Follow Along Nav
6 |
7 |
8 |
9 |
10 |
19 |
20 |
21 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Est explicabo unde natus necessitatibus esse obcaecati distinctio, aut itaque, qui vitae!
22 |
Aspernatur sapiente quae sint soluta modi, atque praesentium laborum pariatur earum quaerat cupiditate consequuntur facilis ullam dignissimos, aperiam quam veniam.
23 |
Cum ipsam quod, incidunt sit ex tempore placeat maxime corrupti possimus veritatis ipsum fugit recusandae est doloremque? Hic, quibusdam, nulla.
24 |
Esse quibusdam, ad, ducimus cupiditate nulla, quae magni odit totam ut consequatur eveniet sunt quam provident sapiente dicta neque quod.
25 |
Aliquam dicta sequi culpa fugiat consequuntur pariatur optio ad minima, maxime odio, distinctio magni impedit tempore enim repellendus repudiandae quas!
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/22-follow-along-links/resources/styles.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 | *, *:before, *:after {
5 | box-sizing: inherit;
6 | }
7 | body {
8 | min-height: 100vh;
9 | margin: 0; /* Important! */
10 | font-family: sans-serif;
11 | background:
12 | linear-gradient(45deg, hsla(340, 100%, 55%, 1) 0%, hsla(340, 100%, 55%, 0) 70%),
13 | linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%),
14 | linear-gradient(225deg, hsla(140, 90%, 50%, 1) 10%, hsla(140, 90%, 50%, 0) 80%),
15 | linear-gradient(315deg, hsla(35, 95%, 55%, 1) 100%, hsla(35, 95%, 55%, 0) 70%);
16 | }
17 |
18 | .wrapper {
19 | margin:0 auto;
20 | max-width:500px;
21 | font-size: 20px;
22 | line-height: 2;
23 | position: relative;
24 | }
25 |
26 | a {
27 | text-decoration: none;
28 | color:black;
29 | background:rgba(0,0,0,0.05);
30 | border-radius: 20px
31 | }
32 |
33 | .highlight {
34 | transition: all 0.2s;
35 | border-bottom:2px solid white;
36 | position: absolute;
37 | top:0;
38 | background:white;
39 | left:0;
40 | z-index: -1;
41 | border-radius:20px;
42 | display: block;
43 | box-shadow: 0 0 10px rgba(0,0,0,0.2)
44 | }
45 |
46 | .menu {
47 | padding: 0;
48 | display: flex;
49 | list-style: none;
50 | justify-content: center;
51 | margin:100px 0;
52 | }
53 |
54 | .menu a {
55 | display: inline-block;
56 | padding:5px;
57 | margin:0 20px;
58 | color:black;
59 | }
60 |
--------------------------------------------------------------------------------
/22-follow-along-links/ro.edn:
--------------------------------------------------------------------------------
1 | {:static-dir ["." "out" "resources"]}
2 |
--------------------------------------------------------------------------------
/22-follow-along-links/src/app/core.cljs:
--------------------------------------------------------------------------------
1 | ;; create main project namespace
2 | (ns app.core
3 | (:require [goog.string :as gstr] goog.string.format)
4 | (:require-macros [app.macros :refer [p pp get-attr]]))
5 |
6 | ;; Globals
7 |
8 | (def triggers (.querySelectorAll js/document "a"))
9 |
10 |
11 | (def highlight (.createElement js/document "span"))
12 |
13 | ;; utils
14 |
15 | (defn make-hw-val
16 | "hw stands for Height and Width"
17 | [coordinate]
18 | (str coordinate"px"))
19 |
20 |
21 | (defn make-trans-val
22 | [left top]
23 | (str "translate(" left"px ," top"px)"))
24 |
25 | ; (defn hello
26 | ; [symbol el]
27 | ; (p symbol)
28 | ; (p (-> el symbol)))
29 | ;
30 | (pp (macroexpand '(.-style hello)))
31 |
32 |
33 | ;; Event handlers
34 |
35 | (defn highlight-link
36 | [e]
37 | (this-as this
38 | (let [link-coords (.getBoundingClientRect this)
39 | width (.-width link-coords)
40 | height (.-height link-coords)
41 | left (+ (.-left link-coords) (.-scrollX js/window))
42 | top (+ (.-top link-coords) (.-scrollY js/window))]
43 |
44 | (doto (-> highlight .-style)
45 | (set! -width (make-hw-val width))
46 | (set! -height (make-hw-val height))
47 | (set! -transform (make-trans-val left top))))))
48 |
49 |
50 | ;; Setup
51 |
52 | (-> highlight .-classList (.add "highlight"))
53 |
54 |
55 | (-> js/document .-body (.append highlight))
56 |
57 |
58 | (doseq [trigger (array-seq triggers)]
59 | (.addEventListener trigger "mouseenter" highlight-link))
60 |
--------------------------------------------------------------------------------
/22-follow-along-links/src/app/macros.clj:
--------------------------------------------------------------------------------
1 | ;; create macros namespace
2 | (ns app.macros)
3 |
4 |
5 | ; (defmacro get-attr
6 | ; ""
7 | ; [el attr]
8 | ; `(let [element ~el
9 | ; attribute ~attr]
10 | ; (attribute element)))
11 |
12 | ; (defmacro get-attr
13 | ; [el attr]
14 | ; `(-> ~el ~attr))
15 |
16 | ; (defmacro get-attr
17 | ; [el attr]
18 | ; `(~attr ~el))
19 |
20 | (defmacro get-attr [i e]
21 | (let [prop-sym i
22 | obj-sym e]
23 | (list prop-sym obj-sym)))
24 |
25 |
26 | (defmacro p
27 | "Print and return native JavaScript argument."
28 | [x]
29 | `(let [res# ~x]
30 | (.log js/console res#)
31 | res#))
32 |
33 |
34 | (defmacro pp
35 | "Pretty print and return argument (uses `prn-str` internally)."
36 | [x]
37 | `(let [res# ~x]
38 | (.log js/console (prn-str res#))
39 | res#))
40 |
--------------------------------------------------------------------------------
/23-speech-synthesis/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 | /.nrepl-history
11 | /.cpcache
12 | /out
13 |
--------------------------------------------------------------------------------
/23-speech-synthesis/README.md:
--------------------------------------------------------------------------------
1 | # Speech Synthesis
2 |
3 | ## Quickstart
4 |
5 | Run the following commands from the root of the `25-speech-synthesis` repo
6 |
7 | * **1. Build and watch the project**
8 |
9 | ```bash
10 | clj --main cljs.main --repl-opts '{:static-dir ["." "out" "resources"]}' --watch src --compile speech-synthesis.core --repl
11 | ```
12 |
13 | ## Learnings
14 |
15 | ### aset and aget
16 |
17 | [Great article that should be read](https://clojurescript.org/news/2017-07-14-checked-array-access)
18 |
19 | ### CLJ Tool
20 |
21 | This is the first project where I worked start to finish using the CLJ tool. Things to keep in mind:
22 |
23 | * Refreshing the browser
24 |
25 | Using Boot w/ reload or figwheel means that your files are being watched for changes and when changes are seen, your browser automatically refreshes to pickup these changes. However, if we are just using the CLJ tool, your browser is not automatically refreshed. You have to do this manually. This is not a big deal, just an FYI.
26 |
27 | * Understanding Errors
28 |
29 | When you make mistakes in your code, and boot w/ reload and are figwheel properly setup, both systems give you some indication of what you might be doing wrong. This is not exactly how it works with the CLJ tool. Clojure will tell you whats wrong, but it logs this to a file in your `out` directory called `watch.log`. For intermediate developers this is fine, but this is actually a bit of a deal breaker for new developers. It is challenging enough to understand the CLJS errors, but now you have to go find the file, try to read through a wall of unformatted text and then decipher. I am sure there is something we can do about this, but that would like be an extra step or configuration and at that point, we might as well use figwheel.
30 |
31 | This is not a critique of the CLJ Tool, just something to watch out for and consider as a new learner.
32 |
--------------------------------------------------------------------------------
/23-speech-synthesis/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}}
3 |
--------------------------------------------------------------------------------
/23-speech-synthesis/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Speech Synthesis
6 |
7 |
8 |
9 |
10 |
11 |