diff --git a/.dir-locals.el b/.dir-locals.el index 856af19..7403d94 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,4 +1,4 @@ -((clojurescript-mode . +((clojurescript-mode . (cider-clojure-cli-aliases . "-M:dev") (cider-preferred-build-tool . clojure-cli) (cider-default-cljs-repl . custom) diff --git a/deps.edn b/deps.edn index f075bfd..dc34219 100644 --- a/deps.edn +++ b/deps.edn @@ -1,5 +1,7 @@ {:deps {org.clojure/clojure {:mvn/version "1.10.0"} org.clojure/clojurescript {:mvn/version "1.11.60"} + org.clojure/core.async {:mvn/version "1.6.673"} + funcool/promesa {:mvn/version "9.0.494"} ;;ajax requests cljs-ajax/cljs-ajax {:mvn/version "0.8.1"} diff --git a/resources/public/documents/clojure-basics.org b/resources/public/documents/clojure-basics.org index a6821b5..aeee30e 100644 --- a/resources/public/documents/clojure-basics.org +++ b/resources/public/documents/clojure-basics.org @@ -19,20 +19,10 @@ The code below can be evaluated live inside the page you can select parts of the #+END_SRC -#+BEGIN_SRC text :export none :results silent :tangle readme.org +#+BEGIN_SRC text :export none :results silent :tangle readme.org #+TITLE: Getting started #+END_SRC - -* Install the npm requirements. - -npx install - -* Launch shadow-cljs watch for source code changes -npx shadow-cljs watch app -#+END_SRC - - ** Clojure comments There a few ways to comment code in this language. diff --git a/resources/public/documents/examples-maps.org b/resources/public/documents/examples-maps.org new file mode 100644 index 0000000..d4fec31 --- /dev/null +++ b/resources/public/documents/examples-maps.org @@ -0,0 +1,211 @@ +#+TITLE: Embedding maps in your apps +#+DESCRIPTION: Example's of embeding maps into your frontend application. + +* Introduction + +Install npm dependencies + +npm install + +Run the project +npx shadow-cljs server app + + +* Google Maps + +Below is an example of using google maps, it pulls in the library directly you could alternatively use an npm dependency. + +You will need to provide your own api key, the examples are blank and render a warning so paste in your google api key to make this work. + + +#+BEGIN_SRC html :results silent :exports none :tangle resources/public/index.html + + +
+ +js {:center {:lng 131.031 :lat -25.344} :zoom 4}))] + (gMarker. (clj->js {:map map + :title "test marker 1" + :position {:lng 131.031 :lat -24.344}}) "marker 1") + (gMarker. (clj->js {:map map + :title "test marker 2" + :position {:lng 131.031 :lat -25.344}}) "marker 2"))) + (fn [] + [:div#google-map-async.m-4 + {:style {:width "400px" :height "400px"} :ref #(reset! map-element %)} + "core async map here"]))) +#+END_SRC + + + +** Create a map using google and shadow js-await + +#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs +(defn google-map-js-await [] + (let [map-element (reagent/atom nil)] + (load-google-maps-script "") + (shadow/js-await + [js-map (js/google.maps.importLibrary "maps")] + (shadow/js-await + [js-marker (js/google.maps.importLibrary "marker")] + (when @map-element + (let [gMap (.-Map ^js/google.maps.Map js-map) + gMarker (.-Marker ^js/google.maps.Marker js-marker) + map (gMap. @map-element + (clj->js {:center {:lng 131.031 :lat -25.344} :zoom 4}))] + (gMarker. (clj->js {:map map + :title "test marker 1" + :position {:lng 131.031 :lat -24.344}}) "marker 1") + (gMarker. (clj->js {:map map + :title "test marker 2" + :position {:lng 131.031 :lat -25.344}}) "marker 2") + nil)))) + + (fn [] + [:div#google-map-js-await.m-4 + {:style {:width "400px" :height "400px"} :ref #(reset! map-element %)} + "shadow js-await map here"]))) + +[google-map-js-await] +#+END_SRC + +** Create a map using google and promesa + +An example using promesa to render a google map. + +#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs +(defn google-map-promesa [] + (let [map-element (reagent/atom nil)] + (load-google-maps-script "") + + (promesa/let + [js-map (js/google.maps.importLibrary "maps") + js-marker (js/google.maps.importLibrary "marker")] + + (let + [gMap (.-Map ^js/google.maps.Map js-map) + gMarker (.-Marker ^js/google.maps.Marker js-marker) + map (gMap. @map-element + (clj->js {:center {:lng 131.031 :lat -25.344} :zoom 4}))] + (gMarker. (clj->js {:map map + :title "test" + :position {:lng 131.031 :lat -24.344}}) "marker 1") + + (gMarker. (clj->js {:map map + :title "test" + :position {:lng 131.031 :lat -25.344}}) "marker 1"))) + + + (fn [] + [:div#google-map-promesa.m-4 + {:style {:width "400px" :height "400px"} :ref #(reset! map-element %)} + "promesa map here"]))) + +[google-map-promesa] +#+END_SRC + + +#+BEGIN_SRC clojure :exports none :tangle src/clojure_demo/core.cljs +(defn current-page [] + [:div + [google-map-core-async] + [google-map-promesa] + [google-map-js-await]]) + +(defn mount-root-page [] + ;; this select the main node from the html file and injects your page content + (.render + (createRoot (.getElementById js/document "app")) + (reagent/as-element [current-page]))) + +(def startup! (mount-root-page)) + +#+END_SRC diff --git a/resources/public/index.html b/resources/public/index.html index fffc482..17b082d 100644 --- a/resources/public/index.html +++ b/resources/public/index.html @@ -12,6 +12,13 @@ name="description" content="Examples & Guides on using various libraries and technologies with in the clojure eco system's" /> + + + + + + + replacements]] [cl-eorg.parser :as o :refer [parse]] @@ -22,6 +25,7 @@ [reitit.frontend.history :refer [ignore-anchor-click?]] [sci.configs.reagent.reagent :as sci-reagent] [sci.configs.tonsky.datascript :as sci-datascript] + [sci.configs.funcool.promesa :as sci-promesa] [sci.core :as sci] [clojure-demo.tailwind-theme :refer [tailwind-theme]] [spec-tools.data-spec :as ds])) @@ -35,18 +39,45 @@ (def yaml-mode (LanguageSupport. (.define StreamLanguage yaml))) +(defn ^:sci/macro my-js-await [_ _ [name thenable] & body] + (let [last-expr (last body) + [body catch] + (if (and (seq? last-expr) (= 'catch (first last-expr))) + [(butlast body) last-expr] + [body nil])] + + ;; FIXME: -> here will always return a promise so shouldn't be necessary to add js hint? + `(-> ~thenable + ~@(when (seq body) + [`(.then (fn [~name] ~@body))]) + ~@(when catch + (let [[name & body] catch] + [`(.catch (fn [~name] ~@body))] + ))))) + ;; https://github.com/babashka/sci.configs (def rf-ns (sci/create-ns 'reitit.frontend nil)) (def rfe-ns (sci/create-ns 'reitit.frontend.easy nil)) (def rss-ns (sci/create-ns 'reitit.coercion.spec nil)) (def sql-ns (sci/create-ns 'honey.sql.core nil)) (def sqlh-ns (sci/create-ns 'honey.sql.helpers nil)) +(def shadow-ns (sci/create-ns 'shadow.cljs.modern nil)) + +;; core async does not work with sci +(def async-ns (sci/create-ns 'cljs.core.async nil)) +(def async-in-ns (sci/create-ns 'cljs.core.async.interop nil)) + (def sci-ctx (sci/init {:classes {'js js/globalThis :allow :all} :namespaces {'reagent sci-reagent/reagent-namespace + 'promesa sci-promesa/promesa-namespace ;'h {'html (sci/copy-var h/html hc-ns)} + ;'async {'go (sci/copy-var async/go async-ns)} + ;'shadow {'js-await (sci/copy-var js-await shadow-ns)} + 'shadow {'js-await (sci/copy-var my-js-await shadow-ns)} + ;'async-in {'
] children) ))}))) (defn code-editor - [{:keys [exports class results]} content] + [{:keys [export class results]} content] (let [language class editor (atom nil) evaled-result (reagent/atom nil) @@ -146,13 +177,13 @@ :parent @editor}))))) :component-will-unmount (fn [_] (.destroy @view)) :reagent-render (fn [] - (if exports + (if export nil - [:div.mb2 - [:div.ba.ma0.f5.b--black-05.pa2.overflow-auto.editor {:ref #(reset! editor %)}] + [:div.mb-8 + [:div.ba.ma-0.f5.b--black-05.pa2.overflow-auto.editor {:ref #(reset! editor %)}] (when @evaled-result [err-boundary - [:pre.ba.ma0.f6.code.b--black-05.pa2.pl4.overflow-auto + [:pre.border-2.pa-4.pl-6.text-xl {:style {:white-space "pre-line"}} ;; See org mode :results key (case results @@ -165,9 +196,9 @@ (def theme-tachyon (merge tachyon-theme {:SRC code-editor - :HEADER1 (fn [v _] [:h1.f2.fw6.f3-ns.lh-title.mt0.mb2 {:id (slugify v)} [:a {:name (slugify v)} v]]) - :HEADER2 (fn [v _] [:h2.f3.fw6.f3-ns.lh-title.mt0.mb2 {:id (slugify v)} [:a {:name (slugify v)} v]]) - :HEADER3 (fn [v _] [:h2.f4.fw6.f3-ns.lh-title.mt0.mb2 {:id (slugify v)} [:a {:name (slugify v)} v]]) + :HEADER1 (fn [v _] [:h1 {:id (slugify v)} [:a {:name (slugify v)} v]]) + :HEADER2 (fn [v _] [:h2 {:id (slugify v)} [:a {:name (slugify v)} v]]) + :HEADER3 (fn [v _] [:h2 {:id (slugify v)} [:a {:name (slugify v)} v]]) :LINE :p.f5.f5-ns.lh-copy.mt0 :BULLETS :ul.mt0 :LINK :a #_link-handler})) @@ -175,14 +206,14 @@ (def theme (merge tailwind-theme {:SRC code-editor - :HEADER1 (fn [v _] [:h1.text-2xl.fw6.lh-title.mt0.mb2 {:id (slugify v)} [:a {:name (slugify v)} v]]) - :HEADER2 (fn [v _] [:h2.text-xl.fw6.lh-title.mt0.mb2 {:id (slugify v)} [:a {:name (slugify v)} v]]) - :HEADER3 (fn [v _] [:h2.text-base.lh-title.mt0.mb2 {:id (slugify v)} [:a {:name (slugify v)} v]]) + :HEADER1 (fn [v _] [:h1.text-2xl.fw6.lh-title.mt-0.mb-2 {:id (slugify v)} [:a {:name (slugify v)} v]]) + :HEADER2 (fn [v _] [:h2.text-xl.fw6.lh-title.mt-0.mb-2 {:id (slugify v)} [:a {:name (slugify v)} v]]) + :HEADER3 (fn [v _] [:h2.text-base.lh-title.mt-0.mb-2 {:id (slugify v)} [:a {:name (slugify v)} v]]) :LINE :p.f5.f5-ns.lh-copy.mt0 :BULLETS :ul.mt0 :LINK :a #_link-handler})) -(def theme-toc-tachyon +#_(def theme-toc-tachyon (merge tachyon-theme {:HEADER1 (fn [v _] [:li [:a {:href (str "#" (slugify v))} [:span.f4.fw6.f3-ns.lh-title.mt0.mb2 v]]]) @@ -199,17 +230,17 @@ (def theme-toc (merge tailwind-theme - {:HEADER1 (fn [v _] [:li [:a {:href (str "#" (slugify v))} + {:HEADER1 (fn [v _] [:li.list-decimal [:a.text-blue-800 {:class "hover:text-blue-500" :href (str "#" (slugify v))} [:span.text-xl.fw6.lh-title.mt0.mb2 v]]]) - :HEADER2 (fn [v _] [:li.ml-4 [:a {:href (str "#" (slugify v))} + :HEADER2 (fn [v _] [:li.list-decimal.ml-4 [:a.text-blue-800 {:href (str "#" (slugify v))} [:span.text-lg.fw6.lh-title.mt0.mb2 v]]]) - :HEADER3 (fn [v _] [:li.ml-8 [:a {:href (str "#" (slugify v))} + :HEADER3 (fn [v _] [:li.list-decimal.ml-8 [:a.text-blue-800 {:class "hover:text-blue-500" :href (str "#" (slugify v))} [:span.text-base.fw6.lh-title.mt0.mb2 v]]]) - :HEADER4 (fn [v _] [:li.ml-12 [:a {:href (str "#" (slugify v))} + :HEADER4 (fn [v _] [:li.list-decimal.ml-12 [:a.text-blue-800 {:href (str "#" (slugify v))} [:span.text-sm.fw6.lh-title.mt0.mb2 v]]]) - :HEADER5 (fn [v _] [:li.ml-26 [:a {:href (str "#" (slugify v))} + :HEADER5 (fn [v _] [:li.list-decimal.ml-26 [:a.text-blue-800 {:href (str "#" (slugify v))} [:span.text-xs.fw6.f5-ns.lh-title.mt0.mb2 v]]]) - :HEADER6 (fn [v _] [:li.ml-30 [:a {:href (str "#" (slugify v))} + :HEADER6 (fn [v _] [:li.list-decimal.ml-30 [:a.hover:decoration-blue-400 {:href (str "#" (slugify v))} [:span.text-xs.fw6.f5-ns.lh-title.mt0.mb2 v]]])})) ;; put constant data here @@ -264,7 +295,11 @@ :icon-image "https://avatars.githubusercontent.com/u/2181346?s=200&v=4"}]} :examples {:title "Examples" :intro "Some example applications" - :key ::examples} + :key ::examples + :demos [{:title "Maps" + :description "" + :page "example-maps" + :icon-image "https://avatars.githubusercontent.com/u/2181346?s=200&v=4"}]} :terminology {:title "Terminology" :intro "" :key ::terminology} @@ -282,6 +317,8 @@ {:file "documents/clojure-basics.org" :git-link "https://github.com/atomjuice/dsl-demo"} :ci-demo {:file "documents/ci-demo.org" :git-link "https://github.com/atomjuice/dsl-demo"} + :example-maps + {:file "documents/examples-maps.org" :git-link "https://github.com/atomjuice/dsl-demo"} :containers {:file "documents/containers.org" :git-link "https://github.com/atomjuice/containers"}}}) @@ -290,7 +327,7 @@ ;; form one component to render an article (defn article [{:keys [title description tagline]}] - [:article {:data-name "article-full-bleed-background"} + [:article.prose {:data-name "article-full-bleed-background"} [:div.cf {:style {:background "url(http://placekitten.com/g/600/300)" :no-repeat "center center fixed" :background-size "cover"}} [:div.fl.pa3.pa4-ns.bg-white.black-70.measure-narrow.f3.times @@ -303,15 +340,13 @@ (defn articles [{:keys [title body articles]}] [:section.mw7.center.avenir [:h2.text-4xl.mt-4.mb-4 title] - (when body [:p.mb-8 body]) - (map (fn [{:keys [title author link description img-src img-alt]}] - [:article.bt.bb.b--black-10 {:key title} + (map (fn [{:keys [title author link description img-src img-alt] :as article}] + [:article.bt.bb.b--black-10.border-b-2 {:key title} [:a {:href link} [:div.flex.flex-column.flex-row-ns.p-4 - (when img-src - [:div.flex-none #_{:class "w-1/3"} - [:img.w-48 #_h-24.block {:src img-src :alt img-alt}]]) - + [:div.flex-none.w-32 + (when img-src + [:img.w-32 #_h-24.block {:src img-src :alt img-alt}])] [:div.ml-4.flex-grow #_{:class "w-2/3"} [:h1.text-2xl title] [:p description] @@ -341,39 +376,6 @@ {:target "_blank" :href "https://matrix.to/#/@oly:matrix.org"} "Contact me"]]]) -(defn home-page [] - [:<> - [articles - {:title "Clojure Demos" - :body (-> site-data :homepage :intro) - :articles - [{:title "Clojure Basics" - :description "Getting started with clojure syntax datatype's sequences conditions" - :link (rfe/href ::demo {:page "clojure-basics"}) - :img-src "https://clojure.org/images/clojure-logo-120b.png"} - {:title "Reagent Demo" - :description "React application using reagent" - :link (rfe/href ::demo {:page "reagent-demo"}) - :img-src "https://raw.githubusercontent.com/reagent-project/reagent/master/logo/logo-text.png"}]}]]) - -(defn grouped-page [route] - (let [group (keyword (name (:name (:data route))))] - [:<> - [articles - {:title (-> site-data :pages group :title) - :body (-> site-data :pages group :intro) - :articles - (mapv (fn fmt-map [demo] - {:title (:title demo) - :description (:description demo) - :link (rfe/href ::demo {:page (:page demo)}) - :img-src (:icon-image demo)}) - (-> site-data :pages group :demos))}]])) - -(defn dialects-page [route] - [:<> - [grouped-page route #_(keyword (name (:name (:data route))))]]) - (def toc (partial contains? (into #{} (map keyword [:HEADER1 :HEADER2 :HEADER3 :HEADER4 :HEADER5 :HEADER6])))) (def org-code (partial contains? (into #{} (map keyword [:SRC])))) @@ -400,6 +402,71 @@ :code (filter (fn filter-code [tag] (org-code (first tag))) dsl) :body (filter (fn filter-body [tag] (body (first tag))) dsl)}) + +(defn home-page [] + [:<> + [articles + {:title "Clojure Demos" + :body (-> site-data :homepage :intro) + :articles + [{:title "Clojure Basics" + :description "Getting started with clojure syntax datatype's sequences conditions" + :link (rfe/href ::page {:page "clojure-basics"}) + :img-src "https://clojure.org/images/clojure-logo-120b.png"} + {:title "Reagent Demo" + :description "React application using reagent" + :link (rfe/href ::page {:page "reagent-demo"}) + :img-src "https://raw.githubusercontent.com/reagent-project/reagent/master/logo/logo-text.png"}]}]]) + +(defn grouped-page [route] + (let [group (keyword (name (:name (:data route))))] + [:<> + [articles + {:title (-> site-data :pages group :title) + :body (-> site-data :pages group :intro) + :articles + (mapv (fn fmt-map [demo] + {:title (:title demo) + :description (:description demo) + :link (rfe/href ::page {:page (:page demo)}) + :img-src (:icon-image demo)}) + (-> site-data :pages group :demos))}]])) + +(defn default-page [route] + (let [demo-key (keyword (-> route :parameters :path :page)) + content (reagent/atom {})] + (GET (str "/" (-> site-data :demos demo-key :file)) + {:response-format (raw-response-format) + :handler (fn [response] + ;;(prn (org->replacements theme (parse response) )) + ;;[:LINE "" [:LINK {:href "https://github.com/weavejester/hiccup"} "https://github.com/weavejester/hiccup"]] + (->> response + parse + org->split2 + (reset! content)))}) + (fn [route] + (if @content + [:main + [:h1.mt-8.mb-8.text-4xl (:content (last (first (:header @content))))] + [:div.mw7.center.avenir + (into [:ol.list-inside.m-6.font-semibold] (org->replacements theme-toc (:toc @content)))] + [:p "The code in these examples is evaluated when modified, you can highlight a partial expression to evalute the selection, You can also download the code as a tar if you like and use it in your favourite editor."] + [:a.bg-sky-500.text-white.m-2.p-2.pl-4.pr-4.inline-block + {:download (str (slugify (:content (last (first (:header @content))))) ".tar") + :title (:content (last (first (:header @content)))) + :href (.createObjectURL + js/URL + (js/Blob. #js [(build-file-tar-hm + (build-tarts-map + (:code @content)))] + {:type "application/tar"}))} + "Download Code"] + [:div + (into [:div] (org->replacements theme (:body @content)))]] + [:<>])))) + + + ;; form one render about page component (defn about-page [] [:div @@ -417,6 +484,12 @@ :my-data "hi" :view home-page}] + ["/page/:page" + {:name ::page + :view default-page + :parameters {:path {:page string?} + :query {(ds/opt :foo) keyword?}}}] + ["/terminology/" {:name ::terminology :view grouped-page}] diff --git a/src/clojure_demo/tailwind_theme.cljs b/src/clojure_demo/tailwind_theme.cljs index 18f470b..86b61bd 100644 --- a/src/clojure_demo/tailwind_theme.cljs +++ b/src/clojure_demo/tailwind_theme.cljs @@ -33,7 +33,7 @@ :LANGUAGE :class :SRC :pre.bg-near-black.silver.pa2.hljs.roboto.overflow-auto.klipse :RESULTS :pre.bg-near-black.silver.pa2.hljs.roboto.overflow-auto - :EXAMPLE :pre.bg-near-black.silver.pa2.hljs.roboto.overflow-auto + :EXAMPLE :pre.text-slate-700.bg-white.rounded-xl :COMMENT nil :CAPTION :span :I :i @@ -42,8 +42,8 @@ ;;:LINK link-handler :IMG :img ;; probably remove these 2 - :A :a - :P :p + :A :a.text-blue-800 + :P :p.mb-2 :BR :br :LINE :p.f5.f4-ns.lh-copy.mt0 :BULLETS-ORDERED :ol