Initial version.
This commit is contained in:
commit
c471a75002
|
@ -0,0 +1,12 @@
|
||||||
|
{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
|
||||||
|
org.clojure/clojurescript {:mvn/version "1.10.520"}
|
||||||
|
reagent {:mvn/version "0.8.1"}
|
||||||
|
reagent-utils {:mvn/version "0.3.3"}
|
||||||
|
cljs-ajax {:mvn/version "0.8.0"}
|
||||||
|
pez/clerk {:mvn/version "1.0.0"}
|
||||||
|
metosin/reitit {:mvn/version "0.3.1"}
|
||||||
|
venantius/accountant {:mvn/version "0.2.4"}
|
||||||
|
com.bhauman/figwheel-main {:mvn/version "0.2.1-SNAPSHOT"}
|
||||||
|
cider/piggieback {:mvn/version "0.4.0"}
|
||||||
|
com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}}
|
||||||
|
:paths ["app" "src" "resources"]}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{:output-to "resources/public/dev-main.js"
|
||||||
|
:optimizations :none
|
||||||
|
:pretty-print true
|
||||||
|
:source-map true
|
||||||
|
:main weecfg.core}
|
|
@ -0,0 +1,2 @@
|
||||||
|
{:target-dir "app/data"
|
||||||
|
:watch-dirs ["src"]}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{:output-to "app/data/main.js"
|
||||||
|
:output-dir "app/data/"
|
||||||
|
:asset-path "app/data/",
|
||||||
|
:optimizations :advanced
|
||||||
|
:main weecfg.core}
|
|
@ -0,0 +1,26 @@
|
||||||
|
#+TITLE: WeeCfg simple web interface to save settings on wemos
|
||||||
|
|
||||||
|
* Introduction
|
||||||
|
This should be used with a companion wemos app which servers up these pages, you will need to copy the files to your sketch data folder and upload them to the wemos from the arduino software or using esptool.
|
||||||
|
|
||||||
|
[[./resources/public/example.png]]
|
||||||
|
|
||||||
|
* Requirements
|
||||||
|
To build or run this tool you will need clojure installed to download the requirements and generate the javascript.
|
||||||
|
|
||||||
|
If your editing the project you can use cider or similar to jack in and live edit the page.
|
||||||
|
|
||||||
|
* Building
|
||||||
|
If your not using cider or just want to do things manually use these commands.
|
||||||
|
|
||||||
|
#+BEGIN_SRC sh
|
||||||
|
clojure -m figwheel.main --build dev --repl
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+BEGIN_SRC sh
|
||||||
|
clojure -m figwheel.main --build prod --repl
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
once running you should be able to see the app under the url below.
|
||||||
|
http://localhost:9500/
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,12 @@
|
||||||
|
var CLOSURE_UNCOMPILED_DEFINES = {"figwheel.repl.connect_url":"ws:\/\/localhost:9500\/figwheel-connect?fwprocess=094d2d&fwbuild=dev"};
|
||||||
|
var CLOSURE_NO_DEPS = true;
|
||||||
|
if(typeof goog == "undefined") document.write('<script src="/cljs-out/dev/goog/base.js"></script>');
|
||||||
|
document.write('<script src="/cljs-out/dev/goog/deps.js"></script>');
|
||||||
|
document.write('<script src="/cljs-out/dev/cljs_deps.js"></script>');
|
||||||
|
document.write('<script>if (typeof goog == "undefined") console.warn("ClojureScript could not load :main, did you forget to specify :asset-path?");</script>');
|
||||||
|
document.write('<script>goog.require("figwheel.core");</script>');
|
||||||
|
document.write('<script>goog.require("figwheel.main");</script>');
|
||||||
|
document.write('<script>goog.require("figwheel.repl.preload");</script>');
|
||||||
|
document.write('<script>goog.require("devtools.preload");</script>');
|
||||||
|
document.write('<script>goog.require("process.env");</script>');
|
||||||
|
document.write('<script>goog.require("weecfg.core");</script>');
|
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
@ -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"}]
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="h-100 bg-green" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Device setup</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="tachyon.css" type="text/css"/>
|
||||||
|
<link rel="stylesheet" href="all.min.css" type="text/css"/>
|
||||||
|
</head>
|
||||||
|
<body class="h-100">
|
||||||
|
<div id="app" class="h-100"><i class="fas fa-spinner"></i>Loading</div>
|
||||||
|
<script src="dev-main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -0,0 +1,180 @@
|
||||||
|
(ns weecfg.core
|
||||||
|
(:require [accountant.core :as accountant]
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
|
[cognitect.transit :as t]
|
||||||
|
[clojure.edn :refer [read-string]]
|
||||||
|
[ajax.core :refer [GET, POST]]
|
||||||
|
[reagent.core :as reagent :refer [atom]]
|
||||||
|
[reagent.session :as session]
|
||||||
|
[reitit.frontend :as reitit]))
|
||||||
|
|
||||||
|
; variables
|
||||||
|
(defonce form-errors (atom nil))
|
||||||
|
(defonce form-title "GASP Sensor setup")
|
||||||
|
(defonce form-data (atom {:server "" :device-id "" :wifi-ssid "" :wifi-key ""}))
|
||||||
|
(defonce form-layout (atom []))
|
||||||
|
(defonce device-id-regex #"[A-Za-z0-9]{4}")
|
||||||
|
(defonce ssid-regex #"^[!#;].|[+\[\]\"\t\s].*$")
|
||||||
|
|
||||||
|
; 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 ::wifi-key-type (s/and string? ::default-size-limit))
|
||||||
|
(s/def ::server-type (s/and string? ::default-size-limit))
|
||||||
|
|
||||||
|
|
||||||
|
; page routes
|
||||||
|
|
||||||
|
|
||||||
|
(def router
|
||||||
|
(reitit/router
|
||||||
|
[["/" :index]
|
||||||
|
["/success" :success]
|
||||||
|
["/about" :about]
|
||||||
|
["/404" :error]]))
|
||||||
|
|
||||||
|
; link builder give a keyword from the router above return a url
|
||||||
|
(defn path-for [route & [params]]
|
||||||
|
(if params
|
||||||
|
(:path (reitit/match-by-name router route params))
|
||||||
|
(:path (reitit/match-by-name router route))))
|
||||||
|
|
||||||
|
(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}
|
||||||
|
[:label.db.fw4.lh-copy.f6 {:for name} title]
|
||||||
|
[:input#wifi-ssid.pa2.input-reset.ba.bg-near-white.w-100.measure.b--silver
|
||||||
|
{:type (or type "text") :name name
|
||||||
|
:placeholder placeholder :autoFocus (or focus nil)
|
||||||
|
:on-change #(swap! data assoc (keyword name) (-> % .-target .-value))
|
||||||
|
:value (get @data (keyword name))}]
|
||||||
|
[:p (if (and (not-empty ((keyword name) @data))
|
||||||
|
(s/explain-data
|
||||||
|
(keyword 'weecfg.core validator)
|
||||||
|
((keyword name) @data)))
|
||||||
|
(validation-message (s/explain-data
|
||||||
|
(keyword 'weecfg.core validator)
|
||||||
|
((keyword name) @data)) error))]])
|
||||||
|
|
||||||
|
|
||||||
|
; handle failures
|
||||||
|
|
||||||
|
|
||||||
|
(defn handle-failure [{:keys [status status-text response]}]
|
||||||
|
(case status
|
||||||
|
500 (do (reset! form-errors "Something bad happened submitting your data."))
|
||||||
|
401 (do (reset! form-errors "Not authorized."))
|
||||||
|
404 (do (reset! form-errors "Could not find device to submit data."))
|
||||||
|
(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]
|
||||||
|
(POST "/submit"
|
||||||
|
{:params @data
|
||||||
|
:error-handler handle-failure
|
||||||
|
:format :raw
|
||||||
|
:handler (fn [response]
|
||||||
|
(swap! form-errors "success"))}))
|
||||||
|
|
||||||
|
|
||||||
|
;simple input form
|
||||||
|
|
||||||
|
|
||||||
|
(defn config-form []
|
||||||
|
(let [form-fields (atom [])]
|
||||||
|
(load-form form-fields)
|
||||||
|
(fn []
|
||||||
|
[:article.black-80.db.tc.black.ba.w-100.bg-light-green.br2
|
||||||
|
[:form.ma3 {:method "get" :accept-charset "utf-8"
|
||||||
|
:on-submit (fn [e] (.preventDefault e) (form-submit form-data))}
|
||||||
|
(into [:fieldset#sign_up.ba.b--transparent.ph0.mh0
|
||||||
|
[:legend.ph0.pb3.mh0.fw6 "Device Setup"]]
|
||||||
|
(map #(form-input-map % form-data) @form-fields))
|
||||||
|
(if @form-errors
|
||||||
|
[:div.flex.items-center.justify-center.pa3.bg-lightest-blue.navy
|
||||||
|
[:i.fas.fa-exclamation-circle]
|
||||||
|
[:span.lh-title.ml3 @form-errors]])
|
||||||
|
[:div.mt3
|
||||||
|
[:input.b.ph3.pv2.input-reset.ba.b--black.bg-transparent.grow.pointer.f6.br2
|
||||||
|
{:type "submit" :value "Save"}]]]])))
|
||||||
|
|
||||||
|
; homepage is a simple form to enter your data
|
||||||
|
(defn home-page []
|
||||||
|
[:div
|
||||||
|
[config-form]])
|
||||||
|
|
||||||
|
(defn success-page []
|
||||||
|
[:div
|
||||||
|
[:div [:h1.m-4 "Settings saved"]
|
||||||
|
[:p "Device will reboot in a few seconds and connect to your wifi."]]])
|
||||||
|
|
||||||
|
(defn about-page []
|
||||||
|
[:div
|
||||||
|
[: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."]
|
||||||
|
[:a.link {:href (path-for :index)} "Back"]]])
|
||||||
|
|
||||||
|
; give a browser route load the relevant defn handler
|
||||||
|
(defn page-for [route]
|
||||||
|
"Map your route keys to pages here"
|
||||||
|
(case route
|
||||||
|
:index #'home-page
|
||||||
|
:error #'home-page
|
||||||
|
:success #'success-page
|
||||||
|
:about #'about-page))
|
||||||
|
|
||||||
|
; default page wrapper html
|
||||||
|
(defn current-page []
|
||||||
|
(fn []
|
||||||
|
(let [page (:current-page (session/get :route))]
|
||||||
|
[:div.bg-green.flex.justify-center.items-center.h-100
|
||||||
|
[:div.w-25
|
||||||
|
[:a.athelas.f3.link.white-70.hover-white.no-underline.flex.items-center.pa3
|
||||||
|
{:href "/"}
|
||||||
|
[:h2.w-100
|
||||||
|
[:a.link.fr.black {:href (path-for :about)} [:i.fas.fa-question-circle]]
|
||||||
|
[:i.fas.fa-surprise.mr3] "GASP Sensor setup"]]
|
||||||
|
[page]]])))
|
||||||
|
|
||||||
|
; sort out routing sessions and basically kickstart everything
|
||||||
|
(defn startup []
|
||||||
|
(accountant/configure-navigation!
|
||||||
|
{:nav-handler
|
||||||
|
(fn [path]
|
||||||
|
(let [match (reitit/match-by-path router path)
|
||||||
|
current-page (:name (:data match))
|
||||||
|
route-params (:path-params match)]
|
||||||
|
(session/put! :route {:current-page (page-for current-page)
|
||||||
|
:route-params route-params})))
|
||||||
|
:path-exists?
|
||||||
|
(fn [path]
|
||||||
|
(boolean (reitit/match-by-path router path)))})
|
||||||
|
(accountant/dispatch-current!)
|
||||||
|
(reagent/render [current-page]
|
||||||
|
(.getElementById js/document "app")))
|
||||||
|
|
||||||
|
(startup)
|
Loading…
Reference in New Issue