diff --git a/.gitignore b/.gitignore index 99b78ef..e65e38a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ pom.xml.asc /checkouts/ /resources/public/content/.git /resources/public/vendor +/bower_components/ .lein-deps-sum .lein-repl-history .lein-plugins/ @@ -19,3 +20,5 @@ pom.xml.asc .DS_Store *-init.clj profiles\.clj +.bowerrc +bower.json \ No newline at end of file diff --git a/README.md b/README.md index a5f923c..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,55 @@ 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 + +1. Clone the project from [my repository](https://github.com/simon-brooke/youyesyet); +2. Visit the [kanban board](https://github.com/simon-brooke/youyesyet#boards?repos=70809242) and choose an issue you'd like to work on; +3. Assign that issue to yourself and move it into the 'in progress' column; +4. Use gitflow: start a new feature; +5. Work on the issue; +6. When you are satisfied with your work, commit it to your repository in your feature branch; +7. Submit a pull request and move the issue to the 'QA/Review' column on the kanban board; +8. If your work works, I'll integrate it into the develop branch, which you can then pull; +9. Rinse and repeat! + +Obviously I'd appreciate it if you'd [mail me](mailto:simon@journeyman.cc) to introduce yourself, but there's no need to do so. + +Finally, if you take a ticket and are not able to finish it, please + +1. mail me to say so; +2. move the issue back to the 'backlog' column on the kanban board. + +And *many thanks*! + +## More about tooling + +Note that all tools recommended in this document are free for non-commercial use; not all are open source. + +### Editors/IDEs + +I (Simon) use, like and recommend [LightTable](http://lighttable.com/) as my editor; I used to use Emacs, and there is excellent Clojure tooling for Emacs, but these days Emacs ways of working seem just too far from everything else to be comfortable to me. [NightCode](https://sekao.net/nightcode/) is a lighter-weight Clojure IDE which you may like. There's also [Cursive](https://cursive-ide.com/) but it isn't free and I haven't tried it. + +### Git + +I'm currently using [GitKraken](https://www.gitkraken.com/), on Linux, Windows, and Mac. Like so many things it works best on Linux, but it's good on both other platforms. However, I confess that although I like the ability to easily view the whole structure of the git repository, I tend to drop back to the command line if I'm doing anything tricky with Git. + +### Database + +The database is [Postgres](https://www.postgresql.org/). I have no plans even to support anything else; I don't feel we have time to mess about. However, if someone wants to fork the code and implement a different database, that's up to them. I'm afraid I do all database stuff from the command line; [there are graphical tools for Postgres](https://wiki.postgresql.org/wiki/Community_Guide_to_PostgreSQL_GUI_Tools) but personally I've never found graphical tools for databases much use. + +## Further Reading + +If you're thinking of joining in development on this I'd strongly recommend you get hold of a copy of [Dmitry Sotnikov](https://github.com/yogthos)'s [Web Development with Clojure, Second Edition](https://pragprog.com/book/dswdcloj2/web-development-with-clojure-second-edition). + +You should also read the [User-Oriented Specification](doc/specification/userspec.md) and any other documentation which appears under the *doc/specification* hierarchy. + ## License Copyright © 2016 Simon Brooke for the Radical Independence Campaign. diff --git a/doc/specification/competitors.md b/doc/specification/competitors.md new file mode 100644 index 0000000..8fa7dd0 --- /dev/null +++ b/doc/specification/competitors.md @@ -0,0 +1,23 @@ +# Competitor Analysis + +Obviously **You Yes Yet?** is my baby; I've put a lot of thought into it. At the time I started working on it I wasn't aware of any open source competitors; I did to a web search, and I emailed the Bernie Sanders campaign to see whether their widely admired tools were open source. I didn't find anything. + +However, I've just been pointed to [Vote Leave](http://www.voteleavetakecontrol.org/)'s [Vics](https://dominiccummings.wordpress.com/2016/10/29/on-the-referendum-20-the-campaign-physics-and-data-science-vote-leaves-voter-intention-collection-system-vics-now-available-for-all/) tool, and there may well be others. + +There is no room here for ego. What matters is that the Yes campaign gets the best available tool for the job. So it's important to do competitor analysis, and not to invest too much work into **You Yes Yet?** unless there's a realistic possibility of producing a tool which is better than any of the available alternatives. But it's also the case that by studying competitors we may find ways to improve the design of **You Yes Yet?**. + +## Vics + +Vics, the **Voter Intention Collection System**, is reputed to have been a significant factor in the successful campaign by Vote Leave to take Britain out of the EU. It has been [released](https://github.com/celestial-winter/vics) as open source under MIT licence, so it is unambiguously available for us to use. + +The architecture comprises a single-page app built using Angular talking to a server built in Java using the Spring framework. The database engine used is Postgres. [Jedis](https://github.com/xetorthio/jedis), a Java port of [Redis](https://redis.io/), is used as an in-memory data cache, server side. + +### Download and initial build + +I checked out the source from the GitHub repository, and following the instructions in the README created the database and ran a maven install process. Unfortunately, run as a normal user, when this process goes into its test sequence many tests fail unable to contact Jedis. I find it slightly worrying to run such a large and complex build as *root*, but as *root* it gets substantially further. The build still doesn't complete but it seems that it is closer to completion. + +The ironic point is that it fails because it depends on the JavaScript package manager [bower](https://bower.io/), and bower (very sensibly) refuses to run as *root*. I therefore made a small modification to the build script to allow it to run *bower* as root, but unfortunately that didn't solve the build problem; the jedis service was still not found where it was expected. + +This is difficult to diagnose; the exception is so deeply nested in framework code that no code from the actual Vics application appears on the stack dump, which makes it very hard to know where to start in debugging. + +So for tonight I've failed. I shall try again. diff --git a/doc/specification/scaling.md b/doc/specification/scaling.md index 5a8de4d..bfc16de 100644 --- a/doc/specification/scaling.md +++ b/doc/specification/scaling.md @@ -42,6 +42,22 @@ This means that the maximum number of transactions per second across Scotland is ## Spreading the load +### Caching and memoizing + +People typically go out canvassing in teams; each member of the team will need the same elector data. + +Glasgow has a population density of about 3,260 per Km^2; that means each half kilometer square has a maximum population of not much more than 1,000. Downloading 1,000 elector records at startup time is not infeasible. If we normalise data requests to a 100 metre square grid and serve records in 500 metre square chunks, all the members of the same team will request the same chunk of data. Also, elector data is not volatile. Therefore it makes sense to memoize requests for elector data. The app should only request fresh elector data when the device moves within 100 metres of the edge of the current 500 metre cell. + +Intention data is volatile: we'll want to update canvassers with fresh intention data frequently, because the other members of their team will be recording intention data as they work, and it's by seeing that intention data that the canvassers know which doors are still unchapped. So we don't want to cache intention data for very long. But nevertheless it still makes sense to deliver it in normalised 500 metre square chunks, because that means we can temporarily cache it server side and do not actually have to hit the database with many requests for the same data. + +Finally, issue data is not volatile over the course of a canvassing session, although it may change over a period of days. So issue data - all the current issues - should be fetched once at app startup time, and not periodically refreshed during a canvassing session. Also, of course, every canvasser will require exactly the same issue data (unless we start thinking of local or regional issues...?), so it absolutely makes sense to memoise requests for issue data. + +All this normalisation and memoisation reduces the number of read requests on the database. + +Note that [clojure.core.memoize](https://github.com/clojure/core.memoize) provides us with functions to create both size-limited, least-recently-used caches and duration limited, time-to-live caches. + +At 56 degrees north there are 111,341 metres per degree of latitude, 62,392 metres per degree of longitude. So a 100 metre box is about 0.0016 degrees east-west and .0009 degrees north-south. If we simplify that slightly (and we don't need square boxes, we need units of area covering a group of people working together) then we can take .001 of a degree in either direction which is computationally cheap. + ### Geographic sharding Volunteers canvassing simultaneously in the same street or the same locality need to see in near real time which dwellings have been canvassed by other volunteers, otherwise we'll get the same households canvassed repeatedly, which wastes volunteer time and annoys voters. So they all need to be sending updates to, and receiving updates from, the same server. But volunteers canvassing in Aberdeen don't need to see in near real time what is happening in Edinburgh. @@ -56,6 +72,28 @@ The geographic sharding strategy is scalable. We could start with a single serve But having considerable numbers of database servers will have cost implications. +### Geographic sharding by DNS + +When I first thought of geographic sharding, I intended sharding by electoral district, but actually that makes no sense because electoral districts are complex polygons, which makes point-within-polygon computationally expensive. 4 degrees west falls just west of Stirling, and divides the country in half north-south. 56 degrees north runs north of Edinburgh and Glasgow, but just south of Falkirk. It divides the country in half east to west. Very few towns (and no large towns) straddle either line. Thus we can divide Scotland neatly into four, and it is computationally extremely cheap to compute which shard each data item should be despatched to. + +We can then set up in DNS four addresses: + + +----------------------+-----------+-----------+ + | Address | longitude | latitude | + +----------------------+-----------+-----------+ + | nw.data.yyy.scot | < -4 | >= 56 | + +----------------------+-----------+-----------+ + | ne.data.yyy.scot | >= -4 | >= 56 | + +----------------------+-----------+-----------+ + | sw.data.yyy.scot | < -4 | < 56 | + +----------------------+-----------+-----------+ + | se.data.yyy.scot | >= -4 | < 56 | + +----------------------+-----------+-----------+ + +giving us an incredibly simple dispatch table. Furthermore, initially all four addresses can point to the same server. This is an incredibly simple scheme, and I'm confident it's good enough. + +Data that's inserted from the canvassing app - that is to say, voter intention data and followup request data - should have an additional field 'shard' (char(2)) which should hold the digraph representing the shard to which it was dispatched, and that field should form part of the primary key, allowing the data from all servers to be integrated. Data that isn't from the canvassing app should probably be directed to the 'nw' shard (which will be lightest loaded), or to a separate master server, and then all servers should be synced overnight. + ### Read servers and write servers It's a common practice in architecting busy web systems to have one master database server to which all write operations are directed, surrounded by a ring of slave databases which replicate from the master and serve all read requests. This works because for the majority of web systems there are many more reads than writes. @@ -76,15 +114,13 @@ From the above I think the scaling problem should be addressed as follows: 4. When the initial cluster of three database servers becomes overloaded, shard into two identical groups ('east' and 'west'); 5. When any shard becomes overloaded, split it into two further shards. -If we have prepared for sharding, all that is required is to duplicate the database and then set geographic polygons to address database requests from clients within a given polygon to the database server for that polygon. - -This means that essentially we should set up one polygon for each electoral district from the launch of the app, but initially requests from all of these polygons should be directed to the same database server group. As shards are added, the map of polygons to database server groups should be updated. +If we have prepared for sharding, all that is required is to duplicate the database. Obviously, once we have split the database into multiple shards, there is a task to integrate the data from the multiple shards in order to create an 'across Scotland' overview of the canvas data; however, again if we have prepared for it in advance, merging the databases should not be difficult, and can be done either in the wee sma' oors or alternatively during the working day, as the system will be relatively lighty loaded during these periods. ## Preparing for sharding -We should prepare a Docker image for the app server and a Docker image for the database server. We should prepare, as part of the app (i.e. not in the database but as a Clojure or Json data file) a datastructure which maps polygons representing each of Scotland's electoral districts to database URLs. For security reasons this datastructure should live server-side and should not be part of the single-page app sent to the client. +We should prepare a Docker image for the app server and an image or setup script for the database server. ## Further reading on optimising Postgres performance diff --git a/doc/specification/userspec.md b/doc/specification/userspec.md index 9c31b39..3f494b1 100644 --- a/doc/specification/userspec.md +++ b/doc/specification/userspec.md @@ -24,7 +24,7 @@ It's my hope that we'd have enough Issue Experts sitting in the comfort of their ### Analysts -*Canvassers* are able to see voter canvas for the streets immediately around where they are working. They must be able to do so, because otherwise they cannot effectively canvas. But we must assume that sooner or later a hostile person will join the system as a canvasser, so canvassers should not have access to wider canvas data. Therefore we need a category of trusted user who can view wider maps of where canvas data has been collected, in order to be able to direct teams of canvassers to areas where it has not been collected. For want of a better word I'll call these *Analysts*. +*Canvassers* are able to see voter canvas data for the streets immediately around where they are working. They must be able to do so, because otherwise they cannot effectively canvas. But we must assume that sooner or later a hostile person will join the system as a canvasser, so canvassers should not have access to wider canvas data. Therefore we need a category of trusted user who can view wider maps of where canvas data has been collected, in order to be able to direct teams of canvassers to areas where it has not been collected. For want of a better word I'll call these *Analysts*. ### Administrators @@ -88,12 +88,14 @@ The *Electors View* shows a schematic of the registered electors in a dwelling: ![Electors View](https://raw.githubusercontent.com/simon-brooke/youyesyet/master/dummies/occupants_800.png) -One figure is shown for each elector, labelled with their name. Below the figure are: +One figure is shown for each elector, labelled with their name. In the dummy pages I've shown gendered stick figures, because I believe that in many casesthis will help the canvasser identify the person who has answered the door; but this may be seen as excluding electors with non-binary gender, and, in any case, I believe we don't actually get gender data (other than salutation) in the electoral roll data. So this may have to be reconsidered. + +Below the figure are: 1. One clear 'voting intention' button for each option (e.g., 'Yes', 'No'), greyed unless selected; 2. One issues button. -Selecting an option icon records that the elector represented by the figure has expressed an opinion that they will vote for that option. Selecting the issues icon brings up and issues view. +Selecting an option icon records that the elector represented by the figure has expressed an intention that they will vote for that option. Selecting the issues icon brings up and issues view. ## Issues View diff --git a/env/dev/clj/user.clj b/env/dev/clj/user.clj index a8adafd..97cb362 100644 --- a/env/dev/clj/user.clj +++ b/env/dev/clj/user.clj @@ -13,4 +13,23 @@ (stop) (start)) +;; Roughly working under Tomcat. +;; Database setup using JNDI: see http://www.luminusweb.net/docs/deployment.md#deploying_to_tomcat +;; Note that this duplicates configuration in profiles.clj; one of these is wrong (and neither +;; should be in the Git repository but this is for now!) +(System/setProperty "java.naming.factory.initial" + "org.apache.naming.java.javaURLContextFactory") +(System/setProperty "java.naming.factory.url.pkgs" + "org.apache.naming") +(doto (new javax.naming.InitialContext) + (.createSubcontext "java:") + (.createSubcontext "java:comp") + (.createSubcontext "java:comp/env") + (.createSubcontext "java:comp/env/jdbc") + (.bind "java:comp/env/jdbc/testdb" + (doto (org.postgresql.ds.PGSimpleDataSource.) + (.setServerName "localhost") + (.setDatabaseName "youyesyet_dev") + (.setUser "youyesyet") + (.setPassword "thisisnotsecure")))) 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/src/cljs/youyesyet/views/followup_request.cljs b/followup.cljs similarity index 51% rename from src/cljs/youyesyet/views/followup_request.cljs rename to followup.cljs index 0ba9455..836d622 100644 --- a/src/cljs/youyesyet/views/followup_request.cljs +++ b/followup.cljs @@ -1,9 +1,16 @@ -(ns youyesyet.views.followup-request - (:require [re-frame.core :refer [reg-sub]])) +(ns youyesyet.views.followup + (:require [reagent.core :as r] + [re-frame.core :refer [reg-sub subscribe]] +;; [re-frame-forms.core :as form] +;; [re-frame-forms.input :as input] +;; [re-com.core :refer [h-box v-box box gap single-dropdown input-text checkbox label title hyperlink-href p]] +;; [re-com.dropdown :refer [filter-choices-by-keyword single-dropdown-args-desc]] + [youyesyet.ui-utils :as ui] +)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; -;;;; youyesyet.views.followup-request: followup-request view for youyesyet. +;;;; youyesyet.views.followup: followup-request 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 @@ -35,4 +42,31 @@ (defn panel "Generate the followup-request panel." [] - []) + (js/console.log (str "Rendering follow-up form")) + + (let [issue @(subscribe [:issue]) + issues @(subscribe [:issues]) + elector @(subscribe [:elector]) + address @(subscribe [:address]) + form (form/make-form {:elector (:id elector) + :issue (:id issue)})] + [:div + [:h1 "Followup Request"] + (let [selected-elector-id (r/atom (:id elector)) + selected-issue (r/atom (:id issue))] + [:form {} + [:p.widget + [:label {:for "elector"} "Elector"] + [single-dropdown + :id elector + :choices (:electors address) + :model selected-elector-id + :label-fn #(:name %)]] + [:p.widget + [:label {:for "issue"} "Issue"] + [single-dropdown + :id issue + :choices (map #({:id % :label %}) (keys issues)) + :model issue]] + + ])])) diff --git a/project.clj b/project.clj index 8130d1e..6108563 100644 --- a/project.clj +++ b/project.clj @@ -3,51 +3,50 @@ :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"] - [clj-oauth "1.5.5"] + :dependencies [[bouncer "1.0.1"] [ch.qos.logback/logback-classic "1.2.2"] - [re-frame "0.9.2"] + [clj-oauth "1.5.5"] + [cljsjs/react-leaflet "0.12.3-4"] [cljs-ajax "0.5.8"] - [secretary "1.2.3"] - [reagent-utils "0.2.1"] - [reagent "0.6.1"] + [compojure "1.5.2"] + [conman "0.6.3"] + [cprop "0.1.10"] [korma "0.4.3"] - [selmer "1.10.6"] + [lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]] + [luminus/ring-ttl-session "0.3.1"] + [luminus-nrepl "0.1.4"] + [luminus-migrations "0.3.0"] [markdown-clj "0.9.98"] - [ring-middleware-format "0.7.2"] + [metosin/compojure-api "1.1.10"] [metosin/ring-http-response "0.8.2"] - [bouncer "1.0.1"] + [migratus "0.8.33"] + [mount "0.1.11"] + [org.clojure/clojure "1.8.0"] + [org.clojure/clojurescript "1.9.229" :scope "provided"] + [org.clojure/tools.cli "0.3.5"] + [org.clojure/tools.logging "0.3.1"] + [org.postgresql/postgresql "9.4.1212"] [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"] + [re-frame "0.9.2"] + [reagent "0.6.1"] + [reagent-utils "0.2.1"] + [ring-middleware-format "0.7.2"] [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"] - ] + [ring/ring-servlet "1.5.1"] + [ring-webjars "0.1.1"] + [secretary "1.2.3"] + [selmer "1.10.6"]] :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 youyesyet.core + :main ^:skip-aot youyesyet.core :migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")} :plugins [[lein-cprop "1.0.1"] @@ -58,17 +57,10 @@ [lein-bower "0.5.1"] [lein-less "1.7.5"]] - :bower-dependencies [ - ;; Problem with using boostrap and font-awsome from Bower: neither - ;; of the distributed packages compile cleanly with less :-( - ;; [bootstrap "2.3.1"] - ;; [font-awesome "3.2.1"] - [leaflet "0.7.3"]] + :bower-dependencies [[leaflet "0.7.3"]] :cucumber-feature-paths ["test/clj/features"] - :hooks [leiningen.less] - :uberwar {:handler youyesyet.handler/app :init youyesyet.handler/init @@ -84,7 +76,6 @@ :css-dirs ["resources/public/css"] :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} - :externs ["externs.js"] :profiles {:uberjar {:omit-source true @@ -95,11 +86,11 @@ {:source-paths ["src/cljc" "src/cljs" "env/prod/cljs"] :compiler {:output-to "target/cljsbuild/public/js/app.js" - :externs ["react/externs/react.js" "externs.js"] :optimizations :advanced :pretty-print false :closure-warnings - {:externs-validation :off :non-standard-jsdoc :off}}}}} + {:externs-validation :off :non-standard-jsdoc :off} + :externs ["react/externs/react.js"]}}}} :aot :all @@ -113,21 +104,18 @@ :project/dev {:dependencies [[prone "1.1.4"] [ring/ring-mock "0.3.0"] [ring/ring-devel "1.5.1"] - [luminus-jetty "0.1.4"] + [org.webjars/webjars-locator-jboss-vfs "0.1.0"] + [luminus-immutant "0.2.3"] [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"] - [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"] + [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.229"]] + [org.clojure/clojurescript "1.9.495"]] :cljsbuild {:builds {:app @@ -144,12 +132,12 @@ :doo {:build "test"} - :source-paths ["env/dev/clj" "test/clj"] + :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/dev/resources" "env/test/resources"] + :project/test {:resource-paths ["env/test/resources"] :cljsbuild {:builds {:test diff --git a/resources/docs/docs.md b/resources/docs/docs.md deleted file mode 100644 index 5930be1..0000000 --- a/resources/docs/docs.md +++ /dev/null @@ -1,35 +0,0 @@ -
- -### Database Configuration is Required - -If you haven't already, then please follow the steps below to configure your database connection and run the necessary migrations. - -* Create the database for your application. -* Update the connection URL in the `profiles.clj` file with your database name and login. -* Run `lein run migrate` in the root of the project to create the tables. -* Let `mount` know to start the database connection by `require`-ing youyesyet.db.core in some other namespace. -* Restart the application. - -
- - -### Managing Your Middleware - -Request middleware functions are located under the `youyesyet.middleware` namespace. - -This namespace is reserved for any custom middleware for the application. Some default middleware is -already defined here. The middleware is assembled in the `wrap-base` function. - -Middleware used for development is placed in the `youyesyet.dev-middleware` namespace found in -the `env/dev/clj/` source path. - -### Here are some links to get started - -1. [HTML templating](http://www.luminusweb.net/docs/html_templating.md) -2. [Accessing the database](http://www.luminusweb.net/docs/database.md) -3. [Setting response types](http://www.luminusweb.net/docs/responses.md) -4. [Defining routes](http://www.luminusweb.net/docs/routes.md) -5. [Adding middleware](http://www.luminusweb.net/docs/middleware.md) -6. [Sessions and cookies](http://www.luminusweb.net/docs/sessions_cookies.md) -7. [Security](http://www.luminusweb.net/docs/security.md) -8. [Deploying the application](http://www.luminusweb.net/docs/deployment.md) diff --git a/resources/public/about.html b/resources/public/about.html deleted file mode 100644 index 370071f..0000000 --- a/resources/public/about.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - About YouYesYet - - -
- - -

- About YouYesYet -

-
-
- -

- This isn't finished and doesn't work yet! This site is just a look-and-feel - dummy. -

-

- YouYesYet is a project to build a canvassing app for the new Scottish - Independence Referendum. The source code is here. The specification - is here. -

-

- If we're going to get this working in time I cannot do it alone: I need help. Contact - me by email or on on Twitter. -

-
- - - - diff --git a/resources/public/blurb.html b/resources/public/blurb.html deleted file mode 100644 index 53ee2e9..0000000 --- a/resources/public/blurb.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - Issues: Currency - - -
- - -

- Issues: Currency -

-
-
- -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer tincidunt - at ex id pretium. Proin nec ultricies mauris. Donec mattis, velit at commodo - vehicula, nisi velit mattis justo, at tempor enim eros eget tortor. Quisque - a porttitor lorem. Vestibulum tempus ex id sem laoreet, id fermentum enim - pharetra. Cras diam ante, pulvinar sed pharetra sed, venenatis eget tellus. - Quisque fermentum sem sed nulla mollis, et fermentum nisl pretium. Pellentesque - porttitor interdum ultricies. Nunc ut accumsan leo, rutrum tempor tellus. - Nam ultricies magna ipsum. -

-

- Pellentesque in est rutrum, consectetur nisi vel, dictum felis. Quisque id - elementum enim. Donec aliquet, massa id mattis semper, lectus elit scelerisque - justo, quis dapibus tortor eros a erat. Vestibulum erat mauris, consectetur id - condimentum ut, luctus vitae diam. Integer faucibus ultrices mi sed consequat. - Aliquam lacinia sapien quis urna blandit, sed consectetur ligula gravida. Ut - eleifend purus id mi vulputate faucibus ut quis risus. Donec dapibus finibus - tincidunt. Nunc luctus libero tellus, eget porta diam lacinia vel. Pellentesque - turpis nunc, venenatis vitae nisl eu, mollis pulvinar erat. Nulla scelerisque - tellus eget ex hendrerit tincidunt. -

-

- Duis tincidunt iaculis magna, ac rutrum velit congue quis. Maecenas feugiat - efficitur sem, in hendrerit erat. Nunc congue, dui sit amet commodo faucibus, - enim nisl feugiat nisl, a tincidunt massa metus nec nisi. Duis viverra nunc ut - libero tempus, sed convallis elit dapibus. Sed venenatis condimentum odio, non - elementum diam. Morbi fermentum metus justo, ac viverra dui fermentum at. - Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere - cubilia Curae; Aliquam erat volutpat. -

-
- - - - diff --git a/resources/public/call-me.html b/resources/public/call-me.html deleted file mode 100644 index 71b5ca9..0000000 --- a/resources/public/call-me.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - Please call me! - - -
- - -

- Please call me! -

-
-
-

- Use this form to request someone to phone you to discuss your concerns about independence. -

-
-

- - -

-

- - -

-

- - -

-

- - -

-

- - -

-
-
- - - diff --git a/resources/public/css/screen.css b/resources/public/css/screen.css deleted file mode 100644 index 534309c..0000000 --- a/resources/public/css/screen.css +++ /dev/null @@ -1,68 +0,0 @@ -html, -body { - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - height: 100%; -} -.navbar { - margin-bottom: 10px; -} -.navbar-brand { - float: none; -} -.navbar-nav .nav-item { - float: none; -} -.navbar-divider, -.navbar-nav .nav-item+.nav-item, -.navbar-nav .nav-link + .nav-link { - margin-left: 0; -} -@media (min-width: 34em) { - .navbar-brand { - float: left; - } - .navbar-nav .nav-item { - float: left; - } - .navbar-divider, - .navbar-nav .nav-item+.nav-item, - .navbar-nav .nav-link + .nav-link { - margin-left: 1rem; - } -} - -@-moz-keyframes three-quarters-loader { - 0% { - -moz-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@-webkit-keyframes three-quarters-loader { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@keyframes three-quarters-loader { - 0% { - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} - diff --git a/resources/public/css/yyy-app.css b/resources/public/css/yyy-app.css new file mode 100644 index 0000000..d7ee8fa --- /dev/null +++ b/resources/public/css/yyy-app.css @@ -0,0 +1,182 @@ +/** + * Additional CSS for the main site. Navigation works differently between + * app and the main site; in the app it's driven by React, while in + * the main site it's CSS. + * + * 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. + */ + +h1 { + width:100%; + background-color: rgb(7, 57, 106); + color: white; + font-family: "Archivo Black", "Sans Bold", "Arial Black", sans-serif; + font-weight: normal; + margin-top: 0; +} + +/* desktops and laptops, primarily. Adapted to mouse; targets may be small */ +@media all and (min-device-width: 1025px) { + + /* top-of-page navigation, not editable, provided by Smeagol */ + #nav{ + margin: 0; + padding: 0; + top: 0; + width: 100%; + _position: absolute; + _top: expression(document.documentElement.scrollTop); + z-index: 149; + background:rgba(7,27,51,0.8); + } + + /* only needed for fly-out menu effect on tablet and phone stylesheets */ + #nav-icon { + display: none; + } + + #nav-menu { + margin: 0; + padding: 0; + } + + #nav menu li { + padding: 0; + margin: 0; + display: inline; + } + + #nav menu li a { + color: white; + text-decoration: none; + font-weight: bold; + padding: 0.1em 0.75em; + margin: 0; + } + + #nav menu li.active a { background: gray;} + li.nav-item a:hover { background: rgb( 240, 240, 240) } + li.nav-item a:active { background: gray; color: white; } + + #nav menu li#user { + padding: 0 1em; + float: right; + } +} + +/* tablets, primarily. Adapted to touch; targets are larger */ +@media all and (min-device-width: 769px) and (max-device-width: 1024px) { + #nav{ + margin: 0; + padding: 0; + position: fixed; + z-index: 149; + color: silver; + background:rgba(40,40,40,0.9); + } + + #nav a { + color: white; + text-decoration: none; + font-weight: bold; + } + + #nav:hover #nav-menu { + display: block; + list-style-type: none; + width: 100%; + } + + #nav-icon { + padding: 0; + } + + #nav-menu { + display: none; + } + + #nav menu li { + padding: 0.5em 2em 0.5em 0.5em; + margin: 0.5 em; + font-size: 150%; + } + + #nav menu li a { + } + + #nav ul li.active a { background: silver;} + li.nav-item a:hover { background: rgb( 240, 240, 240) } + li.nav-item a:active { background: gray; color: white; } + + #nav menu #user { + text-decoration: none; + font-weight: bold; + margin: 0; + } +} + +/* phones, and, indeed, smaller phones. Adapted to touch; display radically + * decluttered */ +@media all and (max-device-width: 768px) { + #nav{ + margin: 0; + padding: 0; + position: fixed; + z-index: 149; + color: silver; + background:rgba(40,40,40,0.9); + } + + #nav:hover #nav-menu { + display: block; + list-style-type: none; + width: 100%; + } + + #nav a { + color: white; + text-decoration: none; + font-weight: bold; + } + + #nav-icon { + padding: 0; + } + + #nav-menu { + list-style-type: none; + display: none; + } + + #nav menu li { + padding: 0.5em 2em 0.5em 0.5em; + margin: 0.5 em; + font-size: 150%; + } + + #nav menu li a { + } + + #nav ul li.active a { background: silver;} + li.nav-item a:hover { background: rgb( 240, 240, 240) } + li.nav-item a:active { background: gray; color: white; } + + #nav menu #user { + text-decoration: none; + font-weight: bold; + margin: 0; + } +} diff --git a/resources/public/css/yyy-static.css b/resources/public/css/yyy-common.css similarity index 70% rename from resources/public/css/yyy-static.css rename to resources/public/css/yyy-common.css index 515088f..074e9bd 100644 --- a/resources/public/css/yyy-static.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 { @@ -141,7 +137,6 @@ select { } table { - border: 2px solid black; border-collapse: collapse; } @@ -201,11 +196,13 @@ th { } -/* left bar for all pages in the Wiki - editable, provided by users. Within main-container */ -#side-bar { - width: 17%; - height: 100%; - float: left; +#big-links { + width: 100%; + min-height: 3.5em; +} + +#content { + clear: both; } /* cookies information box, fixed, in right margin, just above footer */ @@ -337,62 +334,11 @@ th { /* desktops and laptops, primarily. Adapted to mouse; targets may be small */ @media all and (min-device-width: 1025px) { - /* content of the current page in the Wiki - editable, provided by users. Within main-container */ #content { - border: thin solid silver; width: 80%; float: right; padding-bottom: 5em; } - - #phone-side-bar, #phone-credits { - display: none; - } - - /* top-of-page navigation, not editable, provided by Smeagol */ - #nav{ - margin: 0; - padding: 0; - top: 0; - width: 100%; - _position: absolute; - _top: expression(document.documentElement.scrollTop); - z-index: 149; - background:rgba(7,27,51,0.8); - } - - /* only needed for fly-out menu effect on tablet and phone stylesheets */ - #nav-icon { - display: none; - } - - #nav-menu { - margin: 0; - padding: 0; - } - - #nav menu li { - padding: 0; - margin: 0; - display: inline; - } - - #nav menu li a { - color: white; - text-decoration: none; - font-weight: bold; - padding: 0.1em 0.75em; - margin: 0; - } - - #nav menu li.active a { background: gray;} - li.nav-item a:hover { background: rgb( 240, 240, 240) } - li.nav-item a:active { background: gray; color: white; } - - #nav menu li#user { - padding: 0 1em; - float: right; - } } /* tablets, primarily. Adapted to touch; targets are larger */ @@ -410,68 +356,23 @@ th { float: right; padding-bottom: 5em; } - - #nav{ - margin: 0; - padding: 0; - position: fixed; - z-index: 149; - color: silver; - background:rgba(40,40,40,0.9); - } - - #nav a { - color: white; - text-decoration: none; - font-weight: bold; - } - - #nav:hover #nav-menu { - display: block; - list-style-type: none; - width: 100%; - } - - #nav-icon { - padding: 0; - } - - #nav-menu, #phone-side-bar { - display: none; - } - - #nav menu li { - padding: 0.5em 2em 0.5em 0.5em; - margin: 0.5 em; - font-size: 150%; - } - - #nav menu li a { - } - - #nav ul li.active a { background: silver;} - li.nav-item a:hover { background: rgb( 240, 240, 240) } - li.nav-item a:active { background: gray; color: white; } - - #nav menu #user { - text-decoration: none; - font-weight: bold; - margin: 0; - } } /* 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 { /* I wouldn't normally use a px value, but the menu icon is 49px wide */ padding: 0.25em 5%; - padding-left: 100px; - text-align: right; + padding-left: 75px; } .hidden { @@ -499,50 +400,4 @@ th { #cookies { display: none; } - - #nav{ - margin: 0; - padding: 0; - position: fixed; - z-index: 149; - color: silver; - background:rgba(40,40,40,0.9); - } - - #nav a { - color: white; - text-decoration: none; - font-weight: bold; - } - - #nav-icon { - padding: 0; - } - - #nav-menu { - list-style-type: none; - } - - #nav menu li { - padding: 0.5em 2em 0.5em 0.5em; - margin: 0.5 em; - font-size: 150%; - } - - #nav menu li a { - } - - #nav ul li.active a { background: silver;} - li.nav-item a:hover { background: rgb( 240, 240, 240) } - li.nav-item a:active { background: gray; color: white; } - - #nav menu #user { - text-decoration: none; - font-weight: bold; - margin: 0; - } - - #side-bar { - display: none; - } } diff --git a/resources/public/css/yyy-site.css b/resources/public/css/yyy-site.css new file mode 100644 index 0000000..5387c7a --- /dev/null +++ b/resources/public/css/yyy-site.css @@ -0,0 +1,181 @@ +/** + * Additional CSS for the app. Navigation works differently between + * app and the main site; in the app it's driven by React, while in + * the main site it's CSS. + * + * 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. + * + * # The Stylesheet + * + * ## html elements generally in alphabetic order + */ + + +/* desktops and laptops, primarily. Adapted to mouse; targets may be small */ +@media all and (min-device-width: 1025px) { + /* top-of-page navigation, not editable, provided by Smeagol */ + #nav{ + margin: 0; + padding: 0; + top: 0; + width: 100%; + _position: absolute; + _top: expression(document.documentElement.scrollTop); + z-index: 149; + background:rgba(7,27,51,0.8); + } + + /* only needed for fly-out menu effect on tablet and phone stylesheets */ + #nav-icon { + display: none; + } + + #nav-menu { + margin: 0; + padding: 0; + } + + #nav menu li { + padding: 0; + margin: 0; + display: inline; + } + + #nav menu li a { + color: white; + text-decoration: none; + font-weight: bold; + padding: 0.1em 0.75em; + margin: 0; + } + + #nav menu li.active a { background: gray;} + li.nav-item a:hover { background: rgb( 240, 240, 240) } + li.nav-item a:active { background: gray; color: white; } + + #nav menu li#user { + padding: 0 1em; + float: right; + } +} + +/* tablets, primarily. Adapted to touch; targets are larger */ +@media all and (min-device-width: 769px) and (max-device-width: 1024px) { + #nav{ + margin: 0; + padding: 0; + position: fixed; + z-index: 149; + color: silver; + background:rgba(40,40,40,0.9); + } + + #nav a { + color: white; + text-decoration: none; + font-weight: bold; + } + + #nav:hover #nav-menu { + display: block; + list-style-type: none; + width: 100%; + } + + #nav-icon { + padding: 0; + } + + #nav-menu, #phone-side-bar { + display: none; + } + + #nav menu li { + padding: 0.5em 2em 0.5em 0.5em; + margin: 0.5 em; + font-size: 150%; + } + + #nav menu li a { + } + + #nav ul li.active a { background: silver;} + li.nav-item a:hover { background: rgb( 240, 240, 240) } + li.nav-item a:active { background: gray; color: white; } + + #nav menu #user { + text-decoration: none; + font-weight: bold; + margin: 0; + } +} + +/* phones, and, indeed, smaller phones. Adapted to touch; display radically + * decluttered */ +@media all and (max-device-width: 768px) { + footer { + display: none; + } + + #nav{ + margin: 0; + padding: 0; + position: fixed; + z-index: 149; + color: silver; + background:rgba(40,40,40,0.9); + } + + #nav a { + color: white; + text-decoration: none; + font-weight: bold; + } + + #nav:hover #nav-menu { + display: block; + list-style-type: none; + width: 100%; + } + + #nav-icon { + padding: 0; + } + + #nav-menu { + list-style-type: none; + display: none; + } + + #nav menu li { + padding: 0.5em 2em 0.5em 0.5em; + margin: 0.5 em; + font-size: 150%; + } + + #nav menu li a { + } + + #nav ul li.active a { background: silver;} + li.nav-item a:hover { background: rgb( 240, 240, 240) } + li.nav-item a:active { background: gray; color: white; } + + #nav menu #user { + text-decoration: none; + font-weight: bold; + margin: 0; + } +} diff --git a/resources/public/electorsview.html b/resources/public/electorsview.html deleted file mode 100644 index 793ee92..0000000 --- a/resources/public/electorsview.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - Sign up! - - -
- -

- 43 Imaginary Terrace -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Jock TamsonAnne TamsonMary Tamson
-
- - - diff --git a/resources/public/facebook-login.html b/resources/public/facebook-login.html deleted file mode 100644 index 15fe226..0000000 --- a/resources/public/facebook-login.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - Login with Facebook - - -
- - -

- Login with Facebook -

-
-
-

- The actual login page is provided by the 0-auth provider chosen by the user. We don't - handle login ourselves, and we don't store any passwords. -

- - -
- - - 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/resources/public/favicon.xcf b/resources/public/favicon.xcf new file mode 100644 index 0000000..643b2f0 Binary files /dev/null and b/resources/public/favicon.xcf differ diff --git a/resources/public/google-login.html b/resources/public/google-login.html deleted file mode 100644 index 80422de..0000000 --- a/resources/public/google-login.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - Login with Google - - -
- - -

- Login with Google -

-
-
-

- The actual login page is provided by the 0-auth provider chosen by the user. We don't - handle login ourselves, and we don't store any passwords. -

- - -
- - - diff --git a/resources/public/img/basic_button.svg b/resources/public/img/basic_button.svg new file mode 100644 index 0000000..0a4944b --- /dev/null +++ b/resources/public/img/basic_button.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + ? + + 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/credits/luminus-logo.png b/resources/public/img/credits/luminus-logo.png new file mode 100644 index 0000000..df6c283 Binary files /dev/null and b/resources/public/img/credits/luminus-logo.png differ 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/female.png b/resources/public/img/female.png deleted file mode 100644 index 567820d..0000000 Binary files a/resources/public/img/female.png and /dev/null differ diff --git a/resources/public/img/female.xcf b/resources/public/img/female.xcf deleted file mode 100644 index a11a6eb..0000000 Binary files a/resources/public/img/female.xcf and /dev/null differ diff --git a/resources/public/img/gender/female.png b/resources/public/img/gender/female.png new file mode 100644 index 0000000..9a407ff Binary files /dev/null and b/resources/public/img/gender/female.png differ diff --git a/resources/public/img/gender/female.xcf b/resources/public/img/gender/female.xcf new file mode 100644 index 0000000..2163917 Binary files /dev/null and b/resources/public/img/gender/female.xcf differ diff --git a/resources/public/img/gender/fluid.png b/resources/public/img/gender/fluid.png new file mode 100644 index 0000000..e756611 Binary files /dev/null and b/resources/public/img/gender/fluid.png differ diff --git a/resources/public/img/gender/fluid.xcf b/resources/public/img/gender/fluid.xcf new file mode 100644 index 0000000..c26a343 Binary files /dev/null and b/resources/public/img/gender/fluid.xcf differ diff --git a/resources/public/img/gender/male.png b/resources/public/img/gender/male.png new file mode 100644 index 0000000..bfca00f Binary files /dev/null and b/resources/public/img/gender/male.png differ diff --git a/resources/public/img/gender/male.svg b/resources/public/img/gender/male.svg new file mode 100644 index 0000000..828df16 --- /dev/null +++ b/resources/public/img/gender/male.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/resources/public/img/gender/male.xcf b/resources/public/img/gender/male.xcf new file mode 100644 index 0000000..421fac7 Binary files /dev/null and b/resources/public/img/gender/male.xcf differ diff --git a/resources/public/img/gender/unknown.png b/resources/public/img/gender/unknown.png new file mode 100644 index 0000000..5b803d4 Binary files /dev/null and b/resources/public/img/gender/unknown.png differ diff --git a/resources/public/img/gender/unknown.xcf b/resources/public/img/gender/unknown.xcf new file mode 100644 index 0000000..91beda2 Binary files /dev/null and b/resources/public/img/gender/unknown.xcf differ diff --git a/resources/public/img/issues.png b/resources/public/img/issues.png new file mode 100644 index 0000000..50bf308 Binary files /dev/null and b/resources/public/img/issues.png differ diff --git a/resources/public/img/issues.svg b/resources/public/img/issues.svg new file mode 100644 index 0000000..496b704 --- /dev/null +++ b/resources/public/img/issues.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + ? + + + diff --git a/resources/public/img/male.png b/resources/public/img/male.png deleted file mode 100644 index 95bed88..0000000 Binary files a/resources/public/img/male.png and /dev/null differ diff --git a/resources/public/img/male.xcf b/resources/public/img/male.xcf deleted file mode 100644 index cf6954b..0000000 Binary files a/resources/public/img/male.xcf and /dev/null differ diff --git a/resources/public/img/map-pins/basic_map_pin.png b/resources/public/img/map-pins/basic_map_pin.png new file mode 100644 index 0000000..c14e0be Binary files /dev/null and b/resources/public/img/map-pins/basic_map_pin.png differ diff --git a/resources/public/img/map-pins/basic_map_pin.svg b/resources/public/img/map-pins/basic_map_pin.svg new file mode 100644 index 0000000..cd8409d --- /dev/null +++ b/resources/public/img/map-pins/basic_map_pin.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Basic pin for SceneHere + 20100801 + + + Simon Brooke + + + + + Copyright (c) 2010 Simon Brooke + + + basic_map_pin.svg + + Basic default map pin + $Revision: 1.2 $ + + + + + + + + + diff --git a/resources/public/img/map-pins/mixed-pin.png b/resources/public/img/map-pins/mixed-pin.png new file mode 100644 index 0000000..799321b Binary files /dev/null and b/resources/public/img/map-pins/mixed-pin.png differ diff --git a/resources/public/img/map-pins/mixed-pin.svg b/resources/public/img/map-pins/mixed-pin.svg new file mode 100644 index 0000000..8d293e0 --- /dev/null +++ b/resources/public/img/map-pins/mixed-pin.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + 20100801 + + + Simon Brooke + + + + + Copyright (c) 2010 Simon Brooke + + + basic_map_pin.svg + + Basic default map pin + $Revision: 1.2 $ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/public/img/map-pins/no-pin.png b/resources/public/img/map-pins/no-pin.png new file mode 100644 index 0000000..2e426ae Binary files /dev/null and b/resources/public/img/map-pins/no-pin.png differ diff --git a/resources/public/img/map-pins/no-pin.svg b/resources/public/img/map-pins/no-pin.svg new file mode 100644 index 0000000..f59dc4f --- /dev/null +++ b/resources/public/img/map-pins/no-pin.svg @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Basic pin for SceneHere + 20100801 + + + Simon Brooke + + + + + Copyright (c) 2010 Simon Brooke + + + basic_map_pin.svg + + Basic default map pin + $Revision: 1.2 $ + + + + + + + + + + + + + + + + + + + diff --git a/resources/public/img/map-pins/shadow_pin.png b/resources/public/img/map-pins/shadow_pin.png new file mode 100644 index 0000000..07c4daa Binary files /dev/null and b/resources/public/img/map-pins/shadow_pin.png differ diff --git a/resources/public/img/map-pins/shadow_pin.svg b/resources/public/img/map-pins/shadow_pin.svg new file mode 100644 index 0000000..595d104 --- /dev/null +++ b/resources/public/img/map-pins/shadow_pin.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Shadow pin for SceneHere + 20100801 + + + Simon Brooke + + + + + Copyright (c) 2010 Simon Brooke + + + shadow_pin.svg + $Revision: 1.2 $ + + + + + + + diff --git a/resources/public/img/map-pins/unknown-pin.png b/resources/public/img/map-pins/unknown-pin.png new file mode 100644 index 0000000..f0b8fbb Binary files /dev/null and b/resources/public/img/map-pins/unknown-pin.png differ diff --git a/resources/public/img/map-pins/unknown-pin.svg b/resources/public/img/map-pins/unknown-pin.svg new file mode 100644 index 0000000..638daa1 --- /dev/null +++ b/resources/public/img/map-pins/unknown-pin.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Basic pin for SceneHere + 20100801 + + + Simon Brooke + + + + + Copyright (c) 2010 Simon Brooke + + + basic_map_pin.svg + + Basic default map pin + $Revision: 1.2 $ + + + + + + + + + diff --git a/resources/public/img/map-pins/unknown-pin.xcf b/resources/public/img/map-pins/unknown-pin.xcf new file mode 100644 index 0000000..b55b9f8 Binary files /dev/null and b/resources/public/img/map-pins/unknown-pin.xcf differ diff --git a/resources/public/img/map-pins/yes-pin.png b/resources/public/img/map-pins/yes-pin.png new file mode 100644 index 0000000..68a76bd Binary files /dev/null and b/resources/public/img/map-pins/yes-pin.png differ diff --git a/resources/public/img/map-pins/yes-pin.svg b/resources/public/img/map-pins/yes-pin.svg new file mode 100644 index 0000000..182e9b0 --- /dev/null +++ b/resources/public/img/map-pins/yes-pin.svg @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Basic pin for SceneHere + 20100801 + + + Simon Brooke + + + + + Copyright (c) 2010 Simon Brooke + + + basic_map_pin.svg + + Basic default map pin + $Revision: 1.2 $ + + + + + + + + + + + + + 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/option/no-selected.png b/resources/public/img/option/no-selected.png new file mode 100644 index 0000000..c108d89 Binary files /dev/null and b/resources/public/img/option/no-selected.png differ diff --git a/resources/public/img/option/no-selected.svg b/resources/public/img/option/no-selected.svg new file mode 100644 index 0000000..c838c71 --- /dev/null +++ b/resources/public/img/option/no-selected.svg @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/public/img/option/no-unselected.png b/resources/public/img/option/no-unselected.png new file mode 100644 index 0000000..119ae26 Binary files /dev/null and b/resources/public/img/option/no-unselected.png differ diff --git a/resources/public/img/option/no-unselected.svg b/resources/public/img/option/no-unselected.svg new file mode 100644 index 0000000..6a33b6c --- /dev/null +++ b/resources/public/img/option/no-unselected.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/resources/public/img/option/yes-selected.png b/resources/public/img/option/yes-selected.png new file mode 100644 index 0000000..a9fb302 Binary files /dev/null and b/resources/public/img/option/yes-selected.png differ diff --git a/resources/public/img/option/yes-selected.svg b/resources/public/img/option/yes-selected.svg new file mode 100644 index 0000000..d9d292e --- /dev/null +++ b/resources/public/img/option/yes-selected.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/resources/public/img/option/yes-unselected.png b/resources/public/img/option/yes-unselected.png new file mode 100644 index 0000000..11b860c Binary files /dev/null and b/resources/public/img/option/yes-unselected.png differ diff --git a/resources/public/img/option/yes-unselected.svg b/resources/public/img/option/yes-unselected.svg new file mode 100644 index 0000000..18a249c --- /dev/null +++ b/resources/public/img/option/yes-unselected.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/resources/public/img/saltire-grey.png b/resources/public/img/saltire-grey.png deleted file mode 100644 index f49aabe..0000000 Binary files a/resources/public/img/saltire-grey.png and /dev/null differ diff --git a/resources/public/img/saltire-grey.xcf b/resources/public/img/saltire-grey.xcf deleted file mode 100644 index 675ea86..0000000 Binary files a/resources/public/img/saltire-grey.xcf and /dev/null differ diff --git a/resources/public/img/saltire.png b/resources/public/img/saltire.png deleted file mode 100644 index c64e173..0000000 Binary files a/resources/public/img/saltire.png and /dev/null differ diff --git a/resources/public/img/saltire.xcf b/resources/public/img/saltire.xcf deleted file mode 100644 index 15b8d97..0000000 Binary files a/resources/public/img/saltire.xcf and /dev/null differ diff --git a/resources/public/img/ujack-grey.png b/resources/public/img/ujack-grey.png deleted file mode 100644 index b8151ed..0000000 Binary files a/resources/public/img/ujack-grey.png and /dev/null differ diff --git a/resources/public/img/ujack-grey.xcf b/resources/public/img/ujack-grey.xcf deleted file mode 100644 index 4ed056c..0000000 Binary files a/resources/public/img/ujack-grey.xcf and /dev/null differ diff --git a/resources/public/img/ujack.png b/resources/public/img/ujack.png deleted file mode 100644 index d542490..0000000 Binary files a/resources/public/img/ujack.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/public/index.html b/resources/public/index.html deleted file mode 100644 index be48712..0000000 --- a/resources/public/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - You Yes Yet? - - -
- - -

- You yes yet? -

-
-
- - -
- - - diff --git a/resources/public/library.html b/resources/public/library.html deleted file mode 100644 index 21379d8..0000000 --- a/resources/public/library.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - Issues - - -
- - -

- Issues -

-
-
- - - - - - -
- - - - diff --git a/resources/public/login.html b/resources/public/login.html deleted file mode 100644 index 74f0bc3..0000000 --- a/resources/public/login.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - Please Log in - - -
- -

- Please Log in -

-
-
- - - - -
- - - diff --git a/resources/public/mapview.html b/resources/public/mapview.html deleted file mode 100644 index 25bab78..0000000 --- a/resources/public/mapview.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - Sign up! - - -
- -
-
- - Dummy map view - -
- - - diff --git a/resources/public/notyet.html b/resources/public/notyet.html deleted file mode 100644 index c2b8bf9..0000000 --- a/resources/public/notyet.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - Can we persuade you? - - -
- -

- Can we persuade you? -

-
-
- - - -
- - - diff --git a/resources/public/register.html b/resources/public/register.html deleted file mode 100644 index 352b90f..0000000 --- a/resources/public/register.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - Sign up! - - -
- - -

- Sign up! -

-
-
-

- Use this form to request someone to phone you to discuss your concerns about independence. -

-
-

- - -

-

- - -

-

- - -

-

- - -

-

- - -

-

- - -

-

- - -

-
-
- - - diff --git a/resources/public/supporter.html b/resources/public/supporter.html deleted file mode 100644 index 321ac48..0000000 --- a/resources/public/supporter.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - Are you registered? - - -
- - -

- Have you signed up as a canvasser yet? -

-
-
- - - -
- - - diff --git a/resources/public/twitter-login.html b/resources/public/twitter-login.html deleted file mode 100644 index 3676415..0000000 --- a/resources/public/twitter-login.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - Login with Twitter - - -
- - -

- Login with Twitter -

-
-
-

- The actual login page is provided by the 0-auth provider chosen by the user. We don't - handle login ourselves, and we don't store any passwords. -

- - -
- - - diff --git a/resources/templates/app.html b/resources/templates/app.html new file mode 100644 index 0000000..5214806 --- /dev/null +++ b/resources/templates/app.html @@ -0,0 +1,54 @@ + + + + + + + + + + You Yes Yet? + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + + + + + + + + {% script "/js/app.js" %} + + + + + + diff --git a/resources/templates/base-authenticated.html b/resources/templates/base-authenticated.html index 79c1558..7b2d418 100644 --- a/resources/templates/base-authenticated.html +++ b/resources/templates/base-authenticated.html @@ -6,13 +6,10 @@ - {% block title %}{% endblock %}{{title}} + {{title}}
-
- You are logged in as {{username}} -

- {% block title %} - {% endblock %} {{title}}

{{content}}
- -
- - diff --git a/resources/templates/base-unauthenticated.html b/resources/templates/base-unauthenticated.html index ba59e7b..4e41c8f 100644 --- a/resources/templates/base-unauthenticated.html +++ b/resources/templates/base-unauthenticated.html @@ -37,16 +37,16 @@ diff --git a/resources/templates/call-me-accepted.html b/resources/templates/call-me-accepted.html new file mode 100644 index 0000000..a720998 --- /dev/null +++ b/resources/templates/call-me-accepted.html @@ -0,0 +1,41 @@ +{% extends "base-unauthenticated.html" %} +{% block big-links %} + + +{% endblock %} +{% block content %} +

+ Thank you, {{name}}. Someone will call you shortly on {{phone}} to talk to you about + {{concern}}. +

+
+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+
+{% endblock %} diff --git a/resources/templates/call-me.html b/resources/templates/call-me.html new file mode 100644 index 0000000..299e97d --- /dev/null +++ b/resources/templates/call-me.html @@ -0,0 +1,37 @@ +{% extends "base-unauthenticated.html" %} +{% block big-links %} + +{% endblock %} +{% block content %} +

+ Use this form to request someone to phone you to discuss your concerns about independence. +

+
+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+
+{% endblock %} diff --git a/resources/templates/home.html b/resources/templates/home.html index 5c334b5..ea4c068 100644 --- a/resources/templates/home.html +++ b/resources/templates/home.html @@ -1,53 +1,9 @@ - - - - - - - - - You Yes Yet? - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+{% extends "base-unauthenticated.html" %} +{% block big-links %} + - -
- - - - - - - - - - {% script "/js/app.js" %} - - - - - - + +{% endblock %} diff --git a/resources/templates/login.html b/resources/templates/login.html new file mode 100644 index 0000000..f3e8731 --- /dev/null +++ b/resources/templates/login.html @@ -0,0 +1,27 @@ +{% extends "base-unauthenticated.html" %} +{% block big-links %} + +{% endblock %} +{% block content %} +

+ We're not going to do login in the long term; we're going to use oauth. + This is a temporary login form. +

+
+ {% csrf-field %} +

+ + +

+

+ + +

+

+ + +

+
+{% endblock %} diff --git a/resources/templates/notyet.html b/resources/templates/notyet.html new file mode 100644 index 0000000..47b9fec --- /dev/null +++ b/resources/templates/notyet.html @@ -0,0 +1,12 @@ +{% extends "base-unauthenticated.html" %} +{% block big-links %} + + + +{% endblock %} diff --git a/resources/templates/supporter.html b/resources/templates/supporter.html new file mode 100644 index 0000000..9e4f88e --- /dev/null +++ b/resources/templates/supporter.html @@ -0,0 +1,12 @@ +{% extends "base-unauthenticated.html" %} +{% block big-links %} + + + +{% endblock %} diff --git a/src/clj/youyesyet/db/core.clj b/src/clj/youyesyet/db/core.clj index a0eec57..696e40b 100644 --- a/src/clj/youyesyet/db/core.clj +++ b/src/clj/youyesyet/db/core.clj @@ -15,9 +15,6 @@ Timestamp PreparedStatement])) -;; TODO: I am CERTANLY misunderstanding something here. We ought to be getting -;; the database connection string and credentials fomr profiles.clj -;; (def ^:dynamic *db* {:name "java:comp/env/jdbc/EmployeeDB"}) (defstate ^:dynamic *db* :start (conman/connect! {:jdbc-url (env :database-url) :driver-class-name "org.postgresql.Driver"}) diff --git a/src/clj/youyesyet/layout.clj b/src/clj/youyesyet/layout.clj index d1503a1..aaec434 100644 --- a/src/clj/youyesyet/layout.clj +++ b/src/clj/youyesyet/layout.clj @@ -22,7 +22,8 @@ (assoc params :page template :csrf-token *anti-forgery-token* - :servlet-context *app-context*))) + :servlet-context *app-context* + :version (System/getProperty "youyesyet.version")))) "text/html; charset=utf-8")) (defn error-page 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 bbc546b..27e831c 100644 --- a/src/cljs/youyesyet/core.cljs +++ b/src/cljs/youyesyet/core.cljs @@ -1,57 +1,132 @@ (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] [youyesyet.ui-utils :as ui] [youyesyet.views.about :as about] - [youyesyet.views.home :as home] + [youyesyet.views.electors :as electors] + [youyesyet.views.followup :as followup] + [youyesyet.views.issue :as issue] + [youyesyet.views.issues :as issues] [youyesyet.views.map :as maps]) (:import goog.History)) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.core: core of the app. +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; So that we can do debug logging! +(enable-console-print!) (defn about-page [] (about/panel)) +(defn electors-page [] + (electors/panel)) -(defn home-page [] - (home/panel)) +(defn followup-page [] + (followup/panel)) +(defn issues-page [] + (issues/panel)) + +(defn issue-page [] + (issue/panel)) (defn map-page [] (maps/panel)) (def pages - {:home #'home-page + {:about #'about-page + :electors #'electors-page + :followup #'followup-page + :issues #'issues-page + :issue #'issue-page :map #'map-page - :about #'about-page}) + }) -(defn page [] - [:div - [:header - [ui/navbar] - [:h1 "You yes yet?"]] - [(pages @(rf/subscribe [:page]))]]) + +(defn page + "Render the single page of the app, taking the current panel from + 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 + [:header + [ui/navbar]] + (if content [content] + [: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 (secretary/set-config! :prefix "#") (secretary/defroute "/" [] - (rf/dispatch [:set-active-page :home])) + (rf/dispatch [:set-active-page :map])) (secretary/defroute "/about" [] (rf/dispatch [:set-active-page :about])) +(secretary/defroute "/electors" [] + (rf/dispatch [:set-active-page :electors])) + +(secretary/defroute "/electors/:address" {address-id :address} + (rf/dispatch [:set-address address-id])) + +(secretary/defroute "/followup" [] + (rf/dispatch [:set-active-page :followup])) + +(secretary/defroute "/issues" [] + (rf/dispatch [:set-active-page :issues])) + +(secretary/defroute "/issues/:elector" {elector-id :elector} + (rf/dispatch [:set-elector-and-page {:elector-id elector-id :page :issues}])) + +(secretary/defroute "/issue/:issue" {issue :issue} + (rf/dispatch [:set-and-go-to-issue issue])) + (secretary/defroute "/map" [] (rf/dispatch [:set-active-page :map])) +(secretary/defroute "/set-intention/:elector/:intention" {elector-id :elector intention :intention} + (rf/dispatch [:set-intention {:elector-id elector-id :intention intention}])) + ;; ------------------------- ;; History ;; must be called after routes have been defined @@ -65,9 +140,6 @@ ;; ------------------------- ;; Initialize app -(defn fetch-docs! [] - (GET (str js/context "/docs") - {:handler #(rf/dispatch [:set-docs %])})) (defn mount-components [] (r/render [#'page] (.getElementById js/document "app"))) @@ -75,6 +147,5 @@ (defn init! [] (rf/dispatch-sync [:initialize-db]) (load-interceptors!) - (fetch-docs!) (hook-browser-navigation!) (mount-components)) diff --git a/src/cljs/youyesyet/db.cljs b/src/cljs/youyesyet/db.cljs index 4dbaeb0..9659c44 100644 --- a/src/cljs/youyesyet/db.cljs +++ b/src/cljs/youyesyet/db.cljs @@ -1,4 +1,79 @@ (ns youyesyet.db) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.db: the state of the app. +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; This is the constructor for the atom in which the state of the user interface is held. +;;; The atom gets updated by 'events' registered in handler.cljs, q.v. +;;; +;;; not wonderfully happy with 'db' as a name for this namespace; will probably change to +;;; 'client-state'. + (def default-db - {:page :home}) + {;;; 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}] + ;;; 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 queue of items waiting to be transmitted. + :outqueue () + ;;; the currently displayed page within the app. + :page :home + }) diff --git a/src/cljs/youyesyet/handlers.cljs b/src/cljs/youyesyet/handlers.cljs index e19a2d1..deb11f4 100644 --- a/src/cljs/youyesyet/handlers.cljs +++ b/src/cljs/youyesyet/handlers.cljs @@ -1,18 +1,152 @@ (ns youyesyet.handlers - (:require [youyesyet.db :as db] - [re-frame.core :refer [dispatch reg-event-db]])) + (:require [cljs.reader :refer [read-string]] + [re-frame.core :refer [dispatch reg-event-db]] + [youyesyet.db :as db] + )) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.handlers: handlers for events. +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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 - :set-active-page - (fn [db [_ page]] - (assoc db :page page))) (reg-event-db - :set-docs - (fn [db [_ docs]] - (assoc db :docs docs))) + :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 + :elector elector + :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 + :set-issue + (fn [db [_ 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 7a13a1c..b1aa09c 100644 --- a/src/cljs/youyesyet/subscriptions.cljs +++ b/src/cljs/youyesyet/subscriptions.cljs @@ -1,12 +1,86 @@ (ns youyesyet.subscriptions (:require [re-frame.core :refer [reg-sub]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.views.electors: subscriptions for everything in the app state. +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(reg-sub + :motd + (fn [db _] + (:motd db))) + +(reg-sub + :address + (fn [db _] + (:address db))) + +(reg-sub + :addresses + (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 _] + (:issue db))) + +(reg-sub + :issues + (fn [db _] + (:issues db))) + (reg-sub :page (fn [db _] (:page db))) (reg-sub - :docs + :options (fn [db _] - (:docs db))) + (:options db))) + +(reg-sub + :outqueue + (fn [db _] + (:outqueue db))) + diff --git a/src/cljs/youyesyet/ui_utils.cljs b/src/cljs/youyesyet/ui_utils.cljs index 2f651dd..719090b 100644 --- a/src/cljs/youyesyet/ui_utils.cljs +++ b/src/cljs/youyesyet/ui_utils.cljs @@ -32,7 +32,7 @@ (defn big-link [text target] - [:div.big-link-container + [:div.big-link-container {:key target} [:a.big-link {:href target} text]]) @@ -45,6 +45,14 @@ :on-click #(reset! collapsed? true)} title]])) +(defn error-panel + [message] + [:div + [:h1.error message] + [:div.container {:id "main-container"} + (back-link)]]) + + (defn navbar [] (r/with-let [collapsed? (r/atom true)] [:div {:id "nav"} @@ -52,8 +60,7 @@ :src "img/threelines.png" :on-click #(swap! collapsed? not)}] [:menu.nav {:id "nav-menu" :class (if @collapsed? "hidden" "shown")} - (nav-link "#/" "Home" :home collapsed?) - (nav-link "#/library" "Library" :library collapsed?) - (nav-link "#/register" "Register" :register collapsed?) - (nav-link "#/login" "Login" :login collapsed?) + (nav-link "#/map" "Map" :map collapsed?) + (nav-link "#/electors" "Electors" :electors collapsed?) + (nav-link "#/issues" "Issues" :issues collapsed?) (nav-link "#/about" "About" :about collapsed?)]])) diff --git a/src/cljs/youyesyet/views/about.cljs b/src/cljs/youyesyet/views/about.cljs index cbfcc7a..4ca4a4a 100644 --- a/src/cljs/youyesyet/views/about.cljs +++ b/src/cljs/youyesyet/views/about.cljs @@ -1,10 +1,11 @@ (ns youyesyet.views.about - (:require [re-frame.core :refer [reg-sub]] + (:require [re-frame.core :refer [reg-sub subscribe]] + [markdown.core :refer [md->html]] [youyesyet.ui-utils :as ui])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; -;;;; 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 @@ -25,7 +26,6 @@ ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;; The pattern from the re-com demo (https://github.com/Day8/re-com) is to have ;;; one source file/namespace per view. Each namespace contains a function 'panel' ;;; whose output is an enlive-style specification of the view to be redered. @@ -33,22 +33,32 @@ (defn panel "Generate the about panel." [] - [:div.container {:id "main-container"} - [:h2 "Pre-alpha/proof of concept"] - [:p {:class "centre"} - [:img {:src "img/ric-logo.png" :width "24" :height "24"}] - " A project of the " - [:a {:href "https://radical.scot/"} "Radical Independence Campaign"]] - [:p {:class "centre"} - [:img {:src "img/clojure-icon.gif" :alt "Clojure" :height "24" :width "24"}] - " Powered by " - [:a {:href "http://clojure.org"} "Clojure"]] - [:p {:class "centre"} - [:img {:src "img/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 {:class "centre"} - [:img {:src "img/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"]]]) + (let [motd @(subscribe [:motd])] + [:div + [:h1 "You Yes Yet?"] + [:div.container {:id "main-container"} + [:h2 "Pre-alpha/proof of concept"] + [:p.motd {:dangerouslySetInnerHTML + {:__html (md->html motd)}}] + [:p + [: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/credits/luminus-logo.png" :alt "Luminus" :height "24" :width "24"}] + " Built with " + [:a {:href "http://www.luminusweb.net/"} "Luminus Web"]] + [:p + [: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/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/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"]] + (ui/back-link)]])) diff --git a/src/cljs/youyesyet/views/electors.cljs b/src/cljs/youyesyet/views/electors.cljs index 5c1b8f3..05f7af4 100644 --- a/src/cljs/youyesyet/views/electors.cljs +++ b/src/cljs/youyesyet/views/electors.cljs @@ -1,5 +1,7 @@ (ns youyesyet.views.electors - (:require [re-frame.core :refer [reg-sub]])) + (:require [reagent.core :refer [atom]] + [re-frame.core :refer [reg-sub subscribe dispatch]] + [youyesyet.ui-utils :as ui])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -32,7 +34,99 @@ ;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#electors-view +;;; The design for this panel is one column per elector within the address. +;;; Each column contains +;;; 1. a stick figure identifying gender (for recognition); +;;; 2. the elector's name; +;;; 3. one icon for each option on the ballot; +;;; 4. an 'issues' icon. +;;; The mechanics of how this panel is laid out don't matter. + +(defn gender-cell + [elector] + (let [gender (:gender elector) + 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)]) + +(defn names-row + [electors] + [:tr + (map + #(name-cell %) electors)]) + + +(defn options-row + [electors option] + (let [optid (:id option) + optname (name optid)] + [:tr {:key (str "options-" optname)} + (map + (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"}]]]) + + +(defn issues-row + [electors] + [:tr + (map + #(issue-cell %) + electors)]) + (defn panel "Generate the electors panel." [] - []) + (let [address @(subscribe [:address]) + addresses @(subscribe [:addresses]) + electors (sort-by :id (:electors address)) + options @(subscribe [:options]) + changes @(subscribe [:changes])] + (if address + [:div + [:h1 (:address address)] + [:div.container {:id "main-container"} + [:table + [:tbody + ;; genders row + (genders-row electors) + ;; names row + (names-row electors) + ;; options rows + (map + #(options-row electors %) + options) + ;; issues row + (issues-row electors)]] + (ui/back-link)]] + (ui/error-panel "No address selected")))) + + diff --git a/src/cljs/youyesyet/views/followup.cljs b/src/cljs/youyesyet/views/followup.cljs new file mode 100644 index 0000000..78c0c04 --- /dev/null +++ b/src/cljs/youyesyet/views/followup.cljs @@ -0,0 +1,77 @@ +(ns youyesyet.views.followup + (:require [re-frame.core :refer [reg-sub subscribe dispatch]] + [youyesyet.ui-utils :as ui])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.views.followup-request: followup-request 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 +;;;; 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 pattern from the re-com demo (https://github.com/Day8/re-com) is to have +;;; one source file/namespace per view. Each namespace contains a function 'panel' +;;; whose output is an enlive-style specification of the view to be redered. +;;; I propose to follow this pattern. This file will (eventually) provide the followup-request view. + +;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#followup-request-form + +(defn panel + "Generate the followup-request panel." + [] + (let [issue @(subscribe [:issue]) + issues @(subscribe [:issues]) + elector @(subscribe [:elector]) + address @(subscribe [:address])] + (js/console.log (str "Issue is " issue "; elector is " elector)) + (cond + (nil? address) + (ui/error-panel "No address selected") + (nil? issues) + (ui/error-panel "No issues loaded") + true + [:div + [:h1 "Followup Request"] + [:div.container {:id "main-container"} + [:div {} + [:p.widget + [:label {:for "elector"} "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"] + ;; #(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" + :on-change #(dispatch [:set-telephone (.-value (.-target %))])}]] + [:p.widget + [: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/followup_action.cljs b/src/cljs/youyesyet/views/followup_action.cljs deleted file mode 100644 index 22e39ef..0000000 --- a/src/cljs/youyesyet/views/followup_action.cljs +++ /dev/null @@ -1,38 +0,0 @@ -(ns youyesyet.views.followup-action - (:require [re-frame.core :refer [reg-sub]])) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; -;;;; youyesyet.views.followup-action: followup-action 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 -;;;; 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 pattern from the re-com demo (https://github.com/Day8/re-com) is to have -;;; one source file/namespace per view. Each namespace contains a function 'panel' -;;; whose output is an enlive-style specification of the view to be redered. -;;; I propose to follow this pattern. This file will (eventually) provide the followup-action view. - -;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#followup-action-view - -(defn panel - "Generate the followup-action panel." - [] - []) diff --git a/src/cljs/youyesyet/views/followup_requests.cljs b/src/cljs/youyesyet/views/followup_requests.cljs deleted file mode 100644 index 4cdf50f..0000000 --- a/src/cljs/youyesyet/views/followup_requests.cljs +++ /dev/null @@ -1,38 +0,0 @@ -(ns youyesyet.views.login - (:require [re-frame.core :refer [reg-sub]])) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; -;;;; youyesyet.views.login: login 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 -;;;; 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 pattern from the re-com demo (https://github.com/Day8/re-com) is to have -;;; one source file/namespace per view. Each namespace contains a function 'panel' -;;; whose output is an enlive-style specification of the view to be redered. -;;; I propose to follow this pattern. This file will (eventually) provide the login view. - -;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#followup-requests-view - -(defn panel - "Generate the login panel." - [] - []) diff --git a/src/cljs/youyesyet/views/home.cljs b/src/cljs/youyesyet/views/home.cljs deleted file mode 100644 index e877315..0000000 --- a/src/cljs/youyesyet/views/home.cljs +++ /dev/null @@ -1,49 +0,0 @@ -(ns youyesyet.views.home - (:require [re-frame.core :as rf] - [markdown.core :refer [md->html]] - [youyesyet.ui-utils :as ui])) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; -;;;; youyesyet.views.electors: home 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 -;;;; 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 pattern from the re-com demo (https://github.com/Day8/re-com) is to have -;;; one source file/namespace per view. Each namespace contains a function 'panel' -;;; whose output is an enlive-style specification of the view to be redered. -;;; I propose to follow this pattern. This file will provide the home view. - -(defn panel - "Generate the home panel." - [] - [:div.container {:id "main-container"} - (ui/big-link "About" "#/about") - (ui/big-link "Map" "#/map") - [:div.jumbotron - [:h1 "You Yes Yet?"] - [:p "Time to start building your site!"] - [:p [:a.btn.btn-primary.btn-lg {:href "http://luminusweb.net"} "Learn more »"]]]]) - (when-let [docs @(rf/subscribe [:docs])] - [:div.row - [:div.col-md-12 - [:div {:dangerouslySetInnerHTML - {:__html (md->html docs)}}]]]) diff --git a/src/cljs/youyesyet/views/login.cljs b/src/cljs/youyesyet/views/issue.cljs similarity index 65% rename from src/cljs/youyesyet/views/login.cljs rename to src/cljs/youyesyet/views/issue.cljs index 2aaa837..20620d6 100644 --- a/src/cljs/youyesyet/views/login.cljs +++ b/src/cljs/youyesyet/views/issue.cljs @@ -1,9 +1,12 @@ -(ns youyesyet.views.login - (:require [re-frame.core :refer [reg-sub]])) +(ns youyesyet.views.issue + (:require [re-frame.core :refer [reg-sub subscribe]] + [markdown.core :refer [md->html]] + [youyesyet.ui-utils :as ui] + [youyesyet.views.issues :as issues])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; -;;;; youyesyet.views.login: login view for youyesyet. +;;;; youyesyet.views.issues: issues 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 @@ -28,11 +31,19 @@ ;;; The pattern from the re-com demo (https://github.com/Day8/re-com) is to have ;;; one source file/namespace per view. Each namespace contains a function 'panel' ;;; whose output is an enlive-style specification of the view to be redered. -;;; I propose to follow this pattern. This file will (eventually) provide the login view. - -;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#logging-in +;;; I propose to follow this pattern. This file will (eventually) provide the single issue view. (defn panel - "Generate the login panel." + "Generate the issue panel." [] - []) + (let [issue @(subscribe [:issue]) + issues @(subscribe [:issues])] + [:div + [:h1 issue] + [:div.container {:id "main-container"} + [:div {:id "issue"} + [:div {:id "issue-text" + :dangerouslySetInnerHTML + {:__html (md->html (issues issue))}}]] + (ui/big-link "Request call" "#/followup") + (ui/back-link)]])) diff --git a/src/cljs/youyesyet/views/issues.cljs b/src/cljs/youyesyet/views/issues.cljs index fede400..ea8f383 100644 --- a/src/cljs/youyesyet/views/issues.cljs +++ b/src/cljs/youyesyet/views/issues.cljs @@ -1,5 +1,6 @@ (ns youyesyet.views.issues - (:require [re-frame.core :refer [reg-sub]])) + (:require [re-frame.core :refer [reg-sub subscribe]] + [youyesyet.ui-utils :as ui])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -32,7 +33,16 @@ ;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#issues-view +;;; Simple list of the issues of the day. (defn panel "Generate the issues panel." [] - []) + (let [issues @(subscribe [:issues])] + (if issues + [:div + [:h1 "Issues"] + [:div.container {:id "main-container"} + (ui/back-link) + [:div {:id "issue-list"} + (map (fn [k] (ui/big-link k (str "#/issue/" k))) (keys issues))]]] + (ui/error-panel "No issues loaded")))) diff --git a/src/cljs/youyesyet/views/map.cljs b/src/cljs/youyesyet/views/map.cljs index d926295..c725531 100644 --- a/src/cljs/youyesyet/views/map.cljs +++ b/src/cljs/youyesyet/views/map.cljs @@ -1,5 +1,5 @@ (ns youyesyet.views.map - (:require [re-frame.core :refer [reg-sub]] + (:require [re-frame.core :refer [reg-sub subscribe dispatch]] [reagent.core :as reagent])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -33,7 +33,6 @@ ;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#map-view - ;;; Cribbed heavily from ;;; https://github.com/reagent-project/reagent-cookbook/tree/master/recipes/leaflet ;;; but using OSM data because we can't afford commercial, so also cribbed from @@ -41,6 +40,11 @@ ;;; Note that this is raw reagent stylee; it should be refactoed into re-frame stylee ;;; when I understand it better. +;;; There should be one flag on the map for each address record currently in frame. +;;; Clicking the flag sets that address as the current address in the app state, +;;; and redirects to the electors view. How we handle blocks of flats needs further +;;; thought. + ;; which provider to use (def *map-provider* :osm) @@ -48,26 +52,75 @@ (def osm-attrib "Map data © OpenStreetMap contributors") +(defn pin-image + "select the name of a suitable pin image for this address" + [address] + (let [intentions (set (remove nil? (map #(:intention %) (:electors address))))] + (case (count intentions) + 0 "unknown-pin" + 1 (str (name (first intentions)) "-pin") + "mixed-pin"))) + + +(defn map-pin-click-handler + "On clicking on the pin, navigate to the electors at the address. + This way of doing it adds an antry in the browser location history, + so back links work." + [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 history doesn't work: +;; (defn map-pin-click-handler +;; [id] +;; (dispatch [:set-address id])) + + +(defn add-map-pin + "Add a map-pin at this address in this map view" + [address view] + (let [lat (:latitude address) + lng (:longitude address) + pin (.icon js/L + (clj->js + {:iconUrl (str "img/map-pins/" (pin-image address) ".png") + :shadowUrl "img/map-pins/shadow_pin.png" + :iconSize [32 42] + :shadowSize [57 24] + :iconAnchor [16 41] + :shadowAnchor [16 23]})) + marker (.marker js/L + (.latLng js/L lat lng) + (clj->js {:icon pin + :title (:address address)})) + ] + (.on marker "click" (fn [_] (map-pin-click-handler (str (:id address))))) + (.addTo marker view))) + + ;; My gods mapbox is user-hostile! (defn map-did-mount-mapbox - "Did-mount function loading map tile data from MapBox (proprietary)." + "Did-mount function loading map tile data from MapBox (proprietary)." [] - (let [map (.setView (.map js/L "map") #js [55.86 -4.25] 13)] + (let [view (.setView (.map js/L "map" (clj->js {:zoomControl "false"})) #js [55.82 -4.25] 40)] ;; NEED TO REPLACE FIXME with your mapID! (.addTo (.tileLayer js/L "http://{s}.tiles.mapbox.com/v3/FIXME/{z}/{x}/{y}.png" (clj->js {:attribution "Map data © [...]" :maxZoom 18})) - map))) + view))) (defn map-did-mount-osm - "Did-mount function loading map tile data from Open Street Map (open)." + "Did-mount function loading map tile data from Open Street Map." [] - (let [map (.setView (.map js/L "map") #js [55.86 -4.25] 13)] + (let [view (.setView (.map js/L "map" (clj->js {:zoomControl false})) #js [55.82 -4.25] 13) + addresses @(subscribe [:addresses])] + (js/console.log (str "Adding " (count addresses) " pins")) + (doall (map #(add-map-pin % view) addresses)) (.addTo (.tileLayer js/L osm-url (clj->js {:attribution osm-attrib :maxZoom 18})) - map))) + view) + )) (defn map-did-mount @@ -91,4 +144,3 @@ [] (reagent/create-class {:reagent-render map-render :component-did-mount map-did-mount})) - diff --git a/src/cljs/youyesyet/views/role_menu.cljs b/src/cljs/youyesyet/views/role_menu.cljs deleted file mode 100644 index 7374d25..0000000 --- a/src/cljs/youyesyet/views/role_menu.cljs +++ /dev/null @@ -1,39 +0,0 @@ -(ns youyesyet.views.role-menu - (:require [re-frame.core :refer [reg-sub]])) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; -;;;; youyesyet.views.role-menu: role-menu 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 -;;;; 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 pattern from the re-com demo (https://github.com/Day8/re-com) is to have -;;; one source file/namespace per view. Each namespace contains a function 'panel' -;;; whose output is an enlive-style specification of the view to be redered. -;;; I propose to follow this pattern. This file will (eventually) provide the role-menu view. - -;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#after-login - - -(defn panel - "Generate the role-menu panel." - [] - []) diff --git a/src/cljs/youyesyet/views/signup.cljs b/src/cljs/youyesyet/views/signup.cljs deleted file mode 100644 index 72ca2d2..0000000 --- a/src/cljs/youyesyet/views/signup.cljs +++ /dev/null @@ -1,38 +0,0 @@ -(ns youyesyet.views.signup - (:require [re-frame.core :refer [reg-sub]])) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; -;;;; youyesyet.views.signup: signup 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 -;;;; 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 pattern from the re-com demo (https://github.com/Day8/re-com) is to have -;;; one source file/namespace per view. Each namespace contains a function 'panel' -;;; whose output is an enlive-style specification of the view to be redered. -;;; I propose to follow this pattern. This file will (eventually) provide the signup view. - -;;; See https://github.com/simon-brooke/youyesyet/blob/master/doc/specification/userspec.md#creating-an-account - -(defn panel - "Generate the signup panel." - [] - [])