diff --git a/README.md b/README.md index 8ce1a63..dd054c8 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,12 @@ generated using Luminus version "2.9.11.05" ## Status -**Nothing works yet**. This is very early development code, a long way pre-alpha. +Very early pre-alpha; user interface mostly works (enough to demonstrate), back end is hardly started. ## What is it supposed to do? To understand what I'm aiming for, read [this essay](http://blog.journeyman.cc/2016/10/preparing-for-next-independence.html). Design documentation, such as there is of it yet, is in the *dummy* sub-directory. Also look at [src/clj/youyesyet/db/schema.clj](https://github.com/simon-brooke/youyesyet/blob/master/src/clj/youyesyet/db/schema.clj). -### Dummy website - -I've put a [dummy site](http://www.journeyman.cc/~simon/tmp/yyy-dummy/index.html) up for people to play with. Obviously it's intended to work with mobile phones. - ## Prerequisites You will need [Leiningen][1] 2.0 or above installed. The database required must be [Postgres][2] 9.3 or above. @@ -49,6 +45,16 @@ You should also read the [User-Oriented Specification](doc/specification/userspe ## Getting the database up +You'll need a file *profiles.clj*, with content similar to the following; it's not in the repository because it contains passwords. + + ;; WARNING + ;; The profiles.clj file is used for local environment variables, such as database credentials. + ;; This file is listed in .gitignore and will be excluded from version control by Git. + + {:profiles/dev {:env {:database-url "jdbc:postgresql://127.0.0.1/youyesyet_dev?user=youyesyet&password=yourverysecurepassword"}} + :profiles/test {:env {:database-url "jdbc:postgresql://127.0.0.1/youyesyet_test?user=youyesyet&password=yourverysecurepassword"}}} + + Do get the database initialised, run createdb youyesyet_dev @@ -59,9 +65,11 @@ followed by **NOTE THAT** in the namespace *youyesyet.db.schema*, there are a series of functions *create-xxx-table!*. These are a snare and a delusion; they are how I originally bootstrapped the database, but are no longer used (and should probably be deleted). The database is now constructed using [migratus](https://github.com/yogthos/migratus). -## Running +## Running in a dev environment -To start a web server for the application, run: +To run in a dev environment, checkout the *develop* branch + +To start a development web server for the application, run: lein run @@ -73,6 +81,10 @@ as above; in the other, run lein figwheel +## Running in a production environment + +Doesn't really work yet; if you want to try it, see [Bug #36](https://github.com/simon-brooke/youyesyet/issues/36) and check out the associated feature branch. + ## Working on this project You should get the [ZenHub](https://github.com/integrations/zenhub) plug-in for your favourite browser; I use ZenHub for project management, and you (and I) will find collaborating much easier if you do. To join in diff --git a/externs.js b/externs.js index b190e01..55c082e 100644 --- a/externs.js +++ b/externs.js @@ -1,12 +1,14 @@ +/* I'm not certain that this file is still needed at all; however, it doesn't seem to be doing any harm. */ /* Things which should not get renamed when compiling ClojureScript */ /* this block relates to the use of Leaflet */ var L = { - "map": { - "setView": function(){} - }, - "tileLayer": { + "map": { + "setView": function(){}, + "eg": function(){} + }, + "tileLayer": { "addTo": function(){} - } + } }; diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index 5df78a1..0000000 Binary files a/favicon.ico and /dev/null differ diff --git a/project.clj b/project.clj index 7e9a72e..07ca966 100644 --- a/project.clj +++ b/project.clj @@ -8,6 +8,7 @@ [ring/ring-servlet "1.5.1"] [lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]] [clj-oauth "1.5.5"] + [cljsjs/react-leaflet "0.12.3-4"] [ch.qos.logback/logback-classic "1.2.2"] [re-frame "0.9.2"] [cljs-ajax "0.5.8"] @@ -65,12 +66,25 @@ :hooks [leiningen.less] - :uberwar - {:prep-tasks ["compile" "bower" ["cljsbuild" "once" "min"]] - :handler youyesyet.handler/app - :init youyesyet.handler/init - :destroy youyesyet.handler/destroy - :name "youyesyet.war"} + :uberwar {:handler youyesyet.handler/app + :init youyesyet.handler/init + :destroy youyesyet.handler/destroy + :name "youyesyet.war" + :uberjar {:omit-source true + :prep-tasks ["compile" ["bower" "install"] ["cljsbuild" "once" "min"]] + :cljsbuild + {:builds + {:min + {:source-paths ["src/cljc" "src/cljs" "env/prod/cljs"] + :compiler + {:main "youyesyet.core" + :optimizations :advanced + :pretty-print false + :verbose true}}}} + :aot :all + :uberjar-name "youyesyet.jar" + :source-paths ["env/prod/clj"] + :resource-paths ["env/prod/resources"]}} :clean-targets ^{:protect false} [:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]] diff --git a/project.new.clj b/project.new.clj new file mode 100644 index 0000000..b14bf62 --- /dev/null +++ b/project.new.clj @@ -0,0 +1,156 @@ +(defproject youyesyet "0.1.0-SNAPSHOT" + + :description "Canvassing tool for referenda" + :url "https://github.com/simon-brooke/youyesyet" + + :dependencies [[bouncer "1.0.1"] + [clj-oauth "1.5.4"] + [cljs-ajax "0.5.8"] + [compojure "1.5.2"] + [conman "0.6.3"] + [cprop "0.1.10"] + [funcool/struct "1.0.0"] + [korma "0.4.3"] + ;; TODO: Latest Luminus no longer includes noir, and I only + ;; use it in home.clj for routing. Worth looking at how Luminus + ;; currently does roouting, and perhaps removing this dependency. + [lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]] + [luminus-migrations "0.3.0"] + [luminus-nrepl "0.1.4"] + [luminus/ring-ttl-session "0.3.1"] + [markdown-clj "0.9.98"] + [metosin/muuntaja "0.1.0"] + [metosin/ring-http-response "0.8.2"] + [mount "0.1.11"] + [org.clojure/clojure "1.8.0"] + [org.clojure/clojurescript "1.9.495" :scope "provided"] + [org.clojure/tools.cli "0.3.5"] + [org.clojure/tools.logging "0.3.1"] + [org.postgresql/postgresql "42.0.0"] + [org.webjars.bower/tether "1.4.0"] + [org.webjars/bootstrap "4.0.0-alpha.5"] + [org.webjars/font-awesome "4.7.0"] + [re-frame "0.9.2"] + [reagent "0.6.1"] + [reagent-utils "0.2.1"] + [ring-webjars "0.1.1"] + [ring/ring-core "1.6.0-RC1"] + [ring/ring-defaults "0.2.3"] + [ring/ring-servlet "1.4.0"] + [secretary "1.2.3"] + [selmer "1.10.7"]] + + :min-lein-version "2.0.0" + + :license {:name "GNU General Public License v2" + :url "http://www.gnu.org/licenses/gpl-2.0.html"} + + :jvm-opts ["-server" "-Dconf=.lein-env"] + :source-paths ["src/clj" "src/cljc"] + :test-paths ["test/clj"] + :resource-paths ["resources" "target/cljsbuild"] + :target-path "target/%s/" + :main ^:skip-aot youyesyet.core + :migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")} + + :plugins [[lein-cprop "1.0.1"] + [migratus-lein "0.4.4"] + [lein-cljsbuild "1.1.5"] + [lein-immutant "2.1.0"] + [lein-kibit "0.1.2"] + [lein-uberwar "0.2.0"] + [lein-bower "0.5.1"]] + + :bower-dependencies [[leaflet "0.7.3"]] + + :uberwar + {:handler youyesyet.handler/app + :init youyesyet.handler/init + :destroy youyesyet.handler/destroy + :name "youyesyet.war"} + + :clean-targets ^{:protect false} + [:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]] + + :figwheel + {:http-server-root "public" + :nrepl-port 7002 + :css-dirs ["resources/public/css"] + :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} + + :externs ["externs.js"] + + :profiles + {:uberjar {:omit-source true + :prep-tasks ["compile" ["bower" "install"] ["cljsbuild" "once" "min"]] + :cljsbuild + {:builds + {:min + {:source-paths ["src/cljc" "src/cljs" "env/prod/cljs"] + :compiler + {:output-to "target/cljsbuild/public/js/app.js" + :optimizations :advanced + :pretty-print false + :closure-warnings + {:externs-validation :off :non-standard-jsdoc :off} + :externs ["react/externs/react.js"]}}}} + + + :aot :all + :uberjar-name "youyesyet.jar" + :source-paths ["env/prod/clj"] + :resource-paths ["env/prod/resources"]} + + :dev [:project/dev :profiles/dev] + :test [:project/dev :project/test :profiles/test] + + :project/dev {:dependencies [[prone "1.1.4"] + [ring/ring-mock "0.3.0"] + [ring/ring-devel "1.5.1"] + [org.webjars/webjars-locator-jboss-vfs "0.1.0"] + [luminus-immutant "0.2.3"] + [pjstadig/humane-test-output "0.8.1"] + [binaryage/devtools "0.9.2"] + [com.cemerick/piggieback "0.2.2-SNAPSHOT"] + [directory-naming/naming-java "0.8"] + [doo "0.1.7"] + [figwheel-sidecar "0.5.9"]] + :plugins [[com.jakemccrary/lein-test-refresh "0.18.1"] + [lein-doo "0.1.7"] + [lein-figwheel "0.5.9"] + [org.clojure/clojurescript "1.9.495"]] + :cljsbuild + {:builds + {:app + {:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"] + :compiler + {:main "youyesyet.app" + :asset-path "/js/out" + :output-to "target/cljsbuild/public/js/app.js" + :output-dir "target/cljsbuild/public/js/out" + :source-map true + :optimizations :none + :pretty-print true}}}} + + + + :doo {:build "test"} + :source-paths ["env/dev/clj"] + :resource-paths ["env/dev/resources"] + :repl-options {:init-ns user} + :injections [(require 'pjstadig.humane-test-output) + (pjstadig.humane-test-output/activate!)]} + :project/test {:resource-paths ["env/test/resources"] + :cljsbuild + {:builds + {:test + {:source-paths ["src/cljc" "src/cljs" "test/cljs"] + :compiler + {:output-to "target/test.js" + :main "youyesyet.doo-runner" + :optimizations :whitespace + :pretty-print true}}}} + + } + :profiles/dev {} + :profiles/test {}}) diff --git a/project.old.clj b/project.old.clj new file mode 100644 index 0000000..cbfe1e2 --- /dev/null +++ b/project.old.clj @@ -0,0 +1,163 @@ +(defproject youyesyet "0.1.0-SNAPSHOT" + + :description "Canvassing tool for referenda" + :url "https://github.com/simon-brooke/youyesyet" + + :dependencies [[org.clojure/clojure "1.8.0"] + [org.clojure/clojurescript "1.9.229" :scope "provided"] + [ring/ring-servlet "1.5.1"] + [lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]] + [clj-oauth "1.5.5"] + [ch.qos.logback/logback-classic "1.2.2"] + [re-frame "0.9.2"] + [cljs-ajax "0.5.8"] + [secretary "1.2.3"] + [reagent-utils "0.2.1"] + [reagent "0.6.1"] + [korma "0.4.3"] + [selmer "1.10.6"] + [markdown-clj "0.9.98"] + [ring-middleware-format "0.7.2"] + [metosin/ring-http-response "0.8.2"] + [bouncer "1.0.1"] + [org.webjars/bootstrap "4.0.0-alpha.6-1"] + [org.webjars/font-awesome "4.7.0"] + [org.webjars.bower/tether "1.4.0"] + [org.clojure/tools.logging "0.3.1"] + [compojure "1.5.2"] + [metosin/compojure-api "1.1.10"] + [ring-webjars "0.1.1"] + [ring/ring-defaults "0.2.3"] + [luminus/ring-ttl-session "0.3.1"] + [mount "0.1.11"] + [cprop "0.1.10"] + [org.clojure/tools.cli "0.3.5"] + [migratus "0.8.33"] + [luminus-nrepl "0.1.4"] + [luminus-migrations "0.3.0"] + [conman "0.6.3"] + [org.postgresql/postgresql "9.4.1212"] + ] + + :min-lein-version "2.0.0" + + :license {:name "GNU General Public License v2" + :url "http://www.gnu.org/licenses/gpl-2.0.html"} + + :jvm-opts ["-server" "-Dconf=.lein-env"] + :source-paths ["src/clj" "src/cljc"] + :resource-paths ["resources" "target/cljsbuild"] + :target-path "target/%s/" + :main youyesyet.core + :migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")} + + :plugins [[lein-cprop "1.0.1"] + [migratus-lein "0.4.2"] + [org.clojars.punkisdead/lein-cucumber "1.0.5"] + [lein-cljsbuild "1.1.4"] + [lein-uberwar "0.2.0"] + [lein-bower "0.5.1"] + [lein-less "1.7.5"]] + + :bower-dependencies [[leaflet "0.7.3"]] + + :cucumber-feature-paths ["test/clj/features"] + + :hooks [leiningen.less] + + :uberwar + {:handler youyesyet.handler/app + :init youyesyet.handler/init + :destroy youyesyet.handler/destroy + :name "youyesyet.war"} + + :clean-targets ^{:protect false} + [:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]] + + :figwheel + {:http-server-root "public" + :nrepl-port 7002 + :css-dirs ["resources/public/css"] + :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} + + :externs ["externs.js"] + + :profiles + {:uberjar {:omit-source true + :prep-tasks ["compile" ["bower" "install"] ["cljsbuild" "once" "min"]] + :cljsbuild + {:builds + {:min + {:source-paths ["src/cljc" "src/cljs" "env/prod/cljs"] + :compiler + {:asset-path "/youyesyet/js/out" + :externs "externs.js" + :main "youyesyet.core" + :optimizations :advanced + :output-dir "resources/public/js" + :output-to "resources/public/js/app.js" + :pretty-print false + :verbose true}}}} + :aot :all + :uberjar-name "youyesyet.jar" + :source-paths ["env/prod/clj"] + :resource-paths ["env/prod/resources"]} + + :dev [:project/dev :profiles/dev] + :test [:project/dev :project/test :profiles/test] + + :project/dev {:dependencies [[prone "1.1.4"] + [ring/ring-mock "0.3.0"] + [ring/ring-devel "1.5.1"] + [luminus-jetty "0.1.4"] + [pjstadig/humane-test-output "0.8.1"] + [org.clojure/core.cache "0.6.5"] + [org.apache.httpcomponents/httpcore "4.4.6"] + [clj-webdriver/clj-webdriver "0.7.2"] + [org.seleniumhq.selenium/selenium-server "3.3.1" :exclusions [org.seleniumhq.selenium/selenium-support]] + [doo "0.1.7"] + [binaryage/devtools "0.9.2"] + [figwheel-sidecar "0.5.9"] + [com.cemerick/piggieback "0.2.2-SNAPSHOT"] + [directory-naming/naming-java "0.8"]] + :plugins [[com.jakemccrary/lein-test-refresh "0.14.0"] + [lein-doo "0.1.7"] + [lein-figwheel "0.5.9"] + [org.clojure/clojurescript "1.9.229"]] + :cljsbuild + {:builds + {:app + {:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"] + :compiler + {:asset-path "/youyesyet/js/out" + :main "youyesyet.app" + :externs ["react/externs/react.js" "externs.js"] + :output-to "target/cljsbuild/public/js/app.js" + :output-dir "target/cljsbuild/public/js/out" + :source-map true + :optimizations :none + :pretty-print true}}}} + + + + :doo {:build "test"} + :source-paths ["env/dev/clj" "test/clj"] + :resource-paths ["env/dev/resources"] + :repl-options {:init-ns user} + :injections [(require 'pjstadig.humane-test-output) + (pjstadig.humane-test-output/activate!)]} + :project/test {:resource-paths ["env/dev/resources" "env/test/resources"] + :cljsbuild + {:builds + {:test + {:source-paths ["src/cljc" "src/cljs" "test/cljs"] + :compiler + {:output-to "target/test.js" + :externs ["react/externs/react.js" "externs.js"] + :main "youyesyet.doo-runner" + :optimizations :whitespace + :pretty-print true}}}} + + } + :profiles/dev {} + :profiles/test {}}) diff --git a/resources/public/css/yyy-common.css b/resources/public/css/yyy-common.css index 465171a..074e9bd 100644 --- a/resources/public/css/yyy-common.css +++ b/resources/public/css/yyy-common.css @@ -60,13 +60,9 @@ footer { width: 100%; margin: 0; padding: 0.25em 0; - bottom:0; - position:fixed; - vertical-align: top; + bottom: 0; + position: fixed; z-index:150; - _position:absolute; - _top:expression(eval(document.documentElement.scrollTop+ - (document.documentElement.clientHeight-this.offsetHeight))); } footer div { @@ -365,8 +361,12 @@ th { /* phones, and, indeed, smaller phones. Adapted to touch; display radically * decluttered */ @media all and (max-device-width: 768px) { - footer { - display: none; + button, input, select { + background-color: rgb( 50, 109, 177); + color: white; + font-size: 1.1em; + padding: 0.25em 1em; + border-radius: 0.5em; } h1 { @@ -375,14 +375,6 @@ th { padding-left: 75px; } - input, select { - background-color: rgb( 50, 109, 177); - color: white; - font-size: 1.1em; - padding: 0.25em 1em; - border-radius: 0.5em; - } - .hidden { display: none; } diff --git a/resources/public/favicon.ico b/resources/public/favicon.ico index e69de29..5df78a1 100644 Binary files a/resources/public/favicon.ico and b/resources/public/favicon.ico differ diff --git a/favicon.xcf b/resources/public/favicon.xcf similarity index 100% rename from favicon.xcf rename to resources/public/favicon.xcf diff --git a/resources/public/img/clojure-icon.gif b/resources/public/img/credits/clojure-icon.gif similarity index 100% rename from resources/public/img/clojure-icon.gif rename to resources/public/img/credits/clojure-icon.gif diff --git a/resources/public/img/github-logo-transparent.png b/resources/public/img/credits/github-logo-transparent.png similarity index 100% rename from resources/public/img/github-logo-transparent.png rename to resources/public/img/credits/github-logo-transparent.png diff --git a/resources/public/img/gnu.small.png b/resources/public/img/credits/gnu.small.png similarity index 100% rename from resources/public/img/gnu.small.png rename to resources/public/img/credits/gnu.small.png diff --git a/resources/public/img/luminus-logo.png b/resources/public/img/credits/luminus-logo.png similarity index 100% rename from resources/public/img/luminus-logo.png rename to resources/public/img/credits/luminus-logo.png diff --git a/resources/public/img/ric-logo.png b/resources/public/img/credits/ric-logo.png similarity index 100% rename from resources/public/img/ric-logo.png rename to resources/public/img/credits/ric-logo.png diff --git a/resources/public/img/mapview_800.png b/resources/public/img/mapview_800.png deleted file mode 100644 index 5173125..0000000 Binary files a/resources/public/img/mapview_800.png and /dev/null differ diff --git a/resources/public/img/unknown.png b/resources/public/img/unknown.png deleted file mode 100644 index da3ac5b..0000000 Binary files a/resources/public/img/unknown.png and /dev/null differ diff --git a/resources/templates/base-unauthenticated.html b/resources/templates/base-unauthenticated.html index 3c14897..7eb5ef5 100644 --- a/resources/templates/base-unauthenticated.html +++ b/resources/templates/base-unauthenticated.html @@ -37,16 +37,16 @@ diff --git a/src/cljc/youyesyet/outqueue.cljc b/src/cljc/youyesyet/outqueue.cljc new file mode 100644 index 0000000..f2f0df3 --- /dev/null +++ b/src/cljc/youyesyet/outqueue.cljc @@ -0,0 +1,115 @@ +(ns youyesyet.outqueue + (:require + #?(:clj [clojure.core] + :cljs [reagent.core :refer [atom]]))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.outqueue: queue of messages waiting to be sent to the server. +;;;; +;;;; This program is free software; you can redistribute it and/or +;;;; modify it under the terms of the GNU General Public License +;;;; as published by the Free Software Foundation; either version 2 +;;;; of the License, or (at your option) any later version. +;;;; +;;;; This program is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with this program; if not, write to the Free Software +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +;;;; USA. +;;;; +;;;; Copyright (C) 2016 Simon Brooke for Radical Independence Campaign +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; The items are (obviously) the actual items in the queue; +;;; the queue is locked if an attempt is currently being made to transmit +;;; an item. + +(defn new-queue + "Create a new queue" + ([] + (new-queue '())) + ([items] + (atom {:locked false + :items (if + (seq? items) + (reverse items) + (list items))}))) + +(defn add! + "Add this item to the queue." + [q item] + (swap! q + (fn [a] + (assoc a :items + (cons item (:items a)))))) + +(defn q? + [x] + (try + (let [q (deref x) + locked (:locked q)] + (and + (seq? (:items q)) + (or (true? locked) (false? locked)))) + (catch #?(:clj Exception :cljs js/Object) any + #?(:clj (print (.getMessage any)) + :cljs (js/console.log (str any)))))) + +(defn peek + "Look at the next item which could be removed from the queue." + [q] + (last (:items (deref q)))) + +(defn locked? + [q] + (:locked (deref q))) + +(defn unlock! + ([q ] + (unlock! q true)) + ([q value] + (swap! q (fn [a] (assoc a :locked (not (true? value))))))) + +(defn lock! + [q] + (unlock! q false)) + + +(defn count + "Return the count of items currently in the queue." + [q] + (count (deref q))) + +(defn take! + "Return the first item from the queue, rebind the queue to the remaining + items. If the queue is empty return nil." + [q] + (swap! q (fn [a] + (let [items (reverse (:items a)) + item (first items) + new-queue (reverse (rest items))] + (assoc (assoc a :items new-queue) :v item)))) + (:v (deref q))) + +(defn maybe-process-next + "Apply this process, assumed to be a function of one argument, to the next + item in the queue, if the queue is not currently locked; return the value + returned by process." + [q process] + (if (and (q? q)(not (locked? q))) + (try + (lock! q) + (let [v (apply process (list (peek q)))] + (take! q) + v) + (catch #?(:clj Exception :cljs js/Object) any + #?(:clj (print (.getMessage any)) + :cljs (js/console.log (str any)))) + (finally (unlock! q))) + )) diff --git a/src/cljs/youyesyet/core.cljs b/src/cljs/youyesyet/core.cljs index 0b4cae8..27e831c 100644 --- a/src/cljs/youyesyet/core.cljs +++ b/src/cljs/youyesyet/core.cljs @@ -1,11 +1,12 @@ (ns youyesyet.core - (:require [reagent.core :as r] - [re-frame.core :as rf] - [secretary.core :as secretary] + (:require cljsjs.react-leaflet + [ajax.core :refer [GET POST]] [goog.events :as events] [goog.history.EventType :as HistoryEventType] [markdown.core :refer [md->html]] - [ajax.core :refer [GET POST]] + [reagent.core :as r] + [re-frame.core :as rf] + [secretary.core :as secretary] [youyesyet.ajax :refer [load-interceptors!]] [youyesyet.handlers] [youyesyet.subscriptions] @@ -71,16 +72,26 @@ :map #'map-page }) + (defn page "Render the single page of the app, taking the current panel from the :page key in the state map." [] - [:div - [:header - [ui/navbar]] - (let [content (pages @(rf/subscribe [:page]))] + (let [content (pages @(rf/subscribe [:page])) + error @(rf/subscribe [:error]) + feedback @(rf/subscribe [:feedback]) + outqueue @(rf/subscribe [:outqueue])] + [:div + [:header + [ui/navbar]] (if content [content] - [:div.error (str "No content in page " :page)]))]) + [:div.error (str "No content in page " :page)]) + [:footer + [:div.error {:style [:display (if error "block" "none")]} (str error)] + [:div.feedback {:style [:display (if feedback :block :none)]} (str feedback)] + [:div.queue (if + (nil? outqueue) "" + (str (count outqueue) " items queued to send"))]]])) ;; ------------------------- ;; Routes @@ -108,7 +119,7 @@ (rf/dispatch [:set-elector-and-page {:elector-id elector-id :page :issues}])) (secretary/defroute "/issue/:issue" {issue :issue} - (rf/dispatch [:set-issue issue])) + (rf/dispatch [:set-and-go-to-issue issue])) (secretary/defroute "/map" [] (rf/dispatch [:set-active-page :map])) diff --git a/src/cljs/youyesyet/db.cljs b/src/cljs/youyesyet/db.cljs index 2d1efaf..5803072 100644 --- a/src/cljs/youyesyet/db.cljs +++ b/src/cljs/youyesyet/db.cljs @@ -31,42 +31,48 @@ (def default-db {;;; the currently selected address, if any. - :address {:id 1 :address "13 Imaginary Terrace, IM1 3TE" :latitude 55.8253043 :longitude -4.2569057 - :electors [{:id 1 :name "Alan Anderson" :gender :male :intention :no} - {:id 2 :name "Ann Anderson" :gender :female} - {:id 3 :name "Alex Anderson" :gender :fluid :intention :yes} - {:id 4 :name "Andy Anderson" :intention :yes}]} - ;;; a list of the addresses in the current location at which there - ;;; are electors registered. - :addresses [{:id 1 :address "13 Imaginary Terrace, IM1 3TE" :latitude 55.8253043 :longitude -4.2570944 - :electors [{:id 1 :name "Alan Anderson" :gender :male :intention :no} - {:id 2 :name "Ann Anderson" :gender :female} - {:id 3 :name "Alex Anderson" :gender :fluid :intention :yes} - {:id 4 :name "Andy Anderson" :intention :yes}]} - {:id 2 :address "15 Imaginary Terrace, IM1 3TE" :latitude 55.8252354 :longitude -4.2572778 - :electors [{:id 1 :name "Beryl Brown" :gender :female} - {:id 2 :name "Betty Black" :gender :female}]} - {:id 3 :address "17 Imaginary Terrace, IM1 3TE" :latitude 55.825166 :longitude -4.257026 - :electors [{:id 1 :name "Catriona Crathie" :gender :female :intention :yes} - {:id 2 :name "Colin Caruthers" :gender :male :intention :yes} - {:id 3 :name "Calum Crathie" :intention :yes}]} - {:id 4 :address "19 Imaginary Terrace, IM1 3TE" :latitude 55.82506950000001 :longitude -4.2570239 - :electors [{:id 1 :name "David Dewar" :gender :male :intention :no}]}] - ;;; electors at the currently selected address - :electors [{:id 1 :name "Alan Anderson" :gender :male :intention :no} - {:id 2 :name "Ann Anderson" :gender :female} - {:id 3 :name "Alex Anderson" :gender :fluid :intention :yes} - {:id 4 :name "Andy Anderson" :intention :yes}] - ;;; the issue from among the issues which is currently selected. - :issue "Currency" - ;;; the issues selected for the issues page on this day. - :issues {"Currency" "Scotland could keep the Pound, or use the Euro. But we could also set up a new currency of our own. Yada yada yada" - "Monarchy" "Scotland could keep the Queen. This is an issue to be decided after independence. Yada yada yada" - "Defence" "Scotland will not have nuclear weapons, and will probably not choose to engage in far-off wars. But we could remain members of NATO"} - ;;; message of the day - :motd "This is a test version only. There is no real data." - ;;; the options from among which electors can select. - :options [{:id :yes :description "Yes"} {:id :no :description "No"}] - ;;; the currently displayed 'page' within the app. - :page :home - }) + :address {:id 1 :address "13 Imaginary Terrace, IM1 3TE" :latitude 55.8253043 :longitude -4.2569057 + :electors [{:id 1 :name "Alan Anderson" :gender :male :intention :no} + {:id 2 :name "Ann Anderson" :gender :female} + {:id 3 :name "Alex Anderson" :gender :fluid :intention :yes} + {:id 4 :name "Andy Anderson" :intention :yes}]} + ;;; a list of the addresses in the current location at which there + ;;; are electors registered. + :addresses [{:id 1 :address "13 Imaginary Terrace, IM1 3TE" :latitude 55.8253043 :longitude -4.2570944 + :electors [{:id 1 :name "Alan Anderson" :gender :male :intention :no} + {:id 2 :name "Ann Anderson" :gender :female} + {:id 3 :name "Alex Anderson" :gender :fluid :intention :yes} + {:id 4 :name "Andy Anderson" :intention :yes}]} + {:id 2 :address "15 Imaginary Terrace, IM1 3TE" :latitude 55.8252354 :longitude -4.2572778 + :electors [{:id 1 :name "Beryl Brown" :gender :female} + {:id 2 :name "Betty Black" :gender :female}]} + {:id 3 :address "17 Imaginary Terrace, IM1 3TE" :latitude 55.825166 :longitude -4.257026 + :electors [{:id 1 :name "Catriona Crathie" :gender :female :intention :yes} + {:id 2 :name "Colin Caruthers" :gender :male :intention :yes} + {:id 3 :name "Calum Crathie" :intention :yes}]} + {:id 4 :address "19 Imaginary Terrace, IM1 3TE" :latitude 55.82506950000001 :longitude -4.2570239 + :electors [{:id 1 :name "David Dewar" :gender :male :intention :no}]}] + ;;; electors at the currently selected address + :electors [{:id 1 :name "Alan Anderson" :gender :male :intention :no} + {:id 2 :name "Ann Anderson" :gender :female} + {:id 3 :name "Alex Anderson" :gender :fluid :intention :yes} + {:id 4 :name "Andy Anderson" :intention :yes}] + ;;; any error to display + :error nil + ;;; the issue from among the issues which is currently selected. + ;;; any confirmation message to display + :feedback nil + ;;; the currently selected issue + :issue "Currency" + ;;; the issues selected for the issues page on this day. + :issues {"Currency" "Scotland could keep the Pound, or use the Euro. But we could also set up a new currency of our own. Yada yada yada" + "Monarchy" "Scotland could keep the Queen. This is an issue to be decided after independence. Yada yada yada" + "Defence" "Scotland will not have nuclear weapons, and will probably not choose to engage in far-off wars. But we could remain members of NATO"} + ;;; message of the day + :motd "This is a test version only. There is no real data." + ;;; the options from among which electors can select. + :options [{:id :yes :description "Yes"} {:id :no :description "No"}] + ;;; the currently displayed 'page' within the app. + :outqueue () + :page :home + }) diff --git a/src/cljs/youyesyet/handlers.cljs b/src/cljs/youyesyet/handlers.cljs index 4c9b665..60825c4 100644 --- a/src/cljs/youyesyet/handlers.cljs +++ b/src/cljs/youyesyet/handlers.cljs @@ -27,17 +27,81 @@ ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn clear-messages + "Return a state like this state except with the error and feedback messages + set nil" + [state] + (merge state {:error nil :feedback nil})) + + +(defn get-elector + "Return the elector at this address (or the current address if not specified) + with this id." + ([elector-id state] + (get-elector elector-id state (:address state))) + ([elector-id state address] + (first + (remove + nil? + (map + #(if (= elector-id (:id %)) %) + (:electors address)))))) + + (reg-event-db :initialize-db (fn [_ _] db/default-db)) +(reg-event-db + :send-intention + (fn [db [_ args]] + (let [intention (:intention args) + elector-id (:elector-id args) + elector + (first + (remove nil? + (map + #(if (= elector-id (:id %)) %) + (:electors (:address db))))) + old-address (:address db) + new-address (assoc old-address :electors (cons (assoc elector :intention intention) (remove #(= % elector) (:electors old-address))))] + (cond + (nil? elector) + (assoc db :error "No elector found; not setting intention") + (= intention (:intention elector)) (do (js/console.log "Elector's intention hasn't changed; not setting intention") db) + true + (do + (js/console.log (str "Setting intention of elector " elector " to " intention)) + (merge + (clear-messages db) + {:addresses + (cons new-address (remove old-address (:addresses db))) + :address new-address + :outqueue (cons (assoc args :action :set-intention) (:outqueue db))})))))) + + + (reg-event-db + :send-request + (fn [db [_ _]] + (if (and (:elector db) (:issue db) (:telephone db)) + (do + (js/console.log "Sending request") + (assoc db + :feedback "Request has been queued" + :outqueue (cons + {:elector-id (:id (:elector db)) + :issue (:issue db) + :action :add-request} (:outqueue db)))) + (assoc db :error "Please supply a telephone number to call")))) + + (reg-event-db :set-active-page (fn [db [_ page]] (if page - (assoc db :page page)))) + (assoc (clear-messages db) :page page)))) (reg-event-db @@ -45,48 +109,43 @@ (fn [db [_ address-id]] (let [id (read-string address-id) address (first (remove nil? (map #(if (= id (:id %)) %) (:addresses db))))] - (assoc (assoc db :address address) :page :electors)))) + (assoc (clear-messages db) :address address :page :electors)))) (reg-event-db - :set-elector-and-page - (fn [db [_ args]] - (let [page (:page args) - elector-id (read-string (:elector-id args)) - elector - (first - (remove nil? - (map - #(if (= elector-id (:id %)) %) - (:electors (:address db)))))] - (js/console.log (str "Setting page to " page ", elector to " elector)) - (assoc (assoc db :elector elector) :page page)))) + :set-and-go-to-issue + (fn [db [_ issue]] + (js/console.log (str "Setting page to :issue, issue to " issue)) + (assoc (assoc (clear-messages db) :issue issue) :page :issue))) -(reg-event-db - :set-intention + (reg-event-db + :set-elector-and-page (fn [db [_ args]] - (let [intention (:intention args) + (let [page (:page args) elector-id (read-string (:elector-id args)) - elector - (first - (remove nil? - (map - #(if (= elector-id (:id %)) %) - (:electors (:address db))))) - old-address (:address db) - new-address (assoc old-address :electors (cons (assoc elector :intention intention) (remove #(= % elector) (:electors old-address))))] - (cond - (nil? elector)(do (js/console.log "No elector found; not setting intention") db) - (= intention (:intention elector)) (do (js/console.log "Elector's intention hasn't changed; not setting intention") db) - true - (do - (js/console.log (str "Setting intention of elector " elector " to " intention)) - (assoc db :addresses (cons new-address (remove old-address (:addresses db))))))))) + elector (get-elector elector-id db)] + (js/console.log (str "Setting page to " page ", elector to " elector)) + (assoc (clear-messages db) :elector elector :page page)))) + + +(reg-event-db + :set-elector + (fn [db [_ elector-id]] + (let [elector (get-elector (read-string elector-id) db)] + (js/console.log (str "Setting elector to " elector)) + (assoc (clear-messages db) :elector elector)))) (reg-event-db :set-issue (fn [db [_ issue]] - (js/console.log (str "Setting page to :issue, issue to " issue)) - (assoc (assoc db :issue issue) :page :issue))) + (js/console.log (str "Setting issue to " issue)) + (assoc (clear-messages db) :issue issue))) + + +(reg-event-db + :set-telephone + (fn [db [_ telephone]] + (js/console.log (str "Setting telephone to " telephone)) + (assoc (clear-messages db) :telephone telephone))) diff --git a/src/cljs/youyesyet/subscriptions.cljs b/src/cljs/youyesyet/subscriptions.cljs index d31e1a3..b1aa09c 100644 --- a/src/cljs/youyesyet/subscriptions.cljs +++ b/src/cljs/youyesyet/subscriptions.cljs @@ -39,11 +39,26 @@ (fn [db _] (:addresses db))) +(reg-sub + :changes + (fn [db _] + (:changes db))) + (reg-sub :elector (fn [db _] (:elector db))) +(reg-sub + :error + (fn [db _] + (:error db))) + +(reg-sub + :feedback + (fn [db _] + (:feedback db))) + (reg-sub :issue (fn [db _] @@ -63,3 +78,9 @@ :options (fn [db _] (:options db))) + +(reg-sub + :outqueue + (fn [db _] + (:outqueue db))) + diff --git a/src/cljs/youyesyet/views/about.cljs b/src/cljs/youyesyet/views/about.cljs index 966e000..4ca4a4a 100644 --- a/src/cljs/youyesyet/views/about.cljs +++ b/src/cljs/youyesyet/views/about.cljs @@ -5,7 +5,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; -;;;; youyesyet.views.electors: about/credits view for youyesyet. +;;;; youyesyet.views.about: about/credits view for youyesyet. ;;;; ;;;; This program is free software; you can redistribute it and/or ;;;; modify it under the terms of the GNU General Public License @@ -41,23 +41,23 @@ [:p.motd {:dangerouslySetInnerHTML {:__html (md->html motd)}}] [:p - [:img {:src "img/ric-logo.png" :width "24" :height "24"}] + [:img {:src "img/credits/ric-logo.png" :width "24" :height "24"}] " A project of the " [:a {:href "https://radical.scot/"} "Radical Independence Campaign"]] [:p - [:img {:src "img/luminus-logo.png" :alt "Luminus" :height "24" :width "24"}] + [:img {:src "img/credits/luminus-logo.png" :alt "Luminus" :height "24" :width "24"}] " Built with " [:a {:href "http://www.luminusweb.net/"} "Luminus Web"]] [:p - [:img {:src "img/clojure-icon.gif" :alt "Clojure" :height "24" :width "24"}] + [:img {:src "img/credits/clojure-icon.gif" :alt "Clojure" :height "24" :width "24"}] " Powered by " [:a {:href "http://clojure.org"} "Clojure"]] [:p - [:img {:src "img/github-logo-transparent.png" :alt "GitHub" :height "24" :width "24"}] + [:img {:src "img/credits/github-logo-transparent.png" :alt "GitHub" :height "24" :width "24"}] " Find me/fork me on " [:a {:href "https://github.com/simon-brooke/youyesyet"} "GitHub"]] [:p - [:img {:src "img/gnu.small.png" :alt "Free Software Foundation" :height "24" :width "24"}] + [:img {:src "img/credits/gnu.small.png" :alt "Free Software Foundation" :height "24" :width "24"}] " Licensed under the " [:a {:href "http://www.gnu.org/licenses/gpl-2.0.html"} "GNU General Public License v2.0"]] diff --git a/src/cljs/youyesyet/views/electors.cljs b/src/cljs/youyesyet/views/electors.cljs index 01a37a7..05f7af4 100644 --- a/src/cljs/youyesyet/views/electors.cljs +++ b/src/cljs/youyesyet/views/electors.cljs @@ -1,5 +1,6 @@ (ns youyesyet.views.electors - (:require [re-frame.core :refer [reg-sub subscribe]] + (:require [reagent.core :refer [atom]] + [re-frame.core :refer [reg-sub subscribe dispatch]] [youyesyet.ui-utils :as ui])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -47,12 +48,14 @@ image (if gender (name gender) "unknown")] [:td {:key (:id elector)} [:img {:src (str "img/gender/" image ".png") :alt image}]])) + (defn genders-row [electors] [:tr (map #(gender-cell %) electors)]) + (defn name-cell [elector] [:td {:key (str "name-" (:id elector))} (:name elector)]) @@ -63,26 +66,33 @@ (map #(name-cell %) electors)]) + (defn options-row [electors option] (let [optid (:id option) optname (name optid)] - [:tr {:key (str "options-" optid)} + [:tr {:key (str "options-" optname)} (map - #(let [selected (= optid (:intention %)) - image (if selected (str "img/option/" optname "-selected.png") - (str "img/option/" optname "-unselected.png"))] - [:td {:key (str "option-" optid "-" (:id %))} - [:a {:href (str "#/set-intention/" (:id %) "/" optid)} - [:img {:src image :alt optname}]]]) - electors)])) + (fn [elector] (let [selected (= optid (:intention elector)) + image (if selected (str "img/option/" optname "-selected.png") + (str "img/option/" optname "-unselected.png"))] + [:td {:key (str "option-" optid "-" (:id elector))} + [:img + {:src image + :alt optname + :on-click #(dispatch + [:send-intention {:elector-id (:id elector) + :intention optid}])}]])) + ;; TODO: impose an ordering on electors - by name or by id + electors)])) + (defn issue-cell "Create an issue cell for a particular elector" [elector] [:td {:key (:id elector)} [:a {:href (str "#/issues/" (:id elector))} - [:img {:src "/img/issues.png" :alt "Issues"}]]]) + [:img {:src "img/issues.png" :alt "Issues"}]]]) (defn issues-row @@ -97,8 +107,9 @@ [] (let [address @(subscribe [:address]) addresses @(subscribe [:addresses]) - electors (:electors address) - options @(subscribe [:options])] + electors (sort-by :id (:electors address)) + options @(subscribe [:options]) + changes @(subscribe [:changes])] (if address [:div [:h1 (:address address)] diff --git a/src/cljs/youyesyet/views/followup.cljs b/src/cljs/youyesyet/views/followup.cljs index bfa6a0f..78c0c04 100644 --- a/src/cljs/youyesyet/views/followup.cljs +++ b/src/cljs/youyesyet/views/followup.cljs @@ -1,5 +1,5 @@ (ns youyesyet.views.followup - (:require [re-frame.core :refer [reg-sub subscribe]] + (:require [re-frame.core :refer [reg-sub subscribe dispatch]] [youyesyet.ui-utils :as ui])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -50,25 +50,28 @@ [:div [:h1 "Followup Request"] [:div.container {:id "main-container"} - [:form {} + [:div {} [:p.widget [:label {:for "elector"} "Elector"] - [:select {:id "elector" :name "elector" :value (:id elector)} + [:select {:id "elector" :name "elector" :defaultValue (:id elector) + :on-change #(dispatch [:set-elector (.-value (.-target %))])} (map #(let [] [:option {:value (:id %) :key (:id %)} (:name %)]) (:electors address))]] [:p.widget [:label {:for "issue"} "Issue"] - [:select {:id "issue" :name "issue" :value issue} + ;; #(reset! val (-> % .-target .-value)) + [:select {:id "issue" :name "issue" :defaultValue issue + :on-change #(dispatch [:set-issue (.-value (.-target %))])} (map #(let [] [:option {:key % :value %} %]) (keys issues))]] [:p.widget [:label {:for "telephone"} "Telephone number"] - [:input {:type "text" :id "telephone" :name "telephone"}]] + [:input {:type "text" :id "telephone" :name "telephone" + :on-change #(dispatch [:set-telephone (.-value (.-target %))])}]] [:p.widget - [:label {:for "submit"} "To request a call"] - [:input {:id "submit" :name "submit" :type "submit" :value "Send this!"}]] - ] + [:label {:for "send"} "To request a call"] + [:button {:id "send" :on-click #(dispatch [:send-request])} "Send this!"]]] (ui/back-link)]]))) diff --git a/src/cljs/youyesyet/views/map.cljs b/src/cljs/youyesyet/views/map.cljs index 01bae13..c725531 100644 --- a/src/cljs/youyesyet/views/map.cljs +++ b/src/cljs/youyesyet/views/map.cljs @@ -69,7 +69,7 @@ [id] (js/console.log (str "Click handler for address #" id)) (set! window.location.href (str "#electors/" id))) -;; This way is probably more idiomatic React, but back links don't work: +;; This way is probably more idiomatic React, but history doesn't work: ;; (defn map-pin-click-handler ;; [id] ;; (dispatch [:set-address id]))