improved web interface.

This commit is contained in:
Oly 2019-09-06 15:55:23 +01:00
parent aab0689ff8
commit e4dac1eee2
4 changed files with 99 additions and 59 deletions

View File

@ -2,4 +2,4 @@
:optimizations :none :optimizations :none
:pretty-print true :pretty-print true
:source-map true :source-map true
:main DEMOAPP.core} :main weecfg.core}

View File

@ -1,6 +1,5 @@
{:output-to "app/data/main.js" {:output-to "app/data/main.js"
:output-dir "app/data/prod" :output-dir "app/data/"
:asset-path "app/data/prod", :asset-path "app/data/",
:optimizations :advanced :optimizations :advanced
; :source-map none :main weecfg.core}
:main DEMOAPP.core}

12
resources/public/form.edn Normal file
View File

@ -0,0 +1,12 @@
[{:name "server" :title "Server" :placeholder "127.0.0.1"
:validator "server-type"
:error "Invalid server address"}
{:name "device-id" :title "Device Unique ID" :placeholder "AAAA"
:validator "device-id-type"
:error "Invalid identifier" :focus "true"}
{:name "wifi-ssid" :title "WIFI SSID"
:validator "wifi-ssid-type"
:error "Invalid SSID"}
{:name "wifi-key" :title "WIFI Password"
:validator "wifi-key-type"
:error "Invalid wifi password" :type "password"}]

View File

@ -1,63 +1,80 @@
(ns DEMOAPP.core (ns weecfg.core
(:require [accountant.core :as accountant] (:require [accountant.core :as accountant]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cognitect.transit :as t]
[clojure.edn :refer [read-string]]
[ajax.core :refer [GET, POST]] [ajax.core :refer [GET, POST]]
[reagent.core :as reagent :refer [atom]] [reagent.core :as reagent :refer [atom]]
[reagent.session :as session] [reagent.session :as session]
[reitit.frontend :as reitit])) [reitit.frontend :as reitit]))
;;(enable-console-print!) ; variables
(defonce form-errors (atom nil)) (defonce form-errors (atom nil))
(defonce form-title "GASP Sensor setup") (defonce form-title "GASP Sensor setup")
(defonce form-data (atom {:server "" :device-id "" :wifi-ssid "" :wifi-key ""})) (defonce form-data (atom {:server "" :device-id "" :wifi-ssid "" :wifi-key ""}))
(defonce form-layout [{:name "server" :title "Server" :placeholder "127.0.0.1" (defonce form-layout (atom []))
:data form-data :validator "server-type"
:error "Invalid server address"}
{:name "device-id" :title "Device Unique ID" :placeholder "AAAA"
:data form-data :validator "device-id-type"
:error "Invalid identifier" :focus "true"}
{:name "wifi-ssid" :title "WIFI SSID"
:data form-data :validator "wifi-ssid-type"
:error "Invalid SSID"}
{:name "wifi-key" :title "WIFI Password"
:data form-data :validator "wifi-key-type"
:error "Invalid wifi password" :type "password"}])
(defonce device-id-regex #"[A-Za-z0-9]{4}") (defonce device-id-regex #"[A-Za-z0-9]{4}")
(defonce ssid-regex #"^[!#;].|[+\[\]\"\t\s].*$") (defonce ssid-regex #"^[!#;].|[+\[\]\"\t\s].*$")
(s/def ::wifi-ssid-type (s/and string? #(re-matches ssid-regex %)))
; spec validation definitions
(s/def ::size-test #(<= (count %) 2))
(s/def ::ssid-size-limit #(<= (count %) 32))
(s/def ::default-size-limit #(<= (count %) 50))
(s/def ::wifi-ssid-type (s/and string? #(re-matches ssid-regex %) ::ssid-size-limit))
(s/def ::device-id-type (s/and string? #(re-matches device-id-regex %))) (s/def ::device-id-type (s/and string? #(re-matches device-id-regex %)))
(s/def ::wifi-key-type string?) (s/def ::wifi-key-type (s/and string? ::default-size-limit))
(s/def ::server-type (s/and string? ::default-size-limit))
; page routes ; page routes
(def router (def router
(reitit/router (reitit/router
[["/" :index] [["/" :index]
["/404" :about]])) ["/success" :success]
["/about" :about]
["/404" :error]]))
; link builder ; link builder give a keyword from the router above return a url
(defn path-for [route & [params]] (defn path-for [route & [params]]
(if params (if params
(:path (reitit/match-by-name router route params)) (:path (reitit/match-by-name router route params))
(:path (reitit/match-by-name router route)))) (:path (reitit/match-by-name router route))))
(defn form-input-map [{:keys [name title placeholder data validator error focus type]}] (defn validation-message [errors default]
(let [pred (:cljs.spec.alpha/spec errors)]
(case pred
:cljs.core/string? "Sorry value must be a string!"
:weecfg.core/size-test "Sorry value to long!"
:weecfg.core/wifi-ssid-type "Sorry ssid invalid!"
:weecfg.core/wifi-key-type "Invalid wifi key"
:weecfg.core/device-id-type "Device is to short or invalid format."
(or (str default pred) (str "Error not handled " (str pred))))))
; generate form fields dynamically from a hash map
(defn form-input-map [{:keys [name title placeholder validator error focus type]} data]
[:div.mt3.dim {:key name} [:div.mt3.dim {:key name}
[:label.db.fw4.lh-copy.f6 {:for name} title] [:label.db.fw4.lh-copy.f6 {:for name} title]
[:input#wifi-ssid.pa2.input-reset.ba.bg-near-white.w-100.measure.b--silver [:input#wifi-ssid.pa2.input-reset.ba.bg-near-white.w-100.measure.b--silver
{:type (or type "text") :name name {:type (or type "text") :name name
:placeholder placeholder :autoFocus (or focus nil) :placeholder placeholder :autoFocus (or focus nil)
:on-change #(swap! form-data assoc :device-id (-> % .-target .-value)) :on-change #(swap! data assoc (keyword name) (-> % .-target .-value))
:value (get @form-data :device-id) :value (get @data (keyword name))}]
}]
[:p (if (and (not-empty ((keyword name) @data)) [:p (if (and (not-empty ((keyword name) @data))
(s/explain-data (s/explain-data
(keyword 'DEMOAPP.core validator) (keyword 'weecfg.core validator)
((keyword name) @data))) ((keyword name) @data)))
error)]]) (validation-message (s/explain-data
(keyword 'weecfg.core validator)
((keyword name) @data)) error))]])
; handle failures
(swap! form-data assoc :device-id "aa")
(defn handle-failure [{:keys [status status-text response]}] (defn handle-failure [{:keys [status status-text response]}]
(case status (case status
@ -66,40 +83,49 @@
404 (do (reset! form-errors "Could not find device to submit data.")) 404 (do (reset! form-errors "Could not find device to submit data."))
(reset! form-errors (str (:msg response) (:msg response))))) (reset! form-errors (str (:msg response) (:msg response)))))
; load in an edn map to build the form
(defn load-form [form-fields]
(GET "/form.edn"
{:response :transit
:format :transit
:error-handler handle-failure
:handler (fn [response] (reset! form-fields (read-string response)))}))
;submit the data as raw post data
(defn form-submit [data] (defn form-submit [data]
(POST "/submit" (POST "/submit"
{:params @data {:params @data
:error-handler handle-failure :error-handler handle-failure
;:response-format :json
:format :raw :format :raw
;:keywords? true
:handler (fn [response] :handler (fn [response]
(swap! form-errors "success"))})) (swap! form-errors "success"))}))
; simple input form ;simple input form
(defn config-form [] (defn config-form []
{:on-submit (let [form-fields (atom [])]
(fn [e] (.preventDefault e) (load-form form-fields)
(api/save-snippet @title @text))} (fn []
[:article.black-80.db.tc.black.ba.w-100.bg-light-green.br2 [:article.black-80.db.tc.black.ba.w-100.bg-light-green.br2
[:form.ma3 {:method "get" :accept-charset "utf-8" [:form.ma3 {:method "get" :accept-charset "utf-8"
:on-submit (fn [e] (.preventDefault e) (form-submit form-data))} :on-submit (fn [e] (.preventDefault e) (form-submit form-data))}
[:fieldset#sign_up.ba.b--transparent.ph0.mh0 (into [:fieldset#sign_up.ba.b--transparent.ph0.mh0
[:legend.ph0.pb3.mh0.fw6 "Device Setup"] [:legend.ph0.pb3.mh0.fw6 "Device Setup"]]
(if @form-errors (map #(form-input-map % form-data) @form-fields))
[:div.flex.items-center.justify-center.pa3.bg-lightest-blue.navy (if @form-errors
[:i.fas.fa-exclamation-circle] [:div.flex.items-center.justify-center.pa3.bg-lightest-blue.navy
[:span.lh-title.ml3 @form-errors]]) [:i.fas.fa-exclamation-circle]
(map form-input-map form-layout)] [:span.lh-title.ml3 @form-errors]])
[:div.mt3 [:div.mt3
[:input.b.ph3.pv2.input-reset.ba.b--black.bg-transparent.grow.pointer.f6.br2 [:input.b.ph3.pv2.input-reset.ba.b--black.bg-transparent.grow.pointer.f6.br2
{:type "submit" :value "Save"}]]]]) {:type "submit" :value "Save"}]]]])))
; homepage is a simple form to enter your data
(defn home-page [] (defn home-page []
(config-form)) [:div
[config-form]])
(defn success-page [] (defn success-page []
[:div [:div
@ -110,17 +136,18 @@
[:div [:div
[:div [:h1.m-4 "About Page"] [:div [:h1.m-4 "About Page"]
[:p "Sample app, demoing using ajax calls and reagent to render the page with some simple route from reitit, basically enought to get you started on your clojurescript journey."] [:p "Sample app, demoing using ajax calls and reagent to render the page with some simple route from reitit, basically enought to get you started on your clojurescript journey."]
[:a.link {:href (path-for :index)} "Back"]]])
[:a.navbar-item {:href (path-for :index)} "Home Page"]]]) ; give a browser route load the relevant defn handler
(defn page-for [route] (defn page-for [route]
"Map your route keys to pages here" "Map your route keys to pages here"
(case route (case route
:index #'home-page :index #'home-page
:error #'home-page
:success #'success-page
:about #'about-page)) :about #'about-page))
; default page wrapper ; default page wrapper html
; default page wrapper
(defn current-page [] (defn current-page []
(fn [] (fn []
(let [page (:current-page (session/get :route))] (let [page (:current-page (session/get :route))]
@ -128,10 +155,12 @@
[:div.w-25 [:div.w-25
[:a.athelas.f3.link.white-70.hover-white.no-underline.flex.items-center.pa3 [:a.athelas.f3.link.white-70.hover-white.no-underline.flex.items-center.pa3
{:href "/"} {:href "/"}
[:h2 [:h2.w-100
[:a.link.fr.black {:href (path-for :about)} [:i.fas.fa-question-circle]]
[:i.fas.fa-surprise.mr3] "GASP Sensor setup"]] [:i.fas.fa-surprise.mr3] "GASP Sensor setup"]]
[page]]]))) [page]]])))
; sort out routing sessions and basically kickstart everything
(defn startup [] (defn startup []
(accountant/configure-navigation! (accountant/configure-navigation!
{:nav-handler {:nav-handler
@ -141,11 +170,11 @@
route-params (:path-params match)] route-params (:path-params match)]
(session/put! :route {:current-page (page-for current-page) (session/put! :route {:current-page (page-for current-page)
:route-params route-params}))) :route-params route-params})))
:path-exists? :path-exists?
(fn [path] (fn [path]
(boolean (reitit/match-by-path router path)))}) (boolean (reitit/match-by-path router path)))})
(accountant/dispatch-current!) (accountant/dispatch-current!)
(reagent/render [current-page] (.getElementById js/document "app"))) (reagent/render [current-page]
(.getElementById js/document "app")))
(startup) (startup)