Merge branch 'feature/36' into develop

This commit is contained in:
Simon Brooke 2017-03-31 07:31:15 +01:00
commit 0cb097a64d
26 changed files with 714 additions and 149 deletions

View file

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

View file

@ -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(){}
}
}
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

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

156
project.new.clj Normal file
View file

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

163
project.old.clj Normal file
View file

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

View file

@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -37,16 +37,16 @@
<footer>
<div id="credits">
<div>
<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</a> ||
Version {{version}}
</div>
<div>
<img height="16" width="16" alt="Clojure" src="img/luminus-logo.png"/>Built with <a href="http://www.luminusweb.net/">LuminusWeb</a> ||
<img height="16" width="16" alt="Clojure" src="img/clojure-icon.gif"/> Powered by <a href="http://clojure.org">Clojure</a> ||
<img height="16" width="16" alt="GitHub" src="img/github-logo-transparent.png"/>Find me/fork me on <a href="https://github.com/simon-brooke/smeagol">Github</a> ||
<img height="16" width="16" alt="Free Software Foundation" src="img/gnu.small.png"/>Licensed under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU General Public License version 2.0</a>
<img height="16" width="16" alt="Clojure" src="img/credits/luminus-logo.png"/>Built with <a href="http://www.luminusweb.net/">LuminusWeb</a> ||
<img height="16" width="16" alt="Clojure" src="img/credits/clojure-icon.gif"/> Powered by <a href="http://clojure.org">Clojure</a> ||
<img height="16" width="16" alt="GitHub" src="img/credits/github-logo-transparent.png"/>Find me/fork me on <a href="https://github.com/simon-brooke/smeagol">Github</a> ||
<img height="16" width="16" alt="Free Software Foundation" src="img/credits/gnu.small.png"/>Licensed under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU General Public License version 2.0</a>
</div>
</div>
</footer>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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