Signature widget working in app.

This commit is contained in:
Simon Brooke 2018-07-02 21:55:28 +01:00
parent f70d8ee2ff
commit b42a593e34
11 changed files with 126 additions and 74 deletions

View file

@ -28,6 +28,12 @@ h1 {
margin-top: 0; margin-top: 0;
} }
#signature-pad {
width: 300px;
border: thin solid white;
min-height: 150px;
}
/* desktops and laptops, primarily. Adapted to mouse; targets may be small */ /* desktops and laptops, primarily. Adapted to mouse; targets may be small */
@media all and (min-device-width: 1025px) { @media all and (min-device-width: 1025px) {

View file

@ -164,7 +164,7 @@ th {
#main-container{ #main-container{
} }
#back-link, .back-link { .back-link {
min-width: 8em; min-width: 8em;
padding: 0.25em 1em; padding: 0.25em 1em;
background-color: gray; background-color: gray;
@ -174,26 +174,26 @@ th {
border-bottom-right-radius: 0.5em; border-bottom-right-radius: 0.5em;
} }
#back-link:hover, #back-link:active, .back-link:hover, .back-link:active, { .back-link:hover, .back-link:active, {
text-decoration: none; text-decoration: none;
background-color: rgb(160, 160, 160); background-color: rgb(160, 160, 160);
} }
#back-link:hover::before, #back-link:active::before { .back-link:hover::before, .back-link:active::before {
content: "< "; content: "< ";
} }
#back-link-container { .back-link-container {
float: left; float: left;
text-align: left; text-align: left;
} }
#back-link-container, .big-link-container { .back-link-container, .big-link-container {
font-size: 200%; font-size: 200%;
padding: 0.5em 0; padding: 0.5em 0;
} }
#back-link-container > #back-link:hover::before, #back-link-container > #back-link:active::before { .back-link-container > .back-link:hover::before, .back-link-container > .back-link:active::before {
} }

View file

@ -1,4 +1,13 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block head %}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" type="text/css" href="css/yyy-common.css" />
<link rel="stylesheet" type="text/css" href="css/yyy-app.css" />
<link rel="stylesheet" type="text/css" href="css/spinner.css" />
<link href="https://fonts.googleapis.com/css?family=Archivo+Black|Archivo+Narrow" rel="stylesheet"/>
<title>{{site-title}}: {{title}}</title>
{% endblock %}
{% block whole-page %} {% block whole-page %}
<div id="app"> <div id="app">
<div class="splash-screen"> <div class="splash-screen">

View file

@ -40,8 +40,8 @@
</header> </header>
{% endblock %} {% endblock %}
<div id="main-container" class="container"> <div id="main-container" class="container">
<div id="back-link-container"> <div class="back-link-container">
<a href="javascript:history.back()" id="back-link">Back</a> <a href="javascript:history.back()" class="back-link">Back</a>
</div> </div>
<div id="big-links"> <div id="big-links">
{% block big-links %} {% block big-links %}

View file

@ -15,8 +15,8 @@
[youyesyet.canvasser-app.ui-utils :as ui] [youyesyet.canvasser-app.ui-utils :as ui]
[youyesyet.canvasser-app.views.about :as about] [youyesyet.canvasser-app.views.about :as about]
[youyesyet.canvasser-app.views.building :as building] [youyesyet.canvasser-app.views.building :as building]
[youyesyet.canvasser-app.views.dwelling :as dwelling]
[youyesyet.canvasser-app.views.elector :as elector] [youyesyet.canvasser-app.views.elector :as elector]
[youyesyet.canvasser-app.views.electors :as electors]
[youyesyet.canvasser-app.views.followup :as followup] [youyesyet.canvasser-app.views.followup :as followup]
[youyesyet.canvasser-app.views.gdpr :as gdpr] [youyesyet.canvasser-app.views.gdpr :as gdpr]
[youyesyet.canvasser-app.views.issue :as issue] [youyesyet.canvasser-app.views.issue :as issue]
@ -56,8 +56,8 @@
(defn building-page [] (defn building-page []
(building/panel)) (building/panel))
(defn electors-page [] (defn dwelling-page []
(electors/panel)) (dwelling/panel))
(defn elector-page [] (defn elector-page []
(elector/panel)) (elector/panel))
@ -81,7 +81,7 @@
{:about #'about-page {:about #'about-page
:building #'building-page :building #'building-page
:elector #'elector-page :elector #'elector-page
:electors #'electors-page :dwelling #'dwelling-page
:followup #'followup-page :followup #'followup-page
:gdpr #'gdpr-page :gdpr #'gdpr-page
:issues #'issues-page :issues #'issues-page
@ -114,41 +114,49 @@
;; Routes ;; Routes
(secretary/set-config! :prefix "#") (secretary/set-config! :prefix "#")
(defn log-and-dispatch [arg]
(js/console.log (str "Dispatching " arg))
(rf/dispatch arg))
(secretary/defroute "/" [] (secretary/defroute "/" []
(rf/dispatch [:set-active-page :map])) (log-and-dispatch [:set-active-page :map]))
(secretary/defroute "/about" [] (secretary/defroute "/about" []
(rf/dispatch [:set-active-page :about])) (log-and-dispatch [:set-active-page :about]))
(secretary/defroute "/electors/:dwelling" {dwelling-id :dwelling} (secretary/defroute "/dwelling" []
(rf/dispatch [:set-dwelling dwelling-id])) (log-and-dispatch [:set-active-page :dwelling]))
(secretary/defroute "/dwelling/:dwelling" {dwelling-id :dwelling}
(log-and-dispatch [:set-dwelling dwelling-id])
(log-and-dispatch [:set-active-page :dwelling]))
(secretary/defroute "/building/:address" {address-id :address} (secretary/defroute "/building/:address" {address-id :address}
(rf/dispatch [:set-address address-id])) (log-and-dispatch [:set-address address-id]))
(secretary/defroute "/followup" [] (secretary/defroute "/followup" []
(rf/dispatch [:set-active-page :followup])) (log-and-dispatch [:set-active-page :followup]))
(secretary/defroute "/gdpr" [] (secretary/defroute "/gdpr" []
(rf/dispatch [:set-active-page :gdpr])) (log-and-dispatch [:set-active-page :gdpr]))
(secretary/defroute "/gdpr/:elector" {elector-id :elector} (secretary/defroute "/gdpr/:elector" {elector-id :elector}
(rf/dispatch [:set-elector-and-page {:elector-id elector-id :page :gdpr}])) (log-and-dispatch [:set-elector-and-page {:elector-id elector-id :page :gdpr}]))
(secretary/defroute "/issues" [] (secretary/defroute "/issues" []
(rf/dispatch [:set-active-page :issues])) (log-and-dispatch [:set-active-page :issues]))
(secretary/defroute "/issues/:elector" {elector-id :elector} (secretary/defroute "/issues/:elector" {elector-id :elector}
(rf/dispatch [:set-elector-and-page {:elector-id elector-id :page :issues}])) (log-and-dispatch [:set-elector-and-page {:elector-id elector-id :page :issues}]))
(secretary/defroute "/issue/:issue" {issue :issue} (secretary/defroute "/issue/:issue" {issue :issue}
(rf/dispatch [:set-and-go-to-issue issue])) (log-and-dispatch [:set-and-go-to-issue issue]))
(secretary/defroute "/map" [] (secretary/defroute "/map" []
(rf/dispatch [:set-active-page :map])) (log-and-dispatch [:set-active-page :map]))
(secretary/defroute "/set-intention/:elector/:intention" {elector-id :elector intention :intention} (secretary/defroute "/set-intention/:elector/:intention" {elector-id :elector intention :intention}
(rf/dispatch [:set-intention {:elector-id elector-id :intention intention}])) (log-and-dispatch [:set-intention {:elector-id elector-id :intention intention}]))
;; ------------------------- ;; -------------------------
;; History ;; History
@ -173,3 +181,4 @@
(load-interceptors!) (load-interceptors!)
(hook-browser-navigation!) (hook-browser-navigation!)
(mount-components)) (mount-components))

View file

@ -36,18 +36,33 @@
(merge state {:error nil :feedback nil})) (merge state {:error nil :feedback nil}))
(defn coerce-to-number [v]
(if (number? v) v
(try
(read-string (str v))
(catch js/Object any
(js/console.log (str "Could not coerce '" v "' to number: " any))))))
(defn get-elector (defn get-elector
"Return the elector at this address (or the current address if not specified) "Return the elector at this address (or the current address if not specified)
with this id." with this id."
([elector-id state] ([elector-id state]
(get-elector elector-id state (:address state))) (get-elector elector-id state (:address state)))
([elector-id state address] ([elector-id state address]
(try
(first (first
(remove (remove
nil? nil?
(map (map
#(if (= elector-id (:id %)) %) #(if (= (coerce-to-number elector-id) (:id %)) %)
(:electors address)))))) (:electors state))))
(catch js/Object _
(str
"Failed to find id '"
elector-id
"' among '"
(:electors state) "'")))))
(reg-event-db (reg-event-db
@ -60,7 +75,7 @@
:send-intention :send-intention
(fn [db [_ args]] (fn [db [_ args]]
(let [intention (:intention args) (let [intention (:intention args)
elector-id (:elector-id args) elector-id (coerce-to-number (:elector-id args))
old-elector (first old-elector (first
(remove nil? (remove nil?
(map (map
@ -134,17 +149,19 @@
(reg-event-db (reg-event-db
:set-address :set-address
(fn [db [_ address-id]] (fn [db [_ address-id]]
(let [id (read-string address-id) (let [id (coerce-to-number address-id)
address (first (remove nil? (map #(if (= id (:id %)) %) (:addresses db))))] address (first (remove nil? (map #(if (= id (:id %)) %) (:addresses db))))]
(if (if
(= (count (:dwellings address)) 1) (= (count (:dwellings address)) 1)
(assoc (clear-messages db) (assoc (clear-messages db)
:address address :address address
:dwelling (first (:dwellings address)) :dwelling (first (:dwellings address))
:page :electors) :electors (:electors (first (:dwellings address)))
:page :dwelling)
(assoc (clear-messages db) (assoc (clear-messages db)
:address address :address address
:dwelling nil :dwelling nil
:electors nil
:page :building))))) :page :building)))))
@ -152,24 +169,28 @@
:set-consent-and-page :set-consent-and-page
(fn [db [_ args]] (fn [db [_ args]]
(let [page (:page args) (let [page (:page args)
elector-id (read-string (:elector-id args)) elector-id (coerce-to-number (:elector-id args))
elector (get-elector elector-id db)] elector (get-elector elector-id db)]
(js/console.log (str "Setting page to " page ", consent to true for " elector)) (js/console.log (str "Setting page to " page ", consent to true for " elector))
(assoc (clear-messages db) :elector (assoc elector :consent true) :page page)))) (assoc (clear-messages db) :elector (assoc elector :consent true) :page page))))
(reg-event-db (reg-event-db
:set-dwelling :set-dwelling
(fn [db [_ dwelling-id]] (fn [db [_ dwelling-id]]
(let [id (read-string dwelling-id) (let [id (coerce-to-number dwelling-id)
dwelling (first dwelling (first
(remove (remove
nil? nil?
(map (map
#(if (= id (:id %)) %) #(if (= id (:id %)) %)
(mapcat :dwellings (:addresses db)))))] (mapcat :dwellings (:addresses db)))))]
(assoc (clear-messages db) :dwelling dwelling :page :electors)))) (if dwelling
(assoc
(clear-messages db)
:dwelling dwelling
:electors (:electors dwelling)
:page :dwelling)))))
(reg-event-db (reg-event-db
@ -183,7 +204,7 @@
:set-elector-and-page :set-elector-and-page
(fn [db [_ args]] (fn [db [_ args]]
(let [page (:page args) (let [page (:page args)
elector-id (read-string (:elector-id args)) elector-id (coerce-to-number (:elector-id args))
elector (get-elector elector-id db)] elector (get-elector elector-id db)]
(js/console.log (str "Setting page to " page ", elector to " elector)) (js/console.log (str "Setting page to " page ", elector to " elector))
(assoc (clear-messages db) :elector elector :page page)))) (assoc (clear-messages db) :elector elector :page page))))
@ -192,7 +213,7 @@
(reg-event-db (reg-event-db
:set-elector :set-elector
(fn [db [_ elector-id]] (fn [db [_ elector-id]]
(let [elector (get-elector (read-string elector-id) db)] (let [elector (get-elector (coerce-to-number elector-id) db)]
(js/console.log (str "Setting elector to " elector)) (js/console.log (str "Setting elector to " elector))
(assoc (clear-messages db) :elector elector)))) (assoc (clear-messages db) :elector elector))))
@ -206,14 +227,14 @@
(reg-event-db (reg-event-db
:set-latitude :set-latitude
(fn [db [_ issue]] (fn [db [_ v]]
(assoc db :latitude issue))) (assoc db :latitude (coerce-to-number v))))
(reg-event-db (reg-event-db
:set-longitude :set-longitude
(fn [db [_ issue]] (fn [db [_ v]]
(assoc db :longitude issue))) (assoc db :longitude (coerce-to-number v))))
(reg-event-db (reg-event-db

View file

@ -46,14 +46,14 @@
{:id 4 :name "Andy Anderson" :intention :yes}]}]} {:id 4 :name "Andy Anderson" :intention :yes}]}]}
{:id 2 :address "15 Imaginary Terrace, IM1 3TE" :latitude 55.8252354 :longitude -4.2572778 {:id 2 :address "15 Imaginary Terrace, IM1 3TE" :latitude 55.8252354 :longitude -4.2572778
:dwellings [{:id 2 :dwellings [{:id 2
:electors [{:id 1 :name "Beryl Brown" :gender :female} :electors [{:id 5 :name "Beryl Brown" :gender :female}
{:id 2 :name "Betty Black" :gender :female}]}]} {:id 6 :name "Betty Black" :gender :female}]}]}
{:id 3 :address "17 Imaginary Terrace, IM1 3TE" :latitude 55.825166 :longitude -4.257026 {:id 3 :address "17 Imaginary Terrace, IM1 3TE" :latitude 55.825166 :longitude -4.257026
:dwellings [{:id 3 :sub-address "Flat 1" :dwellings [{:id 3 :sub-address "Flat 1"
:electors [{:id 1 :name "Catriona Crathie" :gender :female :intention :yes} :electors [{:id 7 :name "Catriona Crathie" :gender :female :intention :yes}
{:id 2 :name "Colin Caruthers" :gender :male :intention :yes} {:id 8 :name "Colin Caruthers" :gender :male :intention :yes}
{:id 3 :name "Calum Crathie" :intention :yes}]} {:id 9 :name "Calum Crathie" :intention :yes}]}
{:id 4 :sub-address "Flat 2" {:id 4 :sub-address "Flat 2"
:electors [{:id 1 :name "David Dewar" :gender :male :intention :no}]}]}] :electors [{:id 1 :name "David Dewar" :gender :male :intention :no}]}]}]
;;; electors at the currently selected dwelling ;;; electors at the currently selected dwelling

View file

@ -34,21 +34,22 @@
([] ([]
(back-link "javascript:history.back()")) (back-link "javascript:history.back()"))
([target] ([target]
[:div.back-link-container {:id "back-link-container"} [:div.back-link-container {:key (gensym "back-link")}
[:a {:href target :id "back-link"} "Back"]])) [:a.back-link {:href target} "Back"]]))
(defn big-link (defn big-link
[text & {:keys [target handler]}] [text & {:keys [target handler]}]
[:div.big-link-container {:key target} [:div.big-link-container {:key (gensym "big-link")}
[:a.big-link (merge [:a.big-link (merge
(if target {:href target}{}) (if target {:href target}{})
(if handler {:on-click handler})) (if handler {:on-click handler}{}))
text]]) text]])
(defn nav-link [uri title page collapsed?] (defn nav-link [uri title page collapsed?]
(let [selected-page (rf/subscribe [:page])] (let [selected-page @(rf/subscribe [:page])]
[:li.nav-item [:li.nav-item
{:class (when (= page @selected-page) "active")} {:class (when (= page selected-page) "active")
:key (gensym "nav-link")}
[:a.nav-link [:a.nav-link
{:href uri {:href uri
:on-click #(reset! collapsed? true)} title]])) :on-click #(reset! collapsed? true)} title]]))
@ -70,6 +71,6 @@
:on-click #(swap! collapsed? not)}] :on-click #(swap! collapsed? not)}]
[:menu.nav {:id "nav-menu" :class (if @collapsed? "hidden" "shown")} [:menu.nav {:id "nav-menu" :class (if @collapsed? "hidden" "shown")}
(nav-link "#/map" "Map" :map collapsed?) (nav-link "#/map" "Map" :map collapsed?)
(nav-link "#/electors" "Electors" :electors collapsed?) (nav-link "#/dwelling" "Electors" :dwelling collapsed?)
(nav-link "#/issues" "Issues" :issues collapsed?) (nav-link "#/issues" "Issues" :issues collapsed?)
(nav-link "#/about" "About" :about collapsed?)]])) (nav-link "#/about" "About" :about collapsed?)]]))

View file

@ -48,7 +48,7 @@
[dwelling] [dwelling]
(ui/big-link (ui/big-link
(:sub-address dwelling) (:sub-address dwelling)
:target (str "#electors/" (:id dwelling))) ) :target (str "#/dwelling/" (:id dwelling))) )
(sort (sort-by
#(< (:sub-address %1) (:sub-address %2)) :sub-address
(:dwellings address)))]]])) (:dwellings address)))]]]))

View file

@ -1,13 +1,13 @@
(ns ^{:doc "Canvasser app electors in household panel." (ns ^{:doc "Canvasser app electors in household panel."
:author "Simon Brooke"} :author "Simon Brooke"}
youyesyet.canvasser-app.views.electors youyesyet.canvasser-app.views.dwelling
(:require [reagent.core :refer [atom]] (:require [reagent.core :refer [atom]]
[re-frame.core :refer [reg-sub subscribe dispatch]] [re-frame.core :refer [reg-sub subscribe dispatch]]
[youyesyet.canvasser-app.ui-utils :as ui])) [youyesyet.canvasser-app.ui-utils :as ui]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; ;;;;
;;;; youyesyet.canvasser-app.views.electors: electors view for youyesyet. ;;;; youyesyet.canvasser-app.views.dwelling: dweling view for youyesyet.
;;;; ;;;;
;;;; This program is free software; you can redistribute it and/or ;;;; This program is free software; you can redistribute it and/or
;;;; modify it under the terms of the GNU General Public License ;;;; modify it under the terms of the GNU General Public License
@ -42,15 +42,20 @@
;;; 2. the elector's name; ;;; 2. the elector's name;
;;; The mechanics of how this panel is laid out don't matter. ;;; The mechanics of how this panel is laid out don't matter.
(defn go-to-gdpr-for-elector [elector]
(dispatch [:set-elector-and-page {:elector-id (:id elector)
:page :gdpr}]))
(defn gender-cell (defn gender-cell
[elector] [elector]
(let [gender (:gender elector) (let [gender (:gender elector)
image (if gender (name gender) "unknown")] image (if gender (name gender) "unknown")]
[:td {:key (str "gender-" (:id elector))} [:td {:key (str "gender-" (:id elector))}
[:a {:href (str "#gdpr/" (:id elector))}
[:img {:src (str "img/gender/" image ".png") :alt image [:img {:src (str "img/gender/" image ".png") :alt image
:on-click #(dispatch ;; :on-click #(go-to-gdpr-for-elector elector)
[:set-elector-and-page {:elector-id (:id elector) }]]]))
:page "gdpr"}])}]]))
(defn genders-row (defn genders-row
@ -63,10 +68,7 @@
(defn name-cell (defn name-cell
[elector] [elector]
[:td {:key (str "name-" (:id elector)) [:td {:key (str "name-" (:id elector))
:on-click #(dispatch :on-click #(go-to-gdpr-for-elector elector)}
[:set-elector-and-page
{:elector-id (:id elector)
:page "gdpr"}])}
(:name elector)]) (:name elector)])

View file

@ -36,7 +36,6 @@
[:div [:div
[:h1 "GDPR Consent"] [:h1 "GDPR Consent"]
[:div.container {:id "main-container"} [:div.container {:id "main-container"}
(ui/back-link "#electors")
[:table [:table
[:tbody [:tbody
[:tr [:tr
@ -51,16 +50,21 @@
only against your electoral district, and not link it to you"]]]] only against your electoral district, and not link it to you"]]]]
[:tr [:tr
[:td [:td
[:canvas {:id "signature-pad" :style "width: 100%; min-width 300px; min-height 200px;"}]]]]]] [:canvas {:id "signature-pad"}]]]]]]
(ui/back-link "#dwelling")
(ui/big-link "I consent" (ui/big-link "I consent"
:handler (fn [] (dispatch [:set-consent-and-page elector "#/gdpr"]))) :handler #(fn [] (dispatch [:set-consent-and-page {:elector-id (:id elector)
:page :elector}])))
;; TODO: need to save the signature ;; TODO: need to save the signature
(ui/big-link "I DO NOT consent" :target "#elector")])) (ui/big-link "I DO NOT consent"
:handler
#(fn [] (dispatch [:set-elector-and-page {:elector-id (:id elector)
:page :elector}])))]))
(defn gdpr-did-mount (defn gdpr-did-mount
[] []
(js/SignaturePad (.getElementById js/document "signature-pad"))) (js/SignaturePad. (.getElementById js/document "signature-pad")))
(defn panel (defn panel