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 ## 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? ## 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). 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 ## Prerequisites
You will need [Leiningen][1] 2.0 or above installed. The database required must be [Postgres][2] 9.3 or above. 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 ## 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 Do get the database initialised, run
createdb youyesyet_dev 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). **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 lein run
@ -73,6 +81,10 @@ as above; in the other, run
lein figwheel 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 ## 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 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,9 +1,11 @@
/* 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 */ /* Things which should not get renamed when compiling ClojureScript */
/* this block relates to the use of Leaflet */ /* this block relates to the use of Leaflet */
var L = { var L = {
"map": { "map": {
"setView": function(){} "setView": function(){},
"eg": function(){}
}, },
"tileLayer": { "tileLayer": {
"addTo": function(){} "addTo": function(){}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -8,6 +8,7 @@
[ring/ring-servlet "1.5.1"] [ring/ring-servlet "1.5.1"]
[lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]] [lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]]
[clj-oauth "1.5.5"] [clj-oauth "1.5.5"]
[cljsjs/react-leaflet "0.12.3-4"]
[ch.qos.logback/logback-classic "1.2.2"] [ch.qos.logback/logback-classic "1.2.2"]
[re-frame "0.9.2"] [re-frame "0.9.2"]
[cljs-ajax "0.5.8"] [cljs-ajax "0.5.8"]
@ -65,12 +66,25 @@
:hooks [leiningen.less] :hooks [leiningen.less]
:uberwar :uberwar {:handler youyesyet.handler/app
{:prep-tasks ["compile" "bower" ["cljsbuild" "once" "min"]]
:handler youyesyet.handler/app
:init youyesyet.handler/init :init youyesyet.handler/init
:destroy youyesyet.handler/destroy :destroy youyesyet.handler/destroy
:name "youyesyet.war"} :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} :clean-targets ^{:protect false}
[:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]] [: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%; width: 100%;
margin: 0; margin: 0;
padding: 0.25em 0; padding: 0.25em 0;
bottom:0; bottom: 0;
position:fixed; position: fixed;
vertical-align: top;
z-index:150; z-index:150;
_position:absolute;
_top:expression(eval(document.documentElement.scrollTop+
(document.documentElement.clientHeight-this.offsetHeight)));
} }
footer div { footer div {
@ -365,8 +361,12 @@ th {
/* phones, and, indeed, smaller phones. Adapted to touch; display radically /* phones, and, indeed, smaller phones. Adapted to touch; display radically
* decluttered */ * decluttered */
@media all and (max-device-width: 768px) { @media all and (max-device-width: 768px) {
footer { button, input, select {
display: none; background-color: rgb( 50, 109, 177);
color: white;
font-size: 1.1em;
padding: 0.25em 1em;
border-radius: 0.5em;
} }
h1 { h1 {
@ -375,14 +375,6 @@ th {
padding-left: 75px; 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 { .hidden {
display: none; 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> <footer>
<div id="credits"> <div id="credits">
<div> <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 project of the
<a href="https://radical.scot/">Radical Independence Campaign</a> || <a href="https://radical.scot/">Radical Independence Campaign</a> ||
Version {{version}} Version {{version}}
</div> </div>
<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/credits/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="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/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="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/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="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>
</div> </div>
</footer> </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 (ns youyesyet.core
(:require [reagent.core :as r] (:require cljsjs.react-leaflet
[re-frame.core :as rf] [ajax.core :refer [GET POST]]
[secretary.core :as secretary]
[goog.events :as events] [goog.events :as events]
[goog.history.EventType :as HistoryEventType] [goog.history.EventType :as HistoryEventType]
[markdown.core :refer [md->html]] [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.ajax :refer [load-interceptors!]]
[youyesyet.handlers] [youyesyet.handlers]
[youyesyet.subscriptions] [youyesyet.subscriptions]
@ -71,16 +72,26 @@
:map #'map-page :map #'map-page
}) })
(defn page (defn page
"Render the single page of the app, taking the current panel from "Render the single page of the app, taking the current panel from
the :page key in the state map." the :page key in the state map."
[] []
(let [content (pages @(rf/subscribe [:page]))
error @(rf/subscribe [:error])
feedback @(rf/subscribe [:feedback])
outqueue @(rf/subscribe [:outqueue])]
[:div [:div
[:header [:header
[ui/navbar]] [ui/navbar]]
(let [content (pages @(rf/subscribe [:page]))]
(if content [content] (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 ;; Routes
@ -108,7 +119,7 @@
(rf/dispatch [:set-elector-and-page {:elector-id elector-id :page :issues}])) (rf/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-issue issue])) (rf/dispatch [:set-and-go-to-issue issue]))
(secretary/defroute "/map" [] (secretary/defroute "/map" []
(rf/dispatch [:set-active-page :map])) (rf/dispatch [:set-active-page :map]))

View file

@ -57,7 +57,12 @@
{:id 2 :name "Ann Anderson" :gender :female} {:id 2 :name "Ann Anderson" :gender :female}
{:id 3 :name "Alex Anderson" :gender :fluid :intention :yes} {:id 3 :name "Alex Anderson" :gender :fluid :intention :yes}
{:id 4 :name "Andy Anderson" :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. ;;; the issue from among the issues which is currently selected.
;;; any confirmation message to display
:feedback nil
;;; the currently selected issue
:issue "Currency" :issue "Currency"
;;; the issues selected for the issues page on this day. ;;; 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" :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"
@ -68,5 +73,6 @@
;;; the options from among which electors can select. ;;; the options from among which electors can select.
:options [{:id :yes :description "Yes"} {:id :no :description "No"}] :options [{:id :yes :description "Yes"} {:id :no :description "No"}]
;;; the currently displayed 'page' within the app. ;;; the currently displayed 'page' within the app.
:outqueue ()
:page :home :page :home
}) })

View file

@ -27,6 +27,27 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(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 (reg-event-db
:initialize-db :initialize-db
(fn [_ _] (fn [_ _]
@ -34,40 +55,10 @@
(reg-event-db (reg-event-db
:set-active-page :send-intention
(fn [db [_ page]]
(if page
(assoc db :page page))))
(reg-event-db
:set-address
(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))))
(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))))
(reg-event-db
:set-intention
(fn [db [_ args]] (fn [db [_ args]]
(let [intention (:intention args) (let [intention (:intention args)
elector-id (read-string (:elector-id args)) elector-id (:elector-id args)
elector elector
(first (first
(remove nil? (remove nil?
@ -77,16 +68,84 @@
old-address (:address db) old-address (:address db)
new-address (assoc old-address :electors (cons (assoc elector :intention intention) (remove #(= % elector) (:electors old-address))))] new-address (assoc old-address :electors (cons (assoc elector :intention intention) (remove #(= % elector) (:electors old-address))))]
(cond (cond
(nil? elector)(do (js/console.log "No elector found; not setting intention") db) (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) (= intention (:intention elector)) (do (js/console.log "Elector's intention hasn't changed; not setting intention") db)
true true
(do (do
(js/console.log (str "Setting intention of elector " elector " to " intention)) (js/console.log (str "Setting intention of elector " elector " to " intention))
(assoc db :addresses (cons new-address (remove old-address (:addresses db))))))))) (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 (clear-messages db) :page page))))
(reg-event-db
:set-address
(fn [db [_ address-id]]
(let [id (read-string address-id)
address (first (remove nil? (map #(if (= id (:id %)) %) (:addresses db))))]
(assoc (clear-messages db) :address address :page :electors))))
(reg-event-db
: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-elector-and-page
(fn [db [_ args]]
(let [page (:page args)
elector-id (read-string (:elector-id args))
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 (reg-event-db
:set-issue :set-issue
(fn [db [_ issue]] (fn [db [_ issue]]
(js/console.log (str "Setting page to :issue, issue to " issue)) (js/console.log (str "Setting issue to " issue))
(assoc (assoc db :issue issue) :page :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 _] (fn [db _]
(:addresses db))) (:addresses db)))
(reg-sub
:changes
(fn [db _]
(:changes db)))
(reg-sub (reg-sub
:elector :elector
(fn [db _] (fn [db _]
(:elector db))) (:elector db)))
(reg-sub
:error
(fn [db _]
(:error db)))
(reg-sub
:feedback
(fn [db _]
(:feedback db)))
(reg-sub (reg-sub
:issue :issue
(fn [db _] (fn [db _]
@ -63,3 +78,9 @@
:options :options
(fn [db _] (fn [db _]
(:options 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 ;;;; 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
@ -41,23 +41,23 @@
[:p.motd {:dangerouslySetInnerHTML [:p.motd {:dangerouslySetInnerHTML
{:__html (md->html motd)}}] {:__html (md->html motd)}}]
[:p [: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 project of the "
[:a {:href "https://radical.scot/"} "Radical Independence Campaign"]] [:a {:href "https://radical.scot/"} "Radical Independence Campaign"]]
[:p [: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 " " Built with "
[:a {:href "http://www.luminusweb.net/"} "Luminus Web"]] [:a {:href "http://www.luminusweb.net/"} "Luminus Web"]]
[:p [: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 " " Powered by "
[:a {:href "http://clojure.org"} "Clojure"]] [:a {:href "http://clojure.org"} "Clojure"]]
[:p [: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 " " Find me/fork me on "
[:a {:href "https://github.com/simon-brooke/youyesyet"} "GitHub"]] [:a {:href "https://github.com/simon-brooke/youyesyet"} "GitHub"]]
[:p [: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 " " Licensed under the "
[:a {:href "http://www.gnu.org/licenses/gpl-2.0.html"} [:a {:href "http://www.gnu.org/licenses/gpl-2.0.html"}
"GNU General Public License v2.0"]] "GNU General Public License v2.0"]]

View file

@ -1,5 +1,6 @@
(ns youyesyet.views.electors (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])) [youyesyet.ui-utils :as ui]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -47,12 +48,14 @@
image (if gender (name gender) "unknown")] image (if gender (name gender) "unknown")]
[:td {:key (:id elector)} [:img {:src (str "img/gender/" image ".png") :alt image}]])) [:td {:key (:id elector)} [:img {:src (str "img/gender/" image ".png") :alt image}]]))
(defn genders-row (defn genders-row
[electors] [electors]
[:tr [:tr
(map (map
#(gender-cell %) electors)]) #(gender-cell %) electors)])
(defn name-cell (defn name-cell
[elector] [elector]
[:td {:key (str "name-" (:id elector))} (:name elector)]) [:td {:key (str "name-" (:id elector))} (:name elector)])
@ -63,26 +66,33 @@
(map (map
#(name-cell %) electors)]) #(name-cell %) electors)])
(defn options-row (defn options-row
[electors option] [electors option]
(let [optid (:id option) (let [optid (:id option)
optname (name optid)] optname (name optid)]
[:tr {:key (str "options-" optid)} [:tr {:key (str "options-" optname)}
(map (map
#(let [selected (= optid (:intention %)) (fn [elector] (let [selected (= optid (:intention elector))
image (if selected (str "img/option/" optname "-selected.png") image (if selected (str "img/option/" optname "-selected.png")
(str "img/option/" optname "-unselected.png"))] (str "img/option/" optname "-unselected.png"))]
[:td {:key (str "option-" optid "-" (:id %))} [:td {:key (str "option-" optid "-" (:id elector))}
[:a {:href (str "#/set-intention/" (:id %) "/" optid)} [:img
[:img {:src image :alt optname}]]]) {: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)])) electors)]))
(defn issue-cell (defn issue-cell
"Create an issue cell for a particular elector" "Create an issue cell for a particular elector"
[elector] [elector]
[:td {:key (:id elector)} [:td {:key (:id elector)}
[:a {:href (str "#/issues/" (: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 (defn issues-row
@ -97,8 +107,9 @@
[] []
(let [address @(subscribe [:address]) (let [address @(subscribe [:address])
addresses @(subscribe [:addresses]) addresses @(subscribe [:addresses])
electors (:electors address) electors (sort-by :id (:electors address))
options @(subscribe [:options])] options @(subscribe [:options])
changes @(subscribe [:changes])]
(if address (if address
[:div [:div
[:h1 (:address address)] [:h1 (:address address)]

View file

@ -1,5 +1,5 @@
(ns youyesyet.views.followup (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])) [youyesyet.ui-utils :as ui]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -50,25 +50,28 @@
[:div [:div
[:h1 "Followup Request"] [:h1 "Followup Request"]
[:div.container {:id "main-container"} [:div.container {:id "main-container"}
[:form {} [:div {}
[:p.widget [:p.widget
[:label {:for "elector"} "Elector"] [: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 (map
#(let [] #(let []
[:option {:value (:id %) :key (:id %)} (:name %)]) (:electors address))]] [:option {:value (:id %) :key (:id %)} (:name %)]) (:electors address))]]
[:p.widget [:p.widget
[:label {:for "issue"} "Issue"] [: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 (map
#(let [] #(let []
[:option {:key % :value %} %]) (keys issues))]] [:option {:key % :value %} %]) (keys issues))]]
[:p.widget [:p.widget
[:label {:for "telephone"} "Telephone number"] [: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 [:p.widget
[:label {:for "submit"} "To request a call"] [:label {:for "send"} "To request a call"]
[:input {:id "submit" :name "submit" :type "submit" :value "Send this!"}]] [:button {:id "send" :on-click #(dispatch [:send-request])} "Send this!"]]]
]
(ui/back-link)]]))) (ui/back-link)]])))

View file

@ -69,7 +69,7 @@
[id] [id]
(js/console.log (str "Click handler for address #" id)) (js/console.log (str "Click handler for address #" id))
(set! window.location.href (str "#electors/" 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 ;; (defn map-pin-click-handler
;; [id] ;; [id]
;; (dispatch [:set-address id])) ;; (dispatch [:set-address id]))