230 lines
9.3 KiB
Clojure
230 lines
9.3 KiB
Clojure
(ns ^:figwheel-hooks demo.core
|
|
(:require [reagent.core :as reagent]
|
|
[ajax.core :refer [GET raw-response-format]]
|
|
;[demo.org :refer [parse->to-hiccup parse-flat]]
|
|
[cl-eorg.parser :as o :refer [parse parse-flat]]
|
|
[cl-eorg.html :refer [org->replacements]]
|
|
[reitit.frontend :as rf]
|
|
[reitit.frontend.easy :as rfe]
|
|
[reitit.coercion.spec :as rss]
|
|
[spec-tools.data-spec :as ds]))
|
|
|
|
;; put constant data here
|
|
(def site-data
|
|
{:demos {:dsl-demo
|
|
{:file "dsl-demo.org" :git-link "https://github.com/atomjuice/dsl-demo"}
|
|
:datalog-demo
|
|
{:file "datalog-demo.org" :git-link "https://github.com/atomjuice/dsl-demo"}
|
|
:reagent-demo
|
|
{:file "reagent-reitit.org" :git-link "https://github.com/atomjuice/dsl-demo"}
|
|
:clojure-basics
|
|
{:file "clojure-basics.org" :git-link "https://github.com/atomjuice/dsl-demo"}}
|
|
:homepage {:intro "Clojure tutorials examples and exploration"}
|
|
:lorem "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."})
|
|
|
|
;; Store site state
|
|
(defonce site-state (reagent/atom {}))
|
|
|
|
;; for one component to render an article
|
|
(defn article [{:keys [title description tagline]}]
|
|
[:article {: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
|
|
[:header.b--black-70.pv4 {:class (when tagline "bb")}
|
|
[:h3.f2.fw7.ttu.tracked.lh-title.mt0.mb3.avenir title]
|
|
(when tagline [:h4.f3.fw4.i.lh-title.mt0 tagline])]
|
|
[:section.pt5.pb4 [:p.times.lh-copy.measure.f5.mt0 description]]]]])
|
|
|
|
;; form one component to render article tiles
|
|
(defn articles [{:keys [title body articles]}]
|
|
[:section.mw7.center.avenir {:key title}
|
|
[:h2.baskerville.fw1.ph3.ph0-l title]
|
|
(when body [:p body])
|
|
(map (fn [{:keys [title author link description img-src img-alt]}]
|
|
[:article.bt.bb.b--black-10
|
|
[:a.db.pv4.ph3.ph0-l.no-underline.black.dim {:href link}
|
|
[:div.flex.flex-column.flex-row-ns
|
|
(when img-src
|
|
[:div.pr3-ns.mb4.mb0-ns.w-100.w-40-ns
|
|
[:img.db {:src img-src :alt img-alt}]])
|
|
[:div.w-100.w-60-ns.pl3-ns
|
|
[:h1.f3.fw1.baskerville.mt0.lh-title title]
|
|
[:p.f6.f5-l.lh-copy description]
|
|
[:p.f6.lh-copy.mv0 author]]]]])
|
|
articles)])
|
|
|
|
;; form one component to render a product
|
|
(defn product-card
|
|
[{:keys [title amount description link]}]
|
|
[:article.br2.ba.dark-gray.b--black-10.ma2.w-100.w-50-m.w-25-l.mw5
|
|
[:img.db.w-100.br2.br--top {:src link}]
|
|
[:div.pa2.ph3-ns.pb3-ns
|
|
[:div.dt.w-100.mt1
|
|
[:div.dtc [:h1.f5.f4-ns.mv0 title]]
|
|
[:div.dtc.tr [:h2.f5.mv0 amount]]]
|
|
[:p.f6.lh-copy.measure.mt2.mid-gray description]]])
|
|
|
|
(defn circle [{:keys [img alt]}]
|
|
[:div.pa4.tc [:img.br-100.ba.h3.w3.dib {:src img :alt alt}]])
|
|
|
|
;; form one component to render a nav link
|
|
(defn navbar-link [{:keys [href title text] :or {text nil title nil}}]
|
|
[:a.link.dim.white.dib.mr3 {:key href :href href :title title} text])
|
|
|
|
;; form one component to render a navbar
|
|
(defn navbar [links]
|
|
[:header.bg-black-90.w-100.ph3.pv3.pv4-ns.ph4-m.ph5-l
|
|
[:nav.f6.fw6.ttu.tracked
|
|
(map navbar-link links)]])
|
|
|
|
;; form2 component notice the render function takes the same param as the component function
|
|
;; this is important, you can hit issues if you forget this in form 2 components.
|
|
(defn my-component [title value]
|
|
(let [local-state (reagent/atom value)]
|
|
(fn [title]
|
|
[:h1 {:class (when @local-state "hide")
|
|
:on-click (fn [] (swap! local-state inc))} (str title " " @local-state)])))
|
|
|
|
|
|
;; form one homepage component
|
|
|
|
|
|
(defn home-page []
|
|
[:<>
|
|
[articles
|
|
{:title "Clojure Demos"
|
|
:body (-> site-data :homepage :intro)
|
|
:articles
|
|
[{:title "Clojure Basics"
|
|
:link (rfe/href ::demo {:page "clojure-basics"})
|
|
:img-src "https://clojure.org/images/clojure-logo-120b.png"}
|
|
{:title "DSL Demo"
|
|
:link (rfe/href ::demo {:page "dsl-demo"})
|
|
:img-src "https://miro.medium.com/max/1400/1*CEYFj5R57UFyCXts2nsBqA.png"}
|
|
{:title "Datalog Demo"
|
|
:link (rfe/href ::demo {:page "datalog-demo"})
|
|
:img-src "https://raw.githubusercontent.com/tonsky/datascript/master/extras/logo.svg"}
|
|
{:title "Reagent Demo"
|
|
:link (rfe/href ::demo {:page "reagent-demo"})
|
|
:img-src "https://raw.githubusercontent.com/reagent-project/reagent/master/logo/logo-text.png"}]}]])
|
|
|
|
;; form two component render demo
|
|
(defn demo-page [route]
|
|
(let [demo-key (keyword (-> route :parameters :path :page))
|
|
content (reagent/atom {})]
|
|
(GET (-> site-data :demos demo-key :file)
|
|
{:response-format (raw-response-format)
|
|
:handler (fn [response]
|
|
(->> response
|
|
parse
|
|
org->replacements
|
|
(reset! content)))})
|
|
(fn [route]
|
|
[:main.mt4
|
|
[:div.mw7.center.avenir @content]])))
|
|
|
|
;; form one render about page component
|
|
(defn about-page []
|
|
[:main.mt4
|
|
[:section.mw7.center.avenir
|
|
[:h1 "Clojure library examples to aid learning"]
|
|
[:p "Selection of clojure demos, rendered in reagent which itself is an example of using reagent."]
|
|
[my-component "Clickable component" 1]
|
|
[my-component "Clickable component" 2]
|
|
[:p "Image circle componenet"]
|
|
[circle {:img "http://placekitten.com/g/300/300" :alt "test"}]
|
|
[:p "Product card component"]
|
|
[:div.flex.flex-column.flex-row-ns
|
|
[product-card
|
|
{:title "Cat 01"
|
|
:amount "£54.59"
|
|
:description "Cat description here"
|
|
:link "http://placekitten.com/g/600/300"}]
|
|
[product-card
|
|
{:title "Cat 02"
|
|
:amount "£10.59"
|
|
:description "Cat description here"
|
|
:link "http://placekitten.com/g/600/300"}]]
|
|
[:p "Article component"]
|
|
[article {:title "Example Article component"
|
|
:description (-> site-data :lorem)
|
|
:tagline "https://tachyons.io/components/"}]]])
|
|
|
|
;; form 3 component wrap rendering to catch errors and render them
|
|
;; or just render the rest of the page if all is good
|
|
;; this uses =create-class= and a hash map of life cycle functions
|
|
|
|
|
|
(defn err-boundary
|
|
[& children]
|
|
(let [err-state (reagent/atom nil)]
|
|
(reagent/create-class
|
|
{:display-name "ErrBoundary"
|
|
:component-did-catch (fn [err info]
|
|
(reset! err-state [err info]))
|
|
:reagent-render (fn [& children]
|
|
(if (nil? @err-state)
|
|
(into [:<>] children)
|
|
(let [[_ info] @err-state]
|
|
[:pre [:code (pr-str info)]])))})))
|
|
|
|
;; define our routes, just nested vectors of the route definition hash maps
|
|
(def routes
|
|
[["/"
|
|
{:name ::frontpage
|
|
:my-data "hi"
|
|
:view home-page}]
|
|
|
|
["/about"
|
|
{:name ::about
|
|
:view about-page}]
|
|
|
|
["/demo/:page"
|
|
{:name ::demo
|
|
:view demo-page
|
|
:parameters {:path {:page string?}
|
|
:query {(ds/opt :foo) keyword?}}}]])
|
|
|
|
;; top level component contains nav and adds in the select page into a containing element
|
|
;; we are adding in a style sheet but this will often be done in index.html
|
|
(defn current-page []
|
|
[:<> [:link {:rel "stylesheet" :href "https://unpkg.com/tachyons@4.12.0/css/tachyons.min.css"}]
|
|
[navbar [{:href (rfe/href ::frontpage) :title "title here" :text "home"}
|
|
{:href (rfe/href ::about) :text "About"}
|
|
{:href (rfe/href ::i-do-not-exist) :text "missing"}]]
|
|
[:main.mt4
|
|
(when-let [view (-> @site-state :current-route :data :view)] [view (-> @site-state :current-route)])]])
|
|
|
|
|
|
;; This simply calls reagent render and puts the result in a div with the id of app
|
|
;; you can create your own index.html or figwheel provides one with the app id which will replace the default data
|
|
;; ^:after-load is meta data its not needed but informs figwheel to run this code after a page load
|
|
|
|
|
|
(defn mount-root-page []
|
|
;; this select the main node from the html file and injects your page content
|
|
(reagent/render
|
|
(fn [] [err-boundary [current-page]])
|
|
(.getElementById js/document "app")))
|
|
|
|
(defn ^:after-load render-site []
|
|
;; this select the main node from the html file and injects your page content
|
|
(mount-root-page))
|
|
|
|
(defn startup! []
|
|
(rfe/start!
|
|
(rf/router routes {:data {:coercion rss/coercion}})
|
|
(fn [m] (swap! site-state assoc :current-route m))
|
|
;; set to false to enable HistoryAPI
|
|
{:use-fragment true})
|
|
(render-site))
|
|
|
|
;; we defonce the startup so that hot reloading does not reinitialize the state of the site
|
|
(def launch (do (startup!) true))
|
|
|
|
(comment
|
|
@site-state
|
|
|
|
(GET "/test.org" {:handler (fn [response] (swap! site-state assoc :content response))}))
|