From 6299c8223ee6af79b1b9cf3b64a21de03f43f80b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 27 Nov 2020 03:05:29 +0100 Subject: [PATCH] Add composable icons, add icon badges and overlays, add icon badge to drinking water and cyclofix --- Customizations/JSON/LayerConfig.ts | 76 +++++++++-- Customizations/JSON/LayerConfigJson.ts | 8 ++ Svg.ts | 17 ++- UI/LayerSelection.ts | 17 +-- .../bike_repair_station.json | 7 + .../layers/drinking_water/drinking_water.json | 22 ++-- .../layers/drinking_water/drinking_water.svg | 18 --- .../drinking_water/drinking_water_broken.svg | 124 ------------------ assets/layers/drinking_water/drips.svg | 101 ++++++++++++++ assets/svg/circle.svg | 98 ++++++++++++++ assets/svg/cross_bottom_right.svg | 85 ++++++++++++ assets/svg/pin.svg | 102 ++++++++++++++ index.css | 15 +++ 13 files changed, 518 insertions(+), 172 deletions(-) delete mode 100644 assets/layers/drinking_water/drinking_water.svg delete mode 100644 assets/layers/drinking_water/drinking_water_broken.svg create mode 100644 assets/layers/drinking_water/drips.svg create mode 100644 assets/svg/circle.svg create mode 100644 assets/svg/cross_bottom_right.svg create mode 100644 assets/svg/pin.svg diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 1bb0557..c2bbd3f 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -12,7 +12,6 @@ import {SubstitutedTranslation} from "../../UI/SpecialVisualizations"; import {Utils} from "../../Utils"; import Combine from "../../UI/Base/Combine"; import {VariableUiElement} from "../../UI/Base/VariableUIElement"; -import {UIElement} from "../../UI/UIElement"; import {UIEventSource} from "../../Logic/UIEventSource"; export default class LayerConfig { @@ -35,6 +34,7 @@ export default class LayerConfig { titleIcons: TagRenderingConfig[]; icon: TagRenderingConfig; + iconOverlays: { if: TagsFilter, then: string, badge: boolean }[] iconSize: TagRenderingConfig; rotation: TagRenderingConfig; color: TagRenderingConfig; @@ -138,6 +138,14 @@ export default class LayerConfig { this.title = tr("title", undefined); this.icon = tr("icon", Img.AsData(Svg.bug)); + this.iconOverlays = (json.iconOverlays ?? []).map(overlay => { + return { + if: FromJSON.Tag(overlay.if), + then: overlay.then, + badge: overlay.badge ?? false + } + }); + const iconPath = this.icon.GetRenderValue({id: "node/-1"}).txt; if (iconPath.startsWith(Utils.assets_path)) { const iconKey = iconPath.substr(Utils.assets_path.length); @@ -222,23 +230,65 @@ export default class LayerConfig { } const iconUrlStatic = render(this.icon); - - var mappedHtml = tags.map(_ => { + const self = this; + var mappedHtml = tags.map(tags => { // What do you mean, 'tags' is never read? // It is read implicitly in the 'render' method - const iconUrl = render(this.icon); - const rotation = render(this.rotation, "0deg"); - let html = ``; + const iconUrl = render(self.icon); + const rotation = render(self.rotation, "0deg"); - if (iconUrl.startsWith(Utils.assets_path)) { - const key = iconUrl.substr(Utils.assets_path.length); - html = new Combine([ - (Svg.All[key] as string).replace(/stop-color:#000000/g, 'stop-color:' + color) - ]).SetStyle(`width:100%;height:100%;rotate:${rotation};display:block;`) + let htmlParts = []; + let sourceParts = iconUrl.split(";"); - .Render(); + function genHtmlFromString(sourcePart: string, style?: string): string { + style = style ?? `width:100%;height:100%;rotate:${rotation};display:block;position: absolute; top: 0, left: 0`; + let html = ``; + const match = sourcePart.match(/([a-zA-Z0-9_]*):#([0-9a-fA-F]{3,6})/) + if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { + html = new Combine([ + (Svg.All[match[1] + ".svg"] as string) + .replace(/#000000/g, "#" + match[2]) + ]).SetStyle(style).Render(); + } + + if (sourcePart.startsWith(Utils.assets_path)) { + const key = sourcePart.substr(Utils.assets_path.length); + html = new Combine([ + (Svg.All[key] as string).replace(/stop-color:#000000/g, 'stop-color:' + color) + ]).SetStyle(style) + + .Render(); + } + return html; } - return html; + + + for (const sourcePart of sourceParts) { + htmlParts.push(genHtmlFromString(sourcePart)) + } + + + let badges = []; + for (const iconOverlay of self.iconOverlays) { + if (!iconOverlay.if.matchesProperties(tags)) { + continue; + } + if (iconOverlay.badge) { + badges.push(genHtmlFromString(iconOverlay.then, "display: block;height:100%")) + } else { + htmlParts.push(genHtmlFromString(iconOverlay.then)); + } + } + + if (badges.length > 0) { + const badgesComponent = new Combine(badges) + + .SetStyle("display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;") + .Render() + + htmlParts.push(badgesComponent) + } + return htmlParts.join(""); }) diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index ac1b338..3017882 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -60,6 +60,14 @@ export interface LayerConfigJson { */ icon?: string | TagRenderingConfigJson; + /** + * IconsOverlays are a list of extra icons/badges to overlay over the icon. + * The 'badge'-toggle changes their behaviour. + * If badge is set, it will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout. + * If badges is false, it'll be a simple overlay + */ + iconOverlays?: {if: AndOrTagConfigJson, then: string, badge?: boolean}[] + /** * A string containing "width,height" or "width,height,anchorpoint" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ... * Default is '40,40,center' diff --git a/Svg.ts b/Svg.ts index 144fdb3..5010076 100644 --- a/Svg.ts +++ b/Svg.ts @@ -44,6 +44,11 @@ export default class Svg { public static checkmark_svg() { return new FixedUiElement(Svg.checkmark);} public static checkmark_ui() { return new FixedUiElement(Svg.checkmark_img);} + public static circle = " image/svg+xml " + public static circle_img = Img.AsImageElement(Svg.circle) + public static circle_svg() { return new FixedUiElement(Svg.circle);} + public static circle_ui() { return new FixedUiElement(Svg.circle_img);} + public static close = " image/svg+xml " public static close_img = Img.AsImageElement(Svg.close) public static close_svg() { return new FixedUiElement(Svg.close);} @@ -54,6 +59,11 @@ export default class Svg { public static compass_svg() { return new FixedUiElement(Svg.compass);} public static compass_ui() { return new FixedUiElement(Svg.compass_img);} + public static cross_bottom_right = " image/svg+xml " + public static cross_bottom_right_img = Img.AsImageElement(Svg.cross_bottom_right) + public static cross_bottom_right_svg() { return new FixedUiElement(Svg.cross_bottom_right);} + public static cross_bottom_right_ui() { return new FixedUiElement(Svg.cross_bottom_right_img);} + public static crosshair_blue_center = " image/svg+xml " public static crosshair_blue_center_img = Img.AsImageElement(Svg.crosshair_blue_center) public static crosshair_blue_center_svg() { return new FixedUiElement(Svg.crosshair_blue_center);} @@ -179,6 +189,11 @@ export default class Svg { public static phone_svg() { return new FixedUiElement(Svg.phone);} public static phone_ui() { return new FixedUiElement(Svg.phone_img);} + public static pin = " image/svg+xml " + public static pin_img = Img.AsImageElement(Svg.pin) + public static pin_svg() { return new FixedUiElement(Svg.pin);} + public static pin_ui() { return new FixedUiElement(Svg.pin_img);} + public static pop_out = " Svg Vector Icons : http://www.onlinewebfonts.com/icon " public static pop_out_img = Img.AsImageElement(Svg.pop_out) public static pop_out_svg() { return new FixedUiElement(Svg.pop_out);} @@ -229,4 +244,4 @@ export default class Svg { public static wikipedia_svg() { return new FixedUiElement(Svg.wikipedia);} public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);} -public static All = {"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"close.svg": Svg.close,"compass.svg": Svg.compass,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapillary.svg": Svg.mapillary,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"star.svg": Svg.star,"statistics.svg": Svg.statistics,"up.svg": Svg.up,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} +public static All = {"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapillary.svg": Svg.mapillary,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"star.svg": Svg.star,"statistics.svg": Svg.statistics,"up.svg": Svg.up,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} diff --git a/UI/LayerSelection.ts b/UI/LayerSelection.ts index 3391e14..d73ef20 100644 --- a/UI/LayerSelection.ts +++ b/UI/LayerSelection.ts @@ -5,6 +5,7 @@ import State from "../State"; import Translations from "./i18n/Translations"; import {FixedUiElement} from "./Base/FixedUiElement"; import {VariableUiElement} from "./Base/VariableUIElement"; +import {UIEventSource} from "../Logic/UIEventSource"; export class LayerSelection extends UIElement { @@ -15,15 +16,15 @@ export class LayerSelection extends UIElement { this._checkboxes = []; for (const layer of State.state.filteredLayers.data) { - let iconUrl = "./asets/checkbox.svg"; - if (layer.layerDef.icon ) { - iconUrl = layer.layerDef.icon.GetRenderValue({id:"node/-1"}).txt; - } - const icon = new FixedUiElement(``); + const leafletStyle = layer.layerDef.GenerateLeafletStyle(new UIEventSource({id: "node/-1"}), true) + const leafletHtml = leafletStyle.icon.html; + const icon = + new FixedUiElement(leafletHtml) + .SetClass("single-layer-selection-toggle") + let iconUnselected: UIElement = new FixedUiElement(leafletHtml) + .SetClass("single-layer-selection-toggle") + .SetStyle("opacity:0.2;"); - let iconUnselected: UIElement; - iconUnselected = new FixedUiElement(``); - const name = Translations.WT(layer.layerDef.name).Clone() .SetStyle("font-size:large;margin-left: 0.5em;"); diff --git a/assets/layers/bike_repair_station/bike_repair_station.json b/assets/layers/bike_repair_station/bike_repair_station.json index 2506d6c..a0e5432 100644 --- a/assets/layers/bike_repair_station/bike_repair_station.json +++ b/assets/layers/bike_repair_station/bike_repair_station.json @@ -490,6 +490,13 @@ } ] }, + "iconOverlays": [ + { + "if": "operator=De Fietsambassade Gent", + "then": "./assets/themes/cyclofix/fietsambassade_gent_logo_small.svg", + "badge": true + } + ], "iconSize": { "render": { "en": "50,50,bottom" diff --git a/assets/layers/drinking_water/drinking_water.json b/assets/layers/drinking_water/drinking_water.json index 7f0ce2e..b85ff4f 100644 --- a/assets/layers/drinking_water/drinking_water.json +++ b/assets/layers/drinking_water/drinking_water.json @@ -17,15 +17,21 @@ } }, "icon": { - "render": "./assets/layers/drinking_water/drinking_water.svg", - "mappings": [ - { - "if": {"or": ["operational_status=broken", "operational_status=closed"]}, - - "then": "./assets/layers/drinking_water/drinking_water_broken.svg" - } - ] + "render": "pin:#6BC4F7;./assets/layers/drinking_water/drips.svg" }, + "iconOverlays": [ + { + "if": { + "or": [ + "operational_status=broken", + "operational_status=closed" + ] + }, + "then": "close:#c33", + "badge": true + } + ], + "iconSize": "40,40,bottom", "overpassTags": { "and": [ diff --git a/assets/layers/drinking_water/drinking_water.svg b/assets/layers/drinking_water/drinking_water.svg deleted file mode 100644 index 4962afe..0000000 --- a/assets/layers/drinking_water/drinking_water.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/assets/layers/drinking_water/drinking_water_broken.svg b/assets/layers/drinking_water/drinking_water_broken.svg deleted file mode 100644 index 9d0ba40..0000000 --- a/assets/layers/drinking_water/drinking_water_broken.svg +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/layers/drinking_water/drips.svg b/assets/layers/drinking_water/drips.svg new file mode 100644 index 0000000..b4e242e --- /dev/null +++ b/assets/layers/drinking_water/drips.svg @@ -0,0 +1,101 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/circle.svg b/assets/svg/circle.svg new file mode 100644 index 0000000..c4463d6 --- /dev/null +++ b/assets/svg/circle.svg @@ -0,0 +1,98 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/cross_bottom_right.svg b/assets/svg/cross_bottom_right.svg new file mode 100644 index 0000000..5e0b2c3 --- /dev/null +++ b/assets/svg/cross_bottom_right.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/assets/svg/pin.svg b/assets/svg/pin.svg new file mode 100644 index 0000000..97b1577 --- /dev/null +++ b/assets/svg/pin.svg @@ -0,0 +1,102 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/index.css b/index.css index f013127..6259763 100644 --- a/index.css +++ b/index.css @@ -96,6 +96,21 @@ a { box-shadow: 0 0 10px var(--shadow-color); } +.single-layer-selection-toggle{ + position: relative; + width: 2.5em; + height: 2.5em; +} +.single-layer-selection-toggle img{ + max-height: 2.5em !important; + max-width: 2.5em !important; +} + +.single-layer-selection-toggle svg{ + max-height:2.5em !important; + max-width: 2.5em !important; +} + .layer-selection-toggle { border-radius: 1em; display: flex;