From 28f1dc3d33a8b8d4debf0970e989e7a629d8ae64 Mon Sep 17 00:00:00 2001 From: AlexanderRebai Date: Mon, 18 Jul 2022 08:21:16 +0000 Subject: [PATCH 01/12] testing code for slider to change levels --- UI/BaseUIElement.ts | 19 +- assets/layers/governments/government.svg | 3 + assets/layers/governments/governments.json | 59 ++ assets/layers/governments/license_info.json | 12 + assets/themes/governments/crest.svg | 11 + assets/themes/governments/governments.json | 20 + assets/themes/governments/license_info.json | 10 + css/index-tailwind-output.css | 131 ++-- index.css | 714 ++++++++++---------- test.ts | 70 +- 10 files changed, 601 insertions(+), 448 deletions(-) create mode 100644 assets/layers/governments/government.svg create mode 100644 assets/layers/governments/governments.json create mode 100644 assets/layers/governments/license_info.json create mode 100644 assets/themes/governments/crest.svg create mode 100644 assets/themes/governments/governments.json create mode 100644 assets/themes/governments/license_info.json diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index 556ab637f..c993ef5d2 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -1,4 +1,4 @@ -import {Utils} from "../Utils"; +import { Utils } from "../Utils"; /** * A thin wrapper around a html element, which allows to generate a HTML-element. @@ -39,9 +39,9 @@ export default abstract class BaseUIElement { return this; } - - public ScrollToTop(){ - this._constructedHtmlElement?.scrollTo(0,0) + + public ScrollToTop() { + this._constructedHtmlElement?.scrollTo(0, 0) } /** @@ -70,10 +70,13 @@ export default abstract class BaseUIElement { return this; } - public RemoveClass(clss: string): BaseUIElement { - if (this.clss.has(clss)) { - this.clss.delete(clss); - this._constructedHtmlElement?.classList.remove(clss) + public RemoveClass(classes: string): BaseUIElement { + const all = classes.split(" ").map(clsName => clsName.trim()); + for (let clss of all) { + if (this.clss.has(clss)) { + this.clss.delete(clss); + this._constructedHtmlElement?.classList.remove(clss) + } } return this; } diff --git a/assets/layers/governments/government.svg b/assets/layers/governments/government.svg new file mode 100644 index 000000000..7a0577c17 --- /dev/null +++ b/assets/layers/governments/government.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/layers/governments/governments.json b/assets/layers/governments/governments.json new file mode 100644 index 000000000..75bb148ce --- /dev/null +++ b/assets/layers/governments/governments.json @@ -0,0 +1,59 @@ +{ + "id": "governments", + "name": { + "en": "governments" + }, + "source": { + "osmTags": { + "or": [ + "office=government" + ] + } + }, + "title": { + "render": { + "en": "Governmental Office {name}" + } + }, + "minzoom": 13, + "tagRenderings": [ + "images", + "phone", + "email", + "website", + { + "question": { + "en": "What is the name of this Governmental Office?" + }, + "render": { + "en": "This Governmental Office is called {name}" + }, + "freeform": { + "key": "name" + }, + "id": "name" + } + ], + "presets": [ + { + "title": { + "en": "a Governmental Office" + }, + "tags": [ + "office=government" + ] + } + ], + "mapRendering": [ + { + "icon": { + "render": "circle:white;./assets/layers/governments/government.svg" + }, + "iconSize": "40,40,center", + "location": [ + "point", + "centroid" + ] + } + ] +} \ No newline at end of file diff --git a/assets/layers/governments/license_info.json b/assets/layers/governments/license_info.json new file mode 100644 index 000000000..3281710ec --- /dev/null +++ b/assets/layers/governments/license_info.json @@ -0,0 +1,12 @@ +[ + { + "path": "government.svg", + "license": "CC0", + "authors": [ + "OSM Carto" + ], + "sources": [ + "https://wiki.openstreetmap.org/wiki/File:Office-16.svg" + ] + } +] \ No newline at end of file diff --git a/assets/themes/governments/crest.svg b/assets/themes/governments/crest.svg new file mode 100644 index 000000000..383b543b1 --- /dev/null +++ b/assets/themes/governments/crest.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/themes/governments/governments.json b/assets/themes/governments/governments.json new file mode 100644 index 000000000..405aea0c1 --- /dev/null +++ b/assets/themes/governments/governments.json @@ -0,0 +1,20 @@ +{ + "id": "governments", + "title": { + "en": "Governmental Offices" + }, + "description": { + "en": "On this map, Governmental offices are shown and can be easily added" + }, + "maintainer": "MapComplete", + "icon": "./assets/themes/onwheels/crest.svg", + "version": "0", + "startLat": 50.8465573, + "defaultBackgroundId": "CartoDB.Voyager", + "startLon": 4.351697, + "startZoom": 16, + "widenFactor": 2, + "layers": [ + "governments" + ] +} \ No newline at end of file diff --git a/assets/themes/governments/license_info.json b/assets/themes/governments/license_info.json new file mode 100644 index 000000000..9f2dcf81a --- /dev/null +++ b/assets/themes/governments/license_info.json @@ -0,0 +1,10 @@ +[ + { + "path": "crest.svg", + "license": "CC0", + "authors": [ + "Free Wheelies" + ], + "sources": [] + } +] \ No newline at end of file diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 36f99ace6..bc51523d4 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -1042,22 +1042,22 @@ video { height: 6rem; } -.h-8 { - height: 2rem; +.h-10 { + height: 2.5rem; } .h-full { height: 100%; } -.h-10 { - height: 2.5rem; -} - .h-12 { height: 3rem; } +.h-8 { + height: 2rem; +} + .h-1\/2 { height: 50%; } @@ -1126,12 +1126,8 @@ video { width: 100%; } -.w-8 { - width: 2rem; -} - -.w-1 { - width: 0.25rem; +.w-10 { + width: 2.5rem; } .w-24 { @@ -1142,14 +1138,14 @@ video { width: 1.5rem; } -.w-10 { - width: 2.5rem; -} - .w-12 { width: 3rem; } +.w-8 { + width: 2rem; +} + .w-0 { width: 0px; } @@ -1290,6 +1286,10 @@ video { flex-wrap: wrap-reverse; } +.place-content-center { + place-content: center; +} + .content-start { align-content: flex-start; } @@ -1412,14 +1412,14 @@ video { border-bottom-left-radius: 0.25rem; } -.border { - border-width: 1px; -} - .border-2 { border-width: 2px; } +.border { + border-width: 1px; +} + .border-4 { border-width: 4px; } @@ -1432,6 +1432,15 @@ video { border-bottom-width: 1px; } +.border-solid { + border-style: solid; +} + +.border-blue-500 { + --tw-border-opacity: 1; + border-color: rgba(59, 130, 246, var(--tw-border-opacity)); +} + .border-gray-500 { --tw-border-opacity: 1; border-color: rgba(107, 114, 128, var(--tw-border-opacity)); @@ -1466,6 +1475,11 @@ video { --tw-border-opacity: 0.5; } +.bg-blue-200 { + --tw-bg-opacity: 1; + background-color: rgba(191, 219, 254, var(--tw-bg-opacity)); +} + .bg-white { --tw-bg-opacity: 1; background-color: rgba(255, 255, 255, var(--tw-bg-opacity)); @@ -1550,10 +1564,6 @@ video { padding-right: 1rem; } -.pr-2 { - padding-right: 0.5rem; -} - .pb-12 { padding-bottom: 3rem; } @@ -1622,6 +1632,10 @@ video { padding-top: 0.125rem; } +.pr-2 { + padding-right: 0.5rem; +} + .pl-6 { padding-left: 1.5rem; } @@ -1693,10 +1707,6 @@ video { text-transform: lowercase; } -.capitalize { - text-transform: capitalize; -} - .italic { font-style: italic; } @@ -1837,11 +1847,11 @@ video { } .z-above-map { - z-index: 10000 + z-index: 10000; } .z-above-controls { - z-index: 10001 + z-index: 10001; } .bg-subtle { @@ -1863,14 +1873,14 @@ video { * Base colour of interactive elements, mainly the 'subtle button' * */ - --subtle-detail-color: #DBEAFE; + --subtle-detail-color: #dbeafe; --subtle-detail-color-contrast: black; --subtle-detail-color-light-contrast: lightgrey; /** * A stronger variant of the 'subtle-detail-colour' * Used as subtle button hover */ - --unsubtle-detail-color: #BFDBFE; + --unsubtle-detail-color: #bfdbfe; --unsubtle-detail-color-contrast: black; --catch-detail-color: #3a3aeb; --catch-detail-color-contrast: white; @@ -1884,7 +1894,8 @@ video { --variable-title-height: 0px; } -html, body { +html, +body { height: 100%; min-height: 100vh; min-height: -webkit-fill-available; @@ -1892,7 +1903,7 @@ html, body { padding: 0; background-color: var(--background-color); color: var(--foreground-color); - font-family: 'Helvetica Neue', Arial, sans-serif; + font-family: "Helvetica Neue", Arial, sans-serif; } .leaflet-overlay-pane .leaflet-zoom-animated { @@ -1915,7 +1926,8 @@ html, body { height: 100% !important; } -svg, img { +svg, +img { box-sizing: content-box; width: 100%; height: 100%; @@ -2019,6 +2031,39 @@ a { height: min-content; } +/* alex */ + +input[type="range"].vertical { + -webkit-writing-mode: bt-lr; + -ms-writing-mode: bt-lr; + writing-mode: bt-lr; + /* IE */ + -webkit-appearance: slider-vertical; + /* Chromium */ + width: 8px; + height: 310px; + padding: 0 5px; +} + +/* +.elevatorslider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 23px; + height: 24px; + border: 0; + background: url("../MapComplete/assets/svg/bug.svg"); + cursor: pointer; +} + +.elevatorslider::-moz-range-thumb { + width: 23px; + height: 25px; + border: 0; + background: url("../MapComplete/assets/svg/bug.svg"); + cursor: pointer; +} */ + .border-detail { border-color: var(--foreground-color); } @@ -2088,7 +2133,7 @@ p { } li::marker { - content: "•" + content: "•"; } .subtle-background { @@ -2098,7 +2143,7 @@ li::marker { .normal-background { background: var(--background-color); - color: var(--foreground-color) + color: var(--foreground-color); } .subtle-lighter { @@ -2176,7 +2221,8 @@ li::marker { color: unset !important; } -.disable-links a.must-link, .disable-links .must-link a { +.disable-links a.must-link, +.disable-links .must-link a { /* Hide links if they are disabled */ display: none; } @@ -2395,7 +2441,7 @@ li::marker { /***************** Info box (box containing features and questions ******************/ input { - color: var(--foreground-color) + color: var(--foreground-color); } .leaflet-popup-content { @@ -2447,7 +2493,7 @@ input { } .animate-height { - transition: max-height .5s ease-in-out; + transition: max-height 0.5s ease-in-out; overflow-y: hidden; } @@ -2477,7 +2523,7 @@ input { .mapping-icon-small-height { /* A mapping icon type */ - height: 2rem; + height: 1.5rem; margin-right: 0.5rem; width: unset; } @@ -2511,7 +2557,7 @@ input { margin-left: 1rem; } -.mapping-icon-large{ +.mapping-icon-large { /* A mapping icon type */ width: 6rem; max-height: 5rem; @@ -2809,4 +2855,3 @@ input { display: inline; } } - diff --git a/index.css b/index.css index 85da5cdc7..fa09d5984 100644 --- a/index.css +++ b/index.css @@ -12,667 +12,673 @@ @tailwind utilities; @layer utilities { - @variants responsive { - .z-above-map { - z-index: 10000 - } - - .z-above-controls { - z-index: 10001 - } - - .w-160 { - width: 40rem; - } - - .bg-subtle { - background-color: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); - } - - .bg-unsubtle { - background-color: var(--unsubtle-detail-color); - color: var(--unsubtle-detail-color-contrast); - } - - .bg-catch { - background-color: var(--catch-detail-color); - color: var(--catch-detail-color-contrast); - } - - .rounded-left-full { - border-bottom-left-radius: 999rem; - border-top-left-radius: 999rem; - } - - .rounded-right-full { - border-bottom-right-radius: 999rem; - border-top-right-radius: 999rem; - } + @variants responsive { + .z-above-map { + z-index: 10000; } + .z-above-controls { + z-index: 10001; + } + + .w-160 { + width: 40rem; + } + + .bg-subtle { + background-color: var(--subtle-detail-color); + color: var(--subtle-detail-color-contrast); + } + + .bg-unsubtle { + background-color: var(--unsubtle-detail-color); + color: var(--unsubtle-detail-color-contrast); + } + + .bg-catch { + background-color: var(--catch-detail-color); + color: var(--catch-detail-color-contrast); + } + + .rounded-left-full { + border-bottom-left-radius: 999rem; + border-top-left-radius: 999rem; + } + + .rounded-right-full { + border-bottom-right-radius: 999rem; + border-top-right-radius: 999rem; + } + } } - :root { - /* The main colour scheme of mapcomplete is configured here. + /* The main colour scheme of mapcomplete is configured here. * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. */ - /* Main color of the application: the background and text colours */ - --background-color: white; - /* Main text colour. Also styles some elements, such as the 'close popup'-button or 'back-arrow' (in mobile) */ - --foreground-color: black; - - /* A colour to indicate an error or warning */ - --alert-color: #fee4d1; - - /** + /* Main color of the application: the background and text colours */ + --background-color: white; + /* Main text colour. Also styles some elements, such as the 'close popup'-button or 'back-arrow' (in mobile) */ + --foreground-color: black; + + /* A colour to indicate an error or warning */ + --alert-color: #fee4d1; + + /** * Base colour of interactive elements, mainly the 'subtle button' * */ - --subtle-detail-color: #DBEAFE; - --subtle-detail-color-contrast: black; - --subtle-detail-color-light-contrast: lightgrey; + --subtle-detail-color: #dbeafe; + --subtle-detail-color-contrast: black; + --subtle-detail-color-light-contrast: lightgrey; - /** + /** * A stronger variant of the 'subtle-detail-colour' * Used as subtle button hover */ - --unsubtle-detail-color: #BFDBFE; - --unsubtle-detail-color-contrast: black; - - - --catch-detail-color: #3a3aeb; - --catch-detail-color-contrast: white; + --unsubtle-detail-color: #bfdbfe; + --unsubtle-detail-color-contrast: black; - - --non-active-tab-svg: var(--foreground-color); - --shadow-color: #00000066; - - --return-to-the-map-height: 2em; - --image-carousel-height: 350px; + --catch-detail-color: #3a3aeb; + --catch-detail-color-contrast: white; - /* The border colour of the leaflet popup */ - --popup-border: white; + --non-active-tab-svg: var(--foreground-color); + --shadow-color: #00000066; - /* Technical variable to make some dynamic behaviour possible; set by javascript. */ - --variable-title-height: 0px; + --return-to-the-map-height: 2em; + --image-carousel-height: 350px; + + /* The border colour of the leaflet popup */ + --popup-border: white; + + /* Technical variable to make some dynamic behaviour possible; set by javascript. */ + --variable-title-height: 0px; } -html, body { - height: 100%; - min-height: 100vh; - min-height: -webkit-fill-available; - margin: 0; - padding: 0; - background-color: var(--background-color); - color: var(--foreground-color); - font-family: 'Helvetica Neue', Arial, sans-serif; +html, +body { + height: 100%; + min-height: 100vh; + min-height: -webkit-fill-available; + margin: 0; + padding: 0; + background-color: var(--background-color); + color: var(--foreground-color); + font-family: "Helvetica Neue", Arial, sans-serif; } .leaflet-overlay-pane .leaflet-zoom-animated { - /* Another workaround to keep leaflet working */ - width: initial !important; - height: initial !important; - box-sizing: initial !important; + /* Another workaround to keep leaflet working */ + width: initial !important; + height: initial !important; + box-sizing: initial !important; } .leaflet-control-attribution { - display: block ruby; + display: block ruby; } .badge { } .badge svg { - /*Workaround for leaflet*/ - width: unset !important; - height: 100% !important; + /*Workaround for leaflet*/ + width: unset !important; + height: 100% !important; } -svg, img { - box-sizing: content-box; - width: 100%; - height: 100%; +svg, +img { + box-sizing: content-box; + width: 100%; + height: 100%; } .titleicon img { - width: unset; + width: unset; } .titleicon svg { - width: unset; + width: unset; } .svg-catch svg path { - fill: var(--catch-detail-color) !important; - stroke: var(--catch-detail-color) !important; + fill: var(--catch-detail-color) !important; + stroke: var(--catch-detail-color) !important; } .svg-unsubtle svg path { - fill: var(--unsubtle-detail-color) !important; - stroke: var(--unsubtle-detail-color) !important; + fill: var(--unsubtle-detail-color) !important; + stroke: var(--unsubtle-detail-color) !important; } .svg-subtle svg path { - fill: var(--subtle-detail-color) !important; - stroke: var(--subtle-detail-color) !important; + fill: var(--subtle-detail-color) !important; + stroke: var(--subtle-detail-color) !important; } .svg-foreground svg path { - fill: var(--foreground-color) !important; - stroke: var(--foreground-color) !important; + fill: var(--foreground-color) !important; + stroke: var(--foreground-color) !important; } .no-images img { - display: none; + display: none; } .weblate-link { - /* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */ + /* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */ } .mapcontrol svg path { - fill: var(--subtle-detail-color-contrast) !important; + fill: var(--subtle-detail-color-contrast) !important; } .red-svg svg path { - stroke: #d71010 !important; + stroke: #d71010 !important; } a { - color: var(--foreground-color); + color: var(--foreground-color); } .btn { - line-height: 1.25rem; - --tw-text-opacity: 1; - color: var(--catch-detail-color-contrast); - --tw-bg-opacity: 1; - background-color: var(--catch-detail-color); - display: inline-flex; - border-radius: 1.5rem; - padding-top: 0.75rem; - padding-bottom: 0.75rem; - padding-left: 1.25rem; - padding-right: 1.25rem; - font-size: large; - font-weight: bold; - transition: 100ms; - /*-- invisible border: rendered on hover*/ - border: 3px solid var(--unsubtle-detail-color); + line-height: 1.25rem; + --tw-text-opacity: 1; + color: var(--catch-detail-color-contrast); + --tw-bg-opacity: 1; + background-color: var(--catch-detail-color); + display: inline-flex; + border-radius: 1.5rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + padding-left: 1.25rem; + padding-right: 1.25rem; + font-size: large; + font-weight: bold; + transition: 100ms; + /*-- invisible border: rendered on hover*/ + border: 3px solid var(--unsubtle-detail-color); } .btn:hover { - border: 3px solid var(--catch-detail-color); + border: 3px solid var(--catch-detail-color); } .btn-secondary { - background-color: var(--catch-detail-color); - filter: saturate(0.5); - + background-color: var(--catch-detail-color); + filter: saturate(0.5); } .btn-secondary:hover { - background-color: var(--catch-detail-color); - filter: unset; + background-color: var(--catch-detail-color); + filter: unset; } .btn-disabled { - filter: saturate(0.3); - cursor: default; + filter: saturate(0.3); + cursor: default; } .btn-disabled:hover { - border: 3px solid var(--unsubtle-detail-color); + border: 3px solid var(--unsubtle-detail-color); } .h-min { - height: min-content; + height: min-content; } +/* alex */ +input[type="range"].vertical { + writing-mode: bt-lr; /* IE */ + -webkit-appearance: slider-vertical; /* Chromium */ + width: 8px; + height: 310px; + padding: 0 5px; +} +/* +.elevatorslider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 23px; + height: 24px; + border: 0; + background: url("../MapComplete/assets/svg/bug.svg"); + cursor: pointer; +} + +.elevatorslider::-moz-range-thumb { + width: 23px; + height: 25px; + border: 0; + background: url("../MapComplete/assets/svg/bug.svg"); + cursor: pointer; +} */ .border-detail { - border-color: var(--foreground-color); + border-color: var(--foreground-color); } .w-min { - width: min-content; + width: min-content; } .rounded-left-full { - border-bottom-left-radius: 999rem; - border-top-left-radius: 999rem; + border-bottom-left-radius: 999rem; + border-top-left-radius: 999rem; } .rounded-right-full { - border-bottom-right-radius: 999rem; - border-top-right-radius: 999rem; + border-bottom-right-radius: 999rem; + border-top-right-radius: 999rem; } .w-16-imp { - width: 4rem !important; + width: 4rem !important; } .w-32-imp { - width: 8rem !important; + width: 8rem !important; } .w-48-imp { - width: 12rem !important; + width: 12rem !important; } .link-underline a { - text-decoration: underline 1px var(--foreground-color); + text-decoration: underline 1px var(--foreground-color); } .link-no-underline a { - text-decoration: none; + text-decoration: none; } li { - margin-left: 0.5em; - padding-left: 0.2em; - margin-top: 0.1em; + margin-left: 0.5em; + padding-left: 0.2em; + margin-top: 0.1em; } h2 { - font-size: large; - margin-top: 0.5em; - margin-bottom: 0.3em; - font-weight: bold; + font-size: large; + margin-top: 0.5em; + margin-bottom: 0.3em; + font-weight: bold; } h3 { - font-size: larger; - margin-top: 0.6em; - margin-bottom: 0; - font-weight: bold; + font-size: larger; + margin-top: 0.6em; + margin-bottom: 0; + font-weight: bold; } h3 { - font-size: larger; - margin-top: 0.6em; - margin-bottom: 0; - font-weight: bolder; + font-size: larger; + margin-top: 0.6em; + margin-bottom: 0; + font-weight: bolder; } p { - padding-top: 0.1em; + padding-top: 0.1em; } li::marker { - content: "•" + content: "•"; } .subtle-background { - background: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); + background: var(--subtle-detail-color); + color: var(--subtle-detail-color-contrast); } .normal-background { - background: var(--background-color); - color: var(--foreground-color) + background: var(--background-color); + color: var(--foreground-color); } .subtle-lighter { - color: var(--subtle-detail-color-light-contrast); + color: var(--subtle-detail-color-light-contrast); } .border-attention-catch { - border: 5px solid var(--catch-detail-color); + border: 5px solid var(--catch-detail-color); } .border-invisible { - border: 5px solid #00000000; + border: 5px solid #00000000; } .border-attention { - border-color: var(--catch-detail-color); + border-color: var(--catch-detail-color); } .direction-svg svg path { - fill: var(--catch-detail-color) !important; + fill: var(--catch-detail-color) !important; } - #leafletDiv { - height: 100%; + height: 100%; } .leaflet-popup-content-wrapper { - background-color: var(--background-color); - color: var(--foreground-color); - border: 2px solid var(--popup-border); - box-shadow: 0 3px 14px var(--shadow-color) !important; + background-color: var(--background-color); + color: var(--foreground-color); + border: 2px solid var(--popup-border); + box-shadow: 0 3px 14px var(--shadow-color) !important; } .leaflet-container { - font: unset !important; - background-color: var(--background-color) !important; + font: unset !important; + background-color: var(--background-color) !important; } .leaflet-popup-tip { - background-color: var(--popup-border) !important; - color: var(--popup-border) !important; - box-shadow: 0 3px 14px var(--shadow-color) !important; + background-color: var(--popup-border) !important; + color: var(--popup-border) !important; + box-shadow: 0 3px 14px var(--shadow-color) !important; } .single-layer-selection-toggle { - position: relative; - width: 2em; - height: 2em; - flex-shrink: 0; + position: relative; + width: 2em; + height: 2em; + flex-shrink: 0; } .single-layer-selection-toggle img { - max-height: 2em !important; - max-width: 2em !important; + max-height: 2em !important; + max-width: 2em !important; } .single-layer-selection-toggle svg { - max-height: 2em !important; - max-width: 2em !important; + max-height: 2em !important; + max-width: 2em !important; } - .block-ruby { - display: block ruby; + display: block ruby; } .disable-links a { - pointer-events: none; - text-decoration: none !important; - color: var(--subtle-detail-color-contrast) !important; + pointer-events: none; + text-decoration: none !important; + color: var(--subtle-detail-color-contrast) !important; } .enable-links a { - pointer-events: unset; - text-decoration: underline !important; - color: unset !important; + pointer-events: unset; + text-decoration: underline !important; + color: unset !important; } -.disable-links a.must-link, .disable-links .must-link a { - /* Hide links if they are disabled */ - display: none; +.disable-links a.must-link, +.disable-links .must-link a { + /* Hide links if they are disabled */ + display: none; } /**************** GENERIC ****************/ - .alert { - background-color: var(--alert-color); - color: var(--foreground-color); - font-weight: bold; - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; + background-color: var(--alert-color); + color: var(--foreground-color); + font-weight: bold; + border-radius: 1em; + margin: 0.25em; + text-align: center; + padding: 0.15em 0.3em; } .invalid { - box-shadow: 0 0 10px #ff5353; - height: min-content; + box-shadow: 0 0 10px #ff5353; + height: min-content; } .shadow { - box-shadow: 0 0 10px var(--shadow-color); + box-shadow: 0 0 10px var(--shadow-color); } .title-font span { - font-size: xx-large !important; - font-weight: bold; + font-size: xx-large !important; + font-weight: bold; } .soft { - background-color: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); - font-weight: bold; - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; + background-color: var(--subtle-detail-color); + color: var(--subtle-detail-color-contrast); + font-weight: bold; + border-radius: 1em; + margin: 0.25em; + text-align: center; + padding: 0.15em 0.3em; } - .subtle { - color: #999; + color: #999; } .link-underline .subtle a { - text-decoration: underline 1px #7193bb88; - color: #7193bb; + text-decoration: underline 1px #7193bb88; + color: #7193bb; } - .thanks { - background-color: #43d904; - font-weight: bold; - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; + background-color: #43d904; + font-weight: bold; + border-radius: 1em; + margin: 0.25em; + text-align: center; + padding: 0.15em 0.3em; } .clickable { - pointer-events: all; + pointer-events: all; } .unclickable { - pointer-events: none !important; + pointer-events: none !important; } - @keyframes slide { - /* This is the animation on the marker to add a new point - it slides through all the possible presets */ - from { - transform: translateX(0%); - } + /* This is the animation on the marker to add a new point - it slides through all the possible presets */ + from { + transform: translateX(0%); + } - to { - transform: translateX(calc(-100% + 42px)); - } + to { + transform: translateX(calc(-100% + 42px)); + } } .hand-drag-animation { - animation: hand-drag-animation 6s ease-in-out infinite; - transform-origin: 50% 125%; + animation: hand-drag-animation 6s ease-in-out infinite; + transform-origin: 50% 125%; } @keyframes hand-drag-animation { - /* This is the animation on the little extra hand on the location input. If fades in, invites the user to interact/drag the map */ - 0% { - opacity: 0; - transform: rotate(-30deg); - } + /* This is the animation on the little extra hand on the location input. If fades in, invites the user to interact/drag the map */ + 0% { + opacity: 0; + transform: rotate(-30deg); + } - 6% { - opacity: 1; - transform: rotate(-30deg); - } + 6% { + opacity: 1; + transform: rotate(-30deg); + } - 12% { - opacity: 1; - transform: rotate(-45deg); - } + 12% { + opacity: 1; + transform: rotate(-45deg); + } - 24% { - opacity: 1; - transform: rotate(-00deg); - } + 24% { + opacity: 1; + transform: rotate(-00deg); + } - 30% { - opacity: 1; - transform: rotate(-30deg); - } + 30% { + opacity: 1; + transform: rotate(-30deg); + } + 36% { + opacity: 0; + transform: rotate(-30deg); + } - 36% { - opacity: 0; - transform: rotate(-30deg); - } - - 100% { - opacity: 0; - transform: rotate(-30deg); - } - + 100% { + opacity: 0; + transform: rotate(-30deg); + } } /**************************************/ - #topleft-tools { - display: block; - position: absolute; - z-index: 5000; - transition: all 500ms linear; - left: 0; - right: 0; + display: block; + position: absolute; + z-index: 5000; + transition: all 500ms linear; + left: 0; + right: 0; } .welcomeMessage { - display: block; - max-width: calc(100vw - 5em); - width: 40em; - max-height: calc(100vh - 15em); - background-color: var(--background-color); - color: var(--foreground-color); + display: block; + max-width: calc(100vw - 5em); + width: 40em; + max-height: calc(100vh - 15em); + background-color: var(--background-color); + color: var(--foreground-color); } - - /***************** Info box (box containing features and questions ******************/ input { - color: var(--foreground-color) + color: var(--foreground-color); } .leaflet-popup-content { - width: 45em !important; - margin: 0.25rem !important; + width: 45em !important; + margin: 0.25rem !important; } .leaflet-div-icon { - background-color: unset !important; - border: unset !important; + background-color: unset !important; + border: unset !important; } .floating-element-width { - max-width: calc(100vw - 5em); - width: 40em; + max-width: calc(100vw - 5em); + width: 40em; } .leaflet-div-icon svg { - width: calc(100%); - height: calc(100%); + width: calc(100%); + height: calc(100%); } /****** ShareScreen *****/ .literal-code { - display: inline-block; - background-color: lightgray; - padding: 0.5em; - word-break: break-word; - color: black; - box-sizing: border-box; + display: inline-block; + background-color: lightgray; + padding: 0.5em; + word-break: break-word; + color: black; + box-sizing: border-box; } - /** Switch layout **/ .small-image img { - height: 1em; - max-width: 1em; + height: 1em; + max-width: 1em; } .small-image { - height: 1em; - max-width: 1em; + height: 1em; + max-width: 1em; } - .slideshow-item img { - height: var(--image-carousel-height); - width: unset; + height: var(--image-carousel-height); + width: unset; } .animate-height { - transition: max-height .5s ease-in-out; - overflow-y: hidden; + transition: max-height 0.5s ease-in-out; + overflow-y: hidden; } - .zebra-table tr:nth-child(even) { - background-color: #f2f2f2; + background-color: #f2f2f2; } .layer-toggle { - /* The checkbox that toggles a single layer */ + /* The checkbox that toggles a single layer */ } .layer-filters { - /* If needed, the panel which contains the extra filters for a layer */ - margin-bottom: 1rem; - border-bottom: 2px solid var(--foreground-color); + /* If needed, the panel which contains the extra filters for a layer */ + margin-bottom: 1rem; + border-bottom: 2px solid var(--foreground-color); } .filter-panel { - /* The panel for a single layer, containing both the toggle and the filters (if any) */ - border-bottom: 2px solid lightgrey; - margin-bottom: 0.5rem; + /* The panel for a single layer, containing both the toggle and the filters (if any) */ + border-bottom: 2px solid lightgrey; + margin-bottom: 0.5rem; } .first-filter-panel { - /* Additional class on the first layer filter */ + /* Additional class on the first layer filter */ } .mapping-icon-small-height { - /* A mapping icon type */ - height: 1.5rem; - margin-right: 0.5rem; - width: unset; + /* A mapping icon type */ + height: 1.5rem; + margin-right: 0.5rem; + width: unset; } .mapping-icon-medium-height { - /* A mapping icon type */ - height: 3rem; - margin-right: 0.5rem; - width: unset; + /* A mapping icon type */ + height: 3rem; + margin-right: 0.5rem; + width: unset; } .mapping-icon-large-height { - /* A mapping icon type */ - height: 5rem; - margin-right: 0.5rem; - width: unset; + /* A mapping icon type */ + height: 5rem; + margin-right: 0.5rem; + width: unset; } - .mapping-icon-small { - /* A mapping icon type */ - width: 1.5rem; - max-height: 1.5rem; - margin-right: 0.5rem; + /* A mapping icon type */ + width: 1.5rem; + max-height: 1.5rem; + margin-right: 0.5rem; } .mapping-icon-medium { - /* A mapping icon type */ - width: 3rem; - max-height: 3rem; - margin-right: 1rem; - margin-left: 1rem; + /* A mapping icon type */ + width: 3rem; + max-height: 3rem; + margin-right: 1rem; + margin-left: 1rem; } -.mapping-icon-large{ - /* A mapping icon type */ - width: 6rem; - max-height: 5rem; - margin-top: 0.5rem; - margin-bottom: 0.5rem; - margin-right: 1.5rem; - margin-left: 1.5rem; - - +.mapping-icon-large { + /* A mapping icon type */ + width: 6rem; + max-height: 5rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + margin-right: 1.5rem; + margin-left: 1.5rem; } - diff --git a/test.ts b/test.ts index e25a2ad54..55d01a9ae 100644 --- a/test.ts +++ b/test.ts @@ -1,52 +1,36 @@ -import * as shops from "./assets/generated/layers/shops.json" +import { max } from "moment"; +import { Store, UIEventSource } from "./Logic/UIEventSource" import Combine from "./UI/Base/Combine"; -import Img from "./UI/Base/Img"; -import BaseUIElement from "./UI/BaseUIElement"; -import {VariableUiElement} from "./UI/Base/VariableUIElement"; -import LanguagePicker from "./UI/LanguagePicker"; -import TagRenderingConfig, {Mapping} from "./Models/ThemeConfig/TagRenderingConfig"; -import {MappingConfigJson} from "./Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"; -import {FixedUiElement} from "./UI/Base/FixedUiElement"; -import {TagsFilter} from "./Logic/Tags/TagsFilter"; -import {SearchablePillsSelector} from "./UI/Input/SearchableMappingsSelector"; -import {UIEventSource} from "./Logic/UIEventSource"; +import { FixedUiElement } from "./UI/Base/FixedUiElement"; +import { VariableUiElement } from "./UI/Base/VariableUIElement"; +import { FixedInputElement } from "./UI/Input/FixedInputElement"; +import Slider from "./UI/Input/Slider"; +import Toggle from "./UI/Input/Toggle"; -const mappingsRaw: MappingConfigJson[] = shops.tagRenderings.find(tr => tr.id == "shop_types").mappings -const mappings = mappingsRaw.map((m, i) => TagRenderingConfig.ExtractMapping(m, i, "test", "test")) +const testData = ["-1", "0", "0.5", "1", "1.5", "2"] -function fromMapping(m: Mapping): { show: BaseUIElement, value: TagsFilter, mainTerm: Record, searchTerms?: Record } { - const el: BaseUIElement = m.then - let icon: BaseUIElement - if (m.icon !== undefined) { - icon = new Img(m.icon).SetClass("h-8 w-8 pr-2") - } else { - icon = new FixedUiElement("").SetClass("h-8 w-1") +const values = testData.map((data) => new FixedUiElement(data).onClick(() => { + values.map((val) => { + val.RemoveClass("active bg-blue-200") + if (val.content === data) { + const options = { + value : new UIEventSource(testData.indexOf(val.content)), + } + val.SetClass("active bg-blue-200") + const newSlider = new Slider(0, testData.length-1, options).SetClass("flex vertical m-4 elevatorslider"); + new Combine([valCombine, newSlider]).SetClass("flex flex-row h-10").AttachTo("extradiv") + console.log(slider.GetValue()) } - const show = new Combine([ - icon, - el.SetClass("block-ruby") - ]).SetClass("flex items-center") + }) +}).SetClass("flex flex-column bg-slate-200 w-10 h-10 border-2 border-blue-500 border-solid rounded-full place-content-center items-center m-4")) - return {show, mainTerm: m.then.translations, searchTerms: m.searchTerms, value: m.if}; +const valCombine = new Combine(values.reverse()) +// valCombine.AttachTo("maindiv") -} -const search = new UIEventSource("") -const sp = new SearchablePillsSelector( - mappings.map(m => fromMapping(m)), - { - noMatchFound: new VariableUiElement(search.map(s => "Mark this a `"+s+"`")), - onNoSearch: new FixedUiElement("Search in "+mappingsRaw.length+" categories"), - selectIfSingle: true, - searchValue: search - } -) +const slider = new Slider(0, testData.length-1); -sp.AttachTo("maindiv") +slider.SetClass("flex vertical m-4 elevatorslider") -const lp = new LanguagePicker(["en", "nl"], "") +new Combine([valCombine, slider]).SetClass("flex flex-row h-10").AttachTo("extradiv") -new Combine([ - new VariableUiElement(sp.GetValue().map(tf => new FixedUiElement("Selected tags: " + tf.map(tf => tf.asHumanString(false, false, {})).join(", ")))), - lp -]).SetClass("flex flex-col") - .AttachTo("extradiv") \ No newline at end of file +console.log(slider) From 92bd6414e475083eba7636f4002b95a6f2eea172 Mon Sep 17 00:00:00 2001 From: AlexanderRebai Date: Mon, 18 Jul 2022 09:51:49 +0000 Subject: [PATCH 02/12] laatste versie levels --- index.css | 13 ++++++------- test.ts | 33 +++++++++++++++------------------ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/index.css b/index.css index fa09d5984..f1a4ce83e 100644 --- a/index.css +++ b/index.css @@ -228,7 +228,7 @@ a { height: min-content; } -/* alex */ +/* slider */ input[type="range"].vertical { writing-mode: bt-lr; /* IE */ -webkit-appearance: slider-vertical; /* Chromium */ @@ -236,14 +236,13 @@ input[type="range"].vertical { height: 310px; padding: 0 5px; } -/* -.elevatorslider::-webkit-slider-thumb { - -webkit-appearance: none; + +/* .elevatorslider::-webkit-slider-thumb { appearance: none; width: 23px; height: 24px; border: 0; - background: url("../MapComplete/assets/svg/bug.svg"); + background: url("/assets/svg/bug.svg") !important; cursor: pointer; } @@ -251,9 +250,9 @@ input[type="range"].vertical { width: 23px; height: 25px; border: 0; - background: url("../MapComplete/assets/svg/bug.svg"); + background: url("/assets/svg/bug.svg") !important; cursor: pointer; -} */ +} */ .border-detail { border-color: var(--foreground-color); diff --git a/test.ts b/test.ts index 55d01a9ae..21ba35181 100644 --- a/test.ts +++ b/test.ts @@ -5,32 +5,29 @@ import { FixedUiElement } from "./UI/Base/FixedUiElement"; import { VariableUiElement } from "./UI/Base/VariableUIElement"; import { FixedInputElement } from "./UI/Input/FixedInputElement"; import Slider from "./UI/Input/Slider"; -import Toggle from "./UI/Input/Toggle"; +import Toggle, { ClickableToggle } from "./UI/Input/Toggle"; const testData = ["-1", "0", "0.5", "1", "1.5", "2"] +let slider = new Slider(0, testData.length - 1); -const values = testData.map((data) => new FixedUiElement(data).onClick(() => { - values.map((val) => { - val.RemoveClass("active bg-blue-200") - if (val.content === data) { - const options = { - value : new UIEventSource(testData.indexOf(val.content)), - } - val.SetClass("active bg-blue-200") - const newSlider = new Slider(0, testData.length-1, options).SetClass("flex vertical m-4 elevatorslider"); - new Combine([valCombine, newSlider]).SetClass("flex flex-row h-10").AttachTo("extradiv") - console.log(slider.GetValue()) +const toggleClass = "flex border-2 border-blue-500 rounded-full w-10 h-10 place-content-center items-center" + +const values = testData.map((data, i) => new ClickableToggle( + new FixedUiElement(data).SetClass("active bg-subtle " + toggleClass), new FixedUiElement(data).SetClass(toggleClass), slider.GetValue().sync( + (sliderVal) => { + return sliderVal === i + }, + [], + (isSelected) => { + return isSelected ? i : slider.GetValue().data } - }) -}).SetClass("flex flex-column bg-slate-200 w-10 h-10 border-2 border-blue-500 border-solid rounded-full place-content-center items-center m-4")) + )) + .ToggleOnClick() + .SetClass("flex flex-column bg-slate-200 m-4 w-10 h-10")) const valCombine = new Combine(values.reverse()) -// valCombine.AttachTo("maindiv") - -const slider = new Slider(0, testData.length-1); slider.SetClass("flex vertical m-4 elevatorslider") new Combine([valCombine, slider]).SetClass("flex flex-row h-10").AttachTo("extradiv") -console.log(slider) From 3441e7c9f7f9919bb8577feacecb1c20ecb54d2a Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 18 Jul 2022 13:50:14 +0200 Subject: [PATCH 03/12] Add elevator icons --- UI/Input/Slider.ts | 15 ++++++--- assets/svg/license_info.json | 10 ++++++ css/index-tailwind-output.css | 62 +++++++++++++++++++---------------- index.css | 27 ++++++++++----- test.ts | 9 +++-- 5 files changed, 77 insertions(+), 46 deletions(-) diff --git a/UI/Input/Slider.ts b/UI/Input/Slider.ts index 68503e722..e6e2f4a55 100644 --- a/UI/Input/Slider.ts +++ b/UI/Input/Slider.ts @@ -4,9 +4,10 @@ import {UIEventSource} from "../../Logic/UIEventSource"; export default class Slider extends InputElement { private readonly _value: UIEventSource - private min: number; - private max: number; - private step: number; + private readonly min: number; + private readonly max: number; + private readonly step: number; + private readonly vertical: boolean; /** * Constructs a slider input element for natural numbers @@ -16,13 +17,15 @@ export default class Slider extends InputElement { */ constructor(min: number, max: number, options?: { value?: UIEventSource, - step?: 1 | number + step?: 1 | number, + vertical?: false | boolean }) { super(); this.max = max; this.min = min; this._value = options?.value ?? new UIEventSource(min) this.step = options?.step ?? 1; + this.vertical = options?.vertical ?? false; } GetValue(): UIEventSource { @@ -39,6 +42,10 @@ export default class Slider extends InputElement { el.oninput = () => { valuestore.setData(Number(el.value)) } + if(this.vertical){ + el.classList.add("vertical") + el.setAttribute('orient','vertical'); // firefox only workaround... + } valuestore.addCallbackAndRunD(v => el.value = ""+valuestore.data) return el; } diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 58f491f57..44f3be2e1 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -387,6 +387,16 @@ ], "sources": [] }, + { + "path": "elevator.svg", + "license": "CC-BY-SA 4.0", + "authors": [ + "Yveltal" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:HZM_elevator_icon.svg" + ] + }, { "path": "envelope.svg", "license": "CC0; trivial", diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index bc51523d4..b8b950474 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -811,6 +811,10 @@ video { margin: 0.25rem; } +.m-2 { + margin: 0.5rem; +} + .m-4 { margin: 1rem; } @@ -831,10 +835,6 @@ video { margin: 0.75rem; } -.m-2 { - margin: 0.5rem; -} - .m-6 { margin: 1.5rem; } @@ -858,6 +858,14 @@ video { margin-bottom: 0.75rem; } +.mb-0 { + margin-bottom: 0px; +} + +.mt-8 { + margin-top: 2rem; +} + .ml-3 { margin-left: 0.75rem; } @@ -954,10 +962,6 @@ video { margin-right: 0.25rem; } -.mb-0 { - margin-bottom: 0px; -} - .box-border { box-sizing: border-box; } @@ -1046,6 +1050,10 @@ video { height: 2.5rem; } +.h-14 { + height: 3.5rem; +} + .h-full { height: 100%; } @@ -1432,10 +1440,6 @@ video { border-bottom-width: 1px; } -.border-solid { - border-style: solid; -} - .border-blue-500 { --tw-border-opacity: 1; border-color: rgba(59, 130, 246, var(--tw-border-opacity)); @@ -1475,11 +1479,6 @@ video { --tw-border-opacity: 0.5; } -.bg-blue-200 { - --tw-bg-opacity: 1; - background-color: rgba(191, 219, 254, var(--tw-bg-opacity)); -} - .bg-white { --tw-bg-opacity: 1; background-color: rgba(255, 255, 255, var(--tw-bg-opacity)); @@ -2031,7 +2030,7 @@ a { height: min-content; } -/* alex */ +/* slider */ input[type="range"].vertical { -webkit-writing-mode: bt-lr; @@ -2045,24 +2044,31 @@ input[type="range"].vertical { padding: 0 5px; } -/* .elevatorslider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 23px; - height: 24px; + width: 100px; + height: 100px; border: 0; - background: url("../MapComplete/assets/svg/bug.svg"); + background-color: #00000000 !important; + background-image: url("/assets/svg/elevator.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; cursor: pointer; + position: relative; + z-index: 2; } .elevatorslider::-moz-range-thumb { - width: 23px; - height: 25px; + width: 100px; + height: 100px; border: 0; - background: url("../MapComplete/assets/svg/bug.svg"); + background-color: #00000000 !important; + background-image: url("/assets/svg/elevator.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; cursor: pointer; -} */ +} .border-detail { border-color: var(--foreground-color); diff --git a/index.css b/index.css index f1a4ce83e..e4471ff1e 100644 --- a/index.css +++ b/index.css @@ -237,22 +237,31 @@ input[type="range"].vertical { padding: 0 5px; } -/* .elevatorslider::-webkit-slider-thumb { - appearance: none; - width: 23px; - height: 24px; +.elevatorslider::-webkit-slider-thumb { + width: 100px; + height: 100px; border: 0; - background: url("/assets/svg/bug.svg") !important; + background-color: #00000000 !important; + background-image: url("/assets/svg/elevator.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; cursor: pointer; + position: relative; + z-index: 2; } .elevatorslider::-moz-range-thumb { - width: 23px; - height: 25px; + width: 100px; + height: 100px; border: 0; - background: url("/assets/svg/bug.svg") !important; + background-color: #00000000 !important; + background-image: url("/assets/svg/elevator.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; cursor: pointer; -} */ +} .border-detail { border-color: var(--foreground-color); diff --git a/test.ts b/test.ts index 21ba35181..507d46550 100644 --- a/test.ts +++ b/test.ts @@ -8,7 +8,7 @@ import Slider from "./UI/Input/Slider"; import Toggle, { ClickableToggle } from "./UI/Input/Toggle"; const testData = ["-1", "0", "0.5", "1", "1.5", "2"] -let slider = new Slider(0, testData.length - 1); +let slider = new Slider(0, testData.length - 1, {vertical: true}); const toggleClass = "flex border-2 border-blue-500 rounded-full w-10 h-10 place-content-center items-center" @@ -23,11 +23,10 @@ const values = testData.map((data, i) => new ClickableToggle( } )) .ToggleOnClick() - .SetClass("flex flex-column bg-slate-200 m-4 w-10 h-10")) + .SetClass("flex flex-column bg-slate-200 m-2 w-10 h-10")) const valCombine = new Combine(values.reverse()) -slider.SetClass("flex vertical m-4 elevatorslider") - -new Combine([valCombine, slider]).SetClass("flex flex-row h-10").AttachTo("extradiv") +slider.SetClass("flex m-4 elevatorslider mb-0").SetStyle("height: "+3.25*testData.length+"rem") +new Combine([valCombine.SetClass("mt-8"), slider]).SetClass("flex flex-row h-14").AttachTo("extradiv") From de2e39c4eb47067d54851c061807979e26f490c3 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 18 Jul 2022 13:59:32 +0200 Subject: [PATCH 04/12] Add elevator icon --- assets/svg/elevator.svg | 78 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 assets/svg/elevator.svg diff --git a/assets/svg/elevator.svg b/assets/svg/elevator.svg new file mode 100644 index 000000000..ad43fe894 --- /dev/null +++ b/assets/svg/elevator.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + From 98d061b24a83cc7cd221ffca72305ad78e9596fe Mon Sep 17 00:00:00 2001 From: AlexanderRebai Date: Mon, 18 Jul 2022 14:28:12 +0000 Subject: [PATCH 05/12] latest --- assets/svg/elevator_wheelchair.svg | 1 + assets/svg/license_info.json | 10 ++++++ index.css | 50 +++++++++++++++--------------- test.ts | 8 ++--- 4 files changed, 40 insertions(+), 29 deletions(-) create mode 100644 assets/svg/elevator_wheelchair.svg diff --git a/assets/svg/elevator_wheelchair.svg b/assets/svg/elevator_wheelchair.svg new file mode 100644 index 000000000..35b934aee --- /dev/null +++ b/assets/svg/elevator_wheelchair.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 44f3be2e1..6d1d8401e 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -397,6 +397,16 @@ "https://commons.wikimedia.org/wiki/File:HZM_elevator_icon.svg" ] }, + { + "path": "elevator_wheelchair.svg", + "license": "CC-BY-SA", + "authors": [ + "Robin Julien" + ], + "sources": [ + "https://www.ctsteward.com/" + ] + }, { "path": "envelope.svg", "license": "CC0; trivial", diff --git a/index.css b/index.css index e4471ff1e..4636a48a7 100644 --- a/index.css +++ b/index.css @@ -233,36 +233,36 @@ input[type="range"].vertical { writing-mode: bt-lr; /* IE */ -webkit-appearance: slider-vertical; /* Chromium */ width: 8px; - height: 310px; + height: 180px; padding: 0 5px; + cursor: pointer; } -.elevatorslider::-webkit-slider-thumb { - width: 100px; - height: 100px; - border: 0; - background-color: #00000000 !important; - background-image: url("/assets/svg/elevator.svg"); - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; - position: relative; - z-index: 2; +@-moz-document url-prefix() { + input[type="range"].vertical { + height: 280px !important; + width: 65px !important; + padding-top: 25px; + } + .valuesContainer { + padding-top: 30px; + } + input[type="range"].vertical::-moz-range-thumb { + width: 60px; + height: 50px; + border: 2px; + border-style: solid; + border-width: 50px; + background-color: #00000000 !important; + background-image: url("/assets/svg/elevator_wheelchair.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + border-image: linear-gradient(to right, red 50%, transparent 50%) 100% 1; + } } -.elevatorslider::-moz-range-thumb { - width: 100px; - height: 100px; - border: 0; - background-color: #00000000 !important; - background-image: url("/assets/svg/elevator.svg"); - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; -} - .border-detail { border-color: var(--foreground-color); } diff --git a/test.ts b/test.ts index 507d46550..d8eb427be 100644 --- a/test.ts +++ b/test.ts @@ -10,7 +10,9 @@ import Toggle, { ClickableToggle } from "./UI/Input/Toggle"; const testData = ["-1", "0", "0.5", "1", "1.5", "2"] let slider = new Slider(0, testData.length - 1, {vertical: true}); -const toggleClass = "flex border-2 border-blue-500 rounded-full w-10 h-10 place-content-center items-center" +slider.SetClass("flex m-1 elevatorslider mb-0 mt-8").SetStyle("height: "+2.5*testData.length+"rem ") + +const toggleClass = "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center" const values = testData.map((data, i) => new ClickableToggle( new FixedUiElement(data).SetClass("active bg-subtle " + toggleClass), new FixedUiElement(data).SetClass(toggleClass), slider.GetValue().sync( @@ -23,10 +25,8 @@ const values = testData.map((data, i) => new ClickableToggle( } )) .ToggleOnClick() - .SetClass("flex flex-column bg-slate-200 m-2 w-10 h-10")) + .SetClass("flex flex-column ml-5 bg-slate-200 w-10 h-10 valuesContainer")) const valCombine = new Combine(values.reverse()) -slider.SetClass("flex m-4 elevatorslider mb-0").SetStyle("height: "+3.25*testData.length+"rem") - new Combine([valCombine.SetClass("mt-8"), slider]).SetClass("flex flex-row h-14").AttachTo("extradiv") From 45b736308ed6b529d2302a90332d7bcd51654a46 Mon Sep 17 00:00:00 2001 From: AlexanderRebai Date: Mon, 18 Jul 2022 14:37:29 +0000 Subject: [PATCH 06/12] latest --- index.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.css b/index.css index 4636a48a7..ffaecf572 100644 --- a/index.css +++ b/index.css @@ -248,18 +248,18 @@ input[type="range"].vertical { padding-top: 30px; } input[type="range"].vertical::-moz-range-thumb { - width: 60px; - height: 50px; + width: 150px; + height: 35px; border: 2px; border-style: solid; - border-width: 50px; background-color: #00000000 !important; background-image: url("/assets/svg/elevator_wheelchair.svg"); background-size: contain; background-position: center center; background-repeat: no-repeat; cursor: pointer; - border-image: linear-gradient(to right, red 50%, transparent 50%) 100% 1; + border-image: linear-gradient(to right, black 50%, transparent 50%) 100% 1; + padding-bottom: 5px; } } From 0d3e7f816879325af4b2fa8dbcac1c4d39670007 Mon Sep 17 00:00:00 2001 From: AlexanderRebai Date: Tue, 19 Jul 2022 08:47:31 +0000 Subject: [PATCH 07/12] fixed levels slider with elevator icon in firefox --- css/index-tailwind-output.css | 71 +++++++++++++++++++---------------- index.css | 7 ++-- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index b8b950474..71a4ae761 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -803,18 +803,10 @@ video { float: none; } -.m-8 { - margin: 2rem; -} - .m-1 { margin: 0.25rem; } -.m-2 { - margin: 0.5rem; -} - .m-4 { margin: 1rem; } @@ -835,6 +827,14 @@ video { margin: 0.75rem; } +.m-8 { + margin: 2rem; +} + +.m-2 { + margin: 0.5rem; +} + .m-6 { margin: 1.5rem; } @@ -866,6 +866,10 @@ video { margin-top: 2rem; } +.ml-5 { + margin-left: 1.25rem; +} + .ml-3 { margin-left: 0.75rem; } @@ -2040,34 +2044,35 @@ input[type="range"].vertical { -webkit-appearance: slider-vertical; /* Chromium */ width: 8px; - height: 310px; - padding: 0 5px; + height: 180px; + padding: 31px 5px 0 5px; + cursor: pointer; } -.elevatorslider::-webkit-slider-thumb { - width: 100px; - height: 100px; - border: 0; - background-color: #00000000 !important; - background-image: url("/assets/svg/elevator.svg"); - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; - position: relative; - z-index: 2; -} +@-moz-document url-prefix() { + input[type="range"].vertical { + height: 269px !important; + width: 65px !important; + } -.elevatorslider::-moz-range-thumb { - width: 100px; - height: 100px; - border: 0; - background-color: #00000000 !important; - background-image: url("/assets/svg/elevator.svg"); - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; + .valuesContainer { + padding-top: 30px; + } + + input[type="range"].vertical::-moz-range-thumb { + width: 150px; + height: 30px; + border: 2px; + border-style: solid; + background-color: #00000000 !important; + background-image: url("/assets/svg/elevator_wheelchair.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + border-image: linear-gradient(to right, black 50%, transparent 50%) 100% 1; + padding-bottom: 5px; + } } .border-detail { diff --git a/index.css b/index.css index ffaecf572..50b5c9e0e 100644 --- a/index.css +++ b/index.css @@ -234,22 +234,21 @@ input[type="range"].vertical { -webkit-appearance: slider-vertical; /* Chromium */ width: 8px; height: 180px; - padding: 0 5px; + padding: 31px 5px 0 5px; cursor: pointer; } @-moz-document url-prefix() { input[type="range"].vertical { - height: 280px !important; + height: 269px !important; width: 65px !important; - padding-top: 25px; } .valuesContainer { padding-top: 30px; } input[type="range"].vertical::-moz-range-thumb { width: 150px; - height: 35px; + height: 30px; border: 2px; border-style: solid; background-color: #00000000 !important; From 13e949a1cd17278c363722700cf1f500483b1af9 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 21 Jul 2022 15:54:24 +0200 Subject: [PATCH 08/12] Wire in level selector --- Logic/State/MapState.ts | 6 ++ UI/BigComponents/RightControls.ts | 28 ++++++++- UI/Input/LevelSelector.ts | 94 +++++++++++++++++++++++++++++++ test.ts | 24 -------- 4 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 UI/Input/LevelSelector.ts diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index 5a2564c06..d5e5a1fa6 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -78,6 +78,12 @@ export default class MapState extends UserRelatedState { * Which layers are enabled in the current theme and what filters are applied onto them */ public filteredLayers: UIEventSource = new UIEventSource([], "filteredLayers"); + + /** + * Filters which apply onto all layers + */ + public globalFilters: UIEventSource<{ filter: FilterState, id: string }[]> = new UIEventSource([], "globalFilters") + /** * Which overlays are shown */ diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index 6e3e333fa..ec2723210 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -4,10 +4,15 @@ import MapControlButton from "../MapControlButton"; import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"; import Svg from "../../Svg"; import MapState from "../../Logic/State/MapState"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import LevelSelector from "../Input/LevelSelector"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; +import {Utils} from "../../Utils"; export default class RightControls extends Combine { - constructor(state: MapState) { + constructor(state: MapState & { featurePipeline: FeaturePipeline }) { const geolocatioHandler = new GeoLocationHandler( state @@ -38,7 +43,26 @@ export default class RightControls extends Combine { state.locationControl.ping(); }); - super([plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1"))) + const levelsInView = state.currentBounds.map(bbox => { + if(bbox === undefined){ + return [] + } + const allElements = state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox); + const allLevelsRaw: string[] = [].concat(...allElements.map(allElements => allElements.features.map(f => f.properties["level"]))) + const allLevels = [].concat(...allLevelsRaw.map(l => LevelSelector.LevelsParser(l))) + return Utils.Dedup(allLevels) + }) + const levelSelect = new LevelSelector(levelsInView) + + levelsInView.addCallbackAndRun(levelsInView => { + if(levelsInView.length <= 1){ + levelSelect.SetClass("invisible") + }else{ + levelSelect.RemoveClass("invisible") + } + }) + + super([levelSelect, plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1"))) this.SetClass("flex flex-col items-center") } diff --git a/UI/Input/LevelSelector.ts b/UI/Input/LevelSelector.ts new file mode 100644 index 000000000..82e945809 --- /dev/null +++ b/UI/Input/LevelSelector.ts @@ -0,0 +1,94 @@ +import {InputElement} from "./InputElement"; +import {Store, UIEventSource} from "../../Logic/UIEventSource"; +import Combine from "../Base/Combine"; +import Slider from "./Slider"; +import {ClickableToggle} from "./Toggle"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import {Utils} from "../../Utils"; + +export default class LevelSelector extends Combine implements InputElement{ + + private readonly _value : UIEventSource; + + constructor(currentLevels: Store, options?:{ + value?: UIEventSource + }) { + + const testData = ["-1", "0", "0.5", "1", "1.5", "2"] + let slider = new Slider(0, testData.length - 1, {vertical: true}); + slider.SetClass("flex m-1 elevatorslider mb-0 mt-8").SetStyle("height: "+2.5*testData.length+"rem ") + const toggleClass = "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center" + const values = testData.map((data, i) => new ClickableToggle( + new FixedUiElement(data).SetClass("active bg-subtle " + toggleClass), new FixedUiElement(data).SetClass(toggleClass), slider.GetValue().sync( + (sliderVal) => { + return sliderVal === i + }, + [], + (isSelected) => { + return isSelected ? i : slider.GetValue().data + } + )) + .ToggleOnClick() + .SetClass("flex flex-column ml-5 bg-slate-200 w-10 h-10 valuesContainer")) + + super([new Combine(values.reverse()).SetClass("mt-8"), slider]) + this.SetClass("flex flex-row h-14"); + + const value = this._value = options?.value ?? new UIEventSource(undefined) + slider.GetValue().addCallbackAndRun(i => { + if(currentLevels?.data === undefined){ + return + } + value.setData(currentLevels?.data[i]); + }) + value.addCallback(level => { + const i = currentLevels?.data?.findIndex(l => l === level) + slider.GetValue().setData(i) + }) + } + + GetValue(): UIEventSource { + return this._value; + } + + protected InnerConstructElement(): HTMLElement { + return undefined; + } + + IsValid(t: string): boolean { + return false; + } + + + /** + * Parses a level specifier to the various available levels + * + * LevelSelector.LevelsParser("0") // => ["0"] + * LevelSelector.LevelsParser("1") // => ["1"] + * LevelSelector.LevelsParser("0;2") // => ["0","2"] + * LevelSelector.LevelsParser("0-5") // => ["0","1","2","3","4","5"] + * LevelSelector.LevelsParser("0") // => ["0"] + */ + public static LevelsParser(level: string): string[] { + let spec = [level] + spec = [].concat(...spec.map(s => s.split(";"))) + spec = [].concat(...spec.map(s => { + s = s.trim() + if(s.indexOf("-") < 0){ + return s + } + const [start, end] = s.split("-").map(s => Number(s.trim())) + if(isNaN(start) || isNaN(end)){ + return undefined + } + const values = [] + for (let i = start; i <= end; i++) { + values.push(i+"") + } + return values + })) + return Utils.NoNull(spec); + } + + +} \ No newline at end of file diff --git a/test.ts b/test.ts index d8eb427be..287016254 100644 --- a/test.ts +++ b/test.ts @@ -6,27 +6,3 @@ import { VariableUiElement } from "./UI/Base/VariableUIElement"; import { FixedInputElement } from "./UI/Input/FixedInputElement"; import Slider from "./UI/Input/Slider"; import Toggle, { ClickableToggle } from "./UI/Input/Toggle"; - -const testData = ["-1", "0", "0.5", "1", "1.5", "2"] -let slider = new Slider(0, testData.length - 1, {vertical: true}); - -slider.SetClass("flex m-1 elevatorslider mb-0 mt-8").SetStyle("height: "+2.5*testData.length+"rem ") - -const toggleClass = "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center" - -const values = testData.map((data, i) => new ClickableToggle( - new FixedUiElement(data).SetClass("active bg-subtle " + toggleClass), new FixedUiElement(data).SetClass(toggleClass), slider.GetValue().sync( - (sliderVal) => { - return sliderVal === i - }, - [], - (isSelected) => { - return isSelected ? i : slider.GetValue().data - } - )) - .ToggleOnClick() - .SetClass("flex flex-column ml-5 bg-slate-200 w-10 h-10 valuesContainer")) - -const valCombine = new Combine(values.reverse()) - -new Combine([valCombine.SetClass("mt-8"), slider]).SetClass("flex flex-row h-14").AttachTo("extradiv") From b0cd6f3a095b5d8d00cce7da213ff3b74c8afd64 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 21 Jul 2022 19:18:11 +0200 Subject: [PATCH 09/12] Fix sort warning --- UI/BigComponents/RightControls.ts | 3 +- UI/Input/LevelSelector.ts | 89 +++++++++++++++---------------- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index ec2723210..3de47a3ee 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -4,9 +4,7 @@ import MapControlButton from "../MapControlButton"; import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"; import Svg from "../../Svg"; import MapState from "../../Logic/State/MapState"; -import {VariableUiElement} from "../Base/VariableUIElement"; import LevelSelector from "../Input/LevelSelector"; -import {UIEventSource} from "../../Logic/UIEventSource"; import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; import {Utils} from "../../Utils"; @@ -50,6 +48,7 @@ export default class RightControls extends Combine { const allElements = state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox); const allLevelsRaw: string[] = [].concat(...allElements.map(allElements => allElements.features.map(f => f.properties["level"]))) const allLevels = [].concat(...allLevelsRaw.map(l => LevelSelector.LevelsParser(l))) + allLevels.sort((a,b) => a < b ? -1 : 1) return Utils.Dedup(allLevels) }) const levelSelect = new LevelSelector(levelsInView) diff --git a/UI/Input/LevelSelector.ts b/UI/Input/LevelSelector.ts index 82e945809..2c5e6a4cf 100644 --- a/UI/Input/LevelSelector.ts +++ b/UI/Input/LevelSelector.ts @@ -1,60 +1,59 @@ import {InputElement} from "./InputElement"; -import {Store, UIEventSource} from "../../Logic/UIEventSource"; +import {Store, Stores, UIEventSource} from "../../Logic/UIEventSource"; import Combine from "../Base/Combine"; import Slider from "./Slider"; import {ClickableToggle} from "./Toggle"; import {FixedUiElement} from "../Base/FixedUiElement"; import {Utils} from "../../Utils"; +import {VariableUiElement} from "../Base/VariableUIElement"; -export default class LevelSelector extends Combine implements InputElement{ - - private readonly _value : UIEventSource; - - constructor(currentLevels: Store, options?:{ +export default class LevelSelector extends VariableUiElement implements InputElement { + + private readonly _value: UIEventSource; + + constructor(currentLevels: Store, options?: { value?: UIEventSource }) { + const value = options?.value ?? new UIEventSource(undefined) + super(Stores.ListStabilized(currentLevels).map(levels => { + let slider = new Slider(0, levels.length - 1, {vertical: true}); + slider.SetClass("flex m-1 elevatorslider mb-0 mt-8").SetStyle("height: " + 2.5 * levels.length + "rem ") + const toggleClass = "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center" + const values = levels.map((data, i) => new ClickableToggle( + new FixedUiElement(data).SetClass("active bg-subtle " + toggleClass), new FixedUiElement(data).SetClass(toggleClass), slider.GetValue().sync( + (sliderVal) => { + return sliderVal === i + }, + [], + (isSelected) => { + return isSelected ? i : slider.GetValue().data + } + )) + .ToggleOnClick() + .SetClass("flex flex-column ml-5 bg-slate-200 w-10 h-10 valuesContainer")) - const testData = ["-1", "0", "0.5", "1", "1.5", "2"] - let slider = new Slider(0, testData.length - 1, {vertical: true}); - slider.SetClass("flex m-1 elevatorslider mb-0 mt-8").SetStyle("height: "+2.5*testData.length+"rem ") - const toggleClass = "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center" - const values = testData.map((data, i) => new ClickableToggle( - new FixedUiElement(data).SetClass("active bg-subtle " + toggleClass), new FixedUiElement(data).SetClass(toggleClass), slider.GetValue().sync( - (sliderVal) => { - return sliderVal === i - }, - [], - (isSelected) => { - return isSelected ? i : slider.GetValue().data + const combine = new Combine([new Combine(values).SetClass("mt-8"), slider]) + combine.SetClass("flex flex-row h-14"); + + slider.GetValue().addCallbackAndRun(i => { + if (currentLevels?.data === undefined) { + return } - )) - .ToggleOnClick() - .SetClass("flex flex-column ml-5 bg-slate-200 w-10 h-10 valuesContainer")) + value.setData(currentLevels?.data[i]); + }) + value.addCallback(level => { + const i = currentLevels?.data?.findIndex(l => l === level) + slider.GetValue().setData(i) + }) + return combine + })) - super([new Combine(values.reverse()).SetClass("mt-8"), slider]) - this.SetClass("flex flex-row h-14"); - - const value = this._value = options?.value ?? new UIEventSource(undefined) - slider.GetValue().addCallbackAndRun(i => { - if(currentLevels?.data === undefined){ - return - } - value.setData(currentLevels?.data[i]); - }) - value.addCallback(level => { - const i = currentLevels?.data?.findIndex(l => l === level) - slider.GetValue().setData(i) - }) } GetValue(): UIEventSource { return this._value; } - protected InnerConstructElement(): HTMLElement { - return undefined; - } - IsValid(t: string): boolean { return false; } @@ -62,7 +61,7 @@ export default class LevelSelector extends Combine implements InputElement ["0"] * LevelSelector.LevelsParser("1") // => ["1"] * LevelSelector.LevelsParser("0;2") // => ["0","2"] @@ -74,21 +73,21 @@ export default class LevelSelector extends Combine implements InputElement s.split(";"))) spec = [].concat(...spec.map(s => { s = s.trim() - if(s.indexOf("-") < 0){ + if (s.indexOf("-") < 0) { return s } const [start, end] = s.split("-").map(s => Number(s.trim())) - if(isNaN(start) || isNaN(end)){ + if (isNaN(start) || isNaN(end)) { return undefined } const values = [] for (let i = start; i <= end; i++) { - values.push(i+"") + values.push(i + "") } return values })) return Utils.NoNull(spec); } - - + + } \ No newline at end of file From 707961761ca23c131bde7a10a98d2a5d944acf6b Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 22 Jul 2022 01:33:11 +0200 Subject: [PATCH 10/12] First version with working level selector --- .../Sources/FilteringFeatureSource.ts | 29 +++++-- Logic/SimpleMetaTagger.ts | 41 +++++++--- Logic/Tags/TagUtils.ts | 44 +++++++++-- UI/BigComponents/RightControls.ts | 69 ++++++++++++++--- UI/Input/LevelSelector.ts | 51 ++++-------- assets/svg/elevator_wheelchair.svg | 77 ++++++++++++++++++- css/index-tailwind-output.css | 52 ++++--------- index.css | 20 ++--- test.ts | 4 + 9 files changed, 266 insertions(+), 121 deletions(-) diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 1fbb7fbe0..dc97e1d3a 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -1,10 +1,9 @@ -import {UIEventSource} from "../../UIEventSource"; -import FilteredLayer from "../../../Models/FilteredLayer"; +import {Store, UIEventSource} from "../../UIEventSource"; +import FilteredLayer, {FilterState} from "../../../Models/FilteredLayer"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; import {BBox} from "../../BBox"; import {ElementStorage} from "../../ElementStorage"; import {TagsFilter} from "../../Tags/TagsFilter"; -import {tag} from "@turf/turf"; import {OsmFeature} from "../../../Models/OsmFeature"; export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled { @@ -16,7 +15,9 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti public readonly bbox: BBox private readonly upstream: FeatureSourceForLayer; private readonly state: { - locationControl: UIEventSource<{ zoom: number }>; selectedElement: UIEventSource, + locationControl: Store<{ zoom: number }>; + selectedElement: Store, + globalFilters: Store<{ filter: FilterState }[]>, allElements: ElementStorage }; private readonly _alreadyRegistered = new Set>(); @@ -25,9 +26,10 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti constructor( state: { - locationControl: UIEventSource<{ zoom: number }>, - selectedElement: UIEventSource, - allElements: ElementStorage + locationControl: Store<{ zoom: number }>, + selectedElement: Store, + allElements: ElementStorage, + globalFilters: Store<{ filter: FilterState }[]> }, tileIndex, upstream: FeatureSourceForLayer, @@ -60,6 +62,10 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti metataggingUpdated?.addCallback(_ => { self._is_dirty.setData(true) }) + + state.globalFilters.addCallback(_ => { + self.update() + }) this.update(); } @@ -69,6 +75,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti const layer = this.upstream.layer; const features: { feature: OsmFeature; freshness: Date }[] = (this.upstream.features.data ?? []); const includedFeatureIds = new Set(); + const globalFilters = self.state.globalFilters.data.map(f => f.filter); const newFeatures = (features ?? []).filter((f) => { self.registerCallback(f.feature) @@ -88,6 +95,14 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti } } + for (const filter of globalFilters) { + const neededTags: TagsFilter = filter?.currentFilter + if (neededTags !== undefined && !neededTags.matchesProperties(f.feature.properties)) { + // Hidden by the filter on the layer itself - we want to hide it no matter what + return false; + } + } + includedFeatureIds.add(f.feature.properties.id) return true; }); diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index e1383afb7..ab665e6c3 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -8,6 +8,7 @@ import {FixedUiElement} from "../UI/Base/FixedUiElement"; import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import {CountryCoder} from "latlon2country" import Constants from "../Models/Constants"; +import {TagUtils} from "./Tags/TagUtils"; export class SimpleMetaTagger { @@ -32,7 +33,7 @@ export class SimpleMetaTagger { if (!docs.cleanupRetagger) { for (const key of docs.keys) { if (!key.startsWith('_') && key.toLowerCase().indexOf("theme") < 0) { - throw `Incorrect metakey ${key}: it should start with underscore (_)` + throw `Incorrect key for a calculated meta value '${key}': it should start with underscore (_)` } } } @@ -211,6 +212,27 @@ export default class SimpleMetaTaggers { return true; }) ); + private static levels = new SimpleMetaTagger( + { + doc: "Extract the 'level'-tag into a normalized, ';'-separated value", + keys: ["_level"] + }, + ((feature) => { + if (feature.properties["level"] === undefined) { + return false; + } + + const l = feature.properties["level"] + const newValue = TagUtils.LevelsParser(l).join(";") + if(l === newValue) { + return false; + } + feature.properties["level"] = newValue + return true + + }) + ) + private static canonicalize = new SimpleMetaTagger( { doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`)", @@ -218,7 +240,7 @@ export default class SimpleMetaTaggers { }, ((feature, _, __, state) => { - const units = Utils.NoNull([].concat(...state?.layoutToUse?.layers?.map(layer => layer.units )?? [])); + const units = Utils.NoNull([].concat(...state?.layoutToUse?.layers?.map(layer => layer.units) ?? [])); if (units.length == 0) { return; } @@ -317,7 +339,7 @@ export default class SimpleMetaTaggers { country_code: tags._country.toLowerCase(), state: undefined } - }, {tag_key: "opening_hours"}); + }, {tag_key: "opening_hours"}); // Recalculate! return oh.getState() ? "yes" : "no"; @@ -327,12 +349,12 @@ export default class SimpleMetaTaggers { delete tags._isOpen tags["_isOpen"] = "parse_error"; } - }}); - - + } + }); + + const tagsSource = state.allElements.getEventSourceById(feature.properties.id); - - + }) ) @@ -400,7 +422,8 @@ export default class SimpleMetaTaggers { SimpleMetaTaggers.currentTime, SimpleMetaTaggers.objectMetaInfo, SimpleMetaTaggers.noBothButLeftRight, - SimpleMetaTaggers.geometryType + SimpleMetaTaggers.geometryType, + SimpleMetaTaggers.levels ]; public static readonly lazyTags: string[] = [].concat(...SimpleMetaTaggers.metatags.filter(tagger => tagger.isLazy) diff --git a/Logic/Tags/TagUtils.ts b/Logic/Tags/TagUtils.ts index 2688d3140..6ef3a8966 100644 --- a/Logic/Tags/TagUtils.ts +++ b/Logic/Tags/TagUtils.ts @@ -127,7 +127,7 @@ export class TagUtils { * } * ]}) * TagUtils.FlattenMultiAnswer([tag]) // => TagUtils.Tag({and:["x=a;b", "y=0;1;2;3"] }) - * + * * TagUtils.FlattenMultiAnswer(([new Tag("x","y"), new Tag("a","b")])) // => new And([new Tag("x","y"), new Tag("a","b")]) * TagUtils.FlattenMultiAnswer(([new Tag("x","")])) // => new And([new Tag("x","")]) */ @@ -240,7 +240,7 @@ export class TagUtils { * * TagUtils.Tag("xyz<5").matchesProperties({xyz: 4}) // => true * TagUtils.Tag("xyz<5").matchesProperties({xyz: 5}) // => false - * + * * // RegexTags must match values with newlines * TagUtils.Tag("note~.*aed.*").matchesProperties({note: "Hier bevindt zich wss een defibrillator. \\n\\n De aed bevindt zich op de 5de verdieping"}) // => true * TagUtils.Tag("note~i~.*aed.*").matchesProperties({note: "Hier bevindt zich wss een defibrillator. \\n\\n De AED bevindt zich op de 5de verdieping"}) // => true @@ -264,13 +264,13 @@ export class TagUtils { * @constructor */ public static TagD(json?: TagConfigJson, context: string = ""): TagsFilter | undefined { - if(json === undefined){ + if (json === undefined) { return undefined } return TagUtils.Tag(json, context) } - - + + /** * INLINE sort of the given list */ @@ -581,4 +581,38 @@ export class TagUtils { return listToFilter.some(tf => guards.some(guard => guard.shadows(tf))) } + + /** + * Parses a level specifier to the various available levels + * + * TagUtils.LevelsParser("0") // => ["0"] + * TagUtils.LevelsParser("1") // => ["1"] + * TagUtils.LevelsParser("0;2") // => ["0","2"] + * TagUtils.LevelsParser("0-5") // => ["0","1","2","3","4","5"] + * TagUtils.LevelsParser("0") // => ["0"] + * TagUtils.LevelsParser("-1") // => ["-1"] + * TagUtils.LevelsParser("0;-1") // => ["0", "-1"] + */ + public static LevelsParser(level: string): string[] { + let spec = Utils.NoNull([level]) + spec = [].concat(...spec.map(s => s?.split(";"))) + spec = [].concat(...spec.map(s => { + s = s.trim() + if (s.indexOf("-") < 0 || s.startsWith("-")) { + return s + } + const [start, end] = s.split("-").map(s => Number(s.trim())) + if (isNaN(start) || isNaN(end)) { + return undefined + } + const values = [] + for (let i = start; i <= end; i++) { + values.push(i + "") + } + return values + })) + return Utils.NoNull(spec); + } + + } \ No newline at end of file diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index 3de47a3ee..7063c5f7e 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -7,6 +7,11 @@ import MapState from "../../Logic/State/MapState"; import LevelSelector from "../Input/LevelSelector"; import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; import {Utils} from "../../Utils"; +import {TagUtils} from "../../Logic/Tags/TagUtils"; +import {RegexTag} from "../../Logic/Tags/RegexTag"; +import {Or} from "../../Logic/Tags/Or"; +import {Tag} from "../../Logic/Tags/Tag"; +import {TagsFilter} from "../../Logic/Tags/TagsFilter"; export default class RightControls extends Combine { @@ -42,26 +47,72 @@ export default class RightControls extends Combine { }); const levelsInView = state.currentBounds.map(bbox => { - if(bbox === undefined){ + if (bbox === undefined) { return [] } const allElements = state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox); const allLevelsRaw: string[] = [].concat(...allElements.map(allElements => allElements.features.map(f => f.properties["level"]))) - const allLevels = [].concat(...allLevelsRaw.map(l => LevelSelector.LevelsParser(l))) - allLevels.sort((a,b) => a < b ? -1 : 1) + const allLevels = [].concat(...allLevelsRaw.map(l => TagUtils.LevelsParser(l))) + if(allLevels.indexOf("0") < 0){ + allLevels.push("0") + } + allLevels.sort((a, b) => a < b ? -1 : 1) return Utils.Dedup(allLevels) }) + state.globalFilters.data.push({ + filter: { + currentFilter: undefined, + state: undefined + + }, id: "level" + }) const levelSelect = new LevelSelector(levelsInView) - - levelsInView.addCallbackAndRun(levelsInView => { - if(levelsInView.length <= 1){ - levelSelect.SetClass("invisible") - }else{ + + const isShown = levelsInView.map(levelsInView => levelsInView.length !== 0 && state.locationControl.data.zoom >= 17, + [state.locationControl]) + + function setLevelFilter() { + const filter = state.globalFilters.data.find(gf => gf.id === "level") + const oldState = filter.filter.state; + if (!isShown.data) { + filter.filter = { + state: "*", + currentFilter: undefined + } + + } else { + + const l = levelSelect.GetValue().data + let neededLevel : TagsFilter = new RegexTag("level", new RegExp("(^|;)" + l + "(;|$)")); + if(l === "0"){ + neededLevel = new Or([neededLevel, new Tag("level", "")]) + } + filter.filter = { + state: l, + currentFilter: neededLevel + } + } + if(filter.filter.state !== oldState){ + state.globalFilters.ping(); + console.log("Level filter is now ", filter?.filter?.currentFilter?.asHumanString(false, false, {})) + } + return; + } + + + isShown.addCallbackAndRun(shown => { + console.log("Is level selector shown?", shown) + setLevelFilter() + if (shown) { + // levelSelect.SetClass("invisible") + } else { levelSelect.RemoveClass("invisible") } }) + + levelSelect.GetValue().addCallback(_ => setLevelFilter()) - super([levelSelect, plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1"))) + super([new Combine([levelSelect]).SetClass(""), plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1"))) this.SetClass("flex flex-col items-center") } diff --git a/UI/Input/LevelSelector.ts b/UI/Input/LevelSelector.ts index 2c5e6a4cf..766a1f6c8 100644 --- a/UI/Input/LevelSelector.ts +++ b/UI/Input/LevelSelector.ts @@ -4,7 +4,6 @@ import Combine from "../Base/Combine"; import Slider from "./Slider"; import {ClickableToggle} from "./Toggle"; import {FixedUiElement} from "../Base/FixedUiElement"; -import {Utils} from "../../Utils"; import {VariableUiElement} from "../Base/VariableUIElement"; export default class LevelSelector extends VariableUiElement implements InputElement { @@ -16,11 +15,15 @@ export default class LevelSelector extends VariableUiElement implements InputEle }) { const value = options?.value ?? new UIEventSource(undefined) super(Stores.ListStabilized(currentLevels).map(levels => { + console.log("CUrrent levels are", levels) let slider = new Slider(0, levels.length - 1, {vertical: true}); - slider.SetClass("flex m-1 elevatorslider mb-0 mt-8").SetStyle("height: " + 2.5 * levels.length + "rem ") - const toggleClass = "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center" + const toggleClass = "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center border-box" + slider.SetClass("flex elevator w-10").SetStyle(`height: ${2.5 * levels.length}rem; background: #00000000`) + const values = levels.map((data, i) => new ClickableToggle( - new FixedUiElement(data).SetClass("active bg-subtle " + toggleClass), new FixedUiElement(data).SetClass(toggleClass), slider.GetValue().sync( + new FixedUiElement(data).SetClass("font-bold active bg-subtle " + toggleClass), + new FixedUiElement(data).SetClass("normal-background " + toggleClass), + slider.GetValue().sync( (sliderVal) => { return sliderVal === i }, @@ -30,11 +33,13 @@ export default class LevelSelector extends VariableUiElement implements InputEle } )) .ToggleOnClick() - .SetClass("flex flex-column ml-5 bg-slate-200 w-10 h-10 valuesContainer")) + .SetClass("flex w-10 h-10")) - const combine = new Combine([new Combine(values).SetClass("mt-8"), slider]) - combine.SetClass("flex flex-row h-14"); + values.reverse(/* This is a new list, no side-effects */) + const combine = new Combine([new Combine(values), slider]) + combine.SetClass("flex flex-row overflow-hidden"); + slider.GetValue().addCallbackAndRun(i => { if (currentLevels?.data === undefined) { return @@ -47,6 +52,8 @@ export default class LevelSelector extends VariableUiElement implements InputEle }) return combine })) + + this._value = value } @@ -59,35 +66,5 @@ export default class LevelSelector extends VariableUiElement implements InputEle } - /** - * Parses a level specifier to the various available levels - * - * LevelSelector.LevelsParser("0") // => ["0"] - * LevelSelector.LevelsParser("1") // => ["1"] - * LevelSelector.LevelsParser("0;2") // => ["0","2"] - * LevelSelector.LevelsParser("0-5") // => ["0","1","2","3","4","5"] - * LevelSelector.LevelsParser("0") // => ["0"] - */ - public static LevelsParser(level: string): string[] { - let spec = [level] - spec = [].concat(...spec.map(s => s.split(";"))) - spec = [].concat(...spec.map(s => { - s = s.trim() - if (s.indexOf("-") < 0) { - return s - } - const [start, end] = s.split("-").map(s => Number(s.trim())) - if (isNaN(start) || isNaN(end)) { - return undefined - } - const values = [] - for (let i = start; i <= end; i++) { - values.push(i + "") - } - return values - })) - return Utils.NoNull(spec); - } - } \ No newline at end of file diff --git a/assets/svg/elevator_wheelchair.svg b/assets/svg/elevator_wheelchair.svg index 35b934aee..568caa468 100644 --- a/assets/svg/elevator_wheelchair.svg +++ b/assets/svg/elevator_wheelchair.svg @@ -1 +1,76 @@ - \ No newline at end of file + + + + + + + + + + + + + + diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 65bca52fe..d242eee6c 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -946,18 +946,6 @@ video { margin-right: 0px; } -.mb-0 { - margin-bottom: 0px; -} - -.mt-8 { - margin-top: 2rem; -} - -.ml-5 { - margin-left: 1.25rem; -} - .mr-3 { margin-right: 0.75rem; } @@ -974,6 +962,10 @@ video { margin-right: 0.25rem; } +.mb-0 { + margin-bottom: 0px; +} + .box-border { box-sizing: border-box; } @@ -1106,10 +1098,6 @@ video { height: 4rem; } -.h-14 { - height: 3.5rem; -} - .h-0 { height: 0px; } @@ -1527,6 +1515,11 @@ video { background-color: rgba(224, 231, 255, var(--tw-bg-opacity)); } +.bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgba(239, 68, 68, var(--tw-bg-opacity)); +} + .bg-black { --tw-bg-opacity: 1; background-color: rgba(0, 0, 0, var(--tw-bg-opacity)); @@ -1547,11 +1540,6 @@ video { background-color: rgba(209, 213, 219, var(--tw-bg-opacity)); } -.bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgba(239, 68, 68, var(--tw-bg-opacity)); -} - .bg-red-200 { --tw-bg-opacity: 1; background-color: rgba(254, 202, 202, var(--tw-bg-opacity)); @@ -2072,29 +2060,17 @@ input[type="range"].vertical { /* IE */ -webkit-appearance: slider-vertical; /* Chromium */ - width: 8px; - height: 180px; - padding: 31px 5px 0 5px; cursor: pointer; } @-moz-document url-prefix() { - input[type="range"].vertical { - height: 269px !important; - width: 65px !important; - } - - .valuesContainer { - padding-top: 30px; - } - - input[type="range"].vertical::-moz-range-thumb { - width: 150px; - height: 30px; - border: 2px; - border-style: solid; + input[type="range"].elevator::-moz-range-thumb { background-color: #00000000 !important; background-image: url("/assets/svg/elevator_wheelchair.svg"); + width: 150px !important; + height: 30px !important; + border: 2px; + border-style: solid; background-size: contain; background-position: center center; background-repeat: no-repeat; diff --git a/index.css b/index.css index 35d55c462..1db74d868 100644 --- a/index.css +++ b/index.css @@ -232,27 +232,17 @@ a { input[type="range"].vertical { writing-mode: bt-lr; /* IE */ -webkit-appearance: slider-vertical; /* Chromium */ - width: 8px; - height: 180px; - padding: 31px 5px 0 5px; cursor: pointer; } @-moz-document url-prefix() { - input[type="range"].vertical { - height: 269px !important; - width: 65px !important; - } - .valuesContainer { - padding-top: 30px; - } - input[type="range"].vertical::-moz-range-thumb { - width: 150px; - height: 30px; - border: 2px; - border-style: solid; + input[type="range"].elevator::-moz-range-thumb { background-color: #00000000 !important; background-image: url("/assets/svg/elevator_wheelchair.svg"); + width: 150px !important; + height: 30px !important; + border: 2px; + border-style: solid; background-size: contain; background-position: center center; background-repeat: no-repeat; diff --git a/test.ts b/test.ts index e69de29bb..f6a656f45 100644 --- a/test.ts +++ b/test.ts @@ -0,0 +1,4 @@ +import LevelSelector from "./UI/Input/LevelSelector"; +import {UIEventSource} from "./Logic/UIEventSource"; + +new LevelSelector(new UIEventSource(["0","1","2","2.5","x","3"])).AttachTo("maindiv") \ No newline at end of file From 038b2ece4c3376df9ad067c3d8512042c75346ce Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 22 Jul 2022 11:50:25 +0200 Subject: [PATCH 11/12] Fix script --- scripts/generateCache.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts index ce791b189..e1cb4415d 100644 --- a/scripts/generateCache.ts +++ b/scripts/generateCache.ts @@ -10,7 +10,7 @@ import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; import RelationsTracker from "../Logic/Osm/RelationsTracker"; import * as OsmToGeoJson from "osmtogeojson"; import MetaTagging from "../Logic/MetaTagging"; -import {UIEventSource} from "../Logic/UIEventSource"; +import {ImmutableStore, UIEventSource} from "../Logic/UIEventSource"; import {TileRange, Tiles} from "../Models/TileRange"; import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; import ScriptUtils from "./ScriptUtils"; @@ -250,9 +250,10 @@ function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relations } const filteredTile = new FilteringFeatureSource({ - locationControl: new UIEventSource(undefined), + locationControl: new ImmutableStore(undefined), allElements: undefined, - selectedElement: new UIEventSource(undefined) + selectedElement: new ImmutableStore(undefined), + globalFilters: new ImmutableStore([]) }, tileIndex, tile, @@ -323,9 +324,10 @@ function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relations if (pointsOnlyLayers.indexOf(layer.id) >= 0) { const filtered = new FilteringFeatureSource({ - locationControl: new UIEventSource(undefined), + locationControl: new ImmutableStore(undefined), allElements: undefined, - selectedElement: new UIEventSource(undefined) + selectedElement: new ImmutableStore(undefined), + globalFilters: new ImmutableStore([]) }, Tiles.tile_index(0, 0, 0), source, From effd75e95c98940805b9303af77206b512e9def9 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 25 Jul 2022 16:55:44 +0200 Subject: [PATCH 12/12] Add extra check that a feature is added on the right level; automatically add the right level to a new point --- Logic/Osm/Actions/CreateNewNodeAction.ts | 2 +- Logic/State/MapState.ts | 37 +++++++++----- Logic/Tags/TagUtils.ts | 10 ++++ UI/BigComponents/RightControls.ts | 61 ++++++++++++++++-------- UI/BigComponents/SimpleAddUI.ts | 4 +- UI/NewPoint/ConfirmLocationOfPoint.ts | 51 ++++++++++++++++---- UI/i18n/Translation.ts | 17 ++++++- langs/en.json | 4 ++ 8 files changed, 140 insertions(+), 46 deletions(-) diff --git a/Logic/Osm/Actions/CreateNewNodeAction.ts b/Logic/Osm/Actions/CreateNewNodeAction.ts index 7a545c7e0..e6755ecd0 100644 --- a/Logic/Osm/Actions/CreateNewNodeAction.ts +++ b/Logic/Osm/Actions/CreateNewNodeAction.ts @@ -72,7 +72,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { this.setElementId(id) for (const kv of this._basicTags) { if (typeof kv.value !== "string") { - throw "Invalid value: don't use a regex in a preset" + throw "Invalid value: don't use non-string value in a preset. The tag "+kv.key+"="+kv.value+" is not a string, the value is a "+typeof kv.value } properties[kv.key] = kv.value; } diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index d5e5a1fa6..2e4a65a9c 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -19,6 +19,19 @@ import TitleHandler from "../Actors/TitleHandler"; import {BBox} from "../BBox"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import {TiledStaticFeatureSource} from "../FeatureSource/Sources/StaticFeatureSource"; +import {Translation, TypedTranslation} from "../../UI/i18n/Translation"; +import {Tag} from "../Tags/Tag"; + + +export interface GlobalFilter { + filter: FilterState, + id: string, + onNewPoint: { + safetyCheck: Translation, + confirmAddNew: TypedTranslation<{ preset: Translation }> + tags: Tag[] + } +} /** * Contains all the leaflet-map related state @@ -82,8 +95,8 @@ export default class MapState extends UserRelatedState { /** * Filters which apply onto all layers */ - public globalFilters: UIEventSource<{ filter: FilterState, id: string }[]> = new UIEventSource([], "globalFilters") - + public globalFilters: UIEventSource = new UIEventSource([], "globalFilters") + /** * Which overlays are shown */ @@ -127,9 +140,9 @@ export default class MapState extends UserRelatedState { this.overlayToggles = this.layoutToUse?.tileLayerSources ?.filter(c => c.name !== undefined) ?.map(c => ({ - config: c, - isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, c.defaultState, "Wether or not the overlay " + c.id + " is shown") - })) ?? [] + config: c, + isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, c.defaultState, "Wether or not the overlay " + c.id + " is shown") + })) ?? [] this.filteredLayers = this.InitializeFilteredLayers() @@ -212,7 +225,7 @@ export default class MapState extends UserRelatedState { return [feature] }) - this.currentView = new TiledStaticFeatureSource(features, currentViewLayer); + this.currentView = new TiledStaticFeatureSource(features, currentViewLayer); } private initGpsLocation() { @@ -341,15 +354,15 @@ export default class MapState extends UserRelatedState { } private getPref(key: string, layer: LayerConfig): UIEventSource { - const pref = this.osmConnection + const pref = this.osmConnection .GetPreference(key) .sync(v => { - if(v === undefined){ + if (v === undefined) { return undefined } return v === "true"; }, [], b => { - if(b === undefined){ + if (b === undefined) { return undefined } return "" + b; @@ -360,7 +373,7 @@ export default class MapState extends UserRelatedState { private InitializeFilteredLayers() { const layoutToUse = this.layoutToUse; - if(layoutToUse === undefined){ + if (layoutToUse === undefined) { return new UIEventSource([]) } const flayers: FilteredLayer[] = []; @@ -369,11 +382,11 @@ export default class MapState extends UserRelatedState { if (layer.syncSelection === "local") { isDisplayed = LocalStorageSource.GetParsed(layoutToUse.id + "-layer-" + layer.id + "-enabled", layer.shownByDefault) } else if (layer.syncSelection === "theme-only") { - isDisplayed = this.getPref(layoutToUse.id+ "-layer-" + layer.id + "-enabled", layer) + isDisplayed = this.getPref(layoutToUse.id + "-layer-" + layer.id + "-enabled", layer) } else if (layer.syncSelection === "global") { isDisplayed = this.getPref("layer-" + layer.id + "-enabled", layer) } else { - isDisplayed = QueryParameters.GetBooleanQueryParameter("layer-" + layer.id, layer.shownByDefault, "Wether or not layer "+layer.id+" is shown") + isDisplayed = QueryParameters.GetBooleanQueryParameter("layer-" + layer.id, layer.shownByDefault, "Wether or not layer " + layer.id + " is shown") } diff --git a/Logic/Tags/TagUtils.ts b/Logic/Tags/TagUtils.ts index 6ef3a8966..aa91fa9b5 100644 --- a/Logic/Tags/TagUtils.ts +++ b/Logic/Tags/TagUtils.ts @@ -492,6 +492,16 @@ export class TagUtils { } return " (" + joined + ") " } + + public static ExtractSimpleTags(tf: TagsFilter) : Tag[] { + const result: Tag[] = [] + tf.visit(t => { + if(t instanceof Tag){ + result.push(t) + } + }) + return result; + } /** * Returns 'true' is opposite tags are detected. diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index 7063c5f7e..5edf81482 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -3,7 +3,7 @@ import Toggle from "../Input/Toggle"; import MapControlButton from "../MapControlButton"; import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"; import Svg from "../../Svg"; -import MapState from "../../Logic/State/MapState"; +import MapState, {GlobalFilter} from "../../Logic/State/MapState"; import LevelSelector from "../Input/LevelSelector"; import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; import {Utils} from "../../Utils"; @@ -12,6 +12,9 @@ import {RegexTag} from "../../Logic/Tags/RegexTag"; import {Or} from "../../Logic/Tags/Or"; import {Tag} from "../../Logic/Tags/Tag"; import {TagsFilter} from "../../Logic/Tags/TagsFilter"; +import Translations from "../i18n/Translations"; +import {BBox} from "../../Logic/BBox"; +import {OsmFeature} from "../../Models/OsmFeature"; export default class RightControls extends Combine { @@ -50,10 +53,11 @@ export default class RightControls extends Combine { if (bbox === undefined) { return [] } - const allElements = state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox); - const allLevelsRaw: string[] = [].concat(...allElements.map(allElements => allElements.features.map(f => f.properties["level"]))) - const allLevels = [].concat(...allLevelsRaw.map(l => TagUtils.LevelsParser(l))) - if(allLevels.indexOf("0") < 0){ + const allElementsUnfiltered: OsmFeature[] = [].concat(... state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox).map(ff => ff.features)) + const allElements = allElementsUnfiltered.filter(f => BBox.get(f).overlapsWith(bbox)) + const allLevelsRaw: string[] = allElements.map(f => f.properties["level"]) + const allLevels = [].concat(...allLevelsRaw.map(l => TagUtils.LevelsParser(l))) + if (allLevels.indexOf("0") < 0) { allLevels.push("0") } allLevels.sort((a, b) => a < b ? -1 : 1) @@ -62,40 +66,57 @@ export default class RightControls extends Combine { state.globalFilters.data.push({ filter: { currentFilter: undefined, - state: undefined + state: undefined, - }, id: "level" + }, + id: "level", + onNewPoint: undefined }) const levelSelect = new LevelSelector(levelsInView) - const isShown = levelsInView.map(levelsInView => levelsInView.length !== 0 && state.locationControl.data.zoom >= 17, + const isShown = levelsInView.map(levelsInView => { + if (levelsInView.length == 0) { + return false; + } + if (state.locationControl.data.zoom <= 16) { + return false; + } + if (levelsInView.length == 1 && levelsInView[0] == "0") { + return false + } + return true; + }, [state.locationControl]) function setLevelFilter() { - const filter = state.globalFilters.data.find(gf => gf.id === "level") - const oldState = filter.filter.state; + console.log("Updating levels filter") + const filter: GlobalFilter = state.globalFilters.data.find(gf => gf.id === "level") if (!isShown.data) { filter.filter = { state: "*", - currentFilter: undefined + currentFilter: undefined, } + filter.onNewPoint = undefined } else { const l = levelSelect.GetValue().data - let neededLevel : TagsFilter = new RegexTag("level", new RegExp("(^|;)" + l + "(;|$)")); - if(l === "0"){ + let neededLevel: TagsFilter = new RegexTag("level", new RegExp("(^|;)" + l + "(;|$)")); + if (l === "0") { neededLevel = new Or([neededLevel, new Tag("level", "")]) } filter.filter = { state: l, currentFilter: neededLevel } + const t = Translations.t.general.levelSelection + filter.onNewPoint = { + confirmAddNew: t.confirmLevel.PartialSubs({level: l}), + safetyCheck: t.addNewOnLevel.Subs({level: l}), + tags: [new Tag("level", l)] + } } - if(filter.filter.state !== oldState){ - state.globalFilters.ping(); - console.log("Level filter is now ", filter?.filter?.currentFilter?.asHumanString(false, false, {})) - } + state.globalFilters.ping(); return; } @@ -104,12 +125,12 @@ export default class RightControls extends Combine { console.log("Is level selector shown?", shown) setLevelFilter() if (shown) { - // levelSelect.SetClass("invisible") - } else { levelSelect.RemoveClass("invisible") + } else { + levelSelect.SetClass("invisible") } }) - + levelSelect.GetValue().addCallback(_ => setLevelFilter()) super([new Combine([levelSelect]).SetClass(""), plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1"))) diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 4566f4115..4b19456a5 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -25,6 +25,7 @@ import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint"; import BaseLayer from "../../Models/BaseLayer"; import Loading from "../Base/Loading"; import Hash from "../../Logic/Web/Hash"; +import {GlobalFilter} from "../../Logic/State/MapState"; /* * The SimpleAddUI is a single panel, which can have multiple states: @@ -66,7 +67,8 @@ export default class SimpleAddUI extends Toggle { locationControl: UIEventSource, filteredLayers: UIEventSource, featureSwitchFilter: UIEventSource, - backgroundLayer: UIEventSource + backgroundLayer: UIEventSource, + globalFilters: UIEventSource }, takeLocationFrom?: UIEventSource<{lat: number, lon: number}> ) { diff --git a/UI/NewPoint/ConfirmLocationOfPoint.ts b/UI/NewPoint/ConfirmLocationOfPoint.ts index d2eb324f5..88671d310 100644 --- a/UI/NewPoint/ConfirmLocationOfPoint.ts +++ b/UI/NewPoint/ConfirmLocationOfPoint.ts @@ -15,12 +15,16 @@ import SimpleAddUI, {PresetInfo} from "../BigComponents/SimpleAddUI"; import BaseLayer from "../../Models/BaseLayer"; import Img from "../Base/Img"; import Title from "../Base/Title"; +import {GlobalFilter} from "../../Logic/State/MapState"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import {Tag} from "../../Logic/Tags/Tag"; export default class ConfirmLocationOfPoint extends Combine { constructor( state: { + globalFilters: UIEventSource; featureSwitchIsTesting: UIEventSource; osmConnection: OsmConnection, featurePipeline: FeaturePipeline, @@ -38,8 +42,8 @@ export default class ConfirmLocationOfPoint extends Combine { let preciseInput: LocationInput = undefined if (preset.preciseInput !== undefined) { // Create location input - - + + // We uncouple the event source const zloc = {...loc, zoom: 19} const locationSrc = new UIEventSource(zloc); @@ -106,7 +110,11 @@ export default class ConfirmLocationOfPoint extends Combine { ).SetClass("font-bold break-words") .onClick(() => { console.log("The confirmLocationPanel - precise input yielded ", preciseInput?.GetValue()?.data) - confirm(preset.tags, preciseInput?.GetValue()?.data ?? loc, preciseInput?.snappedOnto?.data?.properties?.id); + const globalFilterTagsToAdd: Tag[][] = state.globalFilters.data.filter(gf => gf.onNewPoint !== undefined) + .map(gf => gf.onNewPoint.tags) + const globalTags : Tag[] = [].concat(...globalFilterTagsToAdd) + console.log("Global tags to add are: ", globalTags) + confirm([...preset.tags, ...globalTags], preciseInput?.GetValue()?.data ?? loc, preciseInput?.snappedOnto?.data?.properties?.id); }); if (preciseInput !== undefined) { @@ -126,7 +134,7 @@ export default class ConfirmLocationOfPoint extends Combine { .onClick(() => filterViewIsOpened.setData(true)) - const openLayerOrConfirm = new Toggle( + let openLayerOrConfirm = new Toggle( confirmButton, openLayerControl, preset.layerToAddTo.isDisplayed @@ -152,6 +160,29 @@ export default class ConfirmLocationOfPoint extends Combine { closePopup() }) + + // We assume the number of global filters won't change during the run of the program + for (let i = 0; i < state.globalFilters.data.length; i++) { + const hasBeenCheckedOf = new UIEventSource(false); + + const filterConfirmPanel = new VariableUiElement( + state.globalFilters.map(gfs => { + const gf = gfs[i] + const confirm = gf.onNewPoint?.confirmAddNew?.Subs({preset: preset.title}) + return new Combine([ + gf.onNewPoint?.safetyCheck, + new SubtleButton(Svg.confirm_svg(), confirm).onClick(() => hasBeenCheckedOf.setData(true)) + ]) + } + )) + + + openLayerOrConfirm = new Toggle( + openLayerOrConfirm, filterConfirmPanel, + state.globalFilters.map(f => hasBeenCheckedOf.data || f[i]?.onNewPoint === undefined, [hasBeenCheckedOf]) + ) + } + const hasActiveFilter = preset.layerToAddTo.appliedFilters .map(appliedFilters => { const activeFilters = Array.from(appliedFilters.values()).filter(f => f?.currentFilter !== undefined); @@ -171,16 +202,16 @@ export default class ConfirmLocationOfPoint extends Combine { Translations.t.general.cancel ).onClick(cancel) - - let examples : BaseUIElement = undefined; - if(preset.exampleImages !== undefined && preset.exampleImages.length > 0){ + + let examples: BaseUIElement = undefined; + if (preset.exampleImages !== undefined && preset.exampleImages.length > 0) { examples = new Combine([ - new Title( preset.exampleImages.length == 1 ? Translations.t.general.example : Translations.t.general.examples), + new Title(preset.exampleImages.length == 1 ? Translations.t.general.example : Translations.t.general.examples), new Combine(preset.exampleImages.map(img => new Img(img).SetClass("h-64 m-1 w-auto rounded-lg"))).SetClass("flex flex-wrap items-stretch") ]) - + } - + super([ new Toggle( Translations.t.general.testing.SetClass("alert"), diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts index e9988090a..eef10b59e 100644 --- a/UI/i18n/Translation.ts +++ b/UI/i18n/Translation.ts @@ -317,6 +317,19 @@ export class TypedTranslation extends Translation { return Utils.SubstituteKeys(template, text, lang); }, context) } - - + + + PartialSubs(text: Partial & Record): TypedTranslation> { + const newTranslations : Record = {} + for (const lang in this.translations) { + const template = this.translations[lang] + if(lang === "_context"){ + newTranslations[lang] = template + continue + } + newTranslations[lang] = Utils.SubstituteKeys(template, text, lang) + } + + return new TypedTranslation>(newTranslations, this.context) + } } \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index aa7323d5c..f359f52a9 100644 --- a/langs/en.json +++ b/langs/en.json @@ -140,6 +140,10 @@ "title": "Select layers", "zoomInToSeeThisLayer": "Zoom in to see this layer" }, + "levelSelection": { + "addNewOnLevel": "Is the new point location on level {level}?", + "confirmLevel": "Yes, add {preset} on level {level}" + }, "loading": "Loading…", "loadingTheme": "Loading {theme}…", "loginFailed": "Logging in into OpenStreetMap failed",