├── 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 | 4 | Generated by IcoMoon 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 | -------------------------------------------------------------------------------- /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 | 4 | Generated by IcoMoon 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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------