├── system.properties
├── Procfile
├── resources
└── public
│ ├── css
│ ├── images
│ │ ├── markers-soft.png
│ │ ├── markers-matte.png
│ │ ├── markers-plain.png
│ │ ├── markers-shadow.png
│ │ ├── markers-matte@2x.png
│ │ ├── markers-shadow@2x.png
│ │ └── markers-soft@2x.png
│ ├── MarkerCluster.css
│ ├── leaflet.contextmenu.css
│ ├── MarkerCluster.Default.css
│ ├── leaflet.awesome-markers.css
│ ├── icons.css
│ ├── normalize.css
│ ├── leaflet.css
│ ├── app.css
│ └── ionicons.min.css
│ ├── fonts
│ ├── icomoon
│ │ ├── icomoon.eot
│ │ ├── icomoon.ttf
│ │ ├── icomoon.woff
│ │ └── icomoon.svg
│ ├── goog-material
│ │ ├── icomoon.eot
│ │ ├── icomoon.ttf
│ │ ├── icomoon.woff
│ │ ├── style.css
│ │ ├── icomoon.svg
│ │ └── selection.json
│ └── open_sans
│ │ ├── OpenSans-Bold.ttf
│ │ ├── OpenSans-Light.ttf
│ │ ├── OpenSans-Italic.ttf
│ │ ├── OpenSans-Regular.ttf
│ │ ├── OpenSans-Semibold.ttf
│ │ ├── OpenSans-BoldItalic.ttf
│ │ ├── OpenSans-ExtraBold.ttf
│ │ ├── OpenSans-LightItalic.ttf
│ │ ├── OpenSans-ExtraBoldItalic.ttf
│ │ ├── OpenSans-SemiboldItalic.ttf
│ │ └── LICENSE.txt
│ ├── index.html
│ ├── js
│ └── leaflet
│ │ ├── leaflet.awesome-markers.min.js
│ │ └── leaflet.contextmenu.js
│ └── dist
│ └── leaflet.awesome-markers.js
├── .gitignore
├── src
├── cljs
│ └── lokate
│ │ ├── home.cljs
│ │ ├── core.cljs
│ │ ├── settings.cljs
│ │ ├── db.cljs
│ │ ├── collections.cljs
│ │ ├── util.cljs
│ │ ├── resources.cljs
│ │ ├── check_in.cljs
│ │ ├── map.cljs
│ │ ├── unit.cljs
│ │ ├── app.cljs
│ │ └── components.cljs
└── clj
│ └── lokate
│ └── server.clj
├── LICENSE
├── project.clj
└── README.md
/system.properties:
--------------------------------------------------------------------------------
1 | java.runtime.version=1.8
2 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: java $JVM_OPTS -jar target/lokate.jar $PORT
2 |
--------------------------------------------------------------------------------
/resources/public/css/images/markers-soft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/css/images/markers-soft.png
--------------------------------------------------------------------------------
/resources/public/fonts/icomoon/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/icomoon/icomoon.eot
--------------------------------------------------------------------------------
/resources/public/fonts/icomoon/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/icomoon/icomoon.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/icomoon/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/icomoon/icomoon.woff
--------------------------------------------------------------------------------
/resources/public/css/images/markers-matte.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/css/images/markers-matte.png
--------------------------------------------------------------------------------
/resources/public/css/images/markers-plain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/css/images/markers-plain.png
--------------------------------------------------------------------------------
/resources/public/css/images/markers-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/css/images/markers-shadow.png
--------------------------------------------------------------------------------
/resources/public/css/images/markers-matte@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/css/images/markers-matte@2x.png
--------------------------------------------------------------------------------
/resources/public/css/images/markers-shadow@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/css/images/markers-shadow@2x.png
--------------------------------------------------------------------------------
/resources/public/css/images/markers-soft@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/css/images/markers-soft@2x.png
--------------------------------------------------------------------------------
/resources/public/fonts/goog-material/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/goog-material/icomoon.eot
--------------------------------------------------------------------------------
/resources/public/fonts/goog-material/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/goog-material/icomoon.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/goog-material/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/goog-material/icomoon.woff
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-Bold.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-Light.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-Italic.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-Semibold.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-BoldItalic.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-ExtraBold.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-LightItalic.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-ExtraBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-ExtraBoldItalic.ttf
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/OpenSans-SemiboldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leblowl/lokate/HEAD/resources/public/fonts/open_sans/OpenSans-SemiboldItalic.ttf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | pom.xml
2 | pom.xml.asc
3 | *jar
4 | /lib/
5 | /classes/
6 | /target/
7 | /checkouts/
8 | .lein-deps-sum
9 | .lein-repl-history
10 | .lein-plugins/
11 | .lein-failures
12 | .boot/
13 | .nrepl-port
14 | .nrepl-history
15 | .repl/
16 |
--------------------------------------------------------------------------------
/resources/public/css/MarkerCluster.css:
--------------------------------------------------------------------------------
1 | .leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
2 | -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
3 | -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
4 | -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
5 | transition: transform 0.3s ease-out, opacity 0.3s ease-in;
6 | }
7 |
--------------------------------------------------------------------------------
/src/cljs/lokate/home.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.home
2 | (:require [cljs.core.async :as async]
3 | [om.core :as om :include-macros true]
4 | [sablono.core :as html :refer-macros [html]]
5 | [lokate.util :as u]
6 | [lokate.components :as c]))
7 |
8 | (defn home-drawer-view [menu-items]
9 | (c/item-list {:action (fn [x evt-bus] (u/route! evt-bus (:path x)))}
10 | menu-items))
11 |
12 | (defn home-views [{:keys [drawer]} data state]
13 | [(om/build c/drawer-nav-panel [drawer
14 | (c/title-banner "home" c/home-icon)])
15 | (home-drawer-view [{:title "Collections"
16 | :path :collections}
17 | {:title "Resources"
18 | :path :resources}
19 | {:title "Settings"
20 | :path :settings}])])
21 |
--------------------------------------------------------------------------------
/src/clj/lokate/server.clj:
--------------------------------------------------------------------------------
1 | (ns lokate.server
2 | (:require [compojure.core :refer [GET defroutes]]
3 | [compojure.route :as route]
4 | [ring.util.response :as resp]
5 | [org.httpkit.server :refer [run-server]]
6 | [clojure.java.io :as io]
7 | [ring.middleware.file-info :as file-info])
8 | (:gen-class))
9 |
10 | (defroutes routes
11 | (GET "/" [] (resp/resource-response "index.html" {:root "public"}))
12 | (route/resources "/" {:root "public"})
13 | (route/not-found "Page not found."))
14 |
15 | (defn run [handler & [port]]
16 | (defonce ^:private server
17 | (let [port (Integer. (or port 10333))]
18 | (print "Starting web server on port" port ".\n")
19 | (run-server handler {:port port})))
20 | server)
21 |
22 | (def handler #'routes)
23 |
24 | (defn -main [& [port]]
25 | (run handler port))
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Lucas Leblow
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/cljs/lokate/core.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.core
2 | (:require [om.core :as om :include-macros true]
3 | [sablono.core :as html :refer-macros [html]]
4 | [lokate.util :as u]
5 | [lokate.map :as map]))
6 |
7 | (defn drawer-panel
8 | [[orientation drawer drawer-view] owner]
9 | (om/component
10 | (html [:div#drawer-wrapper
11 | [:div#drawer
12 | {:class (str orientation
13 | (if (:open? drawer) " show" " hide")
14 | (when (:maximized? drawer) " maximized"))}
15 | [:div#drawer-content.frame drawer-view]]])))
16 |
17 | (defn window
18 | [[{:keys [orientation location drawer views-fn views-state] :as window} data] owner]
19 | (om/component
20 | (let [[nav-view drawer-view] (views-fn window data views-state)]
21 | (html [:div#app
22 | nav-view
23 | [:div {:class (str "flex-container " orientation)}
24 | [:div.flex-content
25 | (om/build map/l-map [location
26 | (-> data u/get-collections u/get-units)
27 | (u/get-settings data)])
28 | (om/build drawer-panel [orientation drawer drawer-view])]]]))))
29 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject lokate "0.2.0-SNAPSHOT"
2 | :description "track resources on a map!"
3 | :url "http://lokate-demo.herokuapp.com"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 |
7 | :min-lein-version "2.5.0"
8 |
9 | :dependencies [[org.clojure/clojure "1.8.0"]
10 | [org.clojure/clojurescript "1.9.518"]
11 | [org.clojure/core.async "0.3.442"]
12 | [org.omcljs/om "0.8.6"]
13 | [sablono "0.3.1"]
14 | [org.clojars.leanpixel/cljs-uuid-utils "1.0.0-SNAPSHOT"]
15 | [com.andrewmcveigh/cljs-time "0.4.0"]
16 | [ring "1.6.0-RC2"]
17 | [compojure "1.5.2"]
18 | [http-kit "2.2.0"]]
19 |
20 | :plugins [[lein-cljsbuild "1.1.5"]]
21 |
22 | :source-paths ["src/clj" "src/cljs"]
23 | :uberjar-name "lokate.jar"
24 | :main lokate.server
25 |
26 | :cljsbuild {:builds [{:source-paths ["src/cljs"]
27 | :compiler {:output-to "resources/public/js/main.js"
28 | :output-dir "resources/public/js/out"
29 | :optimizations :none
30 | :pretty-print false}}]}
31 |
32 | :profiles {:uberjar {:hooks [leiningen.cljsbuild]
33 | :omit-source true
34 | :aot :all}})
35 |
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/resources/public/css/leaflet.contextmenu.css:
--------------------------------------------------------------------------------
1 | .leaflet-contextmenu {
2 | display: none;
3 | box-shadow: 0 1px 7px rgba(0,0,0,0.4);
4 | -webkit-border-radius: 4px;
5 | border-radius: 4px;
6 | padding: 4px 0;
7 | background-color: #fff;
8 | cursor: default;
9 | -webkit-user-select: none;
10 | -moz-user-select: none;
11 | user-select: none;
12 | }
13 |
14 | .leaflet-contextmenu a.leaflet-contextmenu-item {
15 | display: block;
16 | color: #222;
17 | font-size: 12px;
18 | line-height: 20px;
19 | text-decoration: none;
20 | padding: 0 12px;
21 | border-top: 1px solid transparent;
22 | border-bottom: 1px solid transparent;
23 | cursor: default;
24 | outline: none;
25 | }
26 |
27 | .leaflet-contextmenu a.leaflet-contextmenu-item-disabled {
28 | opacity: 0.5;
29 | }
30 |
31 | .leaflet-contextmenu a.leaflet-contextmenu-item.over {
32 | background-color: #f4f4f4;
33 | border-top: 1px solid #f0f0f0;
34 | border-bottom: 1px solid #f0f0f0;
35 | }
36 |
37 | .leaflet-contextmenu a.leaflet-contextmenu-item-disabled.over {
38 | background-color: inherit;
39 | border-top: 1px solid transparent;
40 | border-bottom: 1px solid transparent;
41 | }
42 |
43 | .leaflet-contextmenu-icon {
44 | margin: 2px 8px 0 0;
45 | width: 16px;
46 | height: 16px;
47 | float: left;
48 | border: 0;
49 | }
50 |
51 | .leaflet-contextmenu-separator {
52 | border-bottom: 1px solid #ccc;
53 | margin: 5px 0;
54 | }
55 |
--------------------------------------------------------------------------------
/resources/public/css/MarkerCluster.Default.css:
--------------------------------------------------------------------------------
1 | .marker-cluster-small {
2 | background-color: rgba(181, 226, 140, 0.6);
3 | }
4 | .marker-cluster-small div {
5 | background-color: rgba(110, 204, 57, 0.6);
6 | }
7 |
8 | .marker-cluster-medium {
9 | background-color: rgba(241, 211, 87, 0.6);
10 | }
11 | .marker-cluster-medium div {
12 | background-color: rgba(240, 194, 12, 0.6);
13 | }
14 |
15 | .marker-cluster-large {
16 | background-color: rgba(253, 156, 115, 0.6);
17 | }
18 | .marker-cluster-large div {
19 | background-color: rgba(241, 128, 23, 0.6);
20 | }
21 |
22 | /* IE 6-8 fallback colors */
23 | .leaflet-oldie .marker-cluster-small {
24 | background-color: rgb(181, 226, 140);
25 | }
26 | .leaflet-oldie .marker-cluster-small div {
27 | background-color: rgb(110, 204, 57);
28 | }
29 |
30 | .leaflet-oldie .marker-cluster-medium {
31 | background-color: rgb(241, 211, 87);
32 | }
33 | .leaflet-oldie .marker-cluster-medium div {
34 | background-color: rgb(240, 194, 12);
35 | }
36 |
37 | .leaflet-oldie .marker-cluster-large {
38 | background-color: rgb(253, 156, 115);
39 | }
40 | .leaflet-oldie .marker-cluster-large div {
41 | background-color: rgb(241, 128, 23);
42 | }
43 |
44 | .marker-cluster {
45 | background-clip: padding-box;
46 | border-radius: 20px;
47 | }
48 | .marker-cluster div {
49 | width: 30px;
50 | height: 30px;
51 | margin-left: 5px;
52 | margin-top: 5px;
53 |
54 | text-align: center;
55 | border-radius: 15px;
56 | font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
57 | }
58 | .marker-cluster span {
59 | line-height: 30px;
60 | }
61 |
--------------------------------------------------------------------------------
/resources/public/js/leaflet/leaflet.awesome-markers.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | Leaflet.AwesomeMarkers, a plugin that adds colorful iconic markers for Leaflet, based on the Font Awesome icons
3 | (c) 2012-2013, Lennard Voogdt
4 |
5 | http://leafletjs.com
6 | https://github.com/lvoogdt
7 | *//*global L*/(function(e,t,n){"use strict";L.AwesomeMarkers={};L.AwesomeMarkers.version="2.0.1";L.AwesomeMarkers.Icon=L.Icon.extend({options:{iconSize:[35,45],iconAnchor:[17,42],popupAnchor:[1,-32],shadowAnchor:[10,12],shadowSize:[36,16],className:"awesome-marker",prefix:"glyphicon",spinClass:"fa-spin",icon:"home",markerColor:"blue",iconColor:"white"},initialize:function(e){e=L.Util.setOptions(this,e)},createIcon:function(){var e=t.createElement("div"),n=this.options;n.icon&&(e.innerHTML=this._createInner());n.bgPos&&(e.style.backgroundPosition=-n.bgPos.x+"px "+ -n.bgPos.y+"px");this._setIconStyles(e,"icon-"+n.markerColor);return e},_createInner:function(){var e,t="",n="",r="",i=this.options;i.icon.slice(0,i.prefix.length+1)===i.prefix+"-"?e=i.icon:e=i.prefix+"-"+i.icon;i.spin&&typeof i.spinClass=="string"&&(t=i.spinClass);i.iconColor&&(i.iconColor==="white"||i.iconColor==="black"?n="icon-"+i.iconColor:r="style='color: "+i.iconColor+"' ");return""},_setIconStyles:function(e,t){var n=this.options,r=L.point(n[t==="shadow"?"shadowSize":"iconSize"]),i;t==="shadow"?i=L.point(n.shadowAnchor||n.iconAnchor):i=L.point(n.iconAnchor);!i&&r&&(i=r.divideBy(2,!0));e.className="awesome-marker-"+t+" "+n.className;if(i){e.style.marginLeft=-i.x+"px";e.style.marginTop=-i.y+"px"}if(r){e.style.width=r.x+"px";e.style.height=r.y+"px"}},createShadow:function(){var e=t.createElement("div");this._setIconStyles(e,"shadow");return e}});L.AwesomeMarkers.icon=function(e){return new L.AwesomeMarkers.Icon(e)}})(this,document);
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lokate
2 |
3 | Lokate is a little cross-platform web app for tracking location-based resources, originally developed for tracking bee hives for Bee Friendly Honey.
4 |
5 | The app is similar to google maps but with a simple management layer on top. You can create a collection (think folder) & then within that collection you can add units (pins on the map with associated data). You can then create resources & add them to your units for tracking (ex: ["drawers w/ honey" 1]). And you can also create resource blocks, which are just groups of resources, for simplified adding of multiple resources to new units.
6 |
7 | For tracking the data, I decided to go with an immutable commit style workflow (git is awesome). So when you want to update unit resources, you "check-in" your unit, update any values and then commit the dataset with an optional commit message and pin status color. Each commit gets pushed onto a history stack for the unit. This makes tracking data over time for a particular unit really easy, which is the end goal. Ideally you'd want to collect your data, analyze it, & then makes some decisions based on that data. This app only does the collecting.
8 |
9 | In the field, you don't always have access to the internet. Local storage allows users to work offline and eliminates the cost of hosting a database. At the moment, persistent data storage is handled through remoteStorage (https://remotestorage.io/).
10 |
11 | Check out the demo on Heroku @ http://lokate-demo.herokuapp.com/. Expect updates & experimental areas.
12 |
13 | # Build
14 |
15 | Lokate uses the boot build tool (https://github.com/boot-clj/boot). With this installed, in the project directory, you can run ```boot dev``` to get a localhost dev server setup or run ```boot prod``` to package the app as a standalone jar. Always a work in progress, enjoy :)
16 |
17 | ## License
18 |
19 | Copyright © 2014 Lucas Leblow
20 |
--------------------------------------------------------------------------------
/src/cljs/lokate/settings.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.settings
2 | (:require [cljs.core.async :as async]
3 | [om.core :as om :include-macros true]
4 | [sablono.core :as html :refer-macros [html]]
5 | [lokate.util :as u]
6 | [lokate.components :as c]))
7 |
8 | (defn edit-setting [title setting owner]
9 | (c/display-input
10 | title
11 | ""
12 | (:value setting)
13 | #(async/put! (om/get-shared owner :event-bus)
14 | [:app :add-setting (:id setting) %])))
15 |
16 | (defn connect [title setting owner]
17 | (c/display-input
18 | title
19 | ""
20 | (:value setting)
21 | (fn [x]
22 | (async/put! (om/get-shared owner :event-bus)
23 | [:app :connect x]))))
24 |
25 | (defn map-settings [settings owner]
26 | (om/component
27 | (html [:div.border-block.frame
28 | [:div.settings-block-title.txt-wrap
29 | [:span "map"]]
30 | [:div.flex-col
31 | (c/title2
32 | "Tile url: "
33 | (:value (:tile-url settings))
34 | ""
35 | #(edit-setting "Tile url" (:tile-url settings) owner))
36 | (c/title2
37 | "Tile attr: "
38 | (:value (:tile-attr settings))
39 | ""
40 | #(edit-setting "Tile attr" (:tile-attr settings) owner))]])))
41 |
42 | (defn db-settings [settings owner]
43 | (om/component
44 | (html [:div.border-block.frame
45 | [:div.settings-block-title.txt-wrap
46 | [:span "storage"]]
47 | (c/title2
48 | "User address: "
49 | (:value (:user-address settings))
50 | ""
51 | #(connect "User address" (:user-address settings) owner))])))
52 |
53 | (defn settings-drawer-view [settings owner]
54 | (om/component
55 | (html [:div.flex-col
56 | (om/build map-settings settings)
57 | (om/build db-settings settings)])))
58 |
59 | (defn settings-views [{:keys [drawer]} data state]
60 | [(om/build c/drawer-nav-panel [drawer
61 | (c/title-return-banner "settings" #(u/route! % :home))])
62 | (om/build settings-drawer-view (u/get-settings data))])
63 |
--------------------------------------------------------------------------------
/resources/public/fonts/goog-material/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'icomoon';
3 | src:url('fonts/icomoon.eot?-9atxwj');
4 | src:url('fonts/icomoon.eot?#iefix-9atxwj') format('embedded-opentype'),
5 | url('fonts/icomoon.woff?-9atxwj') format('woff'),
6 | url('fonts/icomoon.ttf?-9atxwj') format('truetype'),
7 | url('fonts/icomoon.svg?-9atxwj#icomoon') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | [class^="icon-"], [class*=" icon-"] {
13 | font-family: 'icomoon';
14 | speak: none;
15 | font-style: normal;
16 | font-weight: normal;
17 | font-variant: normal;
18 | text-transform: none;
19 | line-height: 1;
20 |
21 | /* Better Font Rendering =========== */
22 | -webkit-font-smoothing: antialiased;
23 | -moz-osx-font-smoothing: grayscale;
24 | }
25 |
26 | .icon-exit-to-app:before {
27 | content: "\e629";
28 | }
29 |
30 | .icon-input:before {
31 | content: "\e630";
32 | }
33 |
34 | .icon-system-update-tv:before {
35 | content: "\e631";
36 | }
37 |
38 | .icon-lens:before {
39 | content: "\e616";
40 | }
41 |
42 | .icon-panorama-fisheye:before {
43 | content: "\e617";
44 | }
45 |
46 | .icon-radio-button-off:before {
47 | content: "\e618";
48 | }
49 |
50 | .icon-radio-button-on:before {
51 | content: "\e619";
52 | }
53 |
54 | .icon-done:before {
55 | content: "\e613";
56 | }
57 |
58 | .icon-done-all:before {
59 | content: "\e614";
60 | }
61 |
62 | .icon-settings:before {
63 | content: "\e615";
64 | }
65 |
66 | .icon-add:before {
67 | content: "\e628";
68 | }
69 |
70 | .icon-place:before {
71 | content: "\e621";
72 | }
73 |
74 | .icon-arrow-back:before {
75 | content: "\e622";
76 | }
77 |
78 | .icon-arrow-drop-down:before {
79 | content: "\e623";
80 | }
81 |
82 | .icon-expand-less:before {
83 | content: "\e624";
84 | }
85 |
86 | .icon-expand-more:before {
87 | content: "\e625";
88 | }
89 |
90 | .icon-fullscreen:before {
91 | content: "\e626";
92 | }
93 |
94 | .icon-fullscreen-exit:before {
95 | content: "\e627";
96 | }
97 |
98 | .icon-menu:before {
99 | content: "\e620";
100 | }
101 |
102 | .icon-navigate-before:before {
103 | content: "\e633";
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/src/cljs/lokate/db.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.db
2 | (:require [goog.object :as gobject]
3 | [clojure.string :as str]
4 | [lokate.util :as u]))
5 |
6 | (defn log-error [err]
7 | (.log js/console (str "[error] " err)))
8 |
9 | (defn def-lokate []
10 | (.defineModule js/RemoteStorage "lokate"
11 | (fn [privClient pubClient]
12 | (.declareType privClient "collection"
13 | (clj->js
14 | {:description "a collection of units"
15 | :type "object"}))
16 |
17 | (.declareType privClient "resource"
18 | (clj->js
19 | {:description "something that can be tracked by total count. ex: '# of beers in the fridge'"
20 | :type "object"}))
21 |
22 | (.declareType privClient "setting"
23 | (clj->js
24 | {:description "an app setting"
25 | :type "object"}))
26 |
27 | (.declareType privClient "history"
28 | (clj->js
29 | {:description "the commit stack for a particular unit"
30 | :type "array"}))
31 |
32 | (clj->js
33 | {:exports
34 | {:put (fn [type k v pass fail]
35 | (let [path (str type "/" (if (keyword? k) (name k) k))]
36 | (.storeObject privClient type path (clj->js v))))
37 |
38 | :get (fn [path pass fail]
39 | (if (u/ends-with? path "/")
40 | (-> privClient (.getAll path) (.then pass fail))
41 | (-> privClient (.getObject path) (.then pass fail))))
42 |
43 | :delete (fn [type k pass fail]
44 | (let [path (str type "/" (if (keyword? k) (name k) k))]
45 | (-> privClient (.remove path) (.then pass fail))))}}))))
46 |
47 | (defn connect [user-address]
48 | (if (str/blank? user-address)
49 | (.disconnect js/remoteStorage)
50 | (do
51 | (.disconnect js/remoteStorage)
52 | (.connect js/remoteStorage user-address))))
53 |
54 | (defn init []
55 | (def-lokate)
56 | (-> js/remoteStorage .-access (.claim "lokate" "rw")))
57 |
58 | (defn put [type k v]
59 | (-> js/remoteStorage
60 | .-lokate
61 | (.put type k v #() log-error)))
62 |
63 | (defn fetch
64 | ([path pass]
65 | (fetch path pass #()))
66 | ([path pass fail]
67 | (-> js/remoteStorage
68 | .-lokate
69 | (.get path pass fail))))
70 |
71 | (defn delete [type k]
72 | (-> js/remoteStorage
73 | .-lokate
74 | (.delete type k #() log-error)))
75 |
--------------------------------------------------------------------------------
/src/cljs/lokate/collections.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.collections
2 | (:require [cljs.core.async :as async]
3 | [om.core :as om :include-macros true]
4 | [sablono.core :as html :refer-macros [html]]
5 | [clojure.string :as str]
6 | [lokate.util :as u]
7 | [lokate.components :as c]))
8 |
9 | (def collection-tip
10 | [:p.collection-tip-msg
11 | "Right click or long press on the map to add a unit to your collection!"])
12 |
13 | (defn collection-banner []
14 | (c/title-return-banner "collection" #(u/route! % :collections)))
15 |
16 | (defn collection-nav-view [drawer]
17 | (om/build c/drawer-nav-panel [drawer (collection-banner)]))
18 |
19 | (defn edit-collection-name [collection]
20 | (c/display-input
21 | "Collection name"
22 | "Untitled collection"
23 | (:title collection)
24 | #(om/update! collection :title % :collection)))
25 |
26 | (defn collection-drawer-view [collection]
27 | [:div.flex-col.full
28 | (c/title1 (:title collection)
29 | "Collection Name"
30 | #(edit-collection-name collection))
31 | (if (empty? (:units collection))
32 | (c/tip collection-tip)
33 | (c/r-item-list {:action (fn [x evt-bus]
34 | (u/route! evt-bus :unit (:cid x) (:id x)))
35 | :remove-action (fn [x evt-bus]
36 | (om/transact! collection []
37 | #(update-in % [:units]
38 | dissoc (:id x)) :collection))
39 | :placeholder "Untitled_Unit"}
40 | (->> collection :units vals (sort-by :timestamp))))])
41 |
42 | (defn collections-banner []
43 | (c/title-return-banner "collections" #(u/route! % :home)))
44 |
45 | (defn add-collection-btn []
46 | (om/build c/btn ["icon-add" #(async/put! % [:app :add-collection])]))
47 |
48 | (defn collections-nav-view [drawer]
49 | (om/build c/drawer-nav-panel
50 | [drawer (collections-banner) [(add-collection-btn)]]))
51 |
52 | (defn collections-drawer-view [collections]
53 | (c/r-item-list
54 | {:action (fn [x evt-bus] (u/route! evt-bus :collection (:id x)))
55 | :remove-action (fn [x evt-bus]
56 | (async/put! evt-bus [:app :delete-collection (:id x)]))
57 | :placeholder "Untitled_Collection"}
58 | collections))
59 |
60 | (defn collections-views [{:keys [drawer]} data state]
61 | (let [collections (->> data
62 | u/get-collections
63 | vals
64 | (sort-by :timestamp))]
65 | [(collections-nav-view drawer)
66 | (collections-drawer-view collections)]))
67 |
68 | (defn collection-views [{:keys [location drawer]} data state]
69 | (let [collection (apply u/get-collection data (second location))]
70 | [(collection-nav-view drawer)
71 | (collection-drawer-view collection)]))
72 |
--------------------------------------------------------------------------------
/resources/public/css/leaflet.awesome-markers.css:
--------------------------------------------------------------------------------
1 | /*
2 | Author: L. Voogdt
3 | License: MIT
4 | Version: 1.0
5 | */
6 |
7 | /* Marker setup */
8 | .awesome-marker {
9 | background: url('images/markers-soft.png') no-repeat 0 0;
10 | width: 35px;
11 | height: 46px;
12 | position:absolute;
13 | left:0;
14 | top:0;
15 | display: block;
16 | text-align: center;
17 | }
18 |
19 | .awesome-marker-shadow {
20 | background: url('images/markers-shadow.png') no-repeat 0 0;
21 | width: 36px;
22 | height: 16px;
23 | }
24 |
25 | /* Retina displays */
26 | @media (min--moz-device-pixel-ratio: 1.5),(-o-min-device-pixel-ratio: 3/2),
27 | (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5),(min-resolution: 1.5dppx) {
28 | .awesome-marker {
29 | background-image: url('images/markers-soft@2x.png');
30 | background-size: 720px 46px;
31 | }
32 | .awesome-marker-shadow {
33 | background-image: url('images/markers-shadow@2x.png');
34 | background-size: 35px 16px;
35 | }
36 | }
37 |
38 | .awesome-marker i {
39 | color: #333;
40 | margin-top: 10px;
41 | display: inline-block;
42 | font-size: 14px;
43 | }
44 |
45 | .awesome-marker .icon-white {
46 | color: #fff;
47 | }
48 |
49 | /* Colors */
50 | .awesome-marker-icon-red {
51 | background-position: 0 0;
52 | }
53 |
54 | .awesome-marker-icon-darkred {
55 | background-position: -180px 0;
56 | }
57 |
58 | .awesome-marker-icon-lightred {
59 | background-position: -360px 0;
60 | }
61 |
62 | .awesome-marker-icon-orange {
63 | background-position: -36px 0;
64 | }
65 |
66 | .awesome-marker-icon-beige {
67 | background-position: -396px 0;
68 | }
69 |
70 | .awesome-marker-icon-green {
71 | background-position: -72px 0;
72 | }
73 |
74 | .awesome-marker-icon-darkgreen {
75 | background-position: -252px 0;
76 | }
77 |
78 | .awesome-marker-icon-lightgreen {
79 | background-position: -432px 0;
80 | }
81 |
82 | .awesome-marker-icon-blue {
83 | background-position: -108px 0;
84 | }
85 |
86 | .awesome-marker-icon-darkblue {
87 | background-position: -216px 0;
88 | }
89 |
90 | .awesome-marker-icon-lightblue {
91 | background-position: -468px 0;
92 | }
93 |
94 | .awesome-marker-icon-purple {
95 | background-position: -144px 0;
96 | }
97 |
98 | .awesome-marker-icon-darkpurple {
99 | background-position: -288px 0;
100 | }
101 |
102 | .awesome-marker-icon-pink {
103 | background-position: -504px 0;
104 | }
105 |
106 | .awesome-marker-icon-cadetblue {
107 | background-position: -324px 0;
108 | }
109 |
110 | .awesome-marker-icon-white {
111 | background-position: -574px 0;
112 | }
113 |
114 | .awesome-marker-icon-gray {
115 | background-position: -648px 0;
116 | }
117 |
118 | .awesome-marker-icon-lightgray {
119 | background-position: -612px 0;
120 | }
121 |
122 | .awesome-marker-icon-black {
123 | background-position: -682px 0;
124 | }
125 |
--------------------------------------------------------------------------------
/src/cljs/lokate/util.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.util
2 | (:require [cljs.core.async :as async]
3 | [om.core :as om :include-macros true]
4 | [clojure.string :as string]
5 | [goog.string :as gstring]
6 | [goog.string.format]
7 | [cljs-uuid-utils :as uuid]
8 | [cljs-time.core :as time]
9 | [cljs-time.coerce :refer [to-long]])
10 | (:require-macros [cljs.core.async.macros :refer [go-loop]]))
11 |
12 | ;; add to system data
13 | (def status-colors
14 | {"green" "#bbf970"
15 | "yellow" "#ffc991"
16 | "red" "#ff8e7f"})
17 |
18 | (defn uuid []
19 | (str (uuid/make-random-uuid)))
20 |
21 | (defn now []
22 | (to-long (time/now)))
23 |
24 | (defn format [& args]
25 | (apply gstring/format args))
26 |
27 | (defn display [show]
28 | (if show
29 | #js {}
30 | #js {:display "none"}))
31 |
32 | (defn fade-in [show]
33 | (if show
34 | #js {:opacity 1
35 | :transition "opacity .3s"}
36 | #js {:opacity 0}))
37 |
38 | ; switch to haversine
39 | (defn distance
40 | "Euclidean distance between 2 points"
41 | [pos1 pos2]
42 | (Math/pow (+ (Math/pow (- (:lat pos1) (:lat pos2)) 2)
43 | (Math/pow (- (:lng pos1) (:lng pos2)) 2))
44 | 0.5))
45 |
46 | (defn blankf [s]
47 | (when (not (string/blank? s)) s))
48 |
49 | (defn mmap [f m]
50 | (into {} (for [[k v] m]
51 | [k (f v)])))
52 |
53 | (defn mfilter [f m]
54 | (select-keys m (for [[k v] m :when (f v)] k)))
55 |
56 | (defn keyset [m]
57 | (set (keys m)))
58 |
59 | (defn sub-go-loop [ch topic fun]
60 | (let [events (async/sub ch topic (async/chan))]
61 | (go-loop [e ( rsc
115 | :type
116 | (= "block")))
117 |
--------------------------------------------------------------------------------
/src/cljs/lokate/resources.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.resources
2 | (:require [cljs.core.async :as async]
3 | [om.core :as om :include-macros true]
4 | [sablono.core :as html :refer-macros [html]]
5 | [clojure.string :as str]
6 | [lokate.util :as u]
7 | [lokate.components :as c]))
8 |
9 | (defn rscs-nav-view [drawer]
10 | (om/build c/drawer-nav-panel
11 | [drawer
12 | (c/title-return-banner "resources" #(u/route! % :home))
13 | [(om/build c/btn ["icon-flow-tree rsc-btn"
14 | #(async/put! % [:app :add-resource "block"])])
15 | (om/build c/btn ["icon-flow-line rsc-btn"
16 | #(async/put! % [:app :add-resource])])]]))
17 |
18 | (defn get-rsc-view-data [rsc]
19 | (if (= (:type rsc) "block")
20 | {:icon "icon-flow-tree"
21 | :title (or (u/blankf (:title rsc)) "Untitled_Resource_Block")}
22 | {:icon "icon-flow-line"
23 | :title (or (u/blankf (:title rsc)) "Untitled_Resource")}))
24 |
25 | (defn rscs-drawer-view [view-data rscs]
26 | (let [rscs (map #(->> % get-rsc-view-data (merge %)) rscs)]
27 | (c/r-item-list
28 | {:action #(om/update! view-data :selected (:id %))
29 | :icon-class "icon-flow-line item-icon"
30 | :remove-action (fn [x evt-bus]
31 | (async/put! evt-bus [:app :delete-resource (:id x)]))}
32 | rscs)))
33 |
34 | (defn rsc-nav-view [drawer view-data]
35 | (om/build c/drawer-nav-panel
36 | [drawer
37 | (c/title-return-banner "resources" #(om/transact! view-data
38 | (fn [m] (dissoc m :selected))))]))
39 |
40 | (defn rsc-drawer-view [resource]
41 | (c/title1 (:title resource) "Untitled Resource"))
42 |
43 | (defn toggle-rsc [rsc-block rsc evt-bus]
44 | (om/transact! rsc-block [:resources]
45 | (if (:active rsc)
46 | #(dissoc % (:id rsc))
47 | #(assoc % (:id rsc) rsc)) :resource))
48 |
49 | (defn toggle-rsc [rscs rsc]
50 | (if (:active rsc)
51 | (dissoc rscs (:id rsc))
52 | (assoc rscs (:id rsc) (dissoc rsc :active))))
53 |
54 | (defn rsc-block-drawer-view [rsc-block rscs]
55 | (let [active? #(contains? (:resources rsc-block) (:id %))
56 | rscs (map #(assoc % :active (active? %)) rscs)]
57 | [:div.flex-col
58 | (c/title1 (:title rsc-block) "Untitled Resource Block")
59 | (c/select-list
60 | {:id "unit-edit-rscs"
61 | :class "border-select-"
62 | :action (fn [x evt-bus]
63 | (om/transact! rsc-block [:resources]
64 | #(toggle-rsc % x) :resource))
65 | :placeholder "Untitled_Resource"}
66 | rscs)]))
67 |
68 | (defn resources-views [{:keys [drawer]} data {:keys [selected] :as state}]
69 | (let [rscs (u/get-resources data)]
70 | (if-let [rsc (get rscs selected)]
71 | (if (= (:type rsc) "block")
72 | [(rsc-nav-view drawer state)
73 | (rsc-block-drawer-view rsc (->> (dissoc rscs (:id rsc))
74 | vals
75 | (filter (comp not :type))
76 | (sort-by :timestamp)))]
77 | [(rsc-nav-view drawer state)
78 | (rsc-drawer-view rsc)])
79 | [(rscs-nav-view drawer)
80 | (rscs-drawer-view state (->> rscs
81 | vals
82 | (sort-by :timestamp)))])))
83 |
--------------------------------------------------------------------------------
/resources/public/dist/leaflet.awesome-markers.js:
--------------------------------------------------------------------------------
1 | /*
2 | Leaflet.AwesomeMarkers, a plugin that adds colorful iconic markers for Leaflet, based on the Font Awesome icons
3 | (c) 2012-2013, Lennard Voogdt
4 |
5 | http://leafletjs.com
6 | https://github.com/lvoogdt
7 | */
8 |
9 | /*global L*/
10 |
11 | (function (window, document, undefined) {
12 | "use strict";
13 | /*
14 | * Leaflet.AwesomeMarkers assumes that you have already included the Leaflet library.
15 | */
16 |
17 | L.AwesomeMarkers = {};
18 |
19 | L.AwesomeMarkers.version = '2.0.1';
20 |
21 | L.AwesomeMarkers.Icon = L.Icon.extend({
22 | options: {
23 | iconSize: [35, 45],
24 | iconAnchor: [17, 42],
25 | popupAnchor: [1, -32],
26 | shadowAnchor: [10, 12],
27 | shadowSize: [36, 16],
28 | className: 'awesome-marker',
29 | prefix: 'glyphicon',
30 | spinClass: 'fa-spin',
31 | extraClasses: '',
32 | icon: 'home',
33 | markerColor: 'blue',
34 | iconColor: 'white'
35 | },
36 |
37 | initialize: function (options) {
38 | options = L.Util.setOptions(this, options);
39 | },
40 |
41 | createIcon: function () {
42 | var div = document.createElement('div'),
43 | options = this.options;
44 |
45 | if (options.icon) {
46 | div.innerHTML = this._createInner();
47 | }
48 |
49 | if (options.bgPos) {
50 | div.style.backgroundPosition =
51 | (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
52 | }
53 |
54 | this._setIconStyles(div, 'icon-' + options.markerColor);
55 | return div;
56 | },
57 |
58 | _createInner: function() {
59 | var iconClass, iconSpinClass = "", iconColorClass = "", iconColorStyle = "", options = this.options;
60 |
61 | if(options.icon.slice(0,options.prefix.length+1) === options.prefix + "-") {
62 | iconClass = options.icon;
63 | } else {
64 | iconClass = options.prefix + "-" + options.icon;
65 | }
66 |
67 | if(options.spin && typeof options.spinClass === "string") {
68 | iconSpinClass = options.spinClass;
69 | }
70 |
71 | if(options.iconColor) {
72 | if(options.iconColor === 'white' || options.iconColor === 'black') {
73 | iconColorClass = "icon-" + options.iconColor;
74 | } else {
75 | iconColorStyle = "style='color: " + options.iconColor + "' ";
76 | }
77 | }
78 |
79 | return "";
80 | },
81 |
82 | _setIconStyles: function (img, name) {
83 | var options = this.options,
84 | size = L.point(options[name === 'shadow' ? 'shadowSize' : 'iconSize']),
85 | anchor;
86 |
87 | if (name === 'shadow') {
88 | anchor = L.point(options.shadowAnchor || options.iconAnchor);
89 | } else {
90 | anchor = L.point(options.iconAnchor);
91 | }
92 |
93 | if (!anchor && size) {
94 | anchor = size.divideBy(2, true);
95 | }
96 |
97 | img.className = 'awesome-marker-' + name + ' ' + options.className;
98 |
99 | if (anchor) {
100 | img.style.marginLeft = (-anchor.x) + 'px';
101 | img.style.marginTop = (-anchor.y) + 'px';
102 | }
103 |
104 | if (size) {
105 | img.style.width = size.x + 'px';
106 | img.style.height = size.y + 'px';
107 | }
108 | },
109 |
110 | createShadow: function () {
111 | var div = document.createElement('div');
112 |
113 | this._setIconStyles(div, 'shadow');
114 | return div;
115 | }
116 | });
117 |
118 | L.AwesomeMarkers.icon = function (options) {
119 | return new L.AwesomeMarkers.Icon(options);
120 | };
121 |
122 | }(this, document));
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/resources/public/css/icons.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'icomoon';
3 | src:url('/fonts/icomoon/icomoon.eot?q1s9kp');
4 | src:url('/fonts/icomoon/icomoon.eot?#iefixq1s9kp') format('embedded-opentype'),
5 | url('/fonts/icomoon/icomoon.woff?q1s9kp') format('woff'),
6 | url('/fonts/icomoon/icomoon.ttf?q1s9kp') format('truetype'),
7 | url('/fonts/icomoon/icomoon.svg?q1s9kp#icomoon') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | @font-face {
13 | font-family: 'goog-material';
14 | src: url('/fonts/goog-material/icomoon.woff?w1eobz') format('woff'),
15 | url('/fonts/goog-material/icomoon.svg?w1eobz#icomoon') format('svg');
16 | font-weight: normal;
17 | font-style: normal;
18 | }
19 |
20 | [class^="icon-"], [class*=" icon-"] {
21 | font-family: 'icomoon', 'goog-material';
22 | speak: none;
23 | font-style: normal;
24 | font-weight: normal;
25 | font-variant: normal;
26 | text-transform: none;
27 | line-height: 1;
28 |
29 | /* Better Font Rendering =========== */
30 | -webkit-font-smoothing: antialiased;
31 | -moz-osx-font-smoothing: grayscale;
32 | }
33 |
34 | .icon-in-alt:before {
35 | content: "\e600";
36 | }
37 |
38 | .icon-googleplus:before {
39 | content: "\e605";
40 | }
41 |
42 | .icon-pin:before {
43 | content: "\e606";
44 | }
45 |
46 | .icon-pin-alt:before {
47 | content: "\e607";
48 | }
49 |
50 | .icon-location:before {
51 | content: "\e608";
52 | }
53 |
54 | .icon-login:before {
55 | content: "\e601";
56 | }
57 |
58 | .icon-checkmark:before {
59 | content: "\e602";
60 | }
61 |
62 | .icon-plus:before {
63 | content: "\e609";
64 | }
65 |
66 | .icon-resize-enlarge:before {
67 | content: "\e60b";
68 | }
69 |
70 | .icon-resize-shrink:before {
71 | content: "\e60c";
72 | }
73 |
74 | .icon-flow-tree:before {
75 | content: "\e60d";
76 | }
77 |
78 | .icon-flow-line:before {
79 | content: "\e60e";
80 | }
81 |
82 | .icon-flow-parallel:before {
83 | content: "\e60f";
84 | }
85 |
86 | .icon-arrow-right-circle:before {
87 | content: "\e603";
88 | }
89 |
90 | .icon-arrow-left:before {
91 | content: "\e60a";
92 | }
93 |
94 | .icon-arrow-right:before {
95 | content: "\e604";
96 | }
97 |
98 | .icon-ellipsis:before {
99 | content: "\e610";
100 | }
101 |
102 | .icon-dots:before {
103 | content: "\e611";
104 | }
105 |
106 | .icon-dot:before {
107 | content: "\e612";
108 | }
109 |
110 | .icon-box-outline:before {
111 | content: "\f372";
112 | }
113 |
114 | .icon-box-filled:before {
115 | content: "\f371";
116 | }
117 |
118 | .icon-exit-to-app:before {
119 | content: "\e629";
120 | }
121 |
122 | .icon-input:before {
123 | content: "\e630";
124 | }
125 |
126 | .icon-system-update-tv:before {
127 | content: "\e631";
128 | }
129 |
130 | .icon-lens:before {
131 | content: "\e616";
132 | }
133 |
134 | .icon-panorama-fisheye:before {
135 | content: "\e617";
136 | }
137 |
138 | .icon-radio-button-off:before {
139 | content: "\e618";
140 | }
141 |
142 | .icon-radio-button-on:before {
143 | content: "\e619";
144 | }
145 |
146 | .icon-done:before {
147 | content: "\e613";
148 | }
149 |
150 | .icon-done-all:before {
151 | content: "\e614";
152 | }
153 |
154 | .icon-settings:before {
155 | content: "\e615";
156 | }
157 |
158 | .icon-add:before {
159 | content: "\e628";
160 | }
161 |
162 | .icon-place:before {
163 | content: "\e621";
164 | }
165 |
166 | .icon-arrow-back:before {
167 | content: "\e622";
168 | }
169 |
170 | .icon-arrow-drop-down:before {
171 | content: "\e623";
172 | }
173 |
174 | .icon-expand-less:before {
175 | content: "\e624";
176 | }
177 |
178 | .icon-expand-more:before {
179 | content: "\e625";
180 | }
181 |
182 | .icon-fullscreen:before {
183 | content: "\e626";
184 | }
185 |
186 | .icon-fullscreen-exit:before {
187 | content: "\e627";
188 | }
189 |
190 | .icon-menu:before {
191 | content: "\e620";
192 | }
193 |
194 | .icon-navigate-before:before {
195 | content: "\e633";
196 | }
197 |
198 | .icon-home:before {
199 | content: "\2B21";
200 | }
201 |
202 | .t {
203 | border-radius: 50%;
204 | }
205 |
206 | .t3 {
207 | background-color: rgb(150, 33, 255);;
208 | width: 5px;
209 | height: 5px;
210 | position: absolute;
211 | top: 25px;
212 | right: 23px;
213 | }
214 |
215 | .t2 {
216 | background-color: rgb(33, 150, 255);;
217 | height: 11px;
218 | width: 11px;
219 | position: absolute;
220 | top: 20px;
221 | right: 18px;
222 | }
223 |
224 | .t1 {
225 | background-color: rgba(33, 255, 150, 0.9);;
226 | height: 20px;
227 | width: 20px;
228 | margin-left: 2px;
229 | position: relative;
230 | top: -0.2px;
231 | }
232 |
--------------------------------------------------------------------------------
/src/cljs/lokate/check_in.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.check-in
2 | (:require [cljs.core.async :as async]
3 | [om.core :as om :include-macros true]
4 | [om.dom :as dom :include-macros true]
5 | [sablono.core :as html :refer-macros [html]]
6 | [clojure.string :as str]
7 | [lokate.util :as u]
8 | [lokate.components :as c]))
9 |
10 | (defn status-select
11 | [{:keys [color]} owner opts]
12 | (reify
13 | om/IRenderState
14 | (render-state [_ {:keys [active]}]
15 | (html [:div {:class (str "status-select-wrapper"
16 | (when (= active color) " active"))}
17 | [:div {:class "status-select clickable icon-pin"
18 | :style #js {:color (get u/status-colors color)}
19 | :on-click #(async/put! (:event-bus (om/get-shared owner))
20 | [:window :update :commit (fn [m]
21 | (assoc-in m [:data :status] color))])}]]))))
22 |
23 | (defn scroll-to-active []
24 | (let [active (.-activeElement js/document)]
25 | (when (= (str/lower-case (.-tagName active)) "input")
26 | (set! (.-scrollTop (.-offsetParent active)) (- (.-offsetTop active) 14)))))
27 |
28 | (defn unit-resource-editable
29 | [[props resource] owner]
30 | (reify
31 | om/IWillMount
32 | (will-mount [_]
33 | (def timer)
34 | (.addEventListener js/window "resize"
35 | (fn []
36 | ;; wait until we have stopped resizing
37 | (and timer (.clearTimeout js/window timer))
38 | (def timer (.setTimeout js/window
39 | scroll-to-active
40 | 100)))))
41 |
42 | om/IDidMount
43 | (did-mount [_]
44 | ((:on-mount props))
45 | (.addEventListener (om/get-node owner "input") "focus" scroll-to-active))
46 |
47 | om/IWillUnmount
48 | (will-unmount [_]
49 | (.removeEventListener js/window "resize" scroll-to-active))
50 |
51 | om/IRender
52 | (render [_]
53 | (html [:div.unit-resource
54 | [:span.unit-resource-title (:title resource)]
55 | [:div.unit-resource-count-box
56 | [:input.unit-resource-count-input
57 | {:ref "input"
58 | :type "number"
59 | :min 0
60 | :max 100
61 | :value (:count resource)
62 | :on-change #(async/put! (:event-bus (om/get-shared owner))
63 | [:window :update :commit (fn [m]
64 | (assoc-in m [:data :resources (:id resource) :count]
65 | (.-value (om/get-node owner "input"))))])}]]]))))
66 |
67 | (defn check-in-rscs-view
68 | [resources owner]
69 | (reify
70 | om/IWillMount
71 | (will-mount [_]
72 | (async/put! (:event-bus (om/get-shared owner))
73 | [:drawer :set :maximized? true]))
74 |
75 | om/IRender
76 | (render [_]
77 | (let [resources (->> resources vals (sort-by :timestamp))]
78 | (om/build c/input-list [{:id "check-in-rscs-list"
79 | :item-comp unit-resource-editable}
80 | resources])))))
81 |
82 | (defn check-in-commit-view
83 | [commit owner]
84 | (reify
85 | om/IWillMount
86 | (will-mount [_]
87 | (async/put! (:event-bus (om/get-shared owner))
88 | [:drawer :set :maximized? true]))
89 |
90 | om/IWillUnmount
91 | (will-unmount [_]
92 | (async/put! (:event-bus (om/get-shared owner))
93 | [:drawer :set :maximized? false]))
94 |
95 | om/IRender
96 | (render [_]
97 | (html [:div.flex-col.full.frame
98 | [:div#commit
99 | [:div#commit-status-wrapper
100 | (om/build-all status-select [{:color "red"}
101 | {:color "yellow"}
102 | {:color "green"}]
103 | {:state {:active (-> commit :data :status)}})]
104 | [:textarea.multi-line-input
105 | {:ref "input"
106 | :placeholder "Add an optional commit message..."
107 | :on-change #(om/update! commit :message
108 | (.-value (om/get-node owner "input")))}]]]))))
109 |
110 | (defn check-in-rscs-nav [unit]
111 | (om/build c/simple-nav-panel
112 | [(c/done!-btn
113 | (fn [evt-bus]
114 | (async/put! evt-bus [:window :set :page :commit])))]))
115 |
116 | (defn check-in-commit-nav [unit commit]
117 | (om/build c/simple-nav-panel
118 | [(om/build c/btn ["icon-done-all" (fn [evt-bus]
119 | (async/put! evt-bus [:app :commit! (:cid unit) (:id unit) @commit])
120 | (u/route! evt-bus :unit (:cid unit) (:id unit)))])]))
121 |
122 | (defn check-in-views
123 | [{:keys [location]} data {:keys [page commit]}]
124 | (let [unit (apply u/get-unit data (second location))
125 | rsc-types (u/get-resources data)]
126 | (case page
127 | :resources [(check-in-rscs-nav unit)
128 | (om/build check-in-rscs-view (-> commit :data :resources))]
129 | :commit [(check-in-commit-nav unit commit)
130 | (om/build check-in-commit-view commit)])))
131 |
--------------------------------------------------------------------------------
/resources/public/fonts/goog-material/icomoon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/cljs/lokate/map.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.map
2 | (:require [cljs.core.async :as async]
3 | [om.core :as om :include-macros true]
4 | [om.dom :as dom :include-macros true]
5 | [clojure.set :as set]
6 | [lokate.util :as u]))
7 |
8 | (-> js/L .-AwesomeMarkers .-Icon .-prototype .-options .-prefix (set! "icon"))
9 |
10 | (def green-ico (-> js/L .-AwesomeMarkers (.icon #js {:icon "radio-button-off"
11 | :markerColor "lightgreen"
12 | :iconColor "#444444"})))
13 | (def yellow-ico (-> js/L .-AwesomeMarkers (.icon #js {:icon "radio-button-off"
14 | :markerColor "beige"
15 | :iconColor "#444444"})))
16 | (def red-ico (-> js/L .-AwesomeMarkers (.icon #js {:icon "radio-button-off"
17 | :markerColor "lightred"
18 | :iconColor "#444444"})))
19 |
20 | (def ico-map
21 | {"green" green-ico
22 | "yellow" yellow-ico
23 | "red" red-ico})
24 |
25 | (defn reset-ico [icon]
26 | (-> icon .-options .-icon (set! "radio-button-off"))
27 | icon)
28 |
29 | (defn activate-ico [icon]
30 | (-> icon .-options .-icon (set! "radio-button-on"))
31 | icon)
32 |
33 | (defn reset-markers [owner]
34 | (let [units (om/get-state owner :units)]
35 | (dorun
36 | (map #(.setIcon (:marker %) (reset-ico (:icon %))) (vals units)))))
37 |
38 | (defn activate-marker
39 | [owner id]
40 | (let [l-map (om/get-state owner :map)
41 | active (om/get-state owner [:units id])]
42 | (when active
43 | (do (.setIcon (:marker active) (activate-ico (:icon active)))
44 | (.panTo l-map (.getLatLng (:marker active)))))))
45 |
46 | (defn mark-it!
47 | [unit lmap evt-bus]
48 | (let [icon (get ico-map (:status unit))
49 | marker (-> js/L
50 | (.marker (clj->js (:latlng unit)) #js {:icon icon})
51 | (.addTo lmap))]
52 |
53 | (.on marker "click"
54 | #(do
55 | (async/put! evt-bus [:drawer :set :open? true])
56 | (u/route! evt-bus :unit (:cid unit) (:id unit) :info)))
57 |
58 | (assoc unit :marker marker :icon icon)))
59 |
60 | (defn add-markers [units owner]
61 | (let [lmap (om/get-state owner :map)
62 | evt-bus (om/get-shared owner :event-bus)]
63 | (om/update-state! owner :units
64 | (fn [m] (merge m (u/mmap #(mark-it! % lmap evt-bus) units))))))
65 |
66 | (defn delete-markers [owner keys]
67 | (let [l-map (om/get-state owner :map)]
68 | (dorun
69 | (map #(->>
70 | (om/get-state owner [:units % :marker])
71 | (.removeLayer l-map))
72 | keys)))
73 | (om/update-state! owner :markers
74 | #(apply dissoc % keys)))
75 |
76 | (defn add-group [owner places opts]
77 | (map #({:name (:name %) :group (js/L.MarkerClusterGroup.)})))
78 |
79 | (defn add-unit [evt-bus evt]
80 | (async/put! evt-bus
81 | [:app :add-unit [(.-lat (.-latlng evt))
82 | (.-lng (.-latlng evt))]]))
83 |
84 | (defn init-map [tile-url tile-attr owner]
85 | (when-let [l-map (om/get-state owner :map)]
86 | (.remove l-map))
87 | (let [l-map (-> js/L
88 | (.map "map"
89 | (clj->js {:zoomControl false
90 | :contextmenu true
91 | :contextmenuWidth 140
92 | :contextmenuAnchor [-70 -35]
93 | :contextmenuItems [
94 | {:text "Add unit"
95 | :iconCls "icon-pin"
96 | :callback #(add-unit (om/get-shared owner :event-bus) %)}
97 | ]}))
98 | (.setView (om/get-state owner :center) 9))]
99 |
100 | (-> l-map .-contextmenu .removeHooks)
101 |
102 | (-> js/L
103 | (.tileLayer (:value tile-url) #js {:attribution (:value tile-attr)})
104 | (.addTo l-map))
105 |
106 | (.on l-map "contextmenu" #())
107 |
108 | (if navigator.geolocation
109 | (.getCurrentPosition navigator.geolocation
110 | (fn [pos]
111 | (let [initialLoc #js [(.-coords.latitude pos)
112 | (.-coords.longitude pos)]]
113 | (.setView l-map initialLoc 9))))
114 | (println "Hey, where'd you go!? Geolocation Disabled."))
115 |
116 | (om/set-state! owner :map l-map)))
117 |
118 | (defn l-map [[path units {:keys [tile-url tile-attr]}] owner]
119 | (reify
120 | om/IInitState
121 | (init-state [_]
122 | {:center #js [0 0]
123 | :evt-timeout nil
124 | :units {}
125 | :map nil})
126 |
127 | om/IWillReceiveProps
128 | (will-receive-props [this [path units {:keys [tile-url tile-attr]}]]
129 | (let [current-url (get-in (om/get-props owner) [2 :tile-url])
130 | current-attr (get-in (om/get-props owner) [2 :tile-attr])
131 | next-units (set units)
132 | current-units (set (second (om/get-props owner)))
133 | to-add (set/difference next-units current-units)
134 | to-delete (set/difference current-units next-units)]
135 |
136 | (when-not (and (= current-url tile-url) (= current-attr tile-attr))
137 | (init-map tile-url tile-attr owner)
138 | (add-markers units owner))
139 |
140 | (delete-markers owner (keys to-delete))
141 | (add-markers to-add owner)
142 | (reset-markers owner)
143 |
144 | (let [[route args] path]
145 | (when (= route :unit)
146 | (let [[cid uid page] args]
147 | (activate-marker owner uid))))
148 |
149 | ; if a collection is selected, allow map context menu
150 | (let [cm (.-contextmenu (om/get-state owner :map))]
151 | (if (= (first path) :collection)
152 | (.addHooks cm)
153 | (.removeHooks cm)))))
154 |
155 | om/IDidMount
156 | (did-mount [_]
157 | (init-map tile-url tile-attr owner)
158 | (add-markers units owner))
159 |
160 | om/IRenderState
161 | (render-state [_ {:keys [markers]}]
162 | (dom/div #js {:id "map"}))))
163 |
--------------------------------------------------------------------------------
/resources/public/js/leaflet/leaflet.contextmenu.js:
--------------------------------------------------------------------------------
1 | /*
2 | Leaflet.contextmenu, A context menu for Leaflet.
3 | (c) 2014, Adam Ratcliffe, TomTom International BV
4 | */
5 | L.Map.mergeOptions({contextmenuItems:[]});L.Map.ContextMenu=L.Handler.extend({statics:{BASE_CLS:"leaflet-contextmenu"},initialize:function(map){L.Handler.prototype.initialize.call(this,map);this._items=[];this._visible=false;var container=this._container=L.DomUtil.create("div",L.Map.ContextMenu.BASE_CLS,map._container);container.style.zIndex=1e4;container.style.position="absolute";if(map.options.contextmenuWidth){container.style.width=map.options.contextmenuWidth+"px"}this._createItems();L.DomEvent.on(container,"click",L.DomEvent.stop).on(container,"mousedown",L.DomEvent.stop).on(container,"dblclick",L.DomEvent.stop).on(container,"contextmenu",L.DomEvent.stop)},addHooks:function(){L.DomEvent.on(document,L.Browser.touch?"touchstart":"mousedown",this._onMouseDown,this).on(document,"keydown",this._onKeyDown,this);this._map.on({contextmenu:this._show,mouseout:this._hide,mousedown:this._hide,movestart:this._hide,zoomstart:this._hide},this)},removeHooks:function(){L.DomEvent.off(document,"keydown",this._onKeyDown,this);this._map.off({contextmenu:this._show,mouseout:this._hide,mousedown:this._hide,movestart:this._hide,zoomstart:this._hide},this)},showAt:function(point,data){if(point instanceof L.LatLng){point=this._map.latLngToContainerPoint(point)}this._showAtPoint(point,data)},hide:function(){this._hide()},addItem:function(options){return this.insertItem(options)},insertItem:function(options,index){index=index!==undefined?index:this._items.length;var item=this._createItem(this._container,options,index);this._items.push(item);this._sizeChanged=true;this._map.fire("contextmenu.additem",{contextmenu:this,el:item.el,index:index});return item.el},removeItem:function(item){var container=this._container;if(!isNaN(item)){item=container.children[item]}if(item){this._removeItem(L.Util.stamp(item));this._sizeChanged=true;this._map.fire("contextmenu.removeitem",{contextmenu:this,el:item})}},removeAllItems:function(){var item;while(this._container.children.length){item=this._container.children[0];this._removeItem(L.Util.stamp(item))}},hideAllItems:function(){var item,i,l;for(i=0,l=this._items.length;i'}else if(options.iconCls){html=''}el.innerHTML=html+options.text;el.href="#";L.DomEvent.on(el,"mouseover",this._onItemMouseOver,this).on(el,"mouseout",this._onItemMouseOut,this).on(el,"mousedown",L.DomEvent.stopPropagation).on(el,"click",callback);return{id:L.Util.stamp(el),el:el,callback:callback}},_removeItem:function(id){var item,el,i,l;for(i=0,l=this._items.length;imapSize.x){container.style.left="auto";container.style.right=Math.max(mapSize.x-pt.x,0)+"px"}else{container.style.left=Math.max(pt.x,0)+"px";container.style.right="auto"}if(pt.y+containerSize.y>mapSize.y){container.style.top="auto";container.style.bottom=Math.max(mapSize.y-pt.y,0)+"px"}else{container.style.top=Math.max(pt.y,0)+"px";container.style.bottom="auto"}},_getElementSize:function(el){var size=this._size,initialDisplay=el.style.display;if(!size||this._sizeChanged){size={};el.style.left="-999999px";el.style.right="auto";el.style.display="block";size.x=el.offsetWidth;size.y=el.offsetHeight;el.style.left="auto";el.style.display=initialDisplay;this._sizeChanged=false}return size},_onMouseDown:function(e){this._hide()},_onKeyDown:function(e){var key=e.keyCode;if(key===27){this._hide()}},_onItemMouseOver:function(e){L.DomUtil.addClass(e.target||e.srcElement,"over")},_onItemMouseOut:function(e){L.DomUtil.removeClass(e.target||e.srcElement,"over")}});L.Map.addInitHook("addHandler","contextmenu",L.Map.ContextMenu);L.Mixin.ContextMenu={_initContextMenu:function(){this._items=[];this.on("contextmenu",this._showContextMenu,this)},_showContextMenu:function(e){var itemOptions,pt,i,l;if(this._map.contextmenu){pt=this._map.mouseEventToContainerPoint(e.originalEvent);if(!this.options.contextmenuInheritItems){this._map.contextmenu.hideAllItems()}for(i=0,l=this.options.contextmenuItems.length;i
2 |
3 |
--------------------------------------------------------------------------------
/src/cljs/lokate/unit.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.unit
2 | (:require [cljs.core.async :as async]
3 | [om.core :as om :include-macros true]
4 | [sablono.core :as html :refer-macros [html]]
5 | [cljs-time.coerce :refer [from-long to-local-date-time]]
6 | [cljs-time.format :as format]
7 | [lokate.util :as u]
8 | [lokate.components :as c]))
9 |
10 | (defn format-date [timestamp]
11 | (->> timestamp
12 | from-long
13 | to-local-date-time
14 | (format/unparse (format/formatter "dd/MM/yyyy"))))
15 |
16 | (defn format-time [timestamp]
17 | (->> timestamp
18 | from-long
19 | to-local-date-time
20 | (format/unparse (format/formatter "HH:mm:ss"))))
21 |
22 | (defn format-latlng [latlng]
23 | (apply u/format "%1.2f %2.2f" latlng))
24 |
25 | (defn set-unit [unit k v]
26 | (om/transact! unit [] (fn [m] (assoc m k v)) :collection))
27 |
28 | (defn update-unit [unit k fun]
29 | (om/transact! unit [] (fn [m] (update-in m [k] fun)) :collection))
30 |
31 | (defn check-in-btn [unit]
32 | (om/build c/btn ["icon-system-update-tv"
33 | #(u/route! % :check-in (:cid unit) (:id unit))]))
34 |
35 | (defn edit-resources-btn [unit]
36 | (om/build c/btn ["icon-settings"
37 | #(async/put! % [:window :set :page :edit])]))
38 |
39 | (defn unit-nav-menu [page unit]
40 | (om/build c/dropdown-select-list
41 | [{:title "unit :info"
42 | :page :info
43 | :active (= :info page)}
44 | {:title "unit :rscs"
45 | :page :resources
46 | :active (= :resources page)}]
47 | {:opts {:id "page-select"
48 | :class "title-select-"
49 | :action (fn [x evt-bus]
50 | (async/put! evt-bus
51 | [:window :set :page (:page x)]))}}))
52 |
53 | (defn unit-nav-view [drawer page unit controls]
54 | (om/build c/drawer-nav-panel
55 | [drawer
56 | (om/build c/return-banner [(unit-nav-menu page unit)
57 | #(u/route! % :collection (:cid unit))])
58 | controls]))
59 |
60 | (defn edit-unit-name [unit]
61 | (c/display-input
62 | "Unit name"
63 | "Untitled unit"
64 | (:title unit)
65 | #(set-unit unit :title %)))
66 |
67 | (defn origin [timestamp]
68 | [:div.origin
69 | [:span.info-title "Created: "]
70 | (format-date timestamp)])
71 |
72 | (defn location [latlng status]
73 | [:div.location
74 | [:span.location-lat-lng
75 | [:span.info-title "Location: "]
76 | (format-latlng latlng)]
77 | [:span
78 | {:class "img icon-pin status"
79 | :style #js {:color (get u/status-colors status)}}]])
80 |
81 | (defn last-check-in [last-commit]
82 | [:div.last-check-in
83 | [:span.info-title "Last check-in: "]
84 | [:div.last-check-in-data
85 | [:div.last-commit-time
86 | [:span.info-title " Authored on "]
87 | (format-date (:timestamp last-commit))
88 | [:span.info-title " @ "]
89 | (format-time (:timestamp last-commit))]
90 | (when-let [msg (u/blankf (:message last-commit))]
91 | [:div.last-commit-msg [:span.hilight "> "] msg])]])
92 |
93 | (defn unit-info-view [unit last-commit]
94 | [:div.flex-col
95 | (c/title1 (:title unit)
96 | "Unit Name"
97 | #(edit-unit-name unit))
98 | (origin (:timestamp unit))
99 | (location (:latlng unit) (:status unit))
100 | (when last-commit (last-check-in last-commit))])
101 |
102 | (defn unit-resource
103 | [[props resource] owner]
104 | (om/component
105 | (html [:div.unit-resource
106 | [:span.unit-resource-title (-> resource
107 | :title
108 | u/blankf
109 | (or (:placeholder props)))]
110 | [:div.unit-resource-count-box
111 | [:span.unit-resource-count (:count resource)]]])))
112 |
113 | (defn unit-resources-view [unit rscs]
114 | (let [resources (->> unit :resources vals (sort-by :timestamp))]
115 | (om/build c/simple-list [{:id "unit-rscs"
116 | :item-comp unit-resource
117 | :placeholder "Untitled_Resource"}
118 | resources])))
119 |
120 | (defn unit-rsc [rsc]
121 | (-> rsc (dissoc :active) (merge {:count 0})))
122 |
123 | (defn update-unit-rsc [unit rsc active?]
124 | (update-unit unit :resources
125 | (if active?
126 | #(dissoc % (:id rsc))
127 | #(if-not (contains? % (:id rsc))
128 | (assoc % (:id rsc) (unit-rsc rsc))
129 | %))))
130 |
131 | (defn rsc-block
132 | [[{:keys [action]} block] owner]
133 | (om/component
134 | (html [:div {:class (str (when (:active block) "active ")
135 | "border-block frame2 clickable")
136 | :on-click (fn [] (dorun (map #(action % (:active block))
137 | (vals (:resources block)))))}
138 | [:div.block-title.txt-wrap.clickable
139 | [:span.item-title (-> block :title u/blankf (or "Untitled_Resource_Block"))]]
140 | (c/select-list {:class (str (when (:active block) "active ")
141 | "border-select-")
142 | :action (fn [x evt-bus] (action x (:active x)))
143 | :placeholder "Untitled_Resource"}
144 | (->> block :resources vals (sort-by :timestamp)))])))
145 |
146 | (defn selectable-rsc
147 | [[{:keys [class action]} rsc] owner]
148 | (om/component
149 | (if (= (:type rsc) "block")
150 | (om/build rsc-block [{:action action} rsc])
151 | (om/build c/item [{:class (str (when (:active rsc) "active ") class)
152 | :action (fn [x evt-bus] (action x (:active x)))
153 | :placeholder "Untitled_Resource"}
154 | rsc]))))
155 |
156 | (defn filter-dups [resources]
157 | (->> resources
158 | vals
159 | (map (comp keys :resources))
160 | flatten
161 | (remove nil?)
162 | (apply dissoc resources)))
163 |
164 | (defn block? [rsc]
165 | (= (:type rsc) "block"))
166 |
167 | (defn filter-empty-blocks [resources]
168 | (filter #(or
169 | (not (block? %))
170 | (and (block? %) (not (empty? (:resources %)))))
171 | resources))
172 |
173 | (defn contains-all? [coll keys]
174 | (not-any? false? (map #(contains? coll %) keys)))
175 |
176 | (defn active? [unit-rscs rsc]
177 | (if (= (:type rsc) "block")
178 | (->> rsc :resources keys (contains-all? unit-rscs))
179 | (contains? unit-rscs (:id rsc))))
180 |
181 | (defn activate-rsc [unit-rscs rsc]
182 | (let [activate #(assoc % :active (active? unit-rscs %))
183 | rsc (activate rsc)]
184 | (if (= (:type rsc) "block")
185 | (update-in rsc [:resources] #(u/mmap activate %))
186 | rsc)))
187 |
188 | (defn activate-rscs [unit-rscs resources]
189 | (map (partial activate-rsc unit-rscs) resources))
190 |
191 | (defn unit-edit-view
192 | [[unit rscs] owner]
193 | (reify
194 | om/IWillMount
195 | (will-mount [_]
196 | (async/put! (:event-bus (om/get-shared owner))
197 | [:drawer :set :maximized? true]))
198 |
199 | om/IWillUnmount
200 | (will-unmount [_]
201 | (async/put! (:event-bus (om/get-shared owner))
202 | [:drawer :set :maximized? false]))
203 |
204 | om/IRender
205 | (render [_]
206 | (let [resources (->> rscs
207 | filter-dups
208 | vals
209 | filter-empty-blocks
210 | (sort-by :timestamp)
211 | (activate-rscs (:resources unit)))]
212 | (om/build c/simple-list [{:id "unit-edit-rscs"
213 | :class "border-select-"
214 | :item-comp selectable-rsc
215 | :action #(update-unit-rsc unit %1 %2)}
216 | resources])))))
217 |
218 | (defn unit-views
219 | [{:keys [drawer location]} data {:keys [page]}]
220 | (let [unit (apply u/get-unit data (second location))
221 | rscs (u/get-resources data)
222 | history (u/get-unit-history data (:id unit))]
223 | (case page
224 | :info [(unit-nav-view drawer page unit [(check-in-btn unit)])
225 | (unit-info-view unit (peek history))]
226 |
227 | :resources [(unit-nav-view drawer page unit [(check-in-btn unit)
228 | (edit-resources-btn unit)])
229 | (unit-resources-view unit rscs)]
230 |
231 | :edit [(om/build c/simple-nav-panel
232 | [(c/done!-btn
233 | #(async/put! % [:window :set :page :resources]))])
234 | (om/build unit-edit-view [unit rscs])])))
235 |
--------------------------------------------------------------------------------
/resources/public/css/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.1 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox.
29 | * Correct `block` display not defined for `main` in IE 11.
30 | */
31 |
32 | article,
33 | aside,
34 | details,
35 | figcaption,
36 | figure,
37 | footer,
38 | header,
39 | hgroup,
40 | main,
41 | nav,
42 | section,
43 | summary {
44 | display: block;
45 | }
46 |
47 | /**
48 | * 1. Correct `inline-block` display not defined in IE 8/9.
49 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
50 | */
51 |
52 | audio,
53 | canvas,
54 | progress,
55 | video {
56 | display: inline-block; /* 1 */
57 | vertical-align: baseline; /* 2 */
58 | }
59 |
60 | /**
61 | * Prevent modern browsers from displaying `audio` without controls.
62 | * Remove excess height in iOS 5 devices.
63 | */
64 |
65 | audio:not([controls]) {
66 | display: none;
67 | height: 0;
68 | }
69 |
70 | /**
71 | * Address `[hidden]` styling not present in IE 8/9/10.
72 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
73 | */
74 |
75 | [hidden],
76 | template {
77 | display: none;
78 | }
79 |
80 | /* Links
81 | ========================================================================== */
82 |
83 | /**
84 | * Remove the gray background color from active links in IE 10.
85 | */
86 |
87 | a {
88 | background: transparent;
89 | }
90 |
91 | /**
92 | * Improve readability when focused and also mouse hovered in all browsers.
93 | */
94 |
95 | a:active,
96 | a:hover {
97 | outline: 0;
98 | }
99 |
100 | /* Text-level semantics
101 | ========================================================================== */
102 |
103 | /**
104 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
105 | */
106 |
107 | abbr[title] {
108 | border-bottom: 1px dotted;
109 | }
110 |
111 | /**
112 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
113 | */
114 |
115 | b,
116 | strong {
117 | font-weight: bold;
118 | }
119 |
120 | /**
121 | * Address styling not present in Safari and Chrome.
122 | */
123 |
124 | dfn {
125 | font-style: italic;
126 | }
127 |
128 | /**
129 | * Address variable `h1` font-size and margin within `section` and `article`
130 | * contexts in Firefox 4+, Safari, and Chrome.
131 | */
132 |
133 | h1 {
134 | font-size: 2em;
135 | margin: 0.67em 0;
136 | }
137 |
138 | /**
139 | * Address styling not present in IE 8/9.
140 | */
141 |
142 | mark {
143 | background: #ff0;
144 | color: #000;
145 | }
146 |
147 | /**
148 | * Address inconsistent and variable font size in all browsers.
149 | */
150 |
151 | small {
152 | font-size: 80%;
153 | }
154 |
155 | /**
156 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
157 | */
158 |
159 | sub,
160 | sup {
161 | font-size: 75%;
162 | line-height: 0;
163 | position: relative;
164 | vertical-align: baseline;
165 | }
166 |
167 | sup {
168 | top: -0.5em;
169 | }
170 |
171 | sub {
172 | bottom: -0.25em;
173 | }
174 |
175 | /* Embedded content
176 | ========================================================================== */
177 |
178 | /**
179 | * Remove border when inside `a` element in IE 8/9/10.
180 | */
181 |
182 | img {
183 | border: 0;
184 | }
185 |
186 | /**
187 | * Correct overflow not hidden in IE 9/10/11.
188 | */
189 |
190 | svg:not(:root) {
191 | overflow: hidden;
192 | }
193 |
194 | /* Grouping content
195 | ========================================================================== */
196 |
197 | /**
198 | * Address margin not present in IE 8/9 and Safari.
199 | */
200 |
201 | figure {
202 | margin: 1em 40px;
203 | }
204 |
205 | /**
206 | * Address differences between Firefox and other browsers.
207 | */
208 |
209 | hr {
210 | -moz-box-sizing: content-box;
211 | box-sizing: content-box;
212 | height: 0;
213 | }
214 |
215 | /**
216 | * Contain overflow in all browsers.
217 | */
218 |
219 | pre {
220 | overflow: auto;
221 | }
222 |
223 | /**
224 | * Address odd `em`-unit font size rendering in all browsers.
225 | */
226 |
227 | code,
228 | kbd,
229 | pre,
230 | samp {
231 | font-family: monospace, monospace;
232 | font-size: 1em;
233 | }
234 |
235 | /* Forms
236 | ========================================================================== */
237 |
238 | /**
239 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
240 | * styling of `select`, unless a `border` property is set.
241 | */
242 |
243 | /**
244 | * 1. Correct color not being inherited.
245 | * Known issue: affects color of disabled elements.
246 | * 2. Correct font properties not being inherited.
247 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
248 | */
249 |
250 | button,
251 | input,
252 | optgroup,
253 | select,
254 | textarea {
255 | color: inherit; /* 1 */
256 | font: inherit; /* 2 */
257 | margin: 0; /* 3 */
258 | }
259 |
260 | /**
261 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
262 | */
263 |
264 | button {
265 | overflow: visible;
266 | }
267 |
268 | /**
269 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
270 | * All other form control elements do not inherit `text-transform` values.
271 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
272 | * Correct `select` style inheritance in Firefox.
273 | */
274 |
275 | button,
276 | select {
277 | text-transform: none;
278 | }
279 |
280 | /**
281 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
282 | * and `video` controls.
283 | * 2. Correct inability to style clickable `input` types in iOS.
284 | * 3. Improve usability and consistency of cursor style between image-type
285 | * `input` and others.
286 | */
287 |
288 | button,
289 | html input[type="button"], /* 1 */
290 | input[type="reset"],
291 | input[type="submit"] {
292 | -webkit-appearance: button; /* 2 */
293 | cursor: pointer; /* 3 */
294 | }
295 |
296 | /**
297 | * Re-set default cursor for disabled elements.
298 | */
299 |
300 | button[disabled],
301 | html input[disabled] {
302 | cursor: default;
303 | }
304 |
305 | /**
306 | * Remove inner padding and border in Firefox 4+.
307 | */
308 |
309 | button::-moz-focus-inner,
310 | input::-moz-focus-inner {
311 | border: 0;
312 | padding: 0;
313 | }
314 |
315 | /**
316 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
317 | * the UA stylesheet.
318 | */
319 |
320 | input {
321 | line-height: normal;
322 | }
323 |
324 | /**
325 | * It's recommended that you don't attempt to style these elements.
326 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
327 | *
328 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
329 | * 2. Remove excess padding in IE 8/9/10.
330 | */
331 |
332 | input[type="checkbox"],
333 | input[type="radio"] {
334 | box-sizing: border-box; /* 1 */
335 | padding: 0; /* 2 */
336 | }
337 |
338 | /**
339 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
340 | * `font-size` values of the `input`, it causes the cursor style of the
341 | * decrement button to change from `default` to `text`.
342 | */
343 |
344 | input[type="number"]::-webkit-inner-spin-button,
345 | input[type="number"]::-webkit-outer-spin-button {
346 | height: auto;
347 | }
348 |
349 | /**
350 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
351 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
352 | * (include `-moz` to future-proof).
353 | */
354 |
355 | input[type="search"] {
356 | -webkit-appearance: textfield; /* 1 */
357 | -moz-box-sizing: content-box;
358 | -webkit-box-sizing: content-box; /* 2 */
359 | box-sizing: content-box;
360 | }
361 |
362 | /**
363 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
364 | * Safari (but not Chrome) clips the cancel button when the search input has
365 | * padding (and `textfield` appearance).
366 | */
367 |
368 | input[type="search"]::-webkit-search-cancel-button,
369 | input[type="search"]::-webkit-search-decoration {
370 | -webkit-appearance: none;
371 | }
372 |
373 | /**
374 | * Define consistent border, margin, and padding.
375 | */
376 |
377 | fieldset {
378 | border: 1px solid #c0c0c0;
379 | margin: 0 2px;
380 | padding: 0.35em 0.625em 0.75em;
381 | }
382 |
383 | /**
384 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
385 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
386 | */
387 |
388 | legend {
389 | border: 0; /* 1 */
390 | padding: 0; /* 2 */
391 | }
392 |
393 | /**
394 | * Remove default vertical scrollbar in IE 8/9/10/11.
395 | */
396 |
397 | textarea {
398 | overflow: auto;
399 | }
400 |
401 | /**
402 | * Don't inherit the `font-weight` (applied by a rule above).
403 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
404 | */
405 |
406 | optgroup {
407 | font-weight: bold;
408 | }
409 |
410 | /* Tables
411 | ========================================================================== */
412 |
413 | /**
414 | * Remove most spacing between table cells.
415 | */
416 |
417 | table {
418 | border-collapse: collapse;
419 | border-spacing: 0;
420 | }
421 |
422 | td,
423 | th {
424 | padding: 0;
425 | }
426 |
--------------------------------------------------------------------------------
/src/cljs/lokate/app.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.app
2 | (:require [cljs.core.async :as async]
3 | [clojure.set :as set]
4 | [om.core :as om :include-macros true]
5 | [sablono.core :as html :refer-macros [html]]
6 | [lokate.util :as u]
7 | [lokate.db :as db]
8 | [lokate.components :as c]
9 | [lokate.core :as core]
10 | [lokate.home :as home]
11 | [lokate.collections :as collections]
12 | [lokate.unit :as unit]
13 | [lokate.check-in :as check-in]
14 | [lokate.resources :as resources]
15 | [lokate.settings :as settings])
16 | (:require-macros [cljs.core.async.macros :refer [go-loop]]))
17 |
18 | (enable-console-print!)
19 |
20 | (def tile-url "http://{s}.tile.osm.org/{z}/{x}/{y}.png")
21 | (def tile-attr "© OpenStreetMap contributors")
22 |
23 | (def app-state
24 | (atom {:model {:collections {}
25 | :resources {}
26 | :settings {:tile-url {:id :tile-url
27 | :value tile-url}
28 | :tile-attr {:id :tile-attr
29 | :value tile-attr}
30 | :user-address {:id :user-address
31 | :value (-> js/remoteStorage .-remote .-userAddress)}}
32 | :default-settings {}
33 | :history {}}
34 | :view {:window {:orientation ""
35 | :location []
36 | :views-fn home/home-views
37 | :views-state {}
38 | :drawer {:open? false
39 | :maximized? false}}}}))
40 |
41 | (def event-bus (async/chan))
42 | (def event-bus-pub (async/pub event-bus first))
43 |
44 | (defn set-orientation []
45 | (swap! app-state
46 | #(assoc-in % [:view :window :orientation]
47 | (if (> (.-height (.-screen js/window))
48 | (.-width (.-screen js/window)))
49 | "portrait"
50 | "landscape"))))
51 |
52 | (defn set-drawer [k v]
53 | (swap! app-state
54 | #(assoc-in % [:view :window :drawer k] v)))
55 |
56 | (defn update-drawer [k fun]
57 | (swap! app-state
58 | #(update-in % [:view :window :drawer k] fun)))
59 |
60 | (u/sub-go-loop event-bus-pub :drawer
61 | (fn [[topic cmd & args]]
62 | (case cmd
63 | :set (apply set-drawer args)
64 | :update (apply update-drawer args))))
65 |
66 | (defn add-collection [data title]
67 | (let [collection {:id (keyword (u/uuid))
68 | :title title
69 | :timestamp (u/now)
70 | :units {}}]
71 | (om/transact! data [:model :collections] #(assoc % (:id collection) collection) :collection)
72 | collection))
73 |
74 | (defn add-unit [data cid latlng title]
75 | (let [unit {:id (keyword (u/uuid))
76 | :title title
77 | :timestamp (u/now)
78 | :latlng latlng
79 | :status "green"
80 | :resources {}
81 | :cid cid}]
82 | (om/update! data [:model :collections cid :units (:id unit)] unit :collection)
83 | unit))
84 |
85 | (defn add-resource [data title]
86 | (let [resource {:id (keyword (u/uuid))
87 | :title title
88 | :timestamp (u/now)}]
89 | (om/update! data [:model :resources (:id resource)] resource :resource)))
90 |
91 | (defn add-resource-block [data title]
92 | (let [rsc-block {:id (keyword (u/uuid))
93 | :type "block"
94 | :title title
95 | :timestamp (u/now)
96 | :resources {}}]
97 | (om/update! data [:model :resources (:id rsc-block)] rsc-block :resource)
98 | rsc-block))
99 |
100 | (defn prep-commit-data [unit]
101 | ;; only commit tracked data
102 | (select-keys unit [:id :status :resources]))
103 |
104 | (defn new-commit [unit]
105 | {:id (keyword (u/uuid))
106 | :timestamp (u/now)
107 | :message ""
108 | :data (prep-commit-data unit)})
109 |
110 | (defn set-location [route]
111 | (swap! app-state
112 | #(assoc-in % [:view :window :location] route)))
113 |
114 | (defn get-views-data [[path args]]
115 | (case path
116 | :home [home/home-views]
117 | :collections [collections/collections-views]
118 | :collection [collections/collection-views]
119 | :unit [unit/unit-views {:page :info}]
120 | :check-in [check-in/check-in-views
121 | {:page :resources
122 | :commit (new-commit
123 | (apply u/get-unit @app-state args))}]
124 | :resources [resources/resources-views {:selected nil}]
125 | :settings [settings/settings-views]))
126 |
127 | (defn set-views
128 | ([fn]
129 | (set-views fn {}))
130 | ([fn state]
131 | (swap! app-state
132 | #(update-in % [:view :window] merge {:views-fn fn
133 | :views-state state}))))
134 | (defn set-route [path & args]
135 | (set-location [path args])
136 | (apply set-views (get-views-data [path args])))
137 |
138 | (u/sub-go-loop event-bus-pub :set-route
139 | (fn [[topic route]]
140 | (apply set-route route)))
141 |
142 | (u/sub-go-loop event-bus-pub :window
143 | (fn [[topic cmd k vorfn]]
144 | (case cmd
145 | :set (swap! app-state #(assoc-in % [:view :window :views-state k] vorfn))
146 | :update (swap! app-state #(update-in % [:view :window :views-state k] vorfn)))))
147 |
148 | (defn new-collection [data]
149 | (c/display-input
150 | "Collection name"
151 | "Untitled collection"
152 | #(let [collection (add-collection data %)]
153 | (set-route :collection (:id collection)))))
154 |
155 | (defn delete-collection [data id]
156 | (om/transact! data [:model :collections] #(dissoc % id) :collection))
157 |
158 | (defn new-unit [data latlng]
159 | (c/display-input
160 | "Unit name"
161 | "Untitled unit"
162 | #(let [selected-cid (-> @app-state :view :window :location second first)
163 | unit (add-unit data selected-cid latlng %)]
164 | (set-route :unit selected-cid (:id unit)))))
165 |
166 | (defn new-resource [data type]
167 | (if (= type "block")
168 | (c/display-input
169 | "Resource block name"
170 | "Untitled resource block"
171 | #(let [rsc-block (add-resource-block data %)]
172 | (om/update! data [:view :window :views-state :selected]
173 | (:id rsc-block))))
174 | (c/display-input
175 | "Resource name"
176 | "Untitled resource"
177 | #(add-resource data %))))
178 |
179 | (defn delete-resource [data id]
180 | (om/transact! data [:model :resources] #(dissoc % id) :resource)
181 |
182 | ;; remove all instances from blocks
183 | (om/transact! data [:model :resources]
184 | #(u/mmap (fn [rsc]
185 | (if (u/block? rsc)
186 | (update-in rsc [:resources] dissoc id)
187 | rsc))
188 | %) :resource)
189 |
190 | ;; remove all instances of deleted resource in units
191 | (om/transact! data [:model :collections]
192 | (fn [colls]
193 | (u/mmap (fn [coll]
194 | (update-in coll [:units]
195 | #(u/mmap (fn [unit]
196 | (update-in unit [:resources] dissoc id))
197 | %)))
198 | colls))
199 | :collection))
200 |
201 | (defn add-setting [data k v]
202 | (om/update! data [:model :settings k] {:id k :value v} :setting))
203 |
204 | (defn commit! [data cid uid commit]
205 | (om/transact! data [:model :history uid]
206 | #(conj % commit) :history)
207 | (om/transact! data [:model :collections cid :units uid]
208 | #(merge % (:data commit)) :collection))
209 |
210 | (defn handle-event [data [cmd & args]]
211 | (case cmd
212 | :add-collection (new-collection data)
213 | :delete-collection (apply delete-collection data args)
214 | :add-unit (apply new-unit data args)
215 | :add-resource (apply new-resource data args)
216 | :delete-resource (apply delete-resource data args)
217 | :add-setting (apply add-setting data args)
218 | :connect (apply db/connect args)
219 | :commit! (apply commit! data args)))
220 |
221 | (defn init-model [data k v]
222 | (om/transact! data [:model k]
223 | #(merge % (-> v (js->clj :keywordize-keys true) u/keywordize-ids))))
224 |
225 | (defn init-app-model [data]
226 | (db/fetch "collection/" (partial init-model data :collections))
227 | (db/fetch "resource/" (partial init-model data :resources))
228 | (db/fetch "setting/" (partial init-model data :settings))
229 | (db/fetch "history/" (partial init-model data :history)))
230 |
231 | (defn app [data owner]
232 | (reify
233 | om/IWillMount
234 | (will-mount [_]
235 | (init-app-model data)
236 | (u/sub-go-loop event-bus-pub :app
237 | #(handle-event data (rest %))))
238 |
239 | om/IRender
240 | (render [_]
241 | (om/build core/window [(-> data :view :window) data]))))
242 |
243 | (defn update-db [type old new]
244 | (let [to-add (set/difference (set new) (set old))
245 | to-delete (set/difference (u/keyset old) (u/keyset new))]
246 | (dorun (map #(db/delete type %) to-delete))
247 | (dorun (map #(db/put type (first %) (second %)) to-add))))
248 |
249 | (defn tx-listen [m root-cursor]
250 | (case (:tag m)
251 | :collection (update-db "collection"
252 | (-> m :old-state u/get-collections)
253 | (-> m :new-state u/get-collections))
254 | :resource (update-db "resource"
255 | (-> m :old-state u/get-resources)
256 | (-> m :new-state u/get-resources))
257 | :setting (update-db "setting"
258 | (-> m :old-state u/get-settings)
259 | (-> m :new-state u/get-settings))
260 | :history (update-db "history"
261 | (-> m :old-state u/get-history)
262 | (-> m :new-state u/get-history))
263 | nil))
264 |
265 | (defn render []
266 | (om/root app app-state {:target (.getElementById js/document "root")
267 | :shared {:event-bus event-bus
268 | :event-bus-pub event-bus-pub}
269 | :tx-listen tx-listen}))
270 |
271 | (defn go! []
272 | (set-orientation)
273 | (.addEventListener js/window "resize" set-orientation)
274 | (db/init)
275 | (render))
276 |
277 | (go!)
278 |
--------------------------------------------------------------------------------
/src/cljs/lokate/components.cljs:
--------------------------------------------------------------------------------
1 | (ns lokate.components
2 | (:require [cljs.core.async :refer [put!]]
3 | [om.core :as om :include-macros true]
4 | [om.dom :as dom :include-macros true]
5 | [sablono.core :as html :refer-macros [html]]
6 | [clojure.string :as str]
7 | [goog.string :as gstring]
8 | [lokate.util :as u]))
9 |
10 | ;; clickables
11 |
12 | (defn set-effect [owner]
13 | (om/set-state! owner :effect true))
14 |
15 | (defn release-effect [owner]
16 | (om/set-state! owner :effect false))
17 |
18 | ;; div with effect on click
19 | ;; for some reason this doesn't re-render inside modal input
20 | (defn cdiv
21 | [[props contents] owner]
22 | (reify
23 | om/IInitState
24 | (init-state [_]
25 | {:effect false})
26 |
27 | om/IRenderState
28 | (render-state [_ {:keys [effect]}]
29 | (html [:div
30 | (merge
31 | (update-in props [:class] #(str % (when effect " effect")))
32 | {:on-touch-start #(set-effect owner)
33 | :on-touch-move #(release-effect owner)
34 | :on-touch-end #(release-effect owner)
35 | :on-mouse-down #(set-effect owner)
36 | :on-mouse-up #(release-effect owner)
37 | :on-mouse-out #(release-effect owner)})
38 | contents]))))
39 |
40 | (defn btn
41 | [[icon-class action] owner opts]
42 | (om/component
43 | (om/build cdiv [(merge {:class "btn"} opts)
44 | [:div {:class (str "btn-icon clickable " icon-class)
45 | :on-click #(-> (om/get-shared owner)
46 | :event-bus
47 | action)}]])))
48 |
49 | ;; lists && list-items
50 |
51 | (defn list-item
52 | [[{:keys [item-comp] :as props} item]]
53 | (om/component
54 | (html [:li.list-item
55 | (om/build item-comp [props item])])))
56 |
57 | (defn simple-list
58 | [[{:keys [id class] :as props} items] owner]
59 | (om/component
60 | (html [:ol {:id id
61 | :class (str class "list")}
62 | (om/build-all list-item items {:fn #(conj [props] %)})])))
63 |
64 | ;; generic item, has some action on click and optional right click
65 | (defn item
66 | [[{:keys [class action alt-action placeholder]} item] owner]
67 | (let [evt-bus (:event-bus (om/get-shared owner))]
68 | (om/component
69 | (html [:div
70 | {:class (str class "item clickable")
71 | :on-click (fn [e]
72 | (action item evt-bus)
73 | (.stopPropagation e))
74 | :on-context-menu (fn [e]
75 | (when alt-action
76 | (alt-action item evt-bus))
77 | (.preventDefault e))}
78 | [:div.txt-wrap.clickable
79 | [:span.item-title
80 | (or (u/blankf (:title item)) placeholder)]]
81 | (when (:icon item)
82 | [:div {:class (str "item-icon " (:icon item))}])]))))
83 |
84 | (defn set-status [owner status]
85 | (om/set-state! owner :status status))
86 |
87 | (defn warn-or-remove [owner remove-action item evt-bus]
88 | (case (om/get-state owner :status)
89 | :ok (set-status owner :warn)
90 | :warn (do
91 | (set-status owner :remove)
92 | (.setTimeout js/window
93 | (fn []
94 | (remove-action item evt-bus)
95 | (set-status owner :ok))
96 | 500))))
97 |
98 | ;; removable item that calls a remove-action on 2nd right click
99 | (defn removable-item
100 | [[{:keys [icon-class action remove-action placeholder]} r-item] owner]
101 | (reify
102 | om/IInitState
103 | (init-state [_]
104 | {:status :ok})
105 |
106 | om/IRenderState
107 | (render-state [_ {:keys [status]}]
108 | (om/build item [{:class (str (name status) " removable ")
109 | :icon-class icon-class
110 | :action action
111 | :alt-action (partial warn-or-remove owner remove-action)
112 | :placeholder placeholder}
113 | r-item]))))
114 |
115 | ;; selectable item that has active/inactive state
116 | (defn selectable-item
117 | [[{:keys [class action placeholder]} s-item] owner]
118 | (om/component
119 | (om/build item [{:class (str (when (:active s-item) "active ") class)
120 | :action action
121 | :placeholder placeholder}
122 | s-item])))
123 |
124 | (defn item-list
125 | [props items]
126 | (om/build simple-list [(assoc props :item-comp item) items]))
127 |
128 | (defn r-item-list
129 | [props r-items]
130 | (om/build simple-list [(assoc props :item-comp removable-item) r-items]))
131 |
132 | (defn select-list
133 | [props s-items]
134 | (om/build simple-list [(assoc props :item-comp selectable-item) s-items]))
135 |
136 | (defn dropdown-select-list
137 | [items owner {:keys [id class action] :as opts}]
138 | (reify
139 | om/IInitState
140 | (init-state [_]
141 | {:open false})
142 |
143 | om/IRenderState
144 | (render-state [_ {:keys [open]}]
145 | (html [:div {:id id}
146 | [:div.current-select-wrap
147 | [:a.current-select.clickable
148 | {:on-click #(om/update-state! owner :open not)}
149 | [:span.current-select-title
150 | (:title (first (filter :active items)))]]
151 | [:div.drop-down]]
152 | (when open
153 | (select-list (update-in opts [:action]
154 | #(fn [x evt-bus]
155 | (om/set-state! owner :open false)
156 | (% x evt-bus)))
157 | items))]))))
158 |
159 | (defn input-list
160 | [[props items] owner]
161 | (reify
162 | om/IInitState
163 | (init-state [_]
164 | {:children-loaded 0})
165 |
166 | om/IRender
167 | (render [_]
168 | (om/build simple-list
169 | [(merge props
170 | {:on-mount (fn []
171 | (om/update-state! owner :children-mounted inc)
172 | (when (= (om/get-state owner :children-mounted) (count items))
173 | (let [children (.getElementsByTagName (om/get-node owner) "input")]
174 | (.select (.item children 0))
175 | ; also could have tab direct to next page or add hover effect to done!-btn
176 | (.addEventListener (.item children (dec (count items))) "keydown"
177 | (fn [e]
178 | (when (= (.-keyCode e) 9)
179 | (.select (.item children 0))
180 | (.preventDefault e)
181 | false))))))})
182 | items]))))
183 |
184 | ;; modal
185 |
186 | (defn modal-input
187 | [[title placeholder value on-edit] owner]
188 | (reify
189 | om/IDidMount
190 | (did-mount [_]
191 | (.select (om/get-node owner "input")))
192 |
193 | om/IRender
194 | (render [_]
195 | (html [:div.name-input
196 | [:span.name-input-title title]
197 | [:div.name-input-wrap
198 | [:input.name-input-input
199 | {:ref "input"
200 | :type "text"
201 | :placeholder placeholder
202 | :value value
203 | :on-key-down #(when (= (.-keyCode %) 13)
204 | (on-edit (.-value (om/get-node owner "input")))
205 | (.preventDefault %))
206 | ; empty onChange allows uncontrolled input
207 | :on-change #()}]
208 | (om/build btn
209 | ["icon-done"
210 | #(on-edit (.-value (om/get-node owner "input")))]
211 | {:opts {:id "ok-btn"}})]]))))
212 |
213 | (defn mount-overlay [overlay]
214 | (om/root (fn [overlay owner]
215 | (om/component
216 | (html [:div#overlay overlay])))
217 | overlay
218 | {:target (.getElementById js/document "overlay-root")}))
219 |
220 | (defn unmount-overlay []
221 | (om/detach-root (.getElementById js/document "overlay-root")))
222 |
223 | (defn display-input
224 | ([title placeholder on-edit]
225 | (display-input title placeholder nil on-edit))
226 | ([title placeholder initial-value on-edit]
227 | (let [on-edit* (fn [res]
228 | (on-edit res)
229 | (unmount-overlay))]
230 | (mount-overlay (om/build modal-input [title placeholder
231 | initial-value on-edit*])))))
232 |
233 | ;; navigation panel components
234 |
235 | (defn icon
236 | [icon-class]
237 | [:div.icon-wrapper
238 | [:div {:class (str "icon " icon-class)}]])
239 |
240 | (def home-icon
241 | [:div.icon-wrapper
242 | [:div {:class "t t1"}]
243 | [:div {:class "t t2"}]
244 | [:div {:class "t t3"}]])
245 |
246 | (defn back-btn [back-action]
247 | (om/build btn ["icon-navigate-before back-btn"
248 | back-action]))
249 |
250 | (defn resize-btn [drawer]
251 | (om/build btn [(str "icon-fullscreen" (when (:maximized? drawer) "-exit"))
252 | #(om/transact! drawer :maximized? not)]))
253 |
254 | (defn navicon [drawer]
255 | (om/build btn [(str "navicon icon-menu" (when (:open? drawer) " active"))
256 | #(om/transact! drawer :open? not)]))
257 |
258 | (defn cancel-btn [action]
259 | (om/build btn ["icon-cancel"
260 | action]))
261 |
262 | (defn done!-btn [action]
263 | (om/build btn ["icon-done"
264 | action]))
265 |
266 | (defn banner
267 | [children owner]
268 | (om/component
269 | (html [:div.banner-container
270 | (for [child children] child)])))
271 |
272 | (defn title-banner
273 | [title child]
274 | (om/build banner [child [:span.banner-title title]]))
275 |
276 | (defn return-banner
277 | [[child return-action] owner]
278 | (om/component
279 | (om/build banner [(back-btn #(-> (om/get-shared owner)
280 | :event-bus
281 | return-action))
282 | child])))
283 |
284 | (defn title-return-banner
285 | [title return-action]
286 | (om/build return-banner
287 | [[:span.banner-title title] return-action]))
288 |
289 | (defn simple-nav-panel
290 | [controls owner]
291 | (om/component
292 | (html [:div.navigation-container
293 | [:div.control-panel
294 | (for [control controls] control)]])))
295 |
296 | (defn drawer-nav-panel
297 | [[drawer banner controls] owner]
298 | (om/component
299 | (let [open? (:open? drawer)]
300 | (html [:div.navigation-container
301 | (if open?
302 | banner
303 | (title-banner "lokate" home-icon))
304 | [:div.control-panel
305 | (when open?
306 | [:div.drawer-control
307 | (resize-btn drawer)
308 | (for [control controls] control)])
309 | (navicon drawer)]]))))
310 |
311 | ;; etc
312 |
313 | (defn title1
314 | ([title placeholder]
315 | (title1 title placeholder nil))
316 | ([title placeholder action]
317 | [:div.title1-container
318 | [:div
319 | {:class (str "txt-wrap" (when action " clickable"))
320 | :on-click action}
321 | [:span.title1-txt
322 | {:data-ph placeholder} title]]]))
323 |
324 | (defn title2
325 | [key value placeholder action]
326 | [:div.title2-container
327 | [:div {:class (str "txt-wrap" (when action " clickable"))
328 | :on-click action}
329 | [:span.title2-key key]
330 | [:span.title2-value {:data-ph placeholder} value]]])
331 |
332 | (defn tip [tip-msg]
333 | [:div.tip-wrapper [:div.tip tip-msg]])
334 |
--------------------------------------------------------------------------------
/resources/public/fonts/open_sans/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/resources/public/css/leaflet.css:
--------------------------------------------------------------------------------
1 | /* required styles */
2 |
3 | .leaflet-map-pane,
4 | .leaflet-tile,
5 | .leaflet-marker-icon,
6 | .leaflet-marker-shadow,
7 | .leaflet-tile-pane,
8 | .leaflet-tile-container,
9 | .leaflet-overlay-pane,
10 | .leaflet-shadow-pane,
11 | .leaflet-marker-pane,
12 | .leaflet-popup-pane,
13 | .leaflet-overlay-pane svg,
14 | .leaflet-zoom-box,
15 | .leaflet-image-layer,
16 | .leaflet-layer {
17 | position: absolute;
18 | left: 0;
19 | top: 0;
20 | }
21 | .leaflet-container {
22 | overflow: hidden;
23 | -ms-touch-action: none;
24 | }
25 | .leaflet-tile,
26 | .leaflet-marker-icon,
27 | .leaflet-marker-shadow {
28 | -webkit-user-select: none;
29 | -moz-user-select: none;
30 | user-select: none;
31 | -webkit-user-drag: none;
32 | }
33 | .leaflet-marker-icon,
34 | .leaflet-marker-shadow {
35 | display: block;
36 | }
37 | /* map is broken in FF if you have max-width: 100% on tiles */
38 | .leaflet-container img {
39 | max-width: none !important;
40 | }
41 | /* stupid Android 2 doesn't understand "max-width: none" properly */
42 | .leaflet-container img.leaflet-image-layer {
43 | max-width: 15000px !important;
44 | }
45 | .leaflet-tile {
46 | filter: inherit;
47 | visibility: hidden;
48 | }
49 | .leaflet-tile-loaded {
50 | visibility: inherit;
51 | }
52 | .leaflet-zoom-box {
53 | width: 0;
54 | height: 0;
55 | }
56 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
57 | .leaflet-overlay-pane svg {
58 | -moz-user-select: none;
59 | }
60 |
61 | .leaflet-tile-pane { z-index: 2; }
62 | .leaflet-objects-pane { z-index: 3; }
63 | .leaflet-overlay-pane { z-index: 4; }
64 | .leaflet-shadow-pane { z-index: 5; }
65 | .leaflet-marker-pane { z-index: 6; }
66 | .leaflet-popup-pane { z-index: 7; }
67 |
68 | .leaflet-vml-shape {
69 | width: 1px;
70 | height: 1px;
71 | }
72 | .lvml {
73 | behavior: url(#default#VML);
74 | display: inline-block;
75 | position: absolute;
76 | }
77 |
78 |
79 | /* control positioning */
80 |
81 | .leaflet-control {
82 | position: relative;
83 | z-index: 7;
84 | pointer-events: auto;
85 | }
86 | .leaflet-top,
87 | .leaflet-bottom {
88 | position: absolute;
89 | z-index: 1000;
90 | pointer-events: none;
91 | }
92 | .leaflet-top {
93 | top: 0;
94 | }
95 | .leaflet-right {
96 | right: 0;
97 | }
98 | .leaflet-bottom {
99 | bottom: 0;
100 | }
101 | .leaflet-left {
102 | left: 0;
103 | }
104 | .leaflet-control {
105 | float: left;
106 | clear: both;
107 | }
108 | .leaflet-right .leaflet-control {
109 | float: right;
110 | }
111 | .leaflet-top .leaflet-control {
112 | margin-top: 10px;
113 | }
114 | .leaflet-bottom .leaflet-control {
115 | margin-bottom: 10px;
116 | }
117 | .leaflet-left .leaflet-control {
118 | margin-left: 10px;
119 | }
120 | .leaflet-right .leaflet-control {
121 | margin-right: 10px;
122 | }
123 |
124 |
125 | /* zoom and fade animations */
126 |
127 | .leaflet-fade-anim .leaflet-tile,
128 | .leaflet-fade-anim .leaflet-popup {
129 | opacity: 0;
130 | -webkit-transition: opacity 0.2s linear;
131 | -moz-transition: opacity 0.2s linear;
132 | -o-transition: opacity 0.2s linear;
133 | transition: opacity 0.2s linear;
134 | }
135 | .leaflet-fade-anim .leaflet-tile-loaded,
136 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
137 | opacity: 1;
138 | }
139 |
140 | .leaflet-zoom-anim .leaflet-zoom-animated {
141 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
142 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
143 | -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
144 | transition: transform 0.25s cubic-bezier(0,0,0.25,1);
145 | }
146 | .leaflet-zoom-anim .leaflet-tile,
147 | .leaflet-pan-anim .leaflet-tile,
148 | .leaflet-touching .leaflet-zoom-animated {
149 | -webkit-transition: none;
150 | -moz-transition: none;
151 | -o-transition: none;
152 | transition: none;
153 | }
154 |
155 | .leaflet-zoom-anim .leaflet-zoom-hide {
156 | visibility: hidden;
157 | }
158 |
159 |
160 | /* cursors */
161 |
162 | .leaflet-clickable {
163 | cursor: pointer;
164 | }
165 | .leaflet-container {
166 | cursor: -webkit-grab;
167 | cursor: -moz-grab;
168 | }
169 | .leaflet-popup-pane,
170 | .leaflet-control {
171 | cursor: auto;
172 | }
173 | .leaflet-dragging .leaflet-container,
174 | .leaflet-dragging .leaflet-clickable {
175 | cursor: move;
176 | cursor: -webkit-grabbing;
177 | cursor: -moz-grabbing;
178 | }
179 |
180 |
181 | /* visual tweaks */
182 |
183 | .leaflet-container {
184 | background: #ddd;
185 | outline: 0;
186 | }
187 | .leaflet-container a {
188 | color: #0078A8;
189 | }
190 | .leaflet-container a.leaflet-active {
191 | outline: 2px solid orange;
192 | }
193 | .leaflet-zoom-box {
194 | border: 2px dotted #38f;
195 | background: rgba(255,255,255,0.5);
196 | }
197 |
198 |
199 | /* general typography */
200 | .leaflet-container {
201 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
202 | }
203 |
204 |
205 | /* general toolbar styles */
206 |
207 | .leaflet-bar {
208 | box-shadow: 0 1px 5px rgba(0,0,0,0.65);
209 | border-radius: 4px;
210 | }
211 | .leaflet-bar a,
212 | .leaflet-bar a:hover {
213 | background-color: #fff;
214 | border-bottom: 1px solid #ccc;
215 | width: 26px;
216 | height: 26px;
217 | line-height: 26px;
218 | display: block;
219 | text-align: center;
220 | text-decoration: none;
221 | color: black;
222 | }
223 | .leaflet-bar a,
224 | .leaflet-control-layers-toggle {
225 | background-position: 50% 50%;
226 | background-repeat: no-repeat;
227 | display: block;
228 | }
229 | .leaflet-bar a:hover {
230 | background-color: #f4f4f4;
231 | }
232 | .leaflet-bar a:first-child {
233 | border-top-left-radius: 4px;
234 | border-top-right-radius: 4px;
235 | }
236 | .leaflet-bar a:last-child {
237 | border-bottom-left-radius: 4px;
238 | border-bottom-right-radius: 4px;
239 | border-bottom: none;
240 | }
241 | .leaflet-bar a.leaflet-disabled {
242 | cursor: default;
243 | background-color: #f4f4f4;
244 | color: #bbb;
245 | }
246 |
247 | .leaflet-touch .leaflet-bar a {
248 | width: 30px;
249 | height: 30px;
250 | line-height: 30px;
251 | }
252 |
253 |
254 | /* zoom control */
255 |
256 | .leaflet-control-zoom-in,
257 | .leaflet-control-zoom-out {
258 | font: bold 18px 'Lucida Console', Monaco, monospace;
259 | text-indent: 1px;
260 | }
261 | .leaflet-control-zoom-out {
262 | font-size: 20px;
263 | }
264 |
265 | .leaflet-touch .leaflet-control-zoom-in {
266 | font-size: 22px;
267 | }
268 | .leaflet-touch .leaflet-control-zoom-out {
269 | font-size: 24px;
270 | }
271 |
272 |
273 | /* layers control */
274 |
275 | .leaflet-control-layers {
276 | box-shadow: 0 1px 5px rgba(0,0,0,0.4);
277 | background: #fff;
278 | border-radius: 5px;
279 | }
280 | .leaflet-control-layers-toggle {
281 | background-image: url(images/layers.png);
282 | width: 36px;
283 | height: 36px;
284 | }
285 | .leaflet-retina .leaflet-control-layers-toggle {
286 | background-image: url(images/layers-2x.png);
287 | background-size: 26px 26px;
288 | }
289 | .leaflet-touch .leaflet-control-layers-toggle {
290 | width: 44px;
291 | height: 44px;
292 | }
293 | .leaflet-control-layers .leaflet-control-layers-list,
294 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle {
295 | display: none;
296 | }
297 | .leaflet-control-layers-expanded .leaflet-control-layers-list {
298 | display: block;
299 | position: relative;
300 | }
301 | .leaflet-control-layers-expanded {
302 | padding: 6px 10px 6px 6px;
303 | color: #333;
304 | background: #fff;
305 | }
306 | .leaflet-control-layers-selector {
307 | margin-top: 2px;
308 | position: relative;
309 | top: 1px;
310 | }
311 | .leaflet-control-layers label {
312 | display: block;
313 | }
314 | .leaflet-control-layers-separator {
315 | height: 0;
316 | border-top: 1px solid #ddd;
317 | margin: 5px -10px 5px -6px;
318 | }
319 |
320 |
321 | /* attribution and scale controls */
322 |
323 | .leaflet-container .leaflet-control-attribution {
324 | background: #fff;
325 | background: rgba(255, 255, 255, 0.7);
326 | margin: 0;
327 | }
328 | .leaflet-control-attribution,
329 | .leaflet-control-scale-line {
330 | padding: 0 5px;
331 | color: #333;
332 | }
333 | .leaflet-control-attribution a {
334 | text-decoration: none;
335 | }
336 | .leaflet-control-attribution a:hover {
337 | text-decoration: underline;
338 | }
339 | .leaflet-container .leaflet-control-attribution,
340 | .leaflet-container .leaflet-control-scale {
341 | font-size: 11px;
342 | }
343 | .leaflet-left .leaflet-control-scale {
344 | margin-left: 5px;
345 | }
346 | .leaflet-bottom .leaflet-control-scale {
347 | margin-bottom: 5px;
348 | }
349 | .leaflet-control-scale-line {
350 | border: 2px solid #777;
351 | border-top: none;
352 | line-height: 1.1;
353 | padding: 2px 5px 1px;
354 | font-size: 11px;
355 | white-space: nowrap;
356 | overflow: hidden;
357 | -moz-box-sizing: content-box;
358 | box-sizing: content-box;
359 |
360 | background: #fff;
361 | background: rgba(255, 255, 255, 0.5);
362 | }
363 | .leaflet-control-scale-line:not(:first-child) {
364 | border-top: 2px solid #777;
365 | border-bottom: none;
366 | margin-top: -2px;
367 | }
368 | .leaflet-control-scale-line:not(:first-child):not(:last-child) {
369 | border-bottom: 2px solid #777;
370 | }
371 |
372 | .leaflet-touch .leaflet-control-attribution,
373 | .leaflet-touch .leaflet-control-layers,
374 | .leaflet-touch .leaflet-bar {
375 | box-shadow: none;
376 | }
377 | .leaflet-touch .leaflet-control-layers,
378 | .leaflet-touch .leaflet-bar {
379 | border: 2px solid rgba(0,0,0,0.2);
380 | background-clip: padding-box;
381 | }
382 |
383 |
384 | /* popup */
385 |
386 | .leaflet-popup {
387 | position: absolute;
388 | text-align: center;
389 | }
390 | .leaflet-popup-content-wrapper {
391 | padding: 1px;
392 | text-align: left;
393 | border-radius: 12px;
394 | }
395 | .leaflet-popup-content {
396 | margin: 13px 19px;
397 | line-height: 1.4;
398 | }
399 | .leaflet-popup-content p {
400 | margin: 18px 0;
401 | }
402 | .leaflet-popup-tip-container {
403 | margin: 0 auto;
404 | width: 40px;
405 | height: 20px;
406 | position: relative;
407 | overflow: hidden;
408 | }
409 | .leaflet-popup-tip {
410 | width: 17px;
411 | height: 17px;
412 | padding: 1px;
413 |
414 | margin: -10px auto 0;
415 |
416 | -webkit-transform: rotate(45deg);
417 | -moz-transform: rotate(45deg);
418 | -ms-transform: rotate(45deg);
419 | -o-transform: rotate(45deg);
420 | transform: rotate(45deg);
421 | }
422 | .leaflet-popup-content-wrapper,
423 | .leaflet-popup-tip {
424 | background: white;
425 |
426 | box-shadow: 0 3px 14px rgba(0,0,0,0.4);
427 | }
428 | .leaflet-container a.leaflet-popup-close-button {
429 | position: absolute;
430 | top: 0;
431 | right: 0;
432 | padding: 4px 4px 0 0;
433 | text-align: center;
434 | width: 18px;
435 | height: 14px;
436 | font: 16px/14px Tahoma, Verdana, sans-serif;
437 | color: #c3c3c3;
438 | text-decoration: none;
439 | font-weight: bold;
440 | background: transparent;
441 | }
442 | .leaflet-container a.leaflet-popup-close-button:hover {
443 | color: #999;
444 | }
445 | .leaflet-popup-scrolled {
446 | overflow: auto;
447 | border-bottom: 1px solid #ddd;
448 | border-top: 1px solid #ddd;
449 | }
450 |
451 | .leaflet-oldie .leaflet-popup-content-wrapper {
452 | zoom: 1;
453 | }
454 | .leaflet-oldie .leaflet-popup-tip {
455 | width: 24px;
456 | margin: 0 auto;
457 |
458 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
459 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
460 | }
461 | .leaflet-oldie .leaflet-popup-tip-container {
462 | margin-top: -1px;
463 | }
464 |
465 | .leaflet-oldie .leaflet-control-zoom,
466 | .leaflet-oldie .leaflet-control-layers,
467 | .leaflet-oldie .leaflet-popup-content-wrapper,
468 | .leaflet-oldie .leaflet-popup-tip {
469 | border: 1px solid #999;
470 | }
471 |
472 |
473 | /* div icon */
474 |
475 | .leaflet-div-icon {
476 | background: #fff;
477 | border: 1px solid #666;
478 | }
479 |
--------------------------------------------------------------------------------
/resources/public/fonts/goog-material/selection.json:
--------------------------------------------------------------------------------
1 | {
2 | "IcoMoonType": "selection",
3 | "icons": [
4 | {
5 | "icon": {
6 | "paths": [
7 | "M430.293 664.96l60.373 60.373 213.333-213.333-213.333-213.333-60.373 60.373 110.293 110.293h-412.587v85.333h412.587l-110.293 110.293zM810.667 128h-597.333c-47.147 0-85.333 38.187-85.333 85.333v170.667h85.333v-170.667h597.333v597.333h-597.333v-170.667h-85.333v170.667c0 47.147 38.187 85.333 85.333 85.333h597.333c47.147 0 85.333-38.187 85.333-85.333v-597.333c0-47.147-38.187-85.333-85.333-85.333z"
8 | ],
9 | "attrs": [],
10 | "isMulticolor": false,
11 | "tags": [
12 | "exit-to-app"
13 | ],
14 | "grid": 24
15 | },
16 | "attrs": [],
17 | "properties": {
18 | "id": 38,
19 | "order": 1,
20 | "prevSize": 24,
21 | "code": 58921,
22 | "name": "exit-to-app"
23 | },
24 | "setIdx": 0,
25 | "iconIdx": 0
26 | },
27 | {
28 | "icon": {
29 | "paths": [
30 | "M896 128.427h-768c-47.147 0-85.333 38.187-85.333 85.333v170.24h85.333v-171.093h768v598.613h-768v-171.52h-85.333v171.093c0 47.147 38.187 84.48 85.333 84.48h768c47.147 0 85.333-37.547 85.333-84.48v-597.333c0-47.147-38.187-85.333-85.333-85.333zM469.333 682.667l170.667-170.667-170.667-170.667v128h-426.667v85.333h426.667v128z"
31 | ],
32 | "attrs": [],
33 | "isMulticolor": false,
34 | "tags": [
35 | "input"
36 | ],
37 | "grid": 24
38 | },
39 | "attrs": [],
40 | "properties": {
41 | "id": 58,
42 | "order": 2,
43 | "prevSize": 24,
44 | "code": 58928,
45 | "name": "input"
46 | },
47 | "setIdx": 0,
48 | "iconIdx": 1
49 | },
50 | {
51 | "icon": {
52 | "paths": [
53 | "M512 693.333l170.667-170.667h-128v-384h-85.333v384h-128l170.667 170.667zM896 138.667h-256v84.693h256v598.613h-768v-598.613h256v-84.693h-256c-47.147 0-85.333 38.187-85.333 85.333v597.333c0 47.147 38.187 85.333 85.333 85.333h768c47.147 0 85.333-38.187 85.333-85.333v-597.333c0-47.147-38.187-85.333-85.333-85.333z"
54 | ],
55 | "attrs": [],
56 | "isMulticolor": false,
57 | "tags": [
58 | "system-update-tv"
59 | ],
60 | "grid": 24
61 | },
62 | "attrs": [],
63 | "properties": {
64 | "id": 128,
65 | "order": 3,
66 | "prevSize": 24,
67 | "code": 58929,
68 | "name": "system-update-tv"
69 | },
70 | "setIdx": 0,
71 | "iconIdx": 2
72 | },
73 | {
74 | "icon": {
75 | "paths": [
76 | "M512 85.333c-235.733 0-426.667 190.933-426.667 426.667s190.933 426.667 426.667 426.667 426.667-190.933 426.667-426.667-190.933-426.667-426.667-426.667z"
77 | ],
78 | "attrs": [],
79 | "isMulticolor": false,
80 | "tags": [
81 | "lens"
82 | ],
83 | "grid": 24
84 | },
85 | "attrs": [],
86 | "properties": {
87 | "id": 567,
88 | "order": 3,
89 | "prevSize": 24,
90 | "code": 58902,
91 | "name": "lens"
92 | },
93 | "setIdx": 0,
94 | "iconIdx": 3
95 | },
96 | {
97 | "icon": {
98 | "paths": [
99 | "M512 85.333c-235.733 0-426.667 190.933-426.667 426.667s190.933 426.667 426.667 426.667 426.667-190.933 426.667-426.667-190.933-426.667-426.667-426.667zM512 853.333c-188.16 0-341.333-153.173-341.333-341.333s153.173-341.333 341.333-341.333 341.333 153.173 341.333 341.333-153.173 341.333-341.333 341.333z"
100 | ],
101 | "attrs": [],
102 | "isMulticolor": false,
103 | "tags": [
104 | "panorama-fisheye"
105 | ],
106 | "grid": 24
107 | },
108 | "attrs": [],
109 | "properties": {
110 | "id": 583,
111 | "order": 17,
112 | "prevSize": 24,
113 | "code": 58903,
114 | "name": "panorama-fisheye"
115 | },
116 | "setIdx": 0,
117 | "iconIdx": 4
118 | },
119 | {
120 | "icon": {
121 | "paths": [
122 | "M512 85.333c-235.52 0-426.667 191.147-426.667 426.667s191.147 426.667 426.667 426.667 426.667-191.147 426.667-426.667-191.147-426.667-426.667-426.667zM512 853.333c-188.587 0-341.333-152.747-341.333-341.333s152.747-341.333 341.333-341.333 341.333 152.747 341.333 341.333-152.747 341.333-341.333 341.333z"
123 | ],
124 | "attrs": [],
125 | "isMulticolor": false,
126 | "tags": [
127 | "radio-button-off"
128 | ],
129 | "grid": 24
130 | },
131 | "attrs": [],
132 | "properties": {
133 | "id": 752,
134 | "order": 15,
135 | "prevSize": 24,
136 | "code": 58904,
137 | "name": "radio-button-off"
138 | },
139 | "setIdx": 0,
140 | "iconIdx": 5
141 | },
142 | {
143 | "icon": {
144 | "paths": [
145 | "M512 298.667c-117.76 0-213.333 95.573-213.333 213.333s95.573 213.333 213.333 213.333 213.333-95.573 213.333-213.333-95.573-213.333-213.333-213.333zM512 85.333c-235.52 0-426.667 191.147-426.667 426.667s191.147 426.667 426.667 426.667 426.667-191.147 426.667-426.667-191.147-426.667-426.667-426.667zM512 853.333c-188.587 0-341.333-152.747-341.333-341.333s152.747-341.333 341.333-341.333 341.333 152.747 341.333 341.333-152.747 341.333-341.333 341.333z"
146 | ],
147 | "attrs": [],
148 | "isMulticolor": false,
149 | "tags": [
150 | "radio-button-on"
151 | ],
152 | "grid": 24
153 | },
154 | "attrs": [],
155 | "properties": {
156 | "id": 753,
157 | "order": 16,
158 | "prevSize": 24,
159 | "code": 58905,
160 | "name": "radio-button-on"
161 | },
162 | "setIdx": 0,
163 | "iconIdx": 6
164 | },
165 | {
166 | "icon": {
167 | "paths": [
168 | "M384 689.92l-177.92-177.92-60.373 60.373 238.293 238.293 512-512-60.373-60.373z"
169 | ],
170 | "attrs": [],
171 | "isMulticolor": false,
172 | "tags": [
173 | "done"
174 | ],
175 | "grid": 24
176 | },
177 | "attrs": [],
178 | "properties": {
179 | "id": 35,
180 | "order": 4,
181 | "prevSize": 24,
182 | "code": 58899,
183 | "name": "done"
184 | },
185 | "setIdx": 0,
186 | "iconIdx": 7
187 | },
188 | {
189 | "icon": {
190 | "paths": [
191 | "M768 298.667l-60.373-60.373-270.507 270.72 60.373 60.373 270.507-270.72zM949.12 238.293l-451.84 451.627-177.92-177.92-60.373 60.373 238.293 238.293 512-512-60.16-60.373zM17.707 572.373l238.293 238.293 60.373-60.373-238.293-238.293-60.373 60.373z"
192 | ],
193 | "attrs": [],
194 | "isMulticolor": false,
195 | "tags": [
196 | "done-all"
197 | ],
198 | "grid": 24
199 | },
200 | "attrs": [],
201 | "properties": {
202 | "id": 36,
203 | "order": 5,
204 | "prevSize": 24,
205 | "code": 58900,
206 | "name": "done-all"
207 | },
208 | "setIdx": 0,
209 | "iconIdx": 8
210 | },
211 | {
212 | "icon": {
213 | "paths": [
214 | "M829.013 553.6c1.707-13.653 2.987-27.52 2.987-41.6s-1.28-27.947-2.987-41.6l90.24-70.613c8.107-6.4 10.453-17.92 5.12-27.307l-85.333-147.84c-5.333-9.173-16.427-13.013-26.027-9.173l-106.24 42.88c-21.973-16.853-46.080-31.147-72.107-42.027l-16-113.067c-1.92-10.027-10.667-17.92-21.333-17.92h-170.667c-10.667 0-19.413 7.893-21.12 17.92l-16 113.067c-26.027 10.88-50.133 24.96-72.107 42.027l-106.24-42.88c-9.6-3.627-20.693 0-26.027 9.173l-85.333 147.84c-5.333 9.173-2.987 20.693 5.12 27.307l90.027 70.613c-1.707 13.653-2.987 27.52-2.987 41.6s1.28 27.947 2.987 41.6l-90.027 70.613c-8.107 6.4-10.453 17.92-5.12 27.307l85.333 147.84c5.333 9.173 16.427 13.013 26.027 9.173l106.24-42.88c21.973 16.853 46.080 31.147 72.107 42.027l16 113.067c1.707 10.027 10.453 17.92 21.12 17.92h170.667c10.667 0 19.413-7.893 21.12-17.92l16-113.067c26.027-10.88 50.133-24.96 72.107-42.027l106.24 42.88c9.6 3.627 20.693 0 26.027-9.173l85.333-147.84c5.333-9.173 2.987-20.693-5.12-27.307l-90.027-70.613zM512 661.333c-82.56 0-149.333-66.773-149.333-149.333s66.773-149.333 149.333-149.333 149.333 66.773 149.333 149.333-66.773 149.333-149.333 149.333z"
215 | ],
216 | "attrs": [],
217 | "isMulticolor": false,
218 | "tags": [
219 | "settings"
220 | ],
221 | "grid": 24
222 | },
223 | "attrs": [],
224 | "properties": {
225 | "id": 97,
226 | "order": 6,
227 | "prevSize": 24,
228 | "code": 58901,
229 | "name": "settings"
230 | },
231 | "setIdx": 0,
232 | "iconIdx": 9
233 | },
234 | {
235 | "icon": {
236 | "paths": [
237 | "M810.667 554.667h-256v256h-85.333v-256h-256v-85.333h256v-256h85.333v256h256v85.333z"
238 | ],
239 | "attrs": [],
240 | "isMulticolor": false,
241 | "tags": [
242 | "add"
243 | ],
244 | "grid": 24
245 | },
246 | "attrs": [],
247 | "properties": {
248 | "id": 253,
249 | "order": 14,
250 | "prevSize": 24,
251 | "code": 58920,
252 | "name": "add"
253 | },
254 | "setIdx": 0,
255 | "iconIdx": 10
256 | },
257 | {
258 | "icon": {
259 | "paths": [
260 | "M512 85.333c-164.907 0-298.667 133.76-298.667 298.667 0 224 298.667 554.667 298.667 554.667s298.667-330.667 298.667-554.667c0-164.907-133.76-298.667-298.667-298.667zM512 490.667c-58.88 0-106.667-47.787-106.667-106.667s47.787-106.667 106.667-106.667 106.667 47.787 106.667 106.667-47.787 106.667-106.667 106.667z"
261 | ],
262 | "attrs": [],
263 | "isMulticolor": false,
264 | "tags": [
265 | "place"
266 | ],
267 | "grid": 24
268 | },
269 | "attrs": [],
270 | "properties": {
271 | "id": 664,
272 | "order": 15,
273 | "prevSize": 24,
274 | "code": 58913,
275 | "name": "place"
276 | },
277 | "setIdx": 0,
278 | "iconIdx": 11
279 | },
280 | {
281 | "icon": {
282 | "paths": [
283 | "M853.333 469.333h-519.253l238.293-238.293-60.373-60.373-341.333 341.333 341.333 341.333 60.373-60.373-238.293-238.293h519.253v-85.333z"
284 | ],
285 | "attrs": [],
286 | "isMulticolor": false,
287 | "tags": [
288 | "arrow-back"
289 | ],
290 | "grid": 24
291 | },
292 | "attrs": [],
293 | "properties": {
294 | "id": 672,
295 | "order": 10,
296 | "prevSize": 24,
297 | "code": 58914,
298 | "name": "arrow-back"
299 | },
300 | "setIdx": 0,
301 | "iconIdx": 12
302 | },
303 | {
304 | "icon": {
305 | "paths": [
306 | "M298.667 426.667l213.333 213.333 213.333-213.333z"
307 | ],
308 | "attrs": [],
309 | "isMulticolor": false,
310 | "tags": [
311 | "arrow-drop-down"
312 | ],
313 | "grid": 24
314 | },
315 | "attrs": [],
316 | "properties": {
317 | "id": 673,
318 | "order": 13,
319 | "prevSize": 24,
320 | "code": 58915,
321 | "name": "arrow-drop-down"
322 | },
323 | "setIdx": 0,
324 | "iconIdx": 13
325 | },
326 | {
327 | "icon": {
328 | "paths": [
329 | "M512 341.333l-256 256 60.373 60.373 195.627-195.627 195.627 195.627 60.373-60.373z"
330 | ],
331 | "attrs": [],
332 | "isMulticolor": false,
333 | "tags": [
334 | "expand-less"
335 | ],
336 | "grid": 24
337 | },
338 | "attrs": [],
339 | "properties": {
340 | "id": 682,
341 | "order": 11,
342 | "prevSize": 24,
343 | "code": 58916,
344 | "name": "expand-less"
345 | },
346 | "setIdx": 0,
347 | "iconIdx": 14
348 | },
349 | {
350 | "icon": {
351 | "paths": [
352 | "M707.627 366.293l-195.627 195.627-195.627-195.627-60.373 60.373 256 256 256-256z"
353 | ],
354 | "attrs": [],
355 | "isMulticolor": false,
356 | "tags": [
357 | "expand-more"
358 | ],
359 | "grid": 24
360 | },
361 | "attrs": [],
362 | "properties": {
363 | "id": 683,
364 | "order": 12,
365 | "prevSize": 24,
366 | "code": 58917,
367 | "name": "expand-more"
368 | },
369 | "setIdx": 0,
370 | "iconIdx": 15
371 | },
372 | {
373 | "icon": {
374 | "paths": [
375 | "M298.667 597.333h-85.333v213.333h213.333v-85.333h-128v-128zM213.333 426.667h85.333v-128h128v-85.333h-213.333v213.333zM725.333 725.333h-128v85.333h213.333v-213.333h-85.333v128zM597.333 213.333v85.333h128v128h85.333v-213.333h-213.333z"
376 | ],
377 | "attrs": [],
378 | "isMulticolor": false,
379 | "tags": [
380 | "fullscreen"
381 | ],
382 | "grid": 24
383 | },
384 | "attrs": [],
385 | "properties": {
386 | "id": 684,
387 | "order": 8,
388 | "prevSize": 24,
389 | "code": 58918,
390 | "name": "fullscreen"
391 | },
392 | "setIdx": 0,
393 | "iconIdx": 16
394 | },
395 | {
396 | "icon": {
397 | "paths": [
398 | "M213.333 682.667h128v128h85.333v-213.333h-213.333v85.333zM341.333 341.333h-128v85.333h213.333v-213.333h-85.333v128zM597.333 810.667h85.333v-128h128v-85.333h-213.333v213.333zM682.667 341.333v-128h-85.333v213.333h213.333v-85.333h-128z"
399 | ],
400 | "attrs": [],
401 | "isMulticolor": false,
402 | "tags": [
403 | "fullscreen-exit"
404 | ],
405 | "grid": 24
406 | },
407 | "attrs": [],
408 | "properties": {
409 | "id": 685,
410 | "order": 9,
411 | "prevSize": 24,
412 | "code": 58919,
413 | "name": "fullscreen-exit"
414 | },
415 | "setIdx": 0,
416 | "iconIdx": 17
417 | },
418 | {
419 | "icon": {
420 | "paths": [
421 | "M128 768h768v-85.333h-768v85.333zM128 554.667h768v-85.333h-768v85.333zM128 256v85.333h768v-85.333h-768z"
422 | ],
423 | "attrs": [],
424 | "isMulticolor": false,
425 | "tags": [
426 | "menu"
427 | ],
428 | "grid": 24
429 | },
430 | "attrs": [],
431 | "properties": {
432 | "id": 686,
433 | "order": 7,
434 | "prevSize": 24,
435 | "code": 58912,
436 | "name": "menu"
437 | },
438 | "setIdx": 0,
439 | "iconIdx": 18
440 | },
441 | {
442 | "icon": {
443 | "paths": [
444 | "M657.707 316.373l-60.373-60.373-256 256 256 256 60.373-60.373-195.627-195.627z"
445 | ],
446 | "attrs": [],
447 | "isMulticolor": false,
448 | "tags": [
449 | "navigate-before"
450 | ],
451 | "grid": 24
452 | },
453 | "attrs": [],
454 | "properties": {
455 | "id": 579,
456 | "order": 22,
457 | "prevSize": 24,
458 | "code": 58931,
459 | "name": "navigate-before"
460 | },
461 | "setIdx": 1,
462 | "iconIdx": 579
463 | }
464 | ],
465 | "height": 1024,
466 | "metadata": {
467 | "name": "icomoon"
468 | },
469 | "preferences": {
470 | "showGlyphs": true,
471 | "showQuickUse": true,
472 | "showQuickUse2": true,
473 | "showSVGs": true,
474 | "fontPref": {
475 | "prefix": "icon-",
476 | "metadata": {
477 | "fontFamily": "icomoon"
478 | },
479 | "metrics": {
480 | "emSize": 1024,
481 | "baseline": 6.25,
482 | "whitespace": 50
483 | }
484 | },
485 | "imagePref": {
486 | "prefix": "icon-",
487 | "png": true,
488 | "useClassSelector": true,
489 | "color": 4473924,
490 | "bgColor": 16777215,
491 | "classSelector": ".icon"
492 | },
493 | "historySize": 100,
494 | "showCodes": true
495 | }
496 | }
--------------------------------------------------------------------------------
/resources/public/css/app.css:
--------------------------------------------------------------------------------
1 | /* workaround for refresh issues in webkit-based browsers */
2 | @-webkit-keyframes androidBugfix {from { padding: 0; } to { padding: 0; }}
3 | body { -webkit-animation: androidBugfix infinite 1s; }
4 | /* end workaround */
5 |
6 | @font-face {
7 | font-family: 'Open Sans';
8 | font-style: normal;
9 | font-weight: 300;
10 | src: url('/fonts/open_sans/OpenSans-Light.ttf') format('truetype');
11 | }
12 |
13 | @font-face {
14 | font-family: 'Open Sans';
15 | font-style: normal;
16 | font-weight: 400;
17 | src: url('/fonts/open_sans/OpenSans-Regular.ttf') format('truetype');
18 | }
19 |
20 | @font-face {
21 | font-family: 'Open Sans';
22 | font-style: normal;
23 | font-weight: 600;
24 | src: url('/fonts/open_sans/OpenSans-Semibold.ttf') format('truetype');
25 | }
26 |
27 |
28 | html, body, #root, #app {
29 | width: 100%;
30 | height: 100%;
31 | font-family: 'Open Sans', sans-serif;
32 | font-weight: 300;
33 | color: white;
34 | overflow: hidden;
35 | }
36 |
37 | div, li {
38 | box-sizing: border-box;
39 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
40 | -webkit-user-select: none;
41 | cursor: default;
42 | }
43 |
44 | a {
45 | color: inherit;
46 | text-decoration: none;
47 | }
48 |
49 | textarea {
50 | border: none;
51 | overflow: hidden;
52 | outline: none;
53 | }
54 |
55 | .list::-webkit-scrollbar,
56 | [class*="-list"]::-webkit-scrollbar,
57 | #drawer-content::-webkit-scrollbar {
58 | display: none;
59 | }
60 |
61 | .title, [class*="-title"] {
62 | font-size: 1em;
63 | }
64 |
65 | #overlay {
66 | position: fixed;
67 | width: 100%;
68 | height: 100%;
69 | background-color: rgba(33,33,33,0.9);
70 | z-index: 5;
71 | }
72 |
73 | .navigation-container {
74 | width: 100%;
75 | height: 45px;
76 | display: flex;
77 | flex-direction: row;
78 | justify-content: space-between;
79 | align-items: center;
80 | background-color: #444;
81 | }
82 |
83 | .control-panel {
84 | position: fixed;
85 | z-index: 3;
86 | top: 0;
87 | right: 0;
88 | width: 38.1%;
89 | min-width: 233px;
90 | height: 45px;
91 | display: flex;
92 | align-items: center;
93 | justify-content: flex-end;
94 | }
95 |
96 | .drawer-control {
97 | height: 45px;
98 | flex: 1 1 auto;
99 | display: flex;
100 | align-items: center;
101 | flex-direction: row-reverse;
102 | }
103 |
104 | .banner-container {
105 | height: 45px;
106 | display: flex;
107 | align-items: center;
108 | }
109 |
110 | .banner-title {
111 | font-size: 1.3em;
112 | color: white;
113 | position: relative;
114 | top: -0.2px;
115 | }
116 |
117 | .icon-wrapper {
118 | width: 45px;
119 | height: 45px;
120 | display: flex;
121 | flex-direction: row;
122 | justify-content: center;
123 | align-items: center;
124 | position: relative;
125 | }
126 |
127 | .icon {
128 | margin: auto;
129 | }
130 |
131 | .icon.icon-home {
132 | padding-bottom: 5.5px;
133 | font-size: 1.7em;
134 | color: rgba(33, 150, 255, 0.9);
135 | }
136 |
137 | .btn {
138 | display: flex;
139 | height: 45px;
140 | width: 45px;
141 | padding: .3em;
142 | }
143 |
144 | .btn.effect {
145 | background-color: rgb(51, 51, 51);
146 | }
147 |
148 | .btn-icon {
149 | font-size: 1.5em;
150 | margin: auto;
151 | color: #888;
152 | }
153 |
154 | .back-btn {
155 | color: white;
156 | font-size: 2em;
157 | }
158 |
159 | .navicon {
160 | color: white;
161 | }
162 |
163 | .flex-container {
164 | position: relative;
165 | width: 100%;
166 | height: calc(100% - 45px);
167 | margin: 0;
168 | padding: 0;
169 | }
170 |
171 | .flex-container.portrait {
172 | fled-direction: column;
173 | }
174 |
175 | .flex-container.landscape {
176 | flex-direction: row;
177 | }
178 |
179 | .flex-content {
180 | flex: 1 1 100%;
181 | width: 100%;
182 | height: 100%;
183 | }
184 |
185 | .column {
186 | flex-direction: column;
187 | }
188 |
189 | .row {
190 | flex-direction: row;
191 | }
192 |
193 | #map {
194 | width: 100%;
195 | height: 100%;
196 | }
197 |
198 | #drawer-wrapper {
199 | width: 100%;
200 | height: 100%;
201 | }
202 |
203 | #drawer {
204 | position: absolute;
205 | bottom: 0;
206 | right: 0;
207 | color: white;
208 | font-size: .9em;
209 | background-color: rgba(33,33,33,0.9);
210 | transition: width .3s, height .3s, transform .3s;
211 | display: flex;
212 | }
213 |
214 | #drawer-content {
215 | width: 100%;
216 | height: 100%;
217 | max-width: 666px;
218 | overflow: auto;
219 | margin: auto;
220 | border-top: 1px solid rgba(33, 33, 33, 1);
221 | border-left: 1px solid rgba(33, 33, 33, 0);
222 | border-right: 1px solid rgba(33, 33, 33, 0);
223 | border-bottom: 1px solid rgba(33, 33, 33, 0);
224 | transition: border .3s;
225 | }
226 |
227 | #drawer.maximized #drawer-content {
228 | border: 1px solid rgba(33, 33, 33, 1);
229 | }
230 |
231 | #drawer.portrait {
232 | height: 38.1%;
233 | width: 100%;
234 | }
235 |
236 | #drawer.landscape {
237 | width: 38.1%;
238 | height: 100%;
239 | }
240 |
241 | #drawer.maximized {
242 | width: 100%;
243 | height: 100%;
244 | }
245 |
246 | #drawer.portrait.hide {
247 | transition-duration: .1s;
248 | transition-property: transform;
249 | transform: translate3d(0, 100%, 0);
250 | }
251 |
252 | #drawer.landscape.hide {
253 | transition-duration: .1s;
254 | transition-property: transform;
255 | transform: translate3d(100%, 0, 0);
256 | }
257 |
258 | .show {
259 | transition-duration: .3s;
260 | transition-property: transform;
261 | transform: translate3d(0, 0, 0);
262 | }
263 |
264 | .flex-col {
265 | display: flex;
266 | flex-direction: column;
267 | }
268 |
269 | .full {
270 | width: 100%;
271 | height: 100%;
272 | }
273 |
274 | .frame {
275 | padding: .5em;
276 | }
277 |
278 | .frame2 {
279 | padding: .3em;
280 | }
281 |
282 | .clickable:hover {
283 | cursor: pointer;
284 | }
285 |
286 | .txt-wrap {
287 | overflow: hidden;
288 | text-overflow: ellipsis;
289 | white-space: nowrap;
290 | }
291 |
292 | .title1-container {
293 | width: 100%;
294 | height: 37px;
295 | min-height: 37px;
296 | margin-bottom: .3em;
297 | display: flex;
298 | align-items: center;
299 | border-bottom: 1px solid rgb(33, 33, 33);
300 | }
301 |
302 | .title1-txt {
303 | font-size: 1.3em;
304 | }
305 |
306 | .title1-txt:empty:not(:focus):before {
307 | content: attr(data-ph);
308 | color: darkgrey;
309 | }
310 |
311 | .title2-container {
312 | width: 100%;
313 | height: 22px;
314 | padding-bottom: .3em;
315 | margin-bottom: .3em;
316 | border-bottom: 1px solid rgb(33, 33, 33);
317 | }
318 |
319 | .title2-key {
320 | color: #BBB;
321 | }
322 |
323 | .tip-wrapper {
324 | width: 100%;
325 | height: 100%;
326 | display: flex;
327 | padding: .3em;
328 | }
329 |
330 | .tip {
331 | margin: auto;
332 | display: flex;
333 | justify-content: center;
334 | }
335 |
336 | .img {
337 | width: 20px;
338 | font-size: 1.3em;
339 | color: #888;
340 | }
341 |
342 | .list, [class*="-list"] {
343 | list-style-type: none;
344 | margin: 0;
345 | /* added due to weird rendering issue on chrome mobile where it cuts off right edge */
346 | padding: 0;
347 | padding-right: 1px;
348 | height: 100%;
349 | overflow-y: auto;
350 | }
351 |
352 | #check-in-rscs-list {
353 | position: relative;
354 | }
355 |
356 | .item {
357 | width: 100%;
358 | height: 33px;
359 | display: flex;
360 | align-items: center;
361 | justify-content: space-between;
362 | margin-bottom: .5em;
363 | padding-left: .3em;
364 | padding-right: .3em;
365 | border-radius: 3px;
366 | border: 1px solid rgba(136, 136, 136, 0.9);
367 | background-color: rgba(33, 33, 33, 0.6);
368 | }
369 |
370 | .item-title {
371 | font-size: 1.1em;
372 | }
373 |
374 | #resources .item {
375 | justify-content: space-between;
376 | }
377 |
378 | .item-icon {
379 | font-size: 1.2em;
380 | }
381 |
382 | .item-icon:hover {
383 | cursor: pointer;
384 | }
385 |
386 | .title-select-item.active {
387 | color: rgba(33, 150, 255, 0.9)
388 | }
389 |
390 | .title-select-item .item-title {
391 | font-size: 1.05em;
392 | }
393 |
394 | .border-select-item {
395 | width: 100%;
396 | height: 33px;
397 | margin-bottom: .5em;
398 | border-radius: 3px;
399 | display: flex;
400 | align-items: center;
401 | padding-left: .3em;
402 | padding-right: .3em;
403 | border: 1px solid rgba(136, 136, 136, 0.9);
404 | }
405 |
406 | .border-select-item.active {
407 | border: 1px solid rgba(33, 150, 255, 0.9);
408 | }
409 |
410 | .border-block {
411 | width: 100%;
412 | border-radius: 3px;
413 | border: 1px solid #888;
414 | margin-bottom: .5em;
415 | }
416 |
417 | .border-block.active {
418 | border: 1px solid rgba(150, 33, 255, 0.9);
419 | }
420 |
421 | .block-title {
422 | margin-bottom: .3em;
423 | }
424 |
425 | .settings-block-title {
426 | font-size: 1.1em;
427 | margin-bottom: .3em;
428 | }
429 |
430 | .removable {
431 | transition: border .3s;
432 | }
433 |
434 | .removable.warn {
435 | border: 1px solid rgba(255, 201, 145, 0.9);
436 | }
437 |
438 | .removable.remove {
439 | border: 1px solid rgba(255, 142, 127, 0.9);
440 | }
441 |
442 | .drop-down {
443 | width: 0;
444 | height: 0;
445 | border-top: 5px solid transparent;
446 | border-bottom: 5px solid transparent;
447 | border-left: 5px solid rgba(33, 150, 255, 0.9);;
448 | transform: rotate(45deg);
449 | position: absolute;
450 | right: 0;
451 | bottom: 0;
452 | }
453 |
454 | #page-select .title-select-list {
455 | position: absolute;
456 | height: auto;
457 | top: 45px;
458 | left: 0;
459 | z-index: 5;
460 | padding-left: 45px;
461 | padding-right: 54px;
462 | background-color: rgba(33, 33, 33, 0.9);
463 | }
464 |
465 | .current-select-wrap {
466 | position: relative;
467 | padding-right: 1em;
468 | position: relative;
469 | top: -0.2px;
470 | }
471 |
472 | .current-select-title {
473 | font-size: 1.3em;
474 | }
475 |
476 | /* unit specific */
477 |
478 | .info-title {
479 | color: #BBB;
480 | }
481 |
482 | .origin, .location, .last-check-in {
483 | min-height: 15px;
484 | }
485 |
486 | .last-check-in {
487 | padding-top: 2px;
488 | }
489 |
490 | .location-lat-lng {
491 | padding-right: 4px;
492 | }
493 |
494 | .unit-resource {
495 | width: 100%;
496 | height: 40px;
497 | margin-bottom: .5em;
498 | display: flex;
499 | align-items: center;
500 | border-bottom-right-radius: 3px;
501 | border-bottom: 1px solid rgb(34, 34, 34);
502 | }
503 |
504 | .unit-resource-title {
505 | width: 100%;
506 | font-size: 1.1em;
507 | }
508 |
509 | .unit-resource-count-box {
510 | border-radius: 3px;
511 | border-top: 1px solid rgb(34, 34, 34);
512 | border-left: 1px solid rgb(34, 34, 34);
513 | border-right: 1px solid rgb(34, 34, 34);
514 | width: 55px;
515 | height: 40px;
516 | display: flex;
517 | }
518 |
519 | .unit-resource-count {
520 | margin: auto;
521 | font-size: 1.2em;
522 | }
523 |
524 | .unit-resource-count-input {
525 | margin: auto;
526 | font-size: 1.2em;
527 | background-color: inherit;
528 | border: none;
529 | outline: none;
530 | text-align: center;
531 | }
532 |
533 | input[type=number]::-webkit-inner-spin-button,
534 | input[type=number]::-webkit-outer-spin-button {
535 | -webkit-appearance: none;
536 | margin: 0;
537 | }
538 |
539 | .last-check-in-data {
540 | border: 1px solid rgba(136, 136, 136, 0.9);
541 | border-radius: 3px;
542 | padding: .3em;
543 | margin-top: .2em;
544 | }
545 |
546 | .hilight {
547 | color: rgba(33, 150, 255, 0.9);
548 | }
549 |
550 | /* check-in */
551 |
552 | #commit {
553 | margin: auto;
554 | width: 90%;
555 | height: 100%;
556 | min-height: 108px;
557 | display: flex;
558 | flex-direction: column;
559 | align-items: center;
560 | justify-content: center;
561 | }
562 |
563 | #commit-status-wrapper {
564 | display: flex;
565 | flex-direction: row;
566 | justify-content: center;
567 | min-height: 42px;
568 | }
569 |
570 | .status-select-wrapper {
571 | padding: 1em;
572 | opacity: 0.3;
573 | }
574 |
575 | .status-select-wrapper.active {
576 | opacity: 1;
577 | }
578 |
579 | .status-select {
580 | font-size: 2em;
581 | }
582 |
583 | .multi-line-input {
584 | background-color: transparent;
585 | resize: none;
586 | border: 1px solid #888;
587 | border-radius: 3px;
588 | width: 100%;
589 | height: 100%;
590 | max-width: 555px;
591 | max-height: 333px;
592 | padding: .5em;
593 | margin: 1em;
594 | }
595 |
596 | /* resources */
597 |
598 | .rsc-btn {
599 | font-size: 1.2em;
600 | }
601 |
602 | /* modal input */
603 |
604 | .name-input {
605 | width: 80%;
606 | height: 70%;
607 | max-width: 555px;
608 | max-height: 150px;
609 | background: #191919;
610 | color: white;
611 | position: absolute;
612 | top: 0px;
613 | bottom: 0;
614 | left: 0;
615 | right: 0;
616 | margin: auto;
617 | padding: .5em;
618 | display: flex;
619 | flex-direction: column;
620 | overflow: hidden;
621 | }
622 |
623 | .name-input-title {
624 | font-size: 1.3em;
625 | }
626 | .name-input-wrap {
627 | display: flex;
628 | align-items: center;
629 | height: 60%;
630 | }
631 |
632 | .name-input-input {
633 | width: 100%;
634 | margin: auto;
635 | border: none;
636 | background: inherit;
637 | border-bottom: 1px solid #333;
638 | padding-left: .3em;
639 | padding-right: .3em;
640 | }
641 |
642 | input:focus {
643 | outline: none;
644 | }
645 |
646 | /* leaflet */
647 |
648 | .awesome-marker i {
649 | font-size: 2em;
650 | margin-top: 5px;
651 | }
652 |
653 | .leaflet-contextmenu {
654 | background-color: #444;
655 | border-radius: 0;
656 | //padding: .5em;
657 | }
658 |
659 | .leaflet-contextmenu a.leaflet-contextmenu-item {
660 | color: white;
661 | font-size: 1.1em;
662 | line-height: 23px;
663 | padding-left: .5em;
664 | font-family: 'Open Sans';
665 | }
666 |
667 | .leaflet-contextmenu a.leaflet-contextmenu-item.over {
668 | background-color: #444;
669 | border-top: 1px solid #444;
670 | border-bottom: 1px solid #444;
671 | cursor: pointer;
672 | }
673 |
674 | .leaflet-contextmenu .icon-pin {
675 | font-size: 1.5em;
676 | margin: auto;
677 | color: #bbf970;
678 | margin-right: .5em;
679 | padding-top: 1px;
680 | }
681 |
682 | .leaflet-contextmenu .icon-pin:hover {
683 | cursor: pointer;
684 | }
685 |
686 | /* remoteStorage */
687 |
688 | #remotestorage-widget {
689 | bottom: 33px;
690 | left: 230px;
691 | top: auto !important;
692 | right: auto !important;
693 | }
694 |
--------------------------------------------------------------------------------
/resources/public/css/ionicons.min.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";/*!
2 | Ionicons, v2.0.0
3 | Created by Ben Sperry for the Ionic Framework, http://ionicons.com/
4 | https://twitter.com/benjsperry https://twitter.com/ionicframework
5 | MIT License: https://github.com/driftyco/ionicons
6 |
7 | Android-style icons originally built by Google’s
8 | Material Design Icons: https://github.com/google/material-design-icons
9 | used under CC BY http://creativecommons.org/licenses/by/4.0/
10 | Modified icons to fit ionicon’s grid from original.
11 | */@font-face{font-family:"Ionicons";src:url("../fonts/ionicons.eot?v=2.0.0");src:url("../fonts/ionicons.eot?v=2.0.0#iefix") format("embedded-opentype"),url("../fonts/ionicons.ttf?v=2.0.0") format("truetype"),url("../fonts/ionicons.woff?v=2.0.0") format("woff"),url("../fonts/ionicons.svg?v=2.0.0#Ionicons") format("svg");font-weight:normal;font-style:normal}.ion,.ionicons,.ion-alert:before,.ion-alert-circled:before,.ion-android-add:before,.ion-android-add-circle:before,.ion-android-alarm-clock:before,.ion-android-alert:before,.ion-android-apps:before,.ion-android-archive:before,.ion-android-arrow-back:before,.ion-android-arrow-down:before,.ion-android-arrow-dropdown:before,.ion-android-arrow-dropdown-circle:before,.ion-android-arrow-dropleft:before,.ion-android-arrow-dropleft-circle:before,.ion-android-arrow-dropright:before,.ion-android-arrow-dropright-circle:before,.ion-android-arrow-dropup:before,.ion-android-arrow-dropup-circle:before,.ion-android-arrow-forward:before,.ion-android-arrow-up:before,.ion-android-attach:before,.ion-android-bar:before,.ion-android-bicycle:before,.ion-android-boat:before,.ion-android-bookmark:before,.ion-android-bulb:before,.ion-android-bus:before,.ion-android-calendar:before,.ion-android-call:before,.ion-android-camera:before,.ion-android-cancel:before,.ion-android-car:before,.ion-android-cart:before,.ion-android-chat:before,.ion-android-checkbox:before,.ion-android-checkbox-blank:before,.ion-android-checkbox-outline:before,.ion-android-checkbox-outline-blank:before,.ion-android-checkmark-circle:before,.ion-android-clipboard:before,.ion-android-close:before,.ion-android-cloud:before,.ion-android-cloud-circle:before,.ion-android-cloud-done:before,.ion-android-cloud-outline:before,.ion-android-color-palette:before,.ion-android-compass:before,.ion-android-contact:before,.ion-android-contacts:before,.ion-android-contract:before,.ion-android-create:before,.ion-android-delete:before,.ion-android-desktop:before,.ion-android-document:before,.ion-android-done:before,.ion-android-done-all:before,.ion-android-download:before,.ion-android-drafts:before,.ion-android-exit:before,.ion-android-expand:before,.ion-android-favorite:before,.ion-android-favorite-outline:before,.ion-android-film:before,.ion-android-folder:before,.ion-android-folder-open:before,.ion-android-funnel:before,.ion-android-globe:before,.ion-android-hand:before,.ion-android-hangout:before,.ion-android-happy:before,.ion-android-home:before,.ion-android-image:before,.ion-android-laptop:before,.ion-android-list:before,.ion-android-locate:before,.ion-android-lock:before,.ion-android-mail:before,.ion-android-map:before,.ion-android-menu:before,.ion-android-microphone:before,.ion-android-microphone-off:before,.ion-android-more-horizontal:before,.ion-android-more-vertical:before,.ion-android-navigate:before,.ion-android-notifications:before,.ion-android-notifications-none:before,.ion-android-notifications-off:before,.ion-android-open:before,.ion-android-options:before,.ion-android-people:before,.ion-android-person:before,.ion-android-person-add:before,.ion-android-phone-landscape:before,.ion-android-phone-portrait:before,.ion-android-pin:before,.ion-android-plane:before,.ion-android-playstore:before,.ion-android-print:before,.ion-android-radio-button-off:before,.ion-android-radio-button-on:before,.ion-android-refresh:before,.ion-android-remove:before,.ion-android-remove-circle:before,.ion-android-restaurant:before,.ion-android-sad:before,.ion-android-search:before,.ion-android-send:before,.ion-android-settings:before,.ion-android-share:before,.ion-android-share-alt:before,.ion-android-star:before,.ion-android-star-half:before,.ion-android-star-outline:before,.ion-android-stopwatch:before,.ion-android-subway:before,.ion-android-sunny:before,.ion-android-sync:before,.ion-android-textsms:before,.ion-android-time:before,.ion-android-train:before,.ion-android-unlock:before,.ion-android-upload:before,.ion-android-volume-down:before,.ion-android-volume-mute:before,.ion-android-volume-off:before,.ion-android-volume-up:before,.ion-android-walk:before,.ion-android-warning:before,.ion-android-watch:before,.ion-android-wifi:before,.ion-aperture:before,.ion-archive:before,.ion-arrow-down-a:before,.ion-arrow-down-b:before,.ion-arrow-down-c:before,.ion-arrow-expand:before,.ion-arrow-graph-down-left:before,.ion-arrow-graph-down-right:before,.ion-arrow-graph-up-left:before,.ion-arrow-graph-up-right:before,.ion-arrow-left-a:before,.ion-arrow-left-b:before,.ion-arrow-left-c:before,.ion-arrow-move:before,.ion-arrow-resize:before,.ion-arrow-return-left:before,.ion-arrow-return-right:before,.ion-arrow-right-a:before,.ion-arrow-right-b:before,.ion-arrow-right-c:before,.ion-arrow-shrink:before,.ion-arrow-swap:before,.ion-arrow-up-a:before,.ion-arrow-up-b:before,.ion-arrow-up-c:before,.ion-asterisk:before,.ion-at:before,.ion-backspace:before,.ion-backspace-outline:before,.ion-bag:before,.ion-battery-charging:before,.ion-battery-empty:before,.ion-battery-full:before,.ion-battery-half:before,.ion-battery-low:before,.ion-beaker:before,.ion-beer:before,.ion-bluetooth:before,.ion-bonfire:before,.ion-bookmark:before,.ion-bowtie:before,.ion-briefcase:before,.ion-bug:before,.ion-calculator:before,.ion-calendar:before,.ion-camera:before,.ion-card:before,.ion-cash:before,.ion-chatbox:before,.ion-chatbox-working:before,.ion-chatboxes:before,.ion-chatbubble:before,.ion-chatbubble-working:before,.ion-chatbubbles:before,.ion-checkmark:before,.ion-checkmark-circled:before,.ion-checkmark-round:before,.ion-chevron-down:before,.ion-chevron-left:before,.ion-chevron-right:before,.ion-chevron-up:before,.ion-clipboard:before,.ion-clock:before,.ion-close:before,.ion-close-circled:before,.ion-close-round:before,.ion-closed-captioning:before,.ion-cloud:before,.ion-code:before,.ion-code-download:before,.ion-code-working:before,.ion-coffee:before,.ion-compass:before,.ion-compose:before,.ion-connection-bars:before,.ion-contrast:before,.ion-crop:before,.ion-cube:before,.ion-disc:before,.ion-document:before,.ion-document-text:before,.ion-drag:before,.ion-earth:before,.ion-easel:before,.ion-edit:before,.ion-egg:before,.ion-eject:before,.ion-email:before,.ion-email-unread:before,.ion-erlenmeyer-flask:before,.ion-erlenmeyer-flask-bubbles:before,.ion-eye:before,.ion-eye-disabled:before,.ion-female:before,.ion-filing:before,.ion-film-marker:before,.ion-fireball:before,.ion-flag:before,.ion-flame:before,.ion-flash:before,.ion-flash-off:before,.ion-folder:before,.ion-fork:before,.ion-fork-repo:before,.ion-forward:before,.ion-funnel:before,.ion-gear-a:before,.ion-gear-b:before,.ion-grid:before,.ion-hammer:before,.ion-happy:before,.ion-happy-outline:before,.ion-headphone:before,.ion-heart:before,.ion-heart-broken:before,.ion-help:before,.ion-help-buoy:before,.ion-help-circled:before,.ion-home:before,.ion-icecream:before,.ion-image:before,.ion-images:before,.ion-information:before,.ion-information-circled:before,.ion-ionic:before,.ion-ios-alarm:before,.ion-ios-alarm-outline:before,.ion-ios-albums:before,.ion-ios-albums-outline:before,.ion-ios-americanfootball:before,.ion-ios-americanfootball-outline:before,.ion-ios-analytics:before,.ion-ios-analytics-outline:before,.ion-ios-arrow-back:before,.ion-ios-arrow-down:before,.ion-ios-arrow-forward:before,.ion-ios-arrow-left:before,.ion-ios-arrow-right:before,.ion-ios-arrow-thin-down:before,.ion-ios-arrow-thin-left:before,.ion-ios-arrow-thin-right:before,.ion-ios-arrow-thin-up:before,.ion-ios-arrow-up:before,.ion-ios-at:before,.ion-ios-at-outline:before,.ion-ios-barcode:before,.ion-ios-barcode-outline:before,.ion-ios-baseball:before,.ion-ios-baseball-outline:before,.ion-ios-basketball:before,.ion-ios-basketball-outline:before,.ion-ios-bell:before,.ion-ios-bell-outline:before,.ion-ios-body:before,.ion-ios-body-outline:before,.ion-ios-bolt:before,.ion-ios-bolt-outline:before,.ion-ios-book:before,.ion-ios-book-outline:before,.ion-ios-bookmarks:before,.ion-ios-bookmarks-outline:before,.ion-ios-box:before,.ion-ios-box-outline:before,.ion-ios-briefcase:before,.ion-ios-briefcase-outline:before,.ion-ios-browsers:before,.ion-ios-browsers-outline:before,.ion-ios-calculator:before,.ion-ios-calculator-outline:before,.ion-ios-calendar:before,.ion-ios-calendar-outline:before,.ion-ios-camera:before,.ion-ios-camera-outline:before,.ion-ios-cart:before,.ion-ios-cart-outline:before,.ion-ios-chatboxes:before,.ion-ios-chatboxes-outline:before,.ion-ios-chatbubble:before,.ion-ios-chatbubble-outline:before,.ion-ios-checkmark:before,.ion-ios-checkmark-empty:before,.ion-ios-checkmark-outline:before,.ion-ios-circle-filled:before,.ion-ios-circle-outline:before,.ion-ios-clock:before,.ion-ios-clock-outline:before,.ion-ios-close:before,.ion-ios-close-empty:before,.ion-ios-close-outline:before,.ion-ios-cloud:before,.ion-ios-cloud-download:before,.ion-ios-cloud-download-outline:before,.ion-ios-cloud-outline:before,.ion-ios-cloud-upload:before,.ion-ios-cloud-upload-outline:before,.ion-ios-cloudy:before,.ion-ios-cloudy-night:before,.ion-ios-cloudy-night-outline:before,.ion-ios-cloudy-outline:before,.ion-ios-cog:before,.ion-ios-cog-outline:before,.ion-ios-color-filter:before,.ion-ios-color-filter-outline:before,.ion-ios-color-wand:before,.ion-ios-color-wand-outline:before,.ion-ios-compose:before,.ion-ios-compose-outline:before,.ion-ios-contact:before,.ion-ios-contact-outline:before,.ion-ios-copy:before,.ion-ios-copy-outline:before,.ion-ios-crop:before,.ion-ios-crop-strong:before,.ion-ios-download:before,.ion-ios-download-outline:before,.ion-ios-drag:before,.ion-ios-email:before,.ion-ios-email-outline:before,.ion-ios-eye:before,.ion-ios-eye-outline:before,.ion-ios-fastforward:before,.ion-ios-fastforward-outline:before,.ion-ios-filing:before,.ion-ios-filing-outline:before,.ion-ios-film:before,.ion-ios-film-outline:before,.ion-ios-flag:before,.ion-ios-flag-outline:before,.ion-ios-flame:before,.ion-ios-flame-outline:before,.ion-ios-flask:before,.ion-ios-flask-outline:before,.ion-ios-flower:before,.ion-ios-flower-outline:before,.ion-ios-folder:before,.ion-ios-folder-outline:before,.ion-ios-football:before,.ion-ios-football-outline:before,.ion-ios-game-controller-a:before,.ion-ios-game-controller-a-outline:before,.ion-ios-game-controller-b:before,.ion-ios-game-controller-b-outline:before,.ion-ios-gear:before,.ion-ios-gear-outline:before,.ion-ios-glasses:before,.ion-ios-glasses-outline:before,.ion-ios-grid-view:before,.ion-ios-grid-view-outline:before,.ion-ios-heart:before,.ion-ios-heart-outline:before,.ion-ios-help:before,.ion-ios-help-empty:before,.ion-ios-help-outline:before,.ion-ios-home:before,.ion-ios-home-outline:before,.ion-ios-infinite:before,.ion-ios-infinite-outline:before,.ion-ios-information:before,.ion-ios-information-empty:before,.ion-ios-information-outline:before,.ion-ios-ionic-outline:before,.ion-ios-keypad:before,.ion-ios-keypad-outline:before,.ion-ios-lightbulb:before,.ion-ios-lightbulb-outline:before,.ion-ios-list:before,.ion-ios-list-outline:before,.ion-ios-location:before,.ion-ios-location-outline:before,.ion-ios-locked:before,.ion-ios-locked-outline:before,.ion-ios-loop:before,.ion-ios-loop-strong:before,.ion-ios-medical:before,.ion-ios-medical-outline:before,.ion-ios-medkit:before,.ion-ios-medkit-outline:before,.ion-ios-mic:before,.ion-ios-mic-off:before,.ion-ios-mic-outline:before,.ion-ios-minus:before,.ion-ios-minus-empty:before,.ion-ios-minus-outline:before,.ion-ios-monitor:before,.ion-ios-monitor-outline:before,.ion-ios-moon:before,.ion-ios-moon-outline:before,.ion-ios-more:before,.ion-ios-more-outline:before,.ion-ios-musical-note:before,.ion-ios-musical-notes:before,.ion-ios-navigate:before,.ion-ios-navigate-outline:before,.ion-ios-nutrition:before,.ion-ios-nutrition-outline:before,.ion-ios-paper:before,.ion-ios-paper-outline:before,.ion-ios-paperplane:before,.ion-ios-paperplane-outline:before,.ion-ios-partlysunny:before,.ion-ios-partlysunny-outline:before,.ion-ios-pause:before,.ion-ios-pause-outline:before,.ion-ios-paw:before,.ion-ios-paw-outline:before,.ion-ios-people:before,.ion-ios-people-outline:before,.ion-ios-person:before,.ion-ios-person-outline:before,.ion-ios-personadd:before,.ion-ios-personadd-outline:before,.ion-ios-photos:before,.ion-ios-photos-outline:before,.ion-ios-pie:before,.ion-ios-pie-outline:before,.ion-ios-pint:before,.ion-ios-pint-outline:before,.ion-ios-play:before,.ion-ios-play-outline:before,.ion-ios-plus:before,.ion-ios-plus-empty:before,.ion-ios-plus-outline:before,.ion-ios-pricetag:before,.ion-ios-pricetag-outline:before,.ion-ios-pricetags:before,.ion-ios-pricetags-outline:before,.ion-ios-printer:before,.ion-ios-printer-outline:before,.ion-ios-pulse:before,.ion-ios-pulse-strong:before,.ion-ios-rainy:before,.ion-ios-rainy-outline:before,.ion-ios-recording:before,.ion-ios-recording-outline:before,.ion-ios-redo:before,.ion-ios-redo-outline:before,.ion-ios-refresh:before,.ion-ios-refresh-empty:before,.ion-ios-refresh-outline:before,.ion-ios-reload:before,.ion-ios-reverse-camera:before,.ion-ios-reverse-camera-outline:before,.ion-ios-rewind:before,.ion-ios-rewind-outline:before,.ion-ios-rose:before,.ion-ios-rose-outline:before,.ion-ios-search:before,.ion-ios-search-strong:before,.ion-ios-settings:before,.ion-ios-settings-strong:before,.ion-ios-shuffle:before,.ion-ios-shuffle-strong:before,.ion-ios-skipbackward:before,.ion-ios-skipbackward-outline:before,.ion-ios-skipforward:before,.ion-ios-skipforward-outline:before,.ion-ios-snowy:before,.ion-ios-speedometer:before,.ion-ios-speedometer-outline:before,.ion-ios-star:before,.ion-ios-star-half:before,.ion-ios-star-outline:before,.ion-ios-stopwatch:before,.ion-ios-stopwatch-outline:before,.ion-ios-sunny:before,.ion-ios-sunny-outline:before,.ion-ios-telephone:before,.ion-ios-telephone-outline:before,.ion-ios-tennisball:before,.ion-ios-tennisball-outline:before,.ion-ios-thunderstorm:before,.ion-ios-thunderstorm-outline:before,.ion-ios-time:before,.ion-ios-time-outline:before,.ion-ios-timer:before,.ion-ios-timer-outline:before,.ion-ios-toggle:before,.ion-ios-toggle-outline:before,.ion-ios-trash:before,.ion-ios-trash-outline:before,.ion-ios-undo:before,.ion-ios-undo-outline:before,.ion-ios-unlocked:before,.ion-ios-unlocked-outline:before,.ion-ios-upload:before,.ion-ios-upload-outline:before,.ion-ios-videocam:before,.ion-ios-videocam-outline:before,.ion-ios-volume-high:before,.ion-ios-volume-low:before,.ion-ios-wineglass:before,.ion-ios-wineglass-outline:before,.ion-ios-world:before,.ion-ios-world-outline:before,.ion-ipad:before,.ion-iphone:before,.ion-ipod:before,.ion-jet:before,.ion-key:before,.ion-knife:before,.ion-laptop:before,.ion-leaf:before,.ion-levels:before,.ion-lightbulb:before,.ion-link:before,.ion-load-a:before,.ion-load-b:before,.ion-load-c:before,.ion-load-d:before,.ion-location:before,.ion-lock-combination:before,.ion-locked:before,.ion-log-in:before,.ion-log-out:before,.ion-loop:before,.ion-magnet:before,.ion-male:before,.ion-man:before,.ion-map:before,.ion-medkit:before,.ion-merge:before,.ion-mic-a:before,.ion-mic-b:before,.ion-mic-c:before,.ion-minus:before,.ion-minus-circled:before,.ion-minus-round:before,.ion-model-s:before,.ion-monitor:before,.ion-more:before,.ion-mouse:before,.ion-music-note:before,.ion-navicon:before,.ion-navicon-round:before,.ion-navigate:before,.ion-network:before,.ion-no-smoking:before,.ion-nuclear:before,.ion-outlet:before,.ion-paintbrush:before,.ion-paintbucket:before,.ion-paper-airplane:before,.ion-paperclip:before,.ion-pause:before,.ion-person:before,.ion-person-add:before,.ion-person-stalker:before,.ion-pie-graph:before,.ion-pin:before,.ion-pinpoint:before,.ion-pizza:before,.ion-plane:before,.ion-planet:before,.ion-play:before,.ion-playstation:before,.ion-plus:before,.ion-plus-circled:before,.ion-plus-round:before,.ion-podium:before,.ion-pound:before,.ion-power:before,.ion-pricetag:before,.ion-pricetags:before,.ion-printer:before,.ion-pull-request:before,.ion-qr-scanner:before,.ion-quote:before,.ion-radio-waves:before,.ion-record:before,.ion-refresh:before,.ion-reply:before,.ion-reply-all:before,.ion-ribbon-a:before,.ion-ribbon-b:before,.ion-sad:before,.ion-sad-outline:before,.ion-scissors:before,.ion-search:before,.ion-settings:before,.ion-share:before,.ion-shuffle:before,.ion-skip-backward:before,.ion-skip-forward:before,.ion-social-android:before,.ion-social-android-outline:before,.ion-social-angular:before,.ion-social-angular-outline:before,.ion-social-apple:before,.ion-social-apple-outline:before,.ion-social-bitcoin:before,.ion-social-bitcoin-outline:before,.ion-social-buffer:before,.ion-social-buffer-outline:before,.ion-social-chrome:before,.ion-social-chrome-outline:before,.ion-social-codepen:before,.ion-social-codepen-outline:before,.ion-social-css3:before,.ion-social-css3-outline:before,.ion-social-designernews:before,.ion-social-designernews-outline:before,.ion-social-dribbble:before,.ion-social-dribbble-outline:before,.ion-social-dropbox:before,.ion-social-dropbox-outline:before,.ion-social-euro:before,.ion-social-euro-outline:before,.ion-social-facebook:before,.ion-social-facebook-outline:before,.ion-social-foursquare:before,.ion-social-foursquare-outline:before,.ion-social-freebsd-devil:before,.ion-social-github:before,.ion-social-github-outline:before,.ion-social-google:before,.ion-social-google-outline:before,.ion-social-googleplus:before,.ion-social-googleplus-outline:before,.ion-social-hackernews:before,.ion-social-hackernews-outline:before,.ion-social-html5:before,.ion-social-html5-outline:before,.ion-social-instagram:before,.ion-social-instagram-outline:before,.ion-social-javascript:before,.ion-social-javascript-outline:before,.ion-social-linkedin:before,.ion-social-linkedin-outline:before,.ion-social-markdown:before,.ion-social-nodejs:before,.ion-social-octocat:before,.ion-social-pinterest:before,.ion-social-pinterest-outline:before,.ion-social-python:before,.ion-social-reddit:before,.ion-social-reddit-outline:before,.ion-social-rss:before,.ion-social-rss-outline:before,.ion-social-sass:before,.ion-social-skype:before,.ion-social-skype-outline:before,.ion-social-snapchat:before,.ion-social-snapchat-outline:before,.ion-social-tumblr:before,.ion-social-tumblr-outline:before,.ion-social-tux:before,.ion-social-twitch:before,.ion-social-twitch-outline:before,.ion-social-twitter:before,.ion-social-twitter-outline:before,.ion-social-usd:before,.ion-social-usd-outline:before,.ion-social-vimeo:before,.ion-social-vimeo-outline:before,.ion-social-whatsapp:before,.ion-social-whatsapp-outline:before,.ion-social-windows:before,.ion-social-windows-outline:before,.ion-social-wordpress:before,.ion-social-wordpress-outline:before,.ion-social-yahoo:before,.ion-social-yahoo-outline:before,.ion-social-yen:before,.ion-social-yen-outline:before,.ion-social-youtube:before,.ion-social-youtube-outline:before,.ion-soup-can:before,.ion-soup-can-outline:before,.ion-speakerphone:before,.ion-speedometer:before,.ion-spoon:before,.ion-star:before,.ion-stats-bars:before,.ion-steam:before,.ion-stop:before,.ion-thermometer:before,.ion-thumbsdown:before,.ion-thumbsup:before,.ion-toggle:before,.ion-toggle-filled:before,.ion-transgender:before,.ion-trash-a:before,.ion-trash-b:before,.ion-trophy:before,.ion-tshirt:before,.ion-tshirt-outline:before,.ion-umbrella:before,.ion-university:before,.ion-unlocked:before,.ion-upload:before,.ion-usb:before,.ion-videocamera:before,.ion-volume-high:before,.ion-volume-low:before,.ion-volume-medium:before,.ion-volume-mute:before,.ion-wand:before,.ion-waterdrop:before,.ion-wifi:before,.ion-wineglass:before,.ion-woman:before,.ion-wrench:before,.ion-xbox:before{display:inline-block;font-family:"Ionicons";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;text-rendering:auto;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ion-alert:before{content:"\f101"}.ion-alert-circled:before{content:"\f100"}.ion-android-add:before{content:"\f2c7"}.ion-android-add-circle:before{content:"\f359"}.ion-android-alarm-clock:before{content:"\f35a"}.ion-android-alert:before{content:"\f35b"}.ion-android-apps:before{content:"\f35c"}.ion-android-archive:before{content:"\f2c9"}.ion-android-arrow-back:before{content:"\f2ca"}.ion-android-arrow-down:before{content:"\f35d"}.ion-android-arrow-dropdown:before{content:"\f35f"}.ion-android-arrow-dropdown-circle:before{content:"\f35e"}.ion-android-arrow-dropleft:before{content:"\f361"}.ion-android-arrow-dropleft-circle:before{content:"\f360"}.ion-android-arrow-dropright:before{content:"\f363"}.ion-android-arrow-dropright-circle:before{content:"\f362"}.ion-android-arrow-dropup:before{content:"\f365"}.ion-android-arrow-dropup-circle:before{content:"\f364"}.ion-android-arrow-forward:before{content:"\f30f"}.ion-android-arrow-up:before{content:"\f366"}.ion-android-attach:before{content:"\f367"}.ion-android-bar:before{content:"\f368"}.ion-android-bicycle:before{content:"\f369"}.ion-android-boat:before{content:"\f36a"}.ion-android-bookmark:before{content:"\f36b"}.ion-android-bulb:before{content:"\f36c"}.ion-android-bus:before{content:"\f36d"}.ion-android-calendar:before{content:"\f2d1"}.ion-android-call:before{content:"\f2d2"}.ion-android-camera:before{content:"\f2d3"}.ion-android-cancel:before{content:"\f36e"}.ion-android-car:before{content:"\f36f"}.ion-android-cart:before{content:"\f370"}.ion-android-chat:before{content:"\f2d4"}.ion-android-checkbox:before{content:"\f374"}.ion-android-checkbox-blank:before{content:"\f371"}.ion-android-checkbox-outline:before{content:"\f373"}.ion-android-checkbox-outline-blank:before{content:"\f372"}.ion-android-checkmark-circle:before{content:"\f375"}.ion-android-clipboard:before{content:"\f376"}.ion-android-close:before{content:"\f2d7"}.ion-android-cloud:before{content:"\f37a"}.ion-android-cloud-circle:before{content:"\f377"}.ion-android-cloud-done:before{content:"\f378"}.ion-android-cloud-outline:before{content:"\f379"}.ion-android-color-palette:before{content:"\f37b"}.ion-android-compass:before{content:"\f37c"}.ion-android-contact:before{content:"\f2d8"}.ion-android-contacts:before{content:"\f2d9"}.ion-android-contract:before{content:"\f37d"}.ion-android-create:before{content:"\f37e"}.ion-android-delete:before{content:"\f37f"}.ion-android-desktop:before{content:"\f380"}.ion-android-document:before{content:"\f381"}.ion-android-done:before{content:"\f383"}.ion-android-done-all:before{content:"\f382"}.ion-android-download:before{content:"\f2dd"}.ion-android-drafts:before{content:"\f384"}.ion-android-exit:before{content:"\f385"}.ion-android-expand:before{content:"\f386"}.ion-android-favorite:before{content:"\f388"}.ion-android-favorite-outline:before{content:"\f387"}.ion-android-film:before{content:"\f389"}.ion-android-folder:before{content:"\f2e0"}.ion-android-folder-open:before{content:"\f38a"}.ion-android-funnel:before{content:"\f38b"}.ion-android-globe:before{content:"\f38c"}.ion-android-hand:before{content:"\f2e3"}.ion-android-hangout:before{content:"\f38d"}.ion-android-happy:before{content:"\f38e"}.ion-android-home:before{content:"\f38f"}.ion-android-image:before{content:"\f2e4"}.ion-android-laptop:before{content:"\f390"}.ion-android-list:before{content:"\f391"}.ion-android-locate:before{content:"\f2e9"}.ion-android-lock:before{content:"\f392"}.ion-android-mail:before{content:"\f2eb"}.ion-android-map:before{content:"\f393"}.ion-android-menu:before{content:"\f394"}.ion-android-microphone:before{content:"\f2ec"}.ion-android-microphone-off:before{content:"\f395"}.ion-android-more-horizontal:before{content:"\f396"}.ion-android-more-vertical:before{content:"\f397"}.ion-android-navigate:before{content:"\f398"}.ion-android-notifications:before{content:"\f39b"}.ion-android-notifications-none:before{content:"\f399"}.ion-android-notifications-off:before{content:"\f39a"}.ion-android-open:before{content:"\f39c"}.ion-android-options:before{content:"\f39d"}.ion-android-people:before{content:"\f39e"}.ion-android-person:before{content:"\f3a0"}.ion-android-person-add:before{content:"\f39f"}.ion-android-phone-landscape:before{content:"\f3a1"}.ion-android-phone-portrait:before{content:"\f3a2"}.ion-android-pin:before{content:"\f3a3"}.ion-android-plane:before{content:"\f3a4"}.ion-android-playstore:before{content:"\f2f0"}.ion-android-print:before{content:"\f3a5"}.ion-android-radio-button-off:before{content:"\f3a6"}.ion-android-radio-button-on:before{content:"\f3a7"}.ion-android-refresh:before{content:"\f3a8"}.ion-android-remove:before{content:"\f2f4"}.ion-android-remove-circle:before{content:"\f3a9"}.ion-android-restaurant:before{content:"\f3aa"}.ion-android-sad:before{content:"\f3ab"}.ion-android-search:before{content:"\f2f5"}.ion-android-send:before{content:"\f2f6"}.ion-android-settings:before{content:"\f2f7"}.ion-android-share:before{content:"\f2f8"}.ion-android-share-alt:before{content:"\f3ac"}.ion-android-star:before{content:"\f2fc"}.ion-android-star-half:before{content:"\f3ad"}.ion-android-star-outline:before{content:"\f3ae"}.ion-android-stopwatch:before{content:"\f2fd"}.ion-android-subway:before{content:"\f3af"}.ion-android-sunny:before{content:"\f3b0"}.ion-android-sync:before{content:"\f3b1"}.ion-android-textsms:before{content:"\f3b2"}.ion-android-time:before{content:"\f3b3"}.ion-android-train:before{content:"\f3b4"}.ion-android-unlock:before{content:"\f3b5"}.ion-android-upload:before{content:"\f3b6"}.ion-android-volume-down:before{content:"\f3b7"}.ion-android-volume-mute:before{content:"\f3b8"}.ion-android-volume-off:before{content:"\f3b9"}.ion-android-volume-up:before{content:"\f3ba"}.ion-android-walk:before{content:"\f3bb"}.ion-android-warning:before{content:"\f3bc"}.ion-android-watch:before{content:"\f3bd"}.ion-android-wifi:before{content:"\f305"}.ion-aperture:before{content:"\f313"}.ion-archive:before{content:"\f102"}.ion-arrow-down-a:before{content:"\f103"}.ion-arrow-down-b:before{content:"\f104"}.ion-arrow-down-c:before{content:"\f105"}.ion-arrow-expand:before{content:"\f25e"}.ion-arrow-graph-down-left:before{content:"\f25f"}.ion-arrow-graph-down-right:before{content:"\f260"}.ion-arrow-graph-up-left:before{content:"\f261"}.ion-arrow-graph-up-right:before{content:"\f262"}.ion-arrow-left-a:before{content:"\f106"}.ion-arrow-left-b:before{content:"\f107"}.ion-arrow-left-c:before{content:"\f108"}.ion-arrow-move:before{content:"\f263"}.ion-arrow-resize:before{content:"\f264"}.ion-arrow-return-left:before{content:"\f265"}.ion-arrow-return-right:before{content:"\f266"}.ion-arrow-right-a:before{content:"\f109"}.ion-arrow-right-b:before{content:"\f10a"}.ion-arrow-right-c:before{content:"\f10b"}.ion-arrow-shrink:before{content:"\f267"}.ion-arrow-swap:before{content:"\f268"}.ion-arrow-up-a:before{content:"\f10c"}.ion-arrow-up-b:before{content:"\f10d"}.ion-arrow-up-c:before{content:"\f10e"}.ion-asterisk:before{content:"\f314"}.ion-at:before{content:"\f10f"}.ion-backspace:before{content:"\f3bf"}.ion-backspace-outline:before{content:"\f3be"}.ion-bag:before{content:"\f110"}.ion-battery-charging:before{content:"\f111"}.ion-battery-empty:before{content:"\f112"}.ion-battery-full:before{content:"\f113"}.ion-battery-half:before{content:"\f114"}.ion-battery-low:before{content:"\f115"}.ion-beaker:before{content:"\f269"}.ion-beer:before{content:"\f26a"}.ion-bluetooth:before{content:"\f116"}.ion-bonfire:before{content:"\f315"}.ion-bookmark:before{content:"\f26b"}.ion-bowtie:before{content:"\f3c0"}.ion-briefcase:before{content:"\f26c"}.ion-bug:before{content:"\f2be"}.ion-calculator:before{content:"\f26d"}.ion-calendar:before{content:"\f117"}.ion-camera:before{content:"\f118"}.ion-card:before{content:"\f119"}.ion-cash:before{content:"\f316"}.ion-chatbox:before{content:"\f11b"}.ion-chatbox-working:before{content:"\f11a"}.ion-chatboxes:before{content:"\f11c"}.ion-chatbubble:before{content:"\f11e"}.ion-chatbubble-working:before{content:"\f11d"}.ion-chatbubbles:before{content:"\f11f"}.ion-checkmark:before{content:"\f122"}.ion-checkmark-circled:before{content:"\f120"}.ion-checkmark-round:before{content:"\f121"}.ion-chevron-down:before{content:"\f123"}.ion-chevron-left:before{content:"\f124"}.ion-chevron-right:before{content:"\f125"}.ion-chevron-up:before{content:"\f126"}.ion-clipboard:before{content:"\f127"}.ion-clock:before{content:"\f26e"}.ion-close:before{content:"\f12a"}.ion-close-circled:before{content:"\f128"}.ion-close-round:before{content:"\f129"}.ion-closed-captioning:before{content:"\f317"}.ion-cloud:before{content:"\f12b"}.ion-code:before{content:"\f271"}.ion-code-download:before{content:"\f26f"}.ion-code-working:before{content:"\f270"}.ion-coffee:before{content:"\f272"}.ion-compass:before{content:"\f273"}.ion-compose:before{content:"\f12c"}.ion-connection-bars:before{content:"\f274"}.ion-contrast:before{content:"\f275"}.ion-crop:before{content:"\f3c1"}.ion-cube:before{content:"\f318"}.ion-disc:before{content:"\f12d"}.ion-document:before{content:"\f12f"}.ion-document-text:before{content:"\f12e"}.ion-drag:before{content:"\f130"}.ion-earth:before{content:"\f276"}.ion-easel:before{content:"\f3c2"}.ion-edit:before{content:"\f2bf"}.ion-egg:before{content:"\f277"}.ion-eject:before{content:"\f131"}.ion-email:before{content:"\f132"}.ion-email-unread:before{content:"\f3c3"}.ion-erlenmeyer-flask:before{content:"\f3c5"}.ion-erlenmeyer-flask-bubbles:before{content:"\f3c4"}.ion-eye:before{content:"\f133"}.ion-eye-disabled:before{content:"\f306"}.ion-female:before{content:"\f278"}.ion-filing:before{content:"\f134"}.ion-film-marker:before{content:"\f135"}.ion-fireball:before{content:"\f319"}.ion-flag:before{content:"\f279"}.ion-flame:before{content:"\f31a"}.ion-flash:before{content:"\f137"}.ion-flash-off:before{content:"\f136"}.ion-folder:before{content:"\f139"}.ion-fork:before{content:"\f27a"}.ion-fork-repo:before{content:"\f2c0"}.ion-forward:before{content:"\f13a"}.ion-funnel:before{content:"\f31b"}.ion-gear-a:before{content:"\f13d"}.ion-gear-b:before{content:"\f13e"}.ion-grid:before{content:"\f13f"}.ion-hammer:before{content:"\f27b"}.ion-happy:before{content:"\f31c"}.ion-happy-outline:before{content:"\f3c6"}.ion-headphone:before{content:"\f140"}.ion-heart:before{content:"\f141"}.ion-heart-broken:before{content:"\f31d"}.ion-help:before{content:"\f143"}.ion-help-buoy:before{content:"\f27c"}.ion-help-circled:before{content:"\f142"}.ion-home:before{content:"\f144"}.ion-icecream:before{content:"\f27d"}.ion-image:before{content:"\f147"}.ion-images:before{content:"\f148"}.ion-information:before{content:"\f14a"}.ion-information-circled:before{content:"\f149"}.ion-ionic:before{content:"\f14b"}.ion-ios-alarm:before{content:"\f3c8"}.ion-ios-alarm-outline:before{content:"\f3c7"}.ion-ios-albums:before{content:"\f3ca"}.ion-ios-albums-outline:before{content:"\f3c9"}.ion-ios-americanfootball:before{content:"\f3cc"}.ion-ios-americanfootball-outline:before{content:"\f3cb"}.ion-ios-analytics:before{content:"\f3ce"}.ion-ios-analytics-outline:before{content:"\f3cd"}.ion-ios-arrow-back:before{content:"\f3cf"}.ion-ios-arrow-down:before{content:"\f3d0"}.ion-ios-arrow-forward:before{content:"\f3d1"}.ion-ios-arrow-left:before{content:"\f3d2"}.ion-ios-arrow-right:before{content:"\f3d3"}.ion-ios-arrow-thin-down:before{content:"\f3d4"}.ion-ios-arrow-thin-left:before{content:"\f3d5"}.ion-ios-arrow-thin-right:before{content:"\f3d6"}.ion-ios-arrow-thin-up:before{content:"\f3d7"}.ion-ios-arrow-up:before{content:"\f3d8"}.ion-ios-at:before{content:"\f3da"}.ion-ios-at-outline:before{content:"\f3d9"}.ion-ios-barcode:before{content:"\f3dc"}.ion-ios-barcode-outline:before{content:"\f3db"}.ion-ios-baseball:before{content:"\f3de"}.ion-ios-baseball-outline:before{content:"\f3dd"}.ion-ios-basketball:before{content:"\f3e0"}.ion-ios-basketball-outline:before{content:"\f3df"}.ion-ios-bell:before{content:"\f3e2"}.ion-ios-bell-outline:before{content:"\f3e1"}.ion-ios-body:before{content:"\f3e4"}.ion-ios-body-outline:before{content:"\f3e3"}.ion-ios-bolt:before{content:"\f3e6"}.ion-ios-bolt-outline:before{content:"\f3e5"}.ion-ios-book:before{content:"\f3e8"}.ion-ios-book-outline:before{content:"\f3e7"}.ion-ios-bookmarks:before{content:"\f3ea"}.ion-ios-bookmarks-outline:before{content:"\f3e9"}.ion-ios-box:before{content:"\f3ec"}.ion-ios-box-outline:before{content:"\f3eb"}.ion-ios-briefcase:before{content:"\f3ee"}.ion-ios-briefcase-outline:before{content:"\f3ed"}.ion-ios-browsers:before{content:"\f3f0"}.ion-ios-browsers-outline:before{content:"\f3ef"}.ion-ios-calculator:before{content:"\f3f2"}.ion-ios-calculator-outline:before{content:"\f3f1"}.ion-ios-calendar:before{content:"\f3f4"}.ion-ios-calendar-outline:before{content:"\f3f3"}.ion-ios-camera:before{content:"\f3f6"}.ion-ios-camera-outline:before{content:"\f3f5"}.ion-ios-cart:before{content:"\f3f8"}.ion-ios-cart-outline:before{content:"\f3f7"}.ion-ios-chatboxes:before{content:"\f3fa"}.ion-ios-chatboxes-outline:before{content:"\f3f9"}.ion-ios-chatbubble:before{content:"\f3fc"}.ion-ios-chatbubble-outline:before{content:"\f3fb"}.ion-ios-checkmark:before{content:"\f3ff"}.ion-ios-checkmark-empty:before{content:"\f3fd"}.ion-ios-checkmark-outline:before{content:"\f3fe"}.ion-ios-circle-filled:before{content:"\f400"}.ion-ios-circle-outline:before{content:"\f401"}.ion-ios-clock:before{content:"\f403"}.ion-ios-clock-outline:before{content:"\f402"}.ion-ios-close:before{content:"\f406"}.ion-ios-close-empty:before{content:"\f404"}.ion-ios-close-outline:before{content:"\f405"}.ion-ios-cloud:before{content:"\f40c"}.ion-ios-cloud-download:before{content:"\f408"}.ion-ios-cloud-download-outline:before{content:"\f407"}.ion-ios-cloud-outline:before{content:"\f409"}.ion-ios-cloud-upload:before{content:"\f40b"}.ion-ios-cloud-upload-outline:before{content:"\f40a"}.ion-ios-cloudy:before{content:"\f410"}.ion-ios-cloudy-night:before{content:"\f40e"}.ion-ios-cloudy-night-outline:before{content:"\f40d"}.ion-ios-cloudy-outline:before{content:"\f40f"}.ion-ios-cog:before{content:"\f412"}.ion-ios-cog-outline:before{content:"\f411"}.ion-ios-color-filter:before{content:"\f414"}.ion-ios-color-filter-outline:before{content:"\f413"}.ion-ios-color-wand:before{content:"\f416"}.ion-ios-color-wand-outline:before{content:"\f415"}.ion-ios-compose:before{content:"\f418"}.ion-ios-compose-outline:before{content:"\f417"}.ion-ios-contact:before{content:"\f41a"}.ion-ios-contact-outline:before{content:"\f419"}.ion-ios-copy:before{content:"\f41c"}.ion-ios-copy-outline:before{content:"\f41b"}.ion-ios-crop:before{content:"\f41e"}.ion-ios-crop-strong:before{content:"\f41d"}.ion-ios-download:before{content:"\f420"}.ion-ios-download-outline:before{content:"\f41f"}.ion-ios-drag:before{content:"\f421"}.ion-ios-email:before{content:"\f423"}.ion-ios-email-outline:before{content:"\f422"}.ion-ios-eye:before{content:"\f425"}.ion-ios-eye-outline:before{content:"\f424"}.ion-ios-fastforward:before{content:"\f427"}.ion-ios-fastforward-outline:before{content:"\f426"}.ion-ios-filing:before{content:"\f429"}.ion-ios-filing-outline:before{content:"\f428"}.ion-ios-film:before{content:"\f42b"}.ion-ios-film-outline:before{content:"\f42a"}.ion-ios-flag:before{content:"\f42d"}.ion-ios-flag-outline:before{content:"\f42c"}.ion-ios-flame:before{content:"\f42f"}.ion-ios-flame-outline:before{content:"\f42e"}.ion-ios-flask:before{content:"\f431"}.ion-ios-flask-outline:before{content:"\f430"}.ion-ios-flower:before{content:"\f433"}.ion-ios-flower-outline:before{content:"\f432"}.ion-ios-folder:before{content:"\f435"}.ion-ios-folder-outline:before{content:"\f434"}.ion-ios-football:before{content:"\f437"}.ion-ios-football-outline:before{content:"\f436"}.ion-ios-game-controller-a:before{content:"\f439"}.ion-ios-game-controller-a-outline:before{content:"\f438"}.ion-ios-game-controller-b:before{content:"\f43b"}.ion-ios-game-controller-b-outline:before{content:"\f43a"}.ion-ios-gear:before{content:"\f43d"}.ion-ios-gear-outline:before{content:"\f43c"}.ion-ios-glasses:before{content:"\f43f"}.ion-ios-glasses-outline:before{content:"\f43e"}.ion-ios-grid-view:before{content:"\f441"}.ion-ios-grid-view-outline:before{content:"\f440"}.ion-ios-heart:before{content:"\f443"}.ion-ios-heart-outline:before{content:"\f442"}.ion-ios-help:before{content:"\f446"}.ion-ios-help-empty:before{content:"\f444"}.ion-ios-help-outline:before{content:"\f445"}.ion-ios-home:before{content:"\f448"}.ion-ios-home-outline:before{content:"\f447"}.ion-ios-infinite:before{content:"\f44a"}.ion-ios-infinite-outline:before{content:"\f449"}.ion-ios-information:before{content:"\f44d"}.ion-ios-information-empty:before{content:"\f44b"}.ion-ios-information-outline:before{content:"\f44c"}.ion-ios-ionic-outline:before{content:"\f44e"}.ion-ios-keypad:before{content:"\f450"}.ion-ios-keypad-outline:before{content:"\f44f"}.ion-ios-lightbulb:before{content:"\f452"}.ion-ios-lightbulb-outline:before{content:"\f451"}.ion-ios-list:before{content:"\f454"}.ion-ios-list-outline:before{content:"\f453"}.ion-ios-location:before{content:"\f456"}.ion-ios-location-outline:before{content:"\f455"}.ion-ios-locked:before{content:"\f458"}.ion-ios-locked-outline:before{content:"\f457"}.ion-ios-loop:before{content:"\f45a"}.ion-ios-loop-strong:before{content:"\f459"}.ion-ios-medical:before{content:"\f45c"}.ion-ios-medical-outline:before{content:"\f45b"}.ion-ios-medkit:before{content:"\f45e"}.ion-ios-medkit-outline:before{content:"\f45d"}.ion-ios-mic:before{content:"\f461"}.ion-ios-mic-off:before{content:"\f45f"}.ion-ios-mic-outline:before{content:"\f460"}.ion-ios-minus:before{content:"\f464"}.ion-ios-minus-empty:before{content:"\f462"}.ion-ios-minus-outline:before{content:"\f463"}.ion-ios-monitor:before{content:"\f466"}.ion-ios-monitor-outline:before{content:"\f465"}.ion-ios-moon:before{content:"\f468"}.ion-ios-moon-outline:before{content:"\f467"}.ion-ios-more:before{content:"\f46a"}.ion-ios-more-outline:before{content:"\f469"}.ion-ios-musical-note:before{content:"\f46b"}.ion-ios-musical-notes:before{content:"\f46c"}.ion-ios-navigate:before{content:"\f46e"}.ion-ios-navigate-outline:before{content:"\f46d"}.ion-ios-nutrition:before{content:"\f470"}.ion-ios-nutrition-outline:before{content:"\f46f"}.ion-ios-paper:before{content:"\f472"}.ion-ios-paper-outline:before{content:"\f471"}.ion-ios-paperplane:before{content:"\f474"}.ion-ios-paperplane-outline:before{content:"\f473"}.ion-ios-partlysunny:before{content:"\f476"}.ion-ios-partlysunny-outline:before{content:"\f475"}.ion-ios-pause:before{content:"\f478"}.ion-ios-pause-outline:before{content:"\f477"}.ion-ios-paw:before{content:"\f47a"}.ion-ios-paw-outline:before{content:"\f479"}.ion-ios-people:before{content:"\f47c"}.ion-ios-people-outline:before{content:"\f47b"}.ion-ios-person:before{content:"\f47e"}.ion-ios-person-outline:before{content:"\f47d"}.ion-ios-personadd:before{content:"\f480"}.ion-ios-personadd-outline:before{content:"\f47f"}.ion-ios-photos:before{content:"\f482"}.ion-ios-photos-outline:before{content:"\f481"}.ion-ios-pie:before{content:"\f484"}.ion-ios-pie-outline:before{content:"\f483"}.ion-ios-pint:before{content:"\f486"}.ion-ios-pint-outline:before{content:"\f485"}.ion-ios-play:before{content:"\f488"}.ion-ios-play-outline:before{content:"\f487"}.ion-ios-plus:before{content:"\f48b"}.ion-ios-plus-empty:before{content:"\f489"}.ion-ios-plus-outline:before{content:"\f48a"}.ion-ios-pricetag:before{content:"\f48d"}.ion-ios-pricetag-outline:before{content:"\f48c"}.ion-ios-pricetags:before{content:"\f48f"}.ion-ios-pricetags-outline:before{content:"\f48e"}.ion-ios-printer:before{content:"\f491"}.ion-ios-printer-outline:before{content:"\f490"}.ion-ios-pulse:before{content:"\f493"}.ion-ios-pulse-strong:before{content:"\f492"}.ion-ios-rainy:before{content:"\f495"}.ion-ios-rainy-outline:before{content:"\f494"}.ion-ios-recording:before{content:"\f497"}.ion-ios-recording-outline:before{content:"\f496"}.ion-ios-redo:before{content:"\f499"}.ion-ios-redo-outline:before{content:"\f498"}.ion-ios-refresh:before{content:"\f49c"}.ion-ios-refresh-empty:before{content:"\f49a"}.ion-ios-refresh-outline:before{content:"\f49b"}.ion-ios-reload:before{content:"\f49d"}.ion-ios-reverse-camera:before{content:"\f49f"}.ion-ios-reverse-camera-outline:before{content:"\f49e"}.ion-ios-rewind:before{content:"\f4a1"}.ion-ios-rewind-outline:before{content:"\f4a0"}.ion-ios-rose:before{content:"\f4a3"}.ion-ios-rose-outline:before{content:"\f4a2"}.ion-ios-search:before{content:"\f4a5"}.ion-ios-search-strong:before{content:"\f4a4"}.ion-ios-settings:before{content:"\f4a7"}.ion-ios-settings-strong:before{content:"\f4a6"}.ion-ios-shuffle:before{content:"\f4a9"}.ion-ios-shuffle-strong:before{content:"\f4a8"}.ion-ios-skipbackward:before{content:"\f4ab"}.ion-ios-skipbackward-outline:before{content:"\f4aa"}.ion-ios-skipforward:before{content:"\f4ad"}.ion-ios-skipforward-outline:before{content:"\f4ac"}.ion-ios-snowy:before{content:"\f4ae"}.ion-ios-speedometer:before{content:"\f4b0"}.ion-ios-speedometer-outline:before{content:"\f4af"}.ion-ios-star:before{content:"\f4b3"}.ion-ios-star-half:before{content:"\f4b1"}.ion-ios-star-outline:before{content:"\f4b2"}.ion-ios-stopwatch:before{content:"\f4b5"}.ion-ios-stopwatch-outline:before{content:"\f4b4"}.ion-ios-sunny:before{content:"\f4b7"}.ion-ios-sunny-outline:before{content:"\f4b6"}.ion-ios-telephone:before{content:"\f4b9"}.ion-ios-telephone-outline:before{content:"\f4b8"}.ion-ios-tennisball:before{content:"\f4bb"}.ion-ios-tennisball-outline:before{content:"\f4ba"}.ion-ios-thunderstorm:before{content:"\f4bd"}.ion-ios-thunderstorm-outline:before{content:"\f4bc"}.ion-ios-time:before{content:"\f4bf"}.ion-ios-time-outline:before{content:"\f4be"}.ion-ios-timer:before{content:"\f4c1"}.ion-ios-timer-outline:before{content:"\f4c0"}.ion-ios-toggle:before{content:"\f4c3"}.ion-ios-toggle-outline:before{content:"\f4c2"}.ion-ios-trash:before{content:"\f4c5"}.ion-ios-trash-outline:before{content:"\f4c4"}.ion-ios-undo:before{content:"\f4c7"}.ion-ios-undo-outline:before{content:"\f4c6"}.ion-ios-unlocked:before{content:"\f4c9"}.ion-ios-unlocked-outline:before{content:"\f4c8"}.ion-ios-upload:before{content:"\f4cb"}.ion-ios-upload-outline:before{content:"\f4ca"}.ion-ios-videocam:before{content:"\f4cd"}.ion-ios-videocam-outline:before{content:"\f4cc"}.ion-ios-volume-high:before{content:"\f4ce"}.ion-ios-volume-low:before{content:"\f4cf"}.ion-ios-wineglass:before{content:"\f4d1"}.ion-ios-wineglass-outline:before{content:"\f4d0"}.ion-ios-world:before{content:"\f4d3"}.ion-ios-world-outline:before{content:"\f4d2"}.ion-ipad:before{content:"\f1f9"}.ion-iphone:before{content:"\f1fa"}.ion-ipod:before{content:"\f1fb"}.ion-jet:before{content:"\f295"}.ion-key:before{content:"\f296"}.ion-knife:before{content:"\f297"}.ion-laptop:before{content:"\f1fc"}.ion-leaf:before{content:"\f1fd"}.ion-levels:before{content:"\f298"}.ion-lightbulb:before{content:"\f299"}.ion-link:before{content:"\f1fe"}.ion-load-a:before{content:"\f29a"}.ion-load-b:before{content:"\f29b"}.ion-load-c:before{content:"\f29c"}.ion-load-d:before{content:"\f29d"}.ion-location:before{content:"\f1ff"}.ion-lock-combination:before{content:"\f4d4"}.ion-locked:before{content:"\f200"}.ion-log-in:before{content:"\f29e"}.ion-log-out:before{content:"\f29f"}.ion-loop:before{content:"\f201"}.ion-magnet:before{content:"\f2a0"}.ion-male:before{content:"\f2a1"}.ion-man:before{content:"\f202"}.ion-map:before{content:"\f203"}.ion-medkit:before{content:"\f2a2"}.ion-merge:before{content:"\f33f"}.ion-mic-a:before{content:"\f204"}.ion-mic-b:before{content:"\f205"}.ion-mic-c:before{content:"\f206"}.ion-minus:before{content:"\f209"}.ion-minus-circled:before{content:"\f207"}.ion-minus-round:before{content:"\f208"}.ion-model-s:before{content:"\f2c1"}.ion-monitor:before{content:"\f20a"}.ion-more:before{content:"\f20b"}.ion-mouse:before{content:"\f340"}.ion-music-note:before{content:"\f20c"}.ion-navicon:before{content:"\f20e"}.ion-navicon-round:before{content:"\f20d"}.ion-navigate:before{content:"\f2a3"}.ion-network:before{content:"\f341"}.ion-no-smoking:before{content:"\f2c2"}.ion-nuclear:before{content:"\f2a4"}.ion-outlet:before{content:"\f342"}.ion-paintbrush:before{content:"\f4d5"}.ion-paintbucket:before{content:"\f4d6"}.ion-paper-airplane:before{content:"\f2c3"}.ion-paperclip:before{content:"\f20f"}.ion-pause:before{content:"\f210"}.ion-person:before{content:"\f213"}.ion-person-add:before{content:"\f211"}.ion-person-stalker:before{content:"\f212"}.ion-pie-graph:before{content:"\f2a5"}.ion-pin:before{content:"\f2a6"}.ion-pinpoint:before{content:"\f2a7"}.ion-pizza:before{content:"\f2a8"}.ion-plane:before{content:"\f214"}.ion-planet:before{content:"\f343"}.ion-play:before{content:"\f215"}.ion-playstation:before{content:"\f30a"}.ion-plus:before{content:"\f218"}.ion-plus-circled:before{content:"\f216"}.ion-plus-round:before{content:"\f217"}.ion-podium:before{content:"\f344"}.ion-pound:before{content:"\f219"}.ion-power:before{content:"\f2a9"}.ion-pricetag:before{content:"\f2aa"}.ion-pricetags:before{content:"\f2ab"}.ion-printer:before{content:"\f21a"}.ion-pull-request:before{content:"\f345"}.ion-qr-scanner:before{content:"\f346"}.ion-quote:before{content:"\f347"}.ion-radio-waves:before{content:"\f2ac"}.ion-record:before{content:"\f21b"}.ion-refresh:before{content:"\f21c"}.ion-reply:before{content:"\f21e"}.ion-reply-all:before{content:"\f21d"}.ion-ribbon-a:before{content:"\f348"}.ion-ribbon-b:before{content:"\f349"}.ion-sad:before{content:"\f34a"}.ion-sad-outline:before{content:"\f4d7"}.ion-scissors:before{content:"\f34b"}.ion-search:before{content:"\f21f"}.ion-settings:before{content:"\f2ad"}.ion-share:before{content:"\f220"}.ion-shuffle:before{content:"\f221"}.ion-skip-backward:before{content:"\f222"}.ion-skip-forward:before{content:"\f223"}.ion-social-android:before{content:"\f225"}.ion-social-android-outline:before{content:"\f224"}.ion-social-angular:before{content:"\f4d9"}.ion-social-angular-outline:before{content:"\f4d8"}.ion-social-apple:before{content:"\f227"}.ion-social-apple-outline:before{content:"\f226"}.ion-social-bitcoin:before{content:"\f2af"}.ion-social-bitcoin-outline:before{content:"\f2ae"}.ion-social-buffer:before{content:"\f229"}.ion-social-buffer-outline:before{content:"\f228"}.ion-social-chrome:before{content:"\f4db"}.ion-social-chrome-outline:before{content:"\f4da"}.ion-social-codepen:before{content:"\f4dd"}.ion-social-codepen-outline:before{content:"\f4dc"}.ion-social-css3:before{content:"\f4df"}.ion-social-css3-outline:before{content:"\f4de"}.ion-social-designernews:before{content:"\f22b"}.ion-social-designernews-outline:before{content:"\f22a"}.ion-social-dribbble:before{content:"\f22d"}.ion-social-dribbble-outline:before{content:"\f22c"}.ion-social-dropbox:before{content:"\f22f"}.ion-social-dropbox-outline:before{content:"\f22e"}.ion-social-euro:before{content:"\f4e1"}.ion-social-euro-outline:before{content:"\f4e0"}.ion-social-facebook:before{content:"\f231"}.ion-social-facebook-outline:before{content:"\f230"}.ion-social-foursquare:before{content:"\f34d"}.ion-social-foursquare-outline:before{content:"\f34c"}.ion-social-freebsd-devil:before{content:"\f2c4"}.ion-social-github:before{content:"\f233"}.ion-social-github-outline:before{content:"\f232"}.ion-social-google:before{content:"\f34f"}.ion-social-google-outline:before{content:"\f34e"}.ion-social-googleplus:before{content:"\f235"}.ion-social-googleplus-outline:before{content:"\f234"}.ion-social-hackernews:before{content:"\f237"}.ion-social-hackernews-outline:before{content:"\f236"}.ion-social-html5:before{content:"\f4e3"}.ion-social-html5-outline:before{content:"\f4e2"}.ion-social-instagram:before{content:"\f351"}.ion-social-instagram-outline:before{content:"\f350"}.ion-social-javascript:before{content:"\f4e5"}.ion-social-javascript-outline:before{content:"\f4e4"}.ion-social-linkedin:before{content:"\f239"}.ion-social-linkedin-outline:before{content:"\f238"}.ion-social-markdown:before{content:"\f4e6"}.ion-social-nodejs:before{content:"\f4e7"}.ion-social-octocat:before{content:"\f4e8"}.ion-social-pinterest:before{content:"\f2b1"}.ion-social-pinterest-outline:before{content:"\f2b0"}.ion-social-python:before{content:"\f4e9"}.ion-social-reddit:before{content:"\f23b"}.ion-social-reddit-outline:before{content:"\f23a"}.ion-social-rss:before{content:"\f23d"}.ion-social-rss-outline:before{content:"\f23c"}.ion-social-sass:before{content:"\f4ea"}.ion-social-skype:before{content:"\f23f"}.ion-social-skype-outline:before{content:"\f23e"}.ion-social-snapchat:before{content:"\f4ec"}.ion-social-snapchat-outline:before{content:"\f4eb"}.ion-social-tumblr:before{content:"\f241"}.ion-social-tumblr-outline:before{content:"\f240"}.ion-social-tux:before{content:"\f2c5"}.ion-social-twitch:before{content:"\f4ee"}.ion-social-twitch-outline:before{content:"\f4ed"}.ion-social-twitter:before{content:"\f243"}.ion-social-twitter-outline:before{content:"\f242"}.ion-social-usd:before{content:"\f353"}.ion-social-usd-outline:before{content:"\f352"}.ion-social-vimeo:before{content:"\f245"}.ion-social-vimeo-outline:before{content:"\f244"}.ion-social-whatsapp:before{content:"\f4f0"}.ion-social-whatsapp-outline:before{content:"\f4ef"}.ion-social-windows:before{content:"\f247"}.ion-social-windows-outline:before{content:"\f246"}.ion-social-wordpress:before{content:"\f249"}.ion-social-wordpress-outline:before{content:"\f248"}.ion-social-yahoo:before{content:"\f24b"}.ion-social-yahoo-outline:before{content:"\f24a"}.ion-social-yen:before{content:"\f4f2"}.ion-social-yen-outline:before{content:"\f4f1"}.ion-social-youtube:before{content:"\f24d"}.ion-social-youtube-outline:before{content:"\f24c"}.ion-soup-can:before{content:"\f4f4"}.ion-soup-can-outline:before{content:"\f4f3"}.ion-speakerphone:before{content:"\f2b2"}.ion-speedometer:before{content:"\f2b3"}.ion-spoon:before{content:"\f2b4"}.ion-star:before{content:"\f24e"}.ion-stats-bars:before{content:"\f2b5"}.ion-steam:before{content:"\f30b"}.ion-stop:before{content:"\f24f"}.ion-thermometer:before{content:"\f2b6"}.ion-thumbsdown:before{content:"\f250"}.ion-thumbsup:before{content:"\f251"}.ion-toggle:before{content:"\f355"}.ion-toggle-filled:before{content:"\f354"}.ion-transgender:before{content:"\f4f5"}.ion-trash-a:before{content:"\f252"}.ion-trash-b:before{content:"\f253"}.ion-trophy:before{content:"\f356"}.ion-tshirt:before{content:"\f4f7"}.ion-tshirt-outline:before{content:"\f4f6"}.ion-umbrella:before{content:"\f2b7"}.ion-university:before{content:"\f357"}.ion-unlocked:before{content:"\f254"}.ion-upload:before{content:"\f255"}.ion-usb:before{content:"\f2b8"}.ion-videocamera:before{content:"\f256"}.ion-volume-high:before{content:"\f257"}.ion-volume-low:before{content:"\f258"}.ion-volume-medium:before{content:"\f259"}.ion-volume-mute:before{content:"\f25a"}.ion-wand:before{content:"\f358"}.ion-waterdrop:before{content:"\f25b"}.ion-wifi:before{content:"\f25c"}.ion-wineglass:before{content:"\f2b9"}.ion-woman:before{content:"\f25d"}.ion-wrench:before{content:"\f2ba"}.ion-xbox:before{content:"\f30c"}
12 |
--------------------------------------------------------------------------------