Initial version.

This commit is contained in:
Oly 2019-09-06 15:43:06 +01:00
commit c471a75002
15 changed files with 276 additions and 0 deletions

1
LICENSE Normal file
View File

@ -0,0 +1 @@
license for weecfg

12
deps.edn Normal file
View File

@ -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"]}

5
dev.cljs.edn Normal file
View File

@ -0,0 +1,5 @@
{:output-to "resources/public/dev-main.js"
:optimizations :none
:pretty-print true
:source-map true
:main weecfg.core}

2
figwheel-main.edn Normal file
View File

@ -0,0 +1,2 @@
{:target-dir "app/data"
:watch-dirs ["src"]}

5
prod.cljs.edn Normal file
View File

@ -0,0 +1,5 @@
{:output-to "app/data/main.js"
:output-dir "app/data/"
:asset-path "app/data/",
:optimizations :advanced
:main weecfg.core}

26
readme.org Normal file
View File

@ -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/

5
resources/public/all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -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.

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

@ -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.

180
src/weecfg/core.cljs Normal file
View File

@ -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)