diff --git a/.sass-lint.yml b/.sass-lint.yml new file mode 100644 index 0000000..4609bc4 --- /dev/null +++ b/.sass-lint.yml @@ -0,0 +1,3 @@ +rules: + no-color-literals: 0 + single-line-per-selector: 0 diff --git a/Gruntfile.js b/Gruntfile.js index c508723..c0c6fab 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,7 +4,7 @@ module.exports = function (grunt) { grunt.loadTasks("tasks"); grunt.registerTask("default", ["bower-install-simple", "lint", "copy", "sass", "postcss", "requirejs:default", "inline"]); - grunt.registerTask("lint", ["eslint"]); + grunt.registerTask("lint", ["sasslint", "eslint"]); grunt.registerTask("dev", ["bower-install-simple", "lint", "copy", "sass", "requirejs:dev"]); grunt.registerTask("serve", ["dev", "connect:server", "watch"]); }; diff --git a/README.md b/README.md index 4a15dbc..bc93d6e 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,11 @@ Use `grunt serve` for development. Copy `config.json.example` to `build/config.json` and change it to match your community. + +## Customize style + +Start your development and edit files in `scss/custom/`. Additional information in comments. + ## dataPath (string/array) `dataPath` can be either a string containing the address of a [HopGlass Server](https://github.com/plumpudding/hopglass-server) or an array containing multiple addresses. diff --git a/assets/icons/_icon-mixin.scss b/assets/icons/_icon-mixin.scss new file mode 100644 index 0000000..751f37a --- /dev/null +++ b/assets/icons/_icon-mixin.scss @@ -0,0 +1,7 @@ +@mixin icon($name, $code, $prefix: 'ion-') { + .#{$prefix}#{$name} { + &:before { + content: '#{$code}'; + } + } +} diff --git a/assets/icons/hopglass-icons.css b/assets/icons/hopglass-icons.css deleted file mode 100644 index 8cfb80e..0000000 --- a/assets/icons/hopglass-icons.css +++ /dev/null @@ -1,96 +0,0 @@ -@font-face { - font-family: "Ionicons"; - src: url("fonts/icon.woff2") format("woff2"), - url("fonts/icon.woff") format("woff"), - url("fonts/icon.ttf") format("truetype"); - font-weight: normal; - font-style: normal; - -} - -[class^="ion-"]:before, -[class*=" ion-"]:before, .ion-inside { - display: inline-block; - font-family: "ionicons" !important; - 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-android-add:before { - content: "\f2c7"; -} - -.ion-chevron-left:before { - content: "\f124"; -} - -.ion-chevron-right:before { - content: "\f125"; -} - -.ion-pin:before { - content: "\f3a3"; -} - -.ion-wifi:before { - content: "\f25c"; -} - -.ion-eye:before { - content: "\f133"; -} - -.ion-ios-arrow-thin-left:before { - content: "\f3d5"; -} - -.ion-ios-arrow-thin-right:before { - content: "\f3d6"; -} - -.ion-arrow-up-b:before { - content: "\f10d"; -} - -.ion-arrow-down-b:before { - content: "\f104"; -} - -.ion-android-locate:before { - content: "\f2e9"; -} - -.ion-android-close:before { - content: "\f2d7"; -} - -.ion-android-lock:before { - content: "\f392"; -} - -.ion-ios-copy:before { - content: "\f41c"; -} - -.ion-location:before { - content: "\f456"; -} - -.ion-android-remove:before { - content: "\f2f4"; -} - -.ion-ios-person:before { - content: "\f47e"; -} - -.ion-layer:before { - content: "\f229"; -} diff --git a/assets/icons/icon.scss b/assets/icons/icon.scss new file mode 100644 index 0000000..d589691 --- /dev/null +++ b/assets/icons/icon.scss @@ -0,0 +1,45 @@ +// Needed for standalone scss +// @import 'icon-mixin'; + +@font-face { + font-family: 'ionicons'; + font-style: normal; + font-weight: normal; + src: url('fonts/icon.woff2') format('woff2'), + url('fonts/icon.woff') format('woff'), + url('fonts/icon.ttf') format('truetype'); +} + +[class^='ion-'], [class*=' ion-'] { + &:before { + display: inline-block; + font-family: $font-family-icons; + font-style: normal; + font-variant: normal; + font-weight: normal; + line-height: 1; + speak: none; + text-rendering: auto; + text-transform: none; + vertical-align: 0; + } +} + +@include icon('android-add', '\f2c7'); +@include icon('chevron-left', '\f124'); +@include icon('chevron-right', '\f125'); +@include icon('pin', '\f3a3'); +@include icon('wifi', '\f25c'); +@include icon('eye', '\f133'); +@include icon('ios-arrow-thin-left', '\f3d5'); +@include icon('ios-arrow-thin-right', '\f3d6'); +@include icon('arrow-up-b', '\f10d'); +@include icon('arrow-down-b', '\f104'); +@include icon('android-locate', '\f2e9'); +@include icon('android-close', '\f2d7'); +@include icon('android-lock', '\f392'); +@include icon('ios-copy', '\f41c'); +@include icon('location', '\f456'); +@include icon('android-remove', '\f2f4'); +@include icon('ios-person', '\f47e'); +@include icon('layer', '\f229'); diff --git a/html/index.html b/html/index.html index ff5fa76..b090ecd 100644 --- a/html/index.html +++ b/html/index.html @@ -3,7 +3,6 @@
- diff --git a/lib/about.js b/lib/about.js index 9222794..bb71eec 100644 --- a/lib/about.js +++ b/lib/about.js @@ -3,8 +3,6 @@ define(function () { return function () { this.render = function (d) { - var el = document.createElement("div"); - d.appendChild(el); var s = "Mit Doppelklick und Shift+Doppelklick kann man in der Karte "; @@ -34,7 +32,7 @@ define(function () { s += ""; s += "https://github.com/plumpudding/hopglass."; - el.innerHTML = s; + d.innerHTML = s; }; }; }); diff --git a/lib/filters/filtergui.js b/lib/filters/filtergui.js index 4db1b4b..af1f31e 100644 --- a/lib/filters/filtergui.js +++ b/lib/filters/filtergui.js @@ -17,13 +17,11 @@ define(function () { filters.forEach(function (d) { var li = document.createElement("li"); - var div = document.createElement("div"); container.appendChild(li); - li.appendChild(div); - d.render(div); + d.render(li); var button = document.createElement("button"); - button.textContent = ""; + button.classList.add("ion-android-close"); button.onclick = function () { distributor.removeFilter(d); }; diff --git a/lib/filters/genericnode.js b/lib/filters/genericnode.js index 253c38b..6a6ae15 100644 --- a/lib/filters/genericnode.js +++ b/lib/filters/genericnode.js @@ -7,7 +7,7 @@ define(["helper"], function (helper) { var label = document.createElement("label"); var strong = document.createElement("strong"); - label.textContent = name + " "; + label.textContent = name + ": "; label.appendChild(strong); function run(d) { @@ -26,12 +26,12 @@ define(["helper"], function (helper) { function draw(el) { if (negate) { - el.parentNode.classList.add("not"); + el.classList.add("not"); } else { - el.parentNode.classList.remove("not"); + el.classList.remove("not"); } - strong.textContent = (negate ? "¬" : "" ) + value; + strong.textContent = value; } function render(el) { diff --git a/lib/gui.js b/lib/gui.js index 10c3bb2..88640eb 100644 --- a/lib/gui.js +++ b/lib/gui.js @@ -63,7 +63,7 @@ define(["chroma-js", "map", "sidebar", "tabs", "container", "meshstats", contentDiv.appendChild(buttons); var buttonToggle = document.createElement("button"); - buttonToggle.textContent = "\uF133"; + buttonToggle.classList.add("ion-eye", "shadow"); buttonToggle.onclick = function () { if (content.constructor === Map) { router.view("g"); diff --git a/lib/infobox/link.js b/lib/infobox/link.js index 8cfe683..776c2de 100644 --- a/lib/infobox/link.js +++ b/lib/infobox/link.js @@ -11,17 +11,23 @@ define(["helper"], function (helper) { } return function (config, el, router, d) { - var unknown = !(d.source.node); + var unknown = !d.source.node; var h2 = document.createElement("h2"); - var a1 = document.createElement("a"); + var a1; if (!unknown) { + a1 = document.createElement("a"); a1.href = "#"; a1.onclick = router.node(d.source.node); + } else { + a1 = document.createElement("span"); } a1.textContent = unknown ? d.source.id : d.source.node.nodeinfo.hostname; h2.appendChild(a1); - h2.appendChild(document.createTextNode(" \uF3D6 ")); - h2.className = "ion-inside"; + + var arrow = document.createElement("spam"); + arrow.classList.add("ion-ios-arrow-thin-right"); + h2.appendChild(arrow); + var a2 = document.createElement("a"); a2.href = "#"; a2.onclick = router.node(d.target.node); diff --git a/lib/infobox/location.js b/lib/infobox/location.js index fe40011..b4445c0 100644 --- a/lib/infobox/location.js +++ b/lib/infobox/location.js @@ -31,7 +31,7 @@ define(["helper"], function (helper) { "uci set gluon-node-info.@location[0].longitude='" + d.lng.toFixed(9) + "';" + "uci commit gluon-node-info"; - el.appendChild(createBox("uci", "Befehl", editUci, false)); + el.appendChild(createBox("uci", "Uci", editUci)); var linkPlain = document.createElement("a"); linkPlain.textContent = "plain"; @@ -49,22 +49,13 @@ define(["helper"], function (helper) { }; linkUci.href = "#"; - var hintText = document.createElement("p"); - hintText.appendChild(document.createTextNode("Du kannst zwischen ")); - hintText.appendChild(linkPlain); - hintText.appendChild(document.createTextNode(" und ")); - hintText.appendChild(linkUci); - hintText.appendChild(document.createTextNode(" wechseln.")); - el.appendChild(hintText); - - function createBox(name, title, inputElem, isVisible) { - var visible = typeof isVisible !== "undefined" ? isVisible : true; + function createBox(name, title, inputElem) { var box = document.createElement("div"); var heading = document.createElement("h3"); heading.textContent = title; box.appendChild(heading); var btn = document.createElement("button"); - btn.className = "ion-ios-copy"; + btn.classList.add("ion-ios-copy"); btn.title = "Kopieren"; btn.onclick = function () { copy2clip(inputElem.id); @@ -76,7 +67,6 @@ define(["helper"], function (helper) { line.appendChild(btn); box.appendChild(line); box.id = "box-" + name; - box.style.display = visible ? "block" : "none"; return box; } @@ -90,16 +80,5 @@ define(["helper"], function (helper) { } } - function switch2plain() { - document.getElementById("box-uci").style.display = "none"; - document.getElementById("box-lat").style.display = "block"; - document.getElementById("box-lng").style.display = "block"; - } - - function switch2uci() { - document.getElementById("box-uci").style.display = "block"; - document.getElementById("box-lat").style.display = "none"; - document.getElementById("box-lng").style.display = "none"; - } }; }); diff --git a/lib/infobox/main.js b/lib/infobox/main.js index 9690a2a..5731f35 100644 --- a/lib/infobox/main.js +++ b/lib/infobox/main.js @@ -26,7 +26,7 @@ define(["infobox/link", "infobox/node", "infobox/location"], function (Link, Nod el.destroy = destroy; var closeButton = document.createElement("button"); - closeButton.classList.add("close"); + closeButton.classList.add("close", "ion-android-close"); closeButton.onclick = router.reset; el.appendChild(closeButton); } diff --git a/lib/infobox/node.js b/lib/infobox/node.js index b6fcdac..e01a90b 100644 --- a/lib/infobox/node.js +++ b/lib/infobox/node.js @@ -1,5 +1,5 @@ -define(["moment", "tablesort", "helper", "moment.de"], - function (moment, Tablesort, helper) { +define(["chroma-js", "moment", "tablesort", "helper", "moment.de"], + function (chroma, moment, Tablesort, helper) { "use strict"; function showGeoURI(d) { @@ -158,7 +158,7 @@ define(["moment", "tablesort", "helper", "moment.de"], var span = document.createElement("span"); span.classList.add("clients"); - span.textContent = "\uF47E ".repeat(d.statistics.clients); + span.innerHTML = "".repeat(d.statistics.clients); el.appendChild(span); var spanmesh = document.createElement("span"); @@ -255,8 +255,7 @@ define(["moment", "tablesort", "helper", "moment.de"], function showBar(className, v) { var span = document.createElement("span"); - span.classList.add("bar"); - span.classList.add(className); + span.classList.add("bar", className); var bar = document.createElement("span"); bar.style.width = (v * 100) + "%"; @@ -271,8 +270,7 @@ define(["moment", "tablesort", "helper", "moment.de"], function showLoadBar(className, v) { var span = document.createElement("span"); - span.classList.add("bar"); - span.classList.add(className); + span.classList.add("bar", className); var bar = document.createElement("span"); if (v >= 1) { @@ -411,7 +409,7 @@ define(["moment", "tablesort", "helper", "moment.de"], link.href = webpage; if (webpage.search(/^https:\/\//i) !== -1) { var lock = document.createElement("span"); - lock.className = "ion-android-lock"; + lock.classList.add("ion-android-lock"); a.appendChild(lock); var t1 = document.createTextNode(" "); a.appendChild(t1); @@ -476,6 +474,11 @@ define(["moment", "tablesort", "helper", "moment.de"], } return function (config, el, router, d) { + var linkScale = chroma.scale(chroma.bezier(["#04C714", "#FF5500", "#F02311"])).domain([1, 5]); + var h2 = document.createElement("h2"); + h2.textContent = d.nodeinfo.hostname; + el.appendChild(h2); + var attributes = document.createElement("table"); attributes.classList.add("attributes"); @@ -495,9 +498,9 @@ define(["moment", "tablesort", "helper", "moment.de"], } helper.attributeEntry(attributes, top, d.nodeinfo.hostname); } else { - var h2 = document.createElement("h2"); - h2.textContent = d.nodeinfo.hostname; - el.appendChild(h2); + var heading = document.createElement("h2"); + heading.textContent = d.nodeinfo.hostname; + el.appendChild(heading); } helper.attributeEntry(attributes, "Status", showStatus(d)); @@ -579,28 +582,34 @@ define(["moment", "tablesort", "helper", "moment.de"], var tr = document.createElement("tr"); var td1 = document.createElement("td"); - td1.className = "ion-inside"; - td1.appendChild(document.createTextNode(d.incoming ? " \uF3D5 " : " \uF3D6 ")); - tr.appendChild(td1); - var td2 = document.createElement("td"); - td2.appendChild(createLink(d, router)); + var direction = document.createElement("span"); + direction.classList.add(d.incoming ? "ion-ios-arrow-thin-left" : "ion-ios-arrow-thin-right"); + td1.appendChild(direction); if (!unknown && helper.hasLocation(d.node)) { var span = document.createElement("span"); - span.classList.add("icon"); - span.classList.add("ion-location"); - td2.appendChild(span); + span.classList.add("icon", "ion-location"); + td1.appendChild(span); } + tr.appendChild(td1); + + var td2 = document.createElement("td"); + var a1 = document.createElement("a"); + a1.classList.add("hostname", d.link.target.node.flags.online ? "online" : "unseen"); + a1.textContent = unknown ? d.id : d.node.nodeinfo.hostname; + if (!unknown) { + a1.href = "#"; + } + a1.onclick = router.node(d.node); + td2.appendChild(a1); + tr.appendChild(td2); var td3 = document.createElement("td"); - var a2 = document.createElement("a"); - a2.href = "#"; - a2.textContent = helper.showTq(d.link); - a2.onclick = router.link(d.link); - td3.appendChild(a2); + td3.textContent = helper.showTq(d.link); + td3.style.color = linkScale(d.link.tq).hex(); tr.appendChild(td3); var td4 = document.createElement("td"); @@ -612,11 +621,7 @@ define(["moment", "tablesort", "helper", "moment.de"], tr.appendChild(td4); var td5 = document.createElement("td"); - var a4 = document.createElement("a"); - a4.href = "#"; - a4.textContent = helper.showDistance(d.link); - a4.onclick = router.link(d.link); - td5.appendChild(a4); + td5.textContent = helper.showDistance(d.link); td5.setAttribute("data-sort", d.link.distance !== undefined ? -d.link.distance : 1); tr.appendChild(td5); @@ -624,7 +629,7 @@ define(["moment", "tablesort", "helper", "moment.de"], }); table.appendChild(tbody); - table.className = "node-links"; + table.classList.add("node-links"); Tablesort(table); diff --git a/lib/linklist.js b/lib/linklist.js index 0bf6d7e..d7a589e 100644 --- a/lib/linklist.js +++ b/lib/linklist.js @@ -37,6 +37,7 @@ define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) { return function (linkScale, router) { var table = new SortTable(headings, 2, renderRow); + table.el.classList.add("link-list"); function renderRow(d) { var td1Content = [V.h("a", {href: "#", onclick: router.link(d)}, linkName(d))]; @@ -50,15 +51,11 @@ define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) { } this.render = function (d) { - var el = document.createElement("div"); - el.last = V.h("div"); - d.appendChild(el); - var h2 = document.createElement("h2"); h2.textContent = "Verbindungen"; - el.appendChild(h2); + d.appendChild(h2); - el.appendChild(table.el); + d.appendChild(table.el); }; this.setData = function (d) { diff --git a/lib/main.js b/lib/main.js index 693191c..749d82f 100644 --- a/lib/main.js +++ b/lib/main.js @@ -206,7 +206,7 @@ define(["moment", "router", "leaflet", "gui", "helper", "moment.de"], var urls = []; - if (typeof config.dataPath === "string" || config.dataPath instanceof String) { + if (typeof config.dataPath === "string") { config.dataPath = [config.dataPath]; } diff --git a/lib/map.js b/lib/map.js index 0bc17cb..1749008 100644 --- a/lib/map.js +++ b/lib/map.js @@ -20,8 +20,7 @@ define(["map/clientlayer", "map/labelslayer", }, onAdd: function () { - var button = L.DomUtil.create("button", "add-layer"); - button.textContent = "\uF2C7"; + var button = L.DomUtil.create("button", "add-layer ion-android-add shadow"); // L.DomEvent.disableClickPropagation(button) // Click propagation isn't disabled as this causes problems with the @@ -48,9 +47,7 @@ define(["map/clientlayer", "map/labelslayer", }, onAdd: function () { - var button = L.DomUtil.create("button", "locate-user"); - button.textContent = "\uF2E9"; - + var button = L.DomUtil.create("button", "locate-user ion-android-locate shadow"); L.DomEvent.disableClickPropagation(button); L.DomEvent.addListener(button, "click", this.onClick, this); @@ -87,8 +84,7 @@ define(["map/clientlayer", "map/labelslayer", }, onAdd: function () { - var button = L.DomUtil.create("button", "coord-picker"); - button.textContent = "\uF3A3"; + var button = L.DomUtil.create("button", "coord-picker ion-pin shadow"); // Click propagation isn't disabled as this causes problems with the // location picking mode; instead propagation is stopped in onClick(). diff --git a/lib/nodelist.js b/lib/nodelist.js index 471aba4..3cc2aa6 100644 --- a/lib/nodelist.js +++ b/lib/nodelist.js @@ -25,6 +25,8 @@ define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) { } var headings = [{ + name: '' + }, { name: "Knoten", sort: function (a, b) { var aname = typeof a.nodeinfo.hostname === "string" ? a.nodeinfo.hostname : a.nodeinfo.node_id; @@ -61,6 +63,7 @@ define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) { return function (router) { function renderRow(d) { + var td0Content = []; var td1Content = []; var aClass = ["hostname", d.flags.online ? "online" : "offline"]; @@ -71,28 +74,27 @@ define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) { }, d.nodeinfo.hostname)); if (helper.hasLocation(d)) { - td1Content.push(V.h("span", {className: "icon ion-location"})); + td0Content.push(V.h("span", {className: "icon ion-location"})); } + var td0 = V.h("td", td0Content); var td1 = V.h("td", td1Content); var td2 = V.h("td", showUptime(d.uptime)); var td3 = V.h("td", d.meshlinks.toString()); var td4 = V.h("td", ("clients" in d.statistics ? d.statistics.clients : 0).toFixed(0)); - return V.h("tr", [td1, td2, td3, td4]); + return V.h("tr", [td0, td1, td2, td3, td4]); } - var table = new SortTable(headings, 0, renderRow); + var table = new SortTable(headings, 1, renderRow); + table.el.classList.add('node-list'); this.render = function (d) { - var el = document.createElement("div"); - d.appendChild(el); - var h2 = document.createElement("h2"); h2.textContent = "Alle Knoten"; - el.appendChild(h2); + d.appendChild(h2); - el.appendChild(table.el); + d.appendChild(table.el); }; this.setData = function (d) { diff --git a/lib/sidebar.js b/lib/sidebar.js index f644c6e..c303bc7 100644 --- a/lib/sidebar.js +++ b/lib/sidebar.js @@ -11,7 +11,7 @@ define(function () { var button = document.createElement("button"); sidebar.appendChild(button); - button.classList.add("sidebarhandle"); + button.classList.add("sidebarhandle", "shadow"); button.onclick = function () { sidebar.classList.toggle("hidden"); }; diff --git a/lib/simplenodelist.js b/lib/simplenodelist.js index be480d1..45a7205 100644 --- a/lib/simplenodelist.js +++ b/lib/simplenodelist.js @@ -6,8 +6,7 @@ define(["moment", "virtual-dom", "helper", "moment.de"], function (moment, V, he var el, tbody; self.render = function (d) { - el = document.createElement("div"); - d.appendChild(el); + el = d; }; self.setData = function (data) { @@ -29,6 +28,7 @@ define(["moment", "virtual-dom", "helper", "moment.de"], function (moment, V, he el.appendChild(h2); var table = document.createElement("table"); + table.classList.add("node-list"); el.appendChild(table); tbody = document.createElement("tbody"); @@ -38,6 +38,7 @@ define(["moment", "virtual-dom", "helper", "moment.de"], function (moment, V, he var items = list.map(function (d) { var time = moment(d[field]).from(data.now); + var td0Content = []; var td1Content = []; var aClass = ["hostname", d.flags.online ? "online" : "offline"]; @@ -49,13 +50,14 @@ define(["moment", "virtual-dom", "helper", "moment.de"], function (moment, V, he }, d.nodeinfo.hostname)); if (helper.hasLocation(d)) { - td1Content.push(V.h("span", {className: "icon ion-location"})); + td0Content.push(V.h("span", {className: "icon ion-location"})); } + var td0 = V.h("td", td0Content); var td1 = V.h("td", td1Content); var td2 = V.h("td", time); - return V.h("tr", [td1, td2]); + return V.h("tr", [td0, td1, td2]); }); var tbodyNew = V.h("tbody", items); diff --git a/package.json b/package.json index 8996160..4dcbde3 100644 --- a/package.json +++ b/package.json @@ -21,14 +21,14 @@ "grunt-contrib-clean": "^1.0.0", "grunt-contrib-connect": "^1.0.2", "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-cssmin": "^1.0.1", "grunt-contrib-requirejs": "^1.0.0", "grunt-contrib-uglify": "^1.0.1", "grunt-contrib-watch": "^1.0.0", "grunt-eslint": "^18.1.0", "grunt-inline": "^0.3.6", "grunt-postcss": "^0.8.0", - "grunt-sass": "^1.2.0" + "grunt-sass": "^1.2.0", + "grunt-sass-lint": "^0.2.0" }, "eslintConfig": { "env": { diff --git a/scss/_filters.scss b/scss/_filters.scss deleted file mode 100644 index 32e1f8c..0000000 --- a/scss/_filters.scss +++ /dev/null @@ -1,53 +0,0 @@ -.filters { - margin: 0; - display: flex; - flex-wrap: wrap; - font-family: Roboto, sans-serif; - font-size: 0.83em; - font-weight: bold; - padding: 0 6pt 6pt !important; - - li { - border-radius: 20pt; - display: flex; - padding: 0 0 0 8pt; - margin: 3pt; - align-items: center; - background: #009ee0; - color: rgba(255, 255, 255, 0.8); - - label { - cursor: pointer; - - strong { - color: rgba(255, 255, 255, 1); - } - } - - &.not { - background: #dc0067; - } - - button { - box-shadow: none; - margin: 2pt; - padding: 0; - width: 18pt; - height: 18pt; - background: rgba(255, 255, 255, 0.0); - font-size: 12pt; - vertical-align: middle; - color: rgba(255, 255, 255, 0.8); - - &:hover { - box-shadow: none !important; - color: #dc0067; - background: rgba(255, 255, 255, 0.9); - } - - &:active { - box-shadow: none; - } - } - } -} diff --git a/scss/_legend.scss b/scss/_legend.scss deleted file mode 100644 index c6394c0..0000000 --- a/scss/_legend.scss +++ /dev/null @@ -1,23 +0,0 @@ -.legend .symbol { - width: 1em; - height: 1em; - border-radius: 50%; - display: inline-block; - vertical-align: -5%; -} - -.legend-new .symbol { - background-color: #93E929; -} - -.legend-online .symbol { - background-color: #1566A9; -} - -.legend-offline .symbol { - background-color: #D43E2A; -} - -.legend-online, .legend-offline { - margin-left: 1em; -} diff --git a/scss/_map.scss b/scss/_map.scss deleted file mode 100644 index 5f83e97..0000000 --- a/scss/_map.scss +++ /dev/null @@ -1,59 +0,0 @@ -.stroke-first { - paint-order: stroke; -} - -.pick-coordinates { - cursor: crosshair; -} - -.map { - width: 100%; - height: 100%; - - .node-alert { - -webkit-animation: blink 2s linear; - -webkit-animation-iteration-count: infinite; - animation: blink 2s linear; - animation-iteration-count: infinite; - } - - .leaflet-top button.leaflet-control { - margin-top: $buttondistance; - } - - .leaflet-bottom button.leaflet-control { - margin-bottom: $buttondistance; - } - - .leaflet-left button.leaflet-control { - margin-left: $buttondistance; - } - - .leaflet-right button.leaflet-control { - margin-right: $buttondistance; - } -} - -@-webkit-keyframes blink { - 0% { - opacity: 1.0; - } - 80% { - opacity: 1.0; - } - 90% { - opacity: 0.0; - } -} - -@keyframes blink { - 0% { - opacity: 1.0; - } - 80% { - opacity: 1.0; - } - 90% { - opacity: 0.0; - } -} diff --git a/scss/_shadow.scss b/scss/_shadow.scss deleted file mode 100644 index 7c89b34..0000000 --- a/scss/_shadow.scss +++ /dev/null @@ -1,15 +0,0 @@ -/* Original is in LESS and can be found here: https://gist.github.com/gefangenimnetz/3ef3e18364edf105c5af */ - -@mixin shadow($level:1) { - @if $level == 1 { - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); - } @else if $level == 2 { - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); - } @else if $level == 3 { - box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); - } @else if $level == 4 { - box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22); - } @else if $level == 5 { - box-shadow: 0 19px 38px rgba(0, 0, 0, 0.30), 0 15px 12px rgba(0, 0, 0, 0.22); - } -} diff --git a/scss/custom/_custom.scss b/scss/custom/_custom.scss new file mode 100644 index 0000000..138593f --- /dev/null +++ b/scss/custom/_custom.scss @@ -0,0 +1,10 @@ +// Example of overwriting variables. Take a look at modules/variables +// .node-links { +// color: $color-primary; +// } + +// You can also include additional files for style example https://github.com/ffrgb/meshviewer/tree/ffrgb-config/scss/custom +// Include syntax: @include "name" -> Filename: _name.scss + +// SCSS supports css with a lot of additional features like variables or mixins. +// Autoprefixer runs in postcss, no need to add browser-prefixes like -webkit, -moz or -ms diff --git a/scss/custom/_variables.scss b/scss/custom/_variables.scss new file mode 100644 index 0000000..5fbb6c3 --- /dev/null +++ b/scss/custom/_variables.scss @@ -0,0 +1,4 @@ +// Example of overwriting variables. Take a look at modules/variables +//$color-black: #fff; +//$color-white: invert($color-white); +//$color-primary: invert($color-primary); diff --git a/scss/main.scss b/scss/main.scss index e1dd673..39b8260 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -1,495 +1,26 @@ -@import '_reset'; -@import '_shadow'; -@import '_base'; -@import '_leaflet'; -@import '_leaflet.label'; -@import '_leaflet-layer'; -@import '_filters'; -@import '_loader'; - -$minscreenwidth: 630pt; -$sidebarwidth: 420pt; -$sidebarwidthsmall: 320pt; -$buttondistance: 12pt; - -@import '_sidebar'; -@import '_map'; -@import '_forcegraph'; -@import '_legend'; - -.content { - position: fixed; - width: 100%; - height: 100vh; - - .buttons { - direction: rtl; - unicode-bidi: bidi-override; - - z-index: 100; - position: absolute; - top: $buttondistance; - right: $buttondistance; - - button { - margin-left: $buttondistance; - } - } -} - -.tabs, header { - background: rgba(0, 0, 0, 0.02); -} - -.tabs { - padding: 1em 0 0 !important; - margin: 0; - list-style: none; - display: flex; - font-family: Roboto, sans-serif; - @include shadow(1); -} - -.tabs li { - flex: 1 1 auto; - text-transform: uppercase; - text-align: center; - padding: 0.5em 0.5em 1em; - cursor: pointer; - color: rgba(0, 0, 0, 0.5); -} - -.tabs li:hover { - color: #dc0067; -} - -.tabs .visible { - border-bottom: 2pt solid #dc0067; - color: #dc0067; -} - -body { - margin: 0; - padding: 0; - font-family: 'Roboto Slab', serif; - font-size: 11pt; - color: #333; -} - -th.sort-header::selection { - background: transparent; -} - -th.sort-header { - cursor: pointer; -} - -table th.sort-header:after { - font-family: "ionicons"; - padding-left: 0.25em; - content: '\f10d'; - visibility: hidden; -} - -table th.sort-header:hover:after { - visibility: visible; -} - -table th.sort-up:after, table th.sort-down:after, table th.sort-down:hover:after { - visibility: visible; - opacity: 0.4; -} - -table th.sort-up:after { - content: '\f104'; -} - -table.attributes th { - text-align: left; - font-weight: bold; - vertical-align: top; - padding-right: 1em; - white-space: nowrap; - line-height: 1.41em; -} - -table.attributes td { - text-align: left !important; - width: 100%; - line-height: 1.41em; -} - -table.attributes tr.routerpic { - max-height: 128px; - max-width: 128px; - min-width: 128px; - min-height: 128px; - /*height: 128px;*/ - /*background-color: green;*/ -} - -table.attributes tr.routerpic td { - font-weight: bold; - /*background-color: red;*/ - font-size: large; - vertical-align: bottom; -} - -table.attributes tr.routerpic th { - font-weight: bold; - /*background-color: red;*/ - font-size: large; - vertical-align: bottom; -} - -.nodenamesidebar { - position: relative; - font-weight: bold; - /*background-color: red;*/ - font-size: large; - vertical-align: bottom; - bottom: 0px; -} - -.sidebar { - .infobox, .container { - @include shadow(2); - background: rgba(255, 255, 255, 0.97); - border-radius: 2px; - } - - .container.hidden { - display: none; - } - - .container table.hidden { - display: none; - } - - p { - line-height: 1.67em; - } -} - -.infobox .clients { - font-family: "ionicons"; - color: #1566A9; - word-spacing: -0.2em; - white-space: normal; -} - -.infobox .clientsMesh { - font-family: "ionicons"; - color: #dbdbdb; - word-spacing: -0.2em; - white-space: normal; -} - -.infobox { - position: relative; - padding: 0.25em 0; - margin-bottom: $buttondistance; - - img { - max-width: 100%; - } - - input[type="text"], textarea { - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); - border-radius: 5px; - border: none; - line-height: 1.67em; - vertical-align: bottom; - margin-right: 0.7em; - padding: 3px 6px; - font-family: monospace; - width: 70%; - max-width: 500px; - min-height: 42px; - font-size: 1.15em; - } - - textarea { - resize: vertical; - overflow: auto; - max-height: 300px; - } -} - -button { - -webkit-tap-highlight-color: transparent; - font-family: "ionicons"; - @include shadow(1); - border-radius: 0.9em; - background: rgba(255, 255, 255, 0.7); - color: #333; - border: none; - cursor: pointer; - height: 1.8em; - width: 1.8em; - font-size: 20pt; - transition: box-shadow 0.5s, color 0.5s; - outline: none; -} - -button.active { - color: #dc0067 !important; -} - -button:hover { - background: white; - color: #dc0067; - @include shadow(2); -} - -button:active { - box-shadow: inset 0 5px 20px rgba(0, 0, 0, 0.19), inset 0 3px 6px rgba(0, 0, 0, 0.23); -} - -button::-moz-focus-inner { - border: 0; -} - -button.close { - width: auto; - height: auto; - font-size: 20pt; - float: right; - margin-right: $buttondistance; - margin-top: $buttondistance; - box-shadow: none; - background: transparent; - border-radius: 0; - color: rgba(0, 0, 0, 0.5); - font-family: "ionicons"; - position: absolute; - right: 0; - - &:hover { - color: #dc0067; - } - - &:after { - content: '\f2d7'; - } -} - -.sidebar h2, .sidebar h3 { - padding-left: $buttondistance; - padding-right: $buttondistance; -} - -.sidebar { - p, pre, ul, h4 { - padding: 0 $buttondistance 1em; - } - - table { - padding: 0 $buttondistance; - } - img { - max-width: 100%; - } -} - -table { - border-spacing: 0 0.5em; - td, th { - line-height: 1.41em; - } -} - -.sidebar table { - border-collapse: separate; -} - -.sidebar table th { - font-weight: bold; -} - -.sidebar table:not(.node-links) th:first-child, -.sidebar table:not(.node-links) td:first-child { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 50%; -} - -.sidebar table.node-links th:nth-child(2), -.sidebar table.node-links td:nth-child(2) { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 45%; -} - -.sidebar table.node-links th:first-child, -.sidebar table.node-links td:first-child { - width: 1.5em; -} - -.sidebarhandle { - position: fixed; - left: $sidebarwidth + 2 * $buttondistance; - top: $buttondistance; - z-index: 10; - transition: left 0.5s, box-shadow 0.5s, color 0.5s, transform 0.5s; -} - -.sidebarhandle:after { - padding-right: 0.125em; - content: "\f124"; -} - -.sidebar.hidden .sidebarhandle { - transform: scale(-1, 1); - left: $buttondistance; -} - -.online { - color: #558020 !important; -} - -.offline { - color: #D43E2A !important; -} - -.unseen { - color: #D89100 !important; -} - -.sidebar { - z-index: 5; - width: $sidebarwidth; - box-sizing: border-box; - position: absolute; - top: $buttondistance; - left: $buttondistance; - padding-bottom: $buttondistance; - transition: left 0.5s; -} - -.sidebar.hidden { - left: -$sidebarwidth - $buttondistance; -} - -.sidebar .icon { - padding: 0 0.25em; -} - -.sidebar table { - width: 100%; - table-layout: fixed; -} - -.sidebar table th { - text-align: left; -} - -.sidebar table:not(.node-links) td:not(:first-child), -.sidebar table:not(.node-links) th:not(:first-child) { - text-align: right; -} - -.sidebar table.node-links td:not(:nth-child(-n+2)), -.sidebar table.node-links th:not(:nth-child(-n+2)) { - text-align: right; -} - -.sidebar a { - color: #1566A9; -} - -.bar { - display: block; - height: 1.4em; - background: rgba(85, 128, 32, 0.5); - position: relative; - - span { - display: inline-block; - height: 1.4em; - background: rgba(85, 128, 32, 0.8); - } - - label { - font-weight: bold; - white-space: nowrap; - color: white; - position: absolute; - right: 0.5em; - top: 0.1em; - } -} - -.proportion th { - font-weight: normal !important; - text-align: right !important; - font-size: 0.95em; - padding-right: 0.71em; -} - -.proportion td { - text-align: left !important; - width: 100%; -} - -.proportion td, .proportion th { - white-space: nowrap; -} - -.proportion span { - display: inline-block; - background: black; - padding: 0.25em 0.5em; - font-weight: bold; - min-width: 1.5em; - box-sizing: border-box; -} - -@media screen and (max-width: 80em) { - .sidebar { - font-size: 0.8em; - top: 0; - left: 0; - margin: 0; - width: $sidebarwidthsmall; - min-height: 100vh; - @include shadow(2); - background: white; - - .sidebarhandle { - left: $sidebarwidthsmall + $buttondistance; - } - - .container, .infobox { - margin: 0; - box-shadow: none; - border-radius: 0; - } - } -} - -@media screen and (max-width: $minscreenwidth) { - .sidebarhandle { - display: none; - } - - .content { - position: relative; - width: auto; - height: 60vh; - } - - .sidebar { - position: static; - margin: 0 !important; - width: auto; - height: auto; - min-height: 0; - } - - .sidebar.hidden { - width: auto; - } -} +// Set variables +@import 'modules/variables'; +@import 'custom/variables'; +// Add modules +@import 'mixins/shadow'; +@import 'mixins/icon-mixin'; +@import 'modules/reset'; +@import 'modules/base'; +@import 'modules/icon'; +@import 'leaflet'; +@import 'leaflet.label'; +@import 'modules/leaflet-layer'; +@import 'modules/table'; +@import 'modules/filter'; +@import 'modules/sidebar'; +@import 'modules/map'; +@import 'modules/forcegraph'; +@import 'modules/legend'; +@import 'modules/proportion'; +@import 'modules/tabs'; +@import 'modules/node'; +@import 'modules/infobox'; +@import 'modules/button'; + +// Make adjustments in custom scss +@import 'custom/custom'; diff --git a/scss/mixins/_icon-mixin.scss b/scss/mixins/_icon-mixin.scss new file mode 120000 index 0000000..2f2507f --- /dev/null +++ b/scss/mixins/_icon-mixin.scss @@ -0,0 +1 @@ +../../assets/icons/_icon-mixin.scss \ No newline at end of file diff --git a/scss/mixins/_shadow.scss b/scss/mixins/_shadow.scss new file mode 100644 index 0000000..6e86e10 --- /dev/null +++ b/scss/mixins/_shadow.scss @@ -0,0 +1,14 @@ +// Original is in LESS and can be found here: https://gist.github.com/gefangenimnetz/3ef3e18364edf105c5af +@mixin shadow($level: 1) { + @if $level == 1 { + box-shadow: 0 1px 3px rgba($color-black, .12), 0 1px 2px rgba($color-black, .24); + } @else if $level == 2 { + box-shadow: 0 3px 6px rgba($color-black, .16), 0 3px 6px rgba($color-black, .23); + } @else if $level == 3 { + box-shadow: 0 10px 20px rgba($color-black, .19), 0 6px 6px rgba($color-black, .23); + } @else if $level == 4 { + box-shadow: 0 14px 28px rgba($color-black, .25), 0 10px 10px rgba($color-black, .22); + } @else if $level == 5 { + box-shadow: 0 19px 38px rgba($color-black, .3), 0 15px 12px rgba($color-black, .22); + } +} diff --git a/scss/modules/_base.scss b/scss/modules/_base.scss new file mode 100644 index 0000000..f7c4817 --- /dev/null +++ b/scss/modules/_base.scss @@ -0,0 +1,56 @@ +body { + background: $color-white; + color: $color-black; + font-family: $font-family; + font-size: $font-size; + overflow-y: scroll; +} + +header { + background: rgba($color-black, .02); +} + +h1, h2, h3, h4, h5, h6 { + font-weight: bold; +} + +h1 { + font-size: 2em; + padding: .67em 0; +} + +h2 { + font-size: 1.5em; + padding: .83em 0; +} + +h3 { + font-size: 1.17em; + padding: 1em 0; +} + +h2, h3 { + padding-left: $buttondistance; + padding-right: $buttondistance; +} + +p, pre, ul, h4 { + padding: 0 $buttondistance 1em; +} + +img { + max-width: 100%; +} + +a { + color: $color-online; + text-decoration: none; +} + +p { + line-height: 1.67em; +} + +strong { + font-weight: bold; +} diff --git a/scss/modules/_button.scss b/scss/modules/_button.scss new file mode 100644 index 0000000..38c332f --- /dev/null +++ b/scss/modules/_button.scss @@ -0,0 +1,47 @@ +button { + background: $color-white; + border: 0; + border-radius: .9em; + color: $color-black; + cursor: pointer; + font-family: $font-family-icons; + font-size: 20pt; + height: 1.8em; + opacity: .7; + outline: none; + transition: box-shadow .5s, color .5s; + width: 1.8em; + + &.active { + color: $color-primary; + } + + &:hover { + background: $color-white; + color: $color-primary; + } + + &.shadow { + @include shadow(1); + + &:hover { + @include shadow(2); + } + + &:active { + box-shadow: inset 0 5px 20px rgba($color-black, .19), inset 0 3px 6px rgba($color-black, .23); + } + } + + &.close { + background: none; + border-radius: 0; + box-shadow: none; + color: rgba($color-black, .5); + float: right; + font-size: 20pt; + height: auto; + margin: $buttondistance; + width: auto; + } +} diff --git a/scss/modules/_filter.scss b/scss/modules/_filter.scss new file mode 100644 index 0000000..384b84a --- /dev/null +++ b/scss/modules/_filter.scss @@ -0,0 +1,40 @@ +.filters { + display: flex; + flex-wrap: wrap; + font-size: .83em; + padding: 0 6pt 6pt; + + li { + align-items: center; + background: transparent; + border: 1px solid $color-primary; + color: $color-primary; + display: flex; + margin: 3pt; + padding: 0 0 0 8pt; + + label { + cursor: pointer; + } + + button { + background: none; + color: $color-gray-light; + font-size: 12pt; + height: 18pt; + margin: 2pt; + width: 18pt; + + &:hover { + color: $color-primary; + } + } + + &.not { + label { + color: $color-primary; + text-decoration: line-through; + } + } + } +} diff --git a/scss/_forcegraph.scss b/scss/modules/_forcegraph.scss similarity index 74% rename from scss/_forcegraph.scss rename to scss/modules/_forcegraph.scss index 84bdd6f..9815ea6 100644 --- a/scss/_forcegraph.scss +++ b/scss/modules/_forcegraph.scss @@ -1,10 +1,10 @@ .graph { + background: $color-gray-dark; height: 100%; width: 100%; - background: #2B2B2B; canvas { display: block; position: absolute; } -} +} diff --git a/scss/modules/_infobox.scss b/scss/modules/_infobox.scss new file mode 100644 index 0000000..b3f9fc5 --- /dev/null +++ b/scss/modules/_infobox.scss @@ -0,0 +1,27 @@ +.infobox { + .clients { + color: $color-online; + font-family: $font-family-icons; + } + + input, textarea { + border: 1px solid $color-gray-light; + font-family: $font-family-monospace; + font-size: 1.15em; + line-height: 1.67em; + margin-right: .7em; + max-width: 500px; + min-height: 42px; + padding: 3px 6px; + vertical-align: bottom; + width: calc(100% - 80px); + } + + textarea { + font-size: .8em; + height: 100px; + max-height: 300px; + overflow: auto; + resize: vertical; + } +} diff --git a/scss/_leaflet-layer.scss b/scss/modules/_leaflet-layer.scss similarity index 67% rename from scss/_leaflet-layer.scss rename to scss/modules/_leaflet-layer.scss index a22f74e..26e554f 100644 --- a/scss/_leaflet-layer.scss +++ b/scss/modules/_leaflet-layer.scss @@ -1,22 +1,21 @@ .leaflet-control-layers { - box-shadow: none; - border-radius: 0; background: none; + border-radius: 0; + box-shadow: none; } .leaflet-control-layers-toggle { background: none; + &::before { - content: "\f229"; + color: $color-primary; + content: '\f229'; display: inline-block; - font-family: "ionicons" !important; + font-family: $font-family-icons; font-size: 2.3rem; + line-height: 1; speak: none; text-rendering: auto; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - color: #e32d6d; } } @@ -25,8 +24,10 @@ } .leaflet-control-layers-list { - background: rgba(255, 255, 255, 0.9); + background: rgba($color-white, .9); + color: $color-gray-dark; padding: 10px; + label { cursor: pointer; } diff --git a/scss/modules/_legend.scss b/scss/modules/_legend.scss new file mode 100644 index 0000000..11251d1 --- /dev/null +++ b/scss/modules/_legend.scss @@ -0,0 +1,32 @@ +.legend { + .symbol { + border-radius: 50%; + display: inline-block; + height: 1em; + vertical-align: -5%; + width: 1em; + } +} + +// Dot looks compared to thin font a bit darker - lighten it 10% +.legend-new { + .symbol { + background-color: lighten($color-new, 10%); + } +} + +.legend-online { + .symbol { + background-color: lighten($color-online, 10%); + } +} + +.legend-offline { + .symbol { + background-color: lighten($color-offline, 10%); + } +} + +.legend-online, .legend-offline { + margin-left: 1em; +} diff --git a/scss/modules/_map.scss b/scss/modules/_map.scss new file mode 100644 index 0000000..1e4d450 --- /dev/null +++ b/scss/modules/_map.scss @@ -0,0 +1,41 @@ +.content { + height: 100vh; + position: fixed; + width: 100%; + + .buttons { + direction: rtl; + position: absolute; + right: $buttondistance; + top: $buttondistance; + unicode-bidi: bidi-override; + z-index: 100; + + button { + margin-left: $buttondistance; + } + @media screen and (max-width: $minscreenwidth) { + right: 0; + transform: scale(.8); + } + } + + @media screen and (max-width: $minscreenwidth) { + height: 60vh; + position: relative; + width: auto; + } +} + +.stroke-first { + paint-order: stroke; +} + +.pick-coordinates { + cursor: crosshair; +} + +.map { + height: 100%; + width: 100%; +} diff --git a/scss/modules/_node.scss b/scss/modules/_node.scss new file mode 100644 index 0000000..0cb951d --- /dev/null +++ b/scss/modules/_node.scss @@ -0,0 +1,21 @@ +.bar { + background: rgba($color-new, .5); + display: block; + height: 1.4em; + position: relative; + + span { + background: rgba($color-new, .8); + display: inline-block; + height: 1.4em; + } + + label { + color: $color-white; + font-weight: bold; + position: absolute; + right: .5em; + top: .1em; + white-space: nowrap; + } +} diff --git a/scss/modules/_proportion.scss b/scss/modules/_proportion.scss new file mode 100644 index 0000000..11fb59a --- /dev/null +++ b/scss/modules/_proportion.scss @@ -0,0 +1,20 @@ +.proportion { + th { + font-size: .95em; + font-weight: normal; + padding-right: .71em; + text-align: right; + } + + td { + text-align: left; + } + + span { + box-sizing: border-box; + display: inline-block; + font-weight: bold; + min-width: 1.5em; + padding: .25em .5em; + } +} diff --git a/scss/_reset.scss b/scss/modules/_reset.scss similarity index 64% rename from scss/_reset.scss rename to scss/modules/_reset.scss index 297b298..11a108e 100644 --- a/scss/_reset.scss +++ b/scss/modules/_reset.scss @@ -1,7 +1,5 @@ -/** - * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) - * http://cssreset.com - */ +// Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) +// http://cssreset.com html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, @@ -12,23 +10,17 @@ dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, +figure, figcaption, footer, header, menu, nav, output, ruby, section, summary, time, mark, audio, video { + border: 0; + font: inherit; + font-size: 100%; margin: 0; padding: 0; - border: 0; - font-size: 100%; - font: inherit; vertical-align: baseline; } -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} - body { line-height: 1; } @@ -41,12 +33,6 @@ blockquote, q { quotes: none; } -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} - table { border-collapse: collapse; border-spacing: 0; diff --git a/scss/modules/_sidebar.scss b/scss/modules/_sidebar.scss new file mode 100644 index 0000000..57cb798 --- /dev/null +++ b/scss/modules/_sidebar.scss @@ -0,0 +1,127 @@ +.sidebar { + box-sizing: border-box; + position: absolute; + transition: left .5s; + width: $sidebarwidth; + z-index: 5; + + &.hidden { + left: -$sidebarwidth - $buttondistance; + + .sidebarhandle { + left: $buttondistance; + transform: scale(-1, 1); + } + @media screen and (max-width: $minscreenwidth) { + width: auto; + } + } + + .node-list, .node-links { + th, td { + &:first-child { + width: 25px; + } + + &:nth-child(2) { + overflow: hidden; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + width: 50%; + } + } + } + + .node-links { + th, td { + &:first-child { + width: 50px; + } + } + } + + .link-list { + th, td { + &:nth-child(1) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 66%; + } + } + } + + .infobox, .container { + @include shadow(2); + background: rgba($color-white, .97); + min-height: 100vh; + overflow-y: visible; + } + + .container { + &.hidden { + display: none; + } + } + + @media screen and (max-width: 80em) { + @include shadow(2); + background: $color-white; + font-size: .8em; + margin: 0; + width: $sidebarwidthsmall; + + .sidebarhandle { + left: $sidebarwidthsmall + $buttondistance; + } + + .container, .infobox { + border-radius: 0; + box-shadow: none; + margin: 0; + } + } + + @media screen and (max-width: $minscreenwidth) { + height: auto; + min-height: 0; + position: static; + width: auto; + + .sidebarhandle { + display: none; + } + + .content { + height: 60vh; + position: relative; + width: auto; + } + } +} + +.sidebarhandle { + left: $sidebarwidth + 2 * $buttondistance; + position: fixed; + top: $buttondistance; + transition: left .5s, box-shadow .5s, color .5s, transform .5s; + z-index: 10; + + &::after { + content: '\f124'; + padding-right: .125em; + } +} + +.online { + color: $color-new; +} + +.offline { + color: $color-offline; +} + +.unseen { + color: #d89100; +} diff --git a/scss/modules/_table.scss b/scss/modules/_table.scss new file mode 100644 index 0000000..94c2c74 --- /dev/null +++ b/scss/modules/_table.scss @@ -0,0 +1,76 @@ +table { + border-collapse: separate; + border-spacing: 0 .5em; + padding: 0 $buttondistance; + width: 100%; + + &.attributes { + line-height: 1.41em; + + th { + font-weight: bold; + padding-right: 1em; + text-align: left; + vertical-align: top; + white-space: nowrap; + } + + td { + text-align: left; + width: 100%; + } + } +} + +td, th { + line-height: 1.41em; + text-align: right; + + &:first-child { + text-align: left; + } +} + +th { + font-weight: bold; + + &.sort-header { + cursor: pointer; + + &::selection { + background: transparent; + } + + &::after { + content: '\f10d'; + font-family: $font-family-icons; + padding-left: .25em; + visibility: hidden; + } + + &:hover { + &::after { + visibility: visible; + } + } + } + + &.sort-up { + &::after { + content: '\f104'; + } + } + + &.sort-up, &.sort-down { + &:after { + opacity: .4; + visibility: visible; + } + } +} + +.tab { + table { + table-layout: fixed; + } +} diff --git a/scss/modules/_tabs.scss b/scss/modules/_tabs.scss new file mode 100644 index 0000000..6a84d0b --- /dev/null +++ b/scss/modules/_tabs.scss @@ -0,0 +1,27 @@ +.tabs { + @include shadow(1); + background: rgba($color-black, .02); + display: flex; + font-family: $font-family; + list-style: none; + margin: 0; + padding: 1em 0 0; + + li { + color: rgba($color-black, .5); + cursor: pointer; + flex: 1 1 auto; + padding: .5em .5em 1em; + text-align: center; + text-transform: uppercase; + + &:hover { + color: $color-black; + } + } + + .visible { + border-bottom: 2pt solid $color-primary; + color: $color-primary; + } +} diff --git a/scss/modules/_variables.scss b/scss/modules/_variables.scss new file mode 100644 index 0000000..8902c89 --- /dev/null +++ b/scss/modules/_variables.scss @@ -0,0 +1,22 @@ +$color-white: #fff !default; +$color-black: #000 !default; + +$color-gray-light: darken($color-white, 30%) !default; +$color-gray-dark: lighten($color-black, 20%) !default; + +$color-primary: #dc0067 !default; + +$color-new: #459c18 !default; +$color-online: #1566a9 !default; +$color-offline: #cf3e2a !default; + +$font-family: 'Roboto Slab', serif !default; +$font-family-secondary: Roboto, sans-serif !default; +$font-family-icons: ionicons !default; +$font-family-monospace: monospace !default; +$font-size: 11pt !default; + +$minscreenwidth: 630pt !default; +$sidebarwidth: 420pt !default; +$sidebarwidthsmall: 320pt !default; +$buttondistance: 12pt !default; diff --git a/scss/modules/icon.scss b/scss/modules/icon.scss new file mode 120000 index 0000000..1c3e5f8 --- /dev/null +++ b/scss/modules/icon.scss @@ -0,0 +1 @@ +../../assets/icons/icon.scss \ No newline at end of file diff --git a/style.css b/style.css deleted file mode 120000 index 6da54f6..0000000 --- a/style.css +++ /dev/null @@ -1 +0,0 @@ -build/style.css \ No newline at end of file diff --git a/tasks/build.js b/tasks/build.js index 4701cfb..8b029d2 100644 --- a/tasks/build.js +++ b/tasks/build.js @@ -39,9 +39,7 @@ module.exports = function (grunt) { cwd: "bower_components/roboto-fontface" }, ionicons: { - src: ["fonts/*", - "hopglass-icons.css" - ], + src: ["fonts/*"], expand: true, dest: "build/", cwd: "assets/icons/" @@ -77,16 +75,6 @@ module.exports = function (grunt) { src: "build/style.css" } }, - cssmin: { - target: { - files: { - "build/style.css": ["bower_components/leaflet/dist/leaflet.css", - "bower_components/Leaflet.label/dist/leaflet.label.css", - "style.css" - ] - } - } - }, inline: { dist: { options: { diff --git a/tasks/linting.js b/tasks/linting.js index 6667d68..2eefced 100644 --- a/tasks/linting.js +++ b/tasks/linting.js @@ -13,6 +13,12 @@ module.exports = function (grunt) { }, npm: {} }, + sasslint: { + options: { + configFile: '.sass-lint.yml' + }, + target: ['scss/main.scss', 'scss/*/*.scss'] + }, eslint: { sources: { src: ["app.js", "!Gruntfile.js", "lib/**/*.js"] @@ -24,5 +30,6 @@ module.exports = function (grunt) { }); grunt.loadNpmTasks("grunt-check-dependencies"); + grunt.loadNpmTasks('grunt-sass-lint'); grunt.loadNpmTasks("grunt-eslint"); };