diff --git a/Docs/Security/MapComplete Pentest Report 2023 1.0.pdf b/Docs/Security/MapComplete Pentest Report 2023 1.0.pdf new file mode 100644 index 000000000..efc935bdd Binary files /dev/null and b/Docs/Security/MapComplete Pentest Report 2023 1.0.pdf differ diff --git a/Docs/Studio/Introduction.md b/Docs/Studio/Introduction.md index ca858b0cb..40277ef1d 100644 --- a/Docs/Studio/Introduction.md +++ b/Docs/Studio/Introduction.md @@ -34,7 +34,7 @@ OpenStreetMap data can be reused freely, including for commercial purposes. Howe 1. Give attribution 2. Keep the data open - changes to data based on OpenStreetMap must be published under the same license. -You can read the [full copyright notice here](https://osm.org/copyright) +See the [full copyright notice](https://osm.org/copyright) for details This also means that we are *not* allowed to copy data from other maps. Do not enter data based on Google Maps! @@ -83,7 +83,7 @@ These features are based on OpenStreetMap. If some data is not known, the user w -Data can also be loaded and visualised from external sources. No changes can be made to the data in that case. +Data can also be loaded and visualised from external sources. No changes can be made to this externally loaded data in that case. diff --git a/Docs/Studio/TagRenderingIntro.md b/Docs/Studio/TagRenderingIntro.md index 231689497..c122edec4 100644 --- a/Docs/Studio/TagRenderingIntro.md +++ b/Docs/Studio/TagRenderingIntro.md @@ -29,7 +29,7 @@ Note that this also works withing predifined options # Special values -Special components can be summoned by calling them. For example, the relevant wikipedia will be displayed by entering the text `{wikipedia()}`. A table with opening hours is displayed with `{opening_hours()}`. For a full reference, [see the documentation](../SpecialRenderings.md). +Special components can be summoned by calling them. For example, the relevant wikipedia will be displayed by entering the text `{wikipedia()}`. A table with opening hours is displayed with `{opening_hours()}`. For a full reference, [see the documentation](https://github.com/pietervdvn/MapComplete/blob/master/Docs/SpecialRenderings.md). # Requesting data with predefined options diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 35e2a7c36..cb5da7543 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -189,9 +189,6 @@ }, { "id": "rating", - "labels": [ - "defaults" - ], "icon": { "class": "w-20 mx-1 flex items-center" }, diff --git a/assets/layers/note/note.json b/assets/layers/note/note.json index 4ddfd409b..ff548401f 100644 --- a/assets/layers/note/note.json +++ b/assets/layers/note/note.json @@ -66,11 +66,11 @@ "marker": [ { "icon": { - "render": "note", + "render": "./assets/svg/note.svg", "mappings": [ { "if": "closed_at~*", - "then": "resolved" + "then": "./assets/svg/resolved.svg" } ] } @@ -80,11 +80,11 @@ "iconBadges": [ { "if": "_total_comments>1", - "then": "circle:white;speech_bubble" + "then": "circle:white;./assets/svg/speech_bubble.svg" }, { "if": "_is_import_note~*", - "then": "addSmall" + "then": "./assets/svg/addSmall.svg" } ], "anchor": "bottom" diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index 47248ebd2..f52867616 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -160,7 +160,7 @@ "craft=key_cutter" ] }, - "then": "./assets/layers/id_presets/fas-key.svg" + "then": "circle:white;./assets/layers/id_presets/fas-key.svg" } ], "label": { diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index 4034defc8..a98a6536d 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -372,7 +372,7 @@ "mappings": [ { "if": "mapcomplete-show_crosshair=yes", - "then": "Show a crosshair in the center of the map (when zoomed in above level 17)" + "then": "Show a crosshair in the center of the map when zoomed in above level 17" }, { "if": "mapcomplete-show_crosshair=no", @@ -382,6 +382,10 @@ "if": "mapcomplete-show_crosshair=", "then": "Do not show a crosshair in the center of the map", "hideInAnswer": true + }, + { + "if": "mapcomplete-show_crosshair=always", + "then": "Always show a crosshair in the center of the map" } ] }, diff --git a/assets/svg/no_checkmark.svg b/assets/svg/no_checkmark.svg deleted file mode 100644 index 7c6392c8d..000000000 --- a/assets/svg/no_checkmark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/assets/svg/none.svg b/assets/svg/none.svg deleted file mode 100644 index 5adcc79f0..000000000 --- a/assets/svg/none.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/assets/themes/campersite/campersite.json b/assets/themes/campersite/campersite.json index 8ee63fe1c..4636e5027 100644 --- a/assets/themes/campersite/campersite.json +++ b/assets/themes/campersite/campersite.json @@ -84,7 +84,7 @@ "eu": "Akanpatzeko tokiak", "pl": "Miejsca kamperowe" }, - "minzoom": 10, + "minzoom": 7, "source": { "osmTags": { "and": [ @@ -967,7 +967,7 @@ "es": "Estaciones sanitarias", "pl": "Stacje zrzutów sanitarnych" }, - "minzoom": 10, + "minzoom": 7, "source": { "osmTags": { "and": [ diff --git a/assets/themes/cyclofix/cyclofix.json b/assets/themes/cyclofix/cyclofix.json index 4d99cf241..5f94fda48 100644 --- a/assets/themes/cyclofix/cyclofix.json +++ b/assets/themes/cyclofix/cyclofix.json @@ -40,8 +40,20 @@ "Originally created during Open Summer of Code by Pieter Fiers, Thibault Declercq, Pierre Barban, Joost Schouppe and Pieter Vander Vennet" ], "layers": [ - "bike_cafe", - "bike_shop", + { + "builtin": [ + "bike_cafe", + "bike_shop", + "bike_repair_station", + "bicycle_tube_vending_machine", + "drinking_water", + "bike_themed_object", + "bike_cleaning" + ], + "override": { + "minzoom": 11 + } + }, { "builtin": [ "bicycle_rental" @@ -113,11 +125,6 @@ "minzoom": 13 } }, - "bike_repair_station", - "bicycle_tube_vending_machine", - "drinking_water", - "bike_themed_object", - "bike_cleaning", "bike_parking", { "builtin": [ diff --git a/assets/themes/grb/grb.json b/assets/themes/grb/grb.json index 45d54d53d..f240ee15a 100644 --- a/assets/themes/grb/grb.json +++ b/assets/themes/grb/grb.json @@ -785,7 +785,7 @@ } ], "overrideAll": { - "minzoom": 18 + "minzoom": 17 }, "widenFactor": 2, "overpassMaxZoom": 15, diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 985d6eba8..f4986e0aa 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -768,7 +768,7 @@ ], "marker": [ { - "icon": "statistics:black" + "icon": "./assets/svg/statistics.svg" } ], "iconSize": "30,30", @@ -778,4 +778,4 @@ } } ] -} \ No newline at end of file +} diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json index d82eb3043..e7a05ff6e 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json @@ -294,7 +294,7 @@ ], "marker": [ { - "icon": "statistics:black" + "icon": "./assets/svg/statistics.svg" } ], "iconSize": "30,30", diff --git a/assets/themes/onwheels/onwheels.json b/assets/themes/onwheels/onwheels.json index a32ec3c3d..ea77a8823 100644 --- a/assets/themes/onwheels/onwheels.json +++ b/assets/themes/onwheels/onwheels.json @@ -467,7 +467,7 @@ ], "marker": [ { - "icon": "statistics" + "icon": "./assets/svg/statistics.svg" } ] } diff --git a/assets/themes/sidewalks/sidewalks.json b/assets/themes/sidewalks/sidewalks.json index 03b439468..459225a4a 100644 --- a/assets/themes/sidewalks/sidewalks.json +++ b/assets/themes/sidewalks/sidewalks.json @@ -166,31 +166,31 @@ { "if": "sidewalk:left|right=yes", "then": { - "en": "Yes, there is a sidewalk on this side of the road", - "de": "Ja, es gibt einen Bürgersteig auf dieser Straßenseite", - "da": "Ja, der er et fortov på denne side af vejen", - "nl": "Ja, er is een stoep aan deze kant van de weg", - "fr": "Oui, il y a un trottoir de ce côté de la route", - "ca": "Sí, hi ha una vorera a aquest costat del carrer", - "es": "Sí, hay una acera en este lado de la calle", - "cs": "Ano, na této straně silnice je chodník", - "it": "Sì, c'è un marciapiede su questo lato della strada", - "pl": "Tak, jest chodnik z boku drogi" + "en": "There is a sidewalk on this side of the road", + "de": "Es gibt einen Bürgersteig auf dieser Straßenseite", + "da": "Der er et fortov på denne side af vejen", + "nl": "Er is een stoep aan deze kant van de weg", + "fr": "Il y a un trottoir de ce côté de la route", + "ca": "Hi ha una vorera a aquest costat del carrer", + "es": "Hay una acera en este lado de la calle", + "cs": "Na této straně silnice je chodník", + "it": "C'è un marciapiede su questo lato della strada", + "pl": "Jest chodnik z boku drogi" } }, { "if": "sidewalk:left|right=no", "then": { - "en": "No, there is no sidewalk to walk on", - "de": "Nein, es gibt keinen Bürgersteig für Fußgänger", - "da": "Nej, der er ikke noget fortov at gå på", - "nl": "Nee, er is geen stoep om op te lopen", - "fr": "Non, il n'y a pas de trottoir où marcher", - "ca": "No, no hi ha vorera per la que caminar", - "es": "No, no hay acera por la que caminar", - "cs": "Ne, není tu žádný chodník", - "it": "No, non c'è un marciapiede su cui camminare", - "pl": "Nie, nie ma chodnika, którym można chodzić" + "en": "There is no sidewalk to walk on", + "de": "Es gibt keinen Bürgersteig für Fußgänger", + "da": "Der er ikke noget fortov at gå på", + "nl": "Er is geen stoep om op te lopen", + "fr": "Il n'y a pas de trottoir où marcher", + "ca": "No hi ha vorera per la que caminar", + "es": "No hay acera por la que caminar", + "cs": "Není tu žádný chodník", + "it": "Non c'è un marciapiede su cui camminare", + "pl": "Nie ma chodnika, którym można chodzić" } }, { diff --git a/package.json b/package.json index 17496db23..dbd6b213f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.34.9", + "version": "0.35.1", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 6839e5b62..3d49a619b 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -725,14 +725,6 @@ video { left: 0px; } -.top-2 { - top: 0.5rem; -} - -.right-3 { - right: 0.75rem; -} - .bottom-0 { bottom: 0px; } @@ -777,18 +769,22 @@ video { margin: 2rem; } -.m-5 { - margin: 1.25rem; +.m-4 { + margin: 1rem; +} + +.m-3 { + margin: 0.75rem; +} + +.m-0 { + margin: 0px; } .m-2 { margin: 0.5rem; } -.m-4 { - margin: 1rem; -} - .m-1 { margin: 0.25rem; } @@ -797,14 +793,6 @@ video { margin: 0.125rem; } -.m-0 { - margin: 0px; -} - -.m-3 { - margin: 0.75rem; -} - .m-6 { margin: 1.5rem; } @@ -823,16 +811,16 @@ video { margin-bottom: 0.25rem; } -.mx-1 { - margin-left: 0.25rem; - margin-right: 0.25rem; -} - .my-4 { margin-top: 1rem; margin-bottom: 1rem; } +.mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; +} + .my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; @@ -853,14 +841,22 @@ video { margin-right: 3rem; } -.mr-2 { - margin-right: 0.5rem; +.mt-4 { + margin-top: 1rem; } .mr-4 { margin-right: 1rem; } +.mr-2 { + margin-right: 0.5rem; +} + +.mb-16 { + margin-bottom: 4rem; +} + .mr-6 { margin-right: 1.5rem; } @@ -897,10 +893,6 @@ video { margin-top: 0.5rem; } -.mt-4 { - margin-top: 1rem; -} - .mb-2 { margin-bottom: 0.5rem; } @@ -917,10 +909,6 @@ video { margin-left: 1rem; } -.mt-3 { - margin-top: 0.75rem; -} - .mb-10 { margin-bottom: 2.5rem; } @@ -1058,24 +1046,24 @@ video { height: 2rem; } -.h-16 { - height: 4rem; +.h-12 { + height: 3rem; } .h-6 { height: 1.5rem; } -.h-12 { - height: 3rem; -} - .h-fit { height: -webkit-fit-content; height: -moz-fit-content; height: fit-content; } +.h-16 { + height: 4rem; +} + .h-4 { height: 1rem; } @@ -1152,16 +1140,16 @@ video { width: 2rem; } -.w-16 { - width: 4rem; +.w-12 { + width: 3rem; } .w-6 { width: 1.5rem; } -.w-12 { - width: 3rem; +.w-16 { + width: 4rem; } .w-screen { @@ -1809,6 +1797,11 @@ video { line-height: 1.75rem; } +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + .text-lg { font-size: 1.125rem; line-height: 1.75rem; @@ -1829,20 +1822,11 @@ video { line-height: 1.25rem; } -.text-base { - font-size: 1rem; - line-height: 1.5rem; -} - .text-4xl { font-size: 2.25rem; line-height: 2.5rem; } -.font-bold { - font-weight: 700; -} - .font-extrabold { font-weight: 800; } @@ -1851,6 +1835,10 @@ video { font-weight: 600; } +.font-bold { + font-weight: 700; +} + .font-normal { font-weight: 400; } @@ -1927,21 +1915,6 @@ video { color: rgb(255 255 255 / var(--tw-text-opacity)); } -.text-gray-900 { - --tw-text-opacity: 1; - color: rgb(17 24 39 / var(--tw-text-opacity)); -} - -.text-gray-800 { - --tw-text-opacity: 1; - color: rgb(31 41 55 / var(--tw-text-opacity)); -} - -.text-gray-500 { - --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity)); -} - .underline { text-decoration-line: underline; } @@ -2753,11 +2726,6 @@ a.link-underline { margin-right: 0.25rem; } - .sm\:mx-auto { - margin-left: auto; - margin-right: auto; - } - .sm\:mt-2 { margin-top: 0.5rem; } @@ -2766,10 +2734,6 @@ a.link-underline { margin-right: 0.25rem; } - .sm\:mt-5 { - margin-top: 1.25rem; - } - .sm\:mr-4 { margin-right: 1rem; } @@ -2790,10 +2754,6 @@ a.link-underline { width: 6rem; } - .sm\:max-w-xl { - max-width: 36rem; - } - .sm\:flex-nowrap { flex-wrap: nowrap; } @@ -2822,15 +2782,6 @@ a.link-underline { padding-top: 0.25rem; } - .sm\:text-center { - text-align: center; - } - - .sm\:text-5xl { - font-size: 3rem; - line-height: 1; - } - .sm\:text-lg { font-size: 1.125rem; line-height: 1.75rem; @@ -2842,25 +2793,17 @@ a.link-underline { margin: 0.25rem; } - .md\:m-2 { - margin: 0.5rem; - } - .md\:mx-2 { margin-left: 0.5rem; margin-right: 0.5rem; } - .md\:mr-2 { - margin-right: 0.5rem; - } - .md\:mt-5 { margin-top: 1.25rem; } - .md\:mt-4 { - margin-top: 1rem; + .md\:mr-2 { + margin-right: 0.5rem; } .md\:grid { @@ -2932,14 +2875,6 @@ a.link-underline { margin-right: 0px; } - .lg\:ml-40 { - margin-left: 10rem; - } - - .lg\:w-3\/4 { - width: 75%; - } - .lg\:w-5\/12 { width: 41.666667%; } @@ -2951,17 +2886,9 @@ a.link-underline { .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } - - .lg\:text-left { - text-align: left; - } } @media (min-width: 1280px) { - .xl\:inline { - display: inline; - } - .xl\:w-4\/12 { width: 33.333333%; } diff --git a/scripts/generateIncludedImages.ts b/scripts/generateIncludedImages.ts index a8d19237d..dd7160a40 100644 --- a/scripts/generateIncludedImages.ts +++ b/scripts/generateIncludedImages.ts @@ -1,11 +1,71 @@ import * as fs from "fs" + function genImages(dryrun = false) { console.log("Generating images") + // These images are not referenced via 'Svg.ts' anymore and can be ignored + const blacklist: string[] = [ + "add", + "addSmall", + "brick_wall_square", + "clock", + "community", + "copyright", + "cross", + "cross_bottom_right", + "crosshair_locked", + "delete_not_allowed", + "direction_gradient", + "direction_stroke", + "duplicate", + "elevator", + "elevator_wheelchair", + "liberapay", + "length_crosshair", + "speech_bubble_black_outline", + "square", + "star_half", + "star_outline", + "star", + "osm_logo_us", + + "SocialImageForeground", + "wikipedia", + "Upload", + "pin", + "mapillary_black", + "plantnet_logo", + "mastodon", + "move-arrows", + "mapcomplete_logo", + "logo", + "logout", + "hand", + "help", + "home", + "reload", + "min", + "plus", + "not_found", + "osm_logo_us", + "party", + "filter", + "filter_disable", + "floppy", + "eye", + "gear", + "gender_bi", + "compass", + "blocked", + "brick_wall", + "brick_wall_raw", + "brick_wall_round", + "bug", + "back", + ].map((s) => s.toLowerCase()) const dir = fs.readdirSync("./assets/svg") let module = 'import Img from "./UI/Base/Img";\nimport {FixedUiElement} from "./UI/Base/FixedUiElement";\n\n/* @deprecated */\nexport default class Svg {\n\n\n' - const allNames: string[] = [] for (const path of dir) { if (path.endsWith("license_info.json")) { continue @@ -21,6 +81,7 @@ function genImages(dryrun = false) { let svg: string = fs .readFileSync("./assets/svg/" + path, "utf-8") .replace(/<\?xml.*?>/, "") + .replace(/]*>/, "") .replace(/fill: ?none;/g, "fill: none !important;") // This is such a brittle hack... .replace(/\n/g, " ") .replace(/\r/g, "") @@ -37,18 +98,6 @@ function genImages(dryrun = false) { } const name = path.substring(0, path.length - 4).replace(/[ -]/g, "_") - if (dryrun) { - svg = "" - } - - let rawName = name - - module += ` public static ${name} = "${svg}"\n` - module += ` public static ${name}_svg() { return new Img(Svg.${rawName}, true);}\n` - if (!dryrun) { - allNames.push(`"${path}": Svg.${name}`) - } - const nameUC = name.toUpperCase().at(0) + name.substring(1) const svelteCode = '\n' + @@ -60,8 +109,23 @@ function genImages(dryrun = false) { .replace(/\\"/g, '"') .replace(/(rgb\(0%,0%,0%\)|#000000|#000)/g, "{color}") fs.writeFileSync("./src/assets/svg/" + nameUC + ".svelte", svelteCode, "utf8") + + if (blacklist.some((item) => path.toLowerCase().endsWith(item + ".svg"))) { + continue + } + if (dryrun) { + svg = "" + } + + let rawName = name + + module += ` public static ${name} = "${svg}"\n` + if (!dryrun) { + module += ` public static ${name}_svg() { return new Img(Svg.${rawName}, true);}\n` + } else { + module += ` public static ${name}_svg() { return new Img("", true);}\n` + } } - module += `public static All = {${allNames.join(",")}};` module += "}\n" fs.writeFileSync("src/Svg.ts", module) console.log("Done") diff --git a/scripts/generateLayouts.ts b/scripts/generateLayouts.ts index 53465995b..61d178e31 100644 --- a/scripts/generateLayouts.ts +++ b/scripts/generateLayouts.ts @@ -15,6 +15,7 @@ import { ImmutableStore } from "../src/Logic/UIEventSource" import * as crypto from "crypto" import * as eli from "../src/assets/editor-layer-index.json" import * as eli_global from "../src/assets/global-raster-layers.json" +import ValidationUtils from "../src/Models/ThemeConfig/Conversion/ValidationUtils" const sharp = require("sharp") const template = readFileSync("theme.html", "utf8") @@ -264,6 +265,7 @@ async function eliUrls(): Promise { async function generateCsp( layout: LayoutConfig, + layoutJson: LayoutConfigJson, options: { scriptSrcs: string[] } @@ -273,11 +275,28 @@ async function generateCsp( ...Constants.defaultOverpassUrls, Constants.countryCoderEndpoint, Constants.nominatimEndpoint, + "https://www.openstreetmap.org", "https://api.openstreetmap.org", "https://pietervdvn.goatcounter.com", - ] - .concat(...SpecialVisualizations.specialVisualizations.map((sv) => sv.needsUrls)) - .concat(...(await eliUrls())) + ].concat(...(await eliUrls())) + + SpecialVisualizations.specialVisualizations.forEach(sv => { + if(typeof sv.needsUrls === "function"){ + return + } + apiUrls.push(...sv.needsUrls) + }) + + const usedSpecialVisualisations = ValidationUtils.getSpecialVisualisationsWithArgs(layoutJson) + for (const usedSpecialVisualisation of usedSpecialVisualisations) { + if (typeof usedSpecialVisualisation === "string") { + continue + } + const neededUrls = usedSpecialVisualisation.func.needsUrls + if (typeof neededUrls === "function") { + apiUrls.push(...neededUrls(usedSpecialVisualisation.args)) + } + } const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource) const hosts = new Set() @@ -299,6 +318,10 @@ async function generateCsp( } } + if (hosts.has("*")) { + throw "* is not allowed as connect-src" + } + const connectSrc = Array.from(hosts).sort() const newSrcs = connectSrc.filter((newItem) => !previousSrc.has(newItem)) @@ -344,7 +367,13 @@ const removeOtherLanguagesHash = crypto .update(removeOtherLanguages) .digest("base64") -async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) { +async function createLandingPage( + layout: LayoutConfig, + layoutJson: LayoutConfigJson, + manifest, + whiteIcons, + alreadyWritten +) { Locale.language.setData(layout.language[0]) const targetLanguage = layout.language[0] const ogTitle = Translations.T(layout.title).textFor(targetLanguage).replace(/"/g, '\\"') @@ -428,7 +457,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr .replace(/.*/s, themeSpecific) .replace( //, - await generateCsp(layout, { + await generateCsp(layout, layoutJson, { scriptSrcs: [`'sha256-${removeOtherLanguagesHash}'`], }) ) @@ -518,7 +547,13 @@ async function main(): Promise { writeFile("public/" + manifestLocation, manif, err) // Create a landing page for the given theme - const landing = await createLandingPage(layout, manifest, whiteIcons, alreadyWritten) + const landing = await createLandingPage( + layout, + layoutConfigJson, + manifest, + whiteIcons, + alreadyWritten + ) writeFile(enc(layout.id) + ".html", landing, err) await createIndexFor(layout) diff --git a/src/Logic/Actors/GeoLocationHandler.ts b/src/Logic/Actors/GeoLocationHandler.ts index 9333a8413..68efc803d 100644 --- a/src/Logic/Actors/GeoLocationHandler.ts +++ b/src/Logic/Actors/GeoLocationHandler.ts @@ -137,7 +137,6 @@ export default class GeoLocationHandler { } } - console.trace("Moving the map to the GPS-location") mapLocation.setData({ lon: newLocation.longitude, lat: newLocation.latitude, @@ -152,7 +151,6 @@ export default class GeoLocationHandler { private CopyGeolocationIntoMapstate() { const features: UIEventSource = new UIEventSource([]) this.currentUserLocation = new StaticFeatureSource(features) - const keysToCopy = ["speed", "accuracy", "altitude", "altitudeAccuracy", "heading"] let i = 0 this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => { if (location === undefined) { @@ -163,19 +161,15 @@ export default class GeoLocationHandler { id: "gps-" + i, "user:location": "yes", date: new Date().toISOString(), + // GeolocationObject behaves really weird when indexing, so copying it one by one is the most stable + accuracy: location.accuracy, + speed: location.speed, + altitude: location.altitude, + altitudeAccuracy: location.altitudeAccuracy, + heading: location.heading, } i++ - for (const k in keysToCopy) { - // For some weird reason, the 'Object.keys' method doesn't work for the 'location: GeolocationCoordinates'-object and will thus not copy all the properties when using {...location} - // As such, they are copied here - if (location[k]) { - properties[k] = location[k] - } - } - console.debug("Current location object:", location) - properties["_all"] = JSON.stringify(location) - const feature = { type: "Feature", properties, @@ -184,7 +178,6 @@ export default class GeoLocationHandler { coordinates: [location.longitude, location.latitude], }, } - features.setData([feature]) }) } diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index f33f2df04..b6ed467b2 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -21,6 +21,7 @@ export default class UserDetails { public account_created: string public tracesCount: number = 0 public description: string + public languages: string[] constructor(backend: string) { this.backend = backend @@ -89,6 +90,7 @@ export class OsmConnection { ud.unreadMessages = 0 ud.name = "Fake user" ud.totalMessages = 42 + ud.languages = ["en"] } const self = this this.UpdateCapabilities() @@ -193,7 +195,7 @@ export class OsmConnection { method: "GET", path: "/api/0.6/user/details", }, - function (err, details) { + function (err, details: XMLDocument) { if (err != null) { console.log(err) self.loadingStatus.setData("error") @@ -222,11 +224,14 @@ export class OsmConnection { data.name = userInfo.getAttribute("display_name") data.account_created = userInfo.getAttribute("account_created") data.uid = Number(userInfo.getAttribute("id")) + data.languages = Array.from( + userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang") + ).map((l) => l.textContent) data.csCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 + userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0" ) data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0" ) data.img = undefined diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index dbcd371b6..f38f32bad 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -42,6 +42,10 @@ export default class UserRelatedState { public readonly showCrosshair: UIEventSource<"yes" | undefined> public readonly fixateNorth: UIEventSource public readonly homeLocation: FeatureSource + /** + * The language as saved into the preferences of the user, if logged in. + * Note that this is _different_ from the languages a user can set via the osm.org interface here: https://www.openstreetmap.org/preferences + */ public readonly language: UIEventSource public readonly preferredBackgroundLayer: UIEventSource< string | "photo" | "map" | "osmbasedmap" | undefined @@ -134,7 +138,7 @@ export default class UserRelatedState { return } - this.language.addCallbackAndRunD((language) => Locale.language.setData(language)) + this.language.syncWith(Locale.language) } private static initUserRelatedState(): LayerConfig { diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index 25f0de6ba..f15232e3d 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -123,6 +123,14 @@ export default class Constants { "teardrop", "triangle", "crosshair", + "brick_wall_square", + "brick_wall_round", + "gps_arrow", + "checkmark", + "help", + "clock", + "invalid", + "close", ] as const public static readonly defaultPinIcons: string[] = Constants._defaultPinIcons diff --git a/src/Models/RasterLayers.ts b/src/Models/RasterLayers.ts index 95ded771c..3b1ce0115 100644 --- a/src/Models/RasterLayers.ts +++ b/src/Models/RasterLayers.ts @@ -9,14 +9,16 @@ import { RasterLayerProperties } from "./RasterLayerProperties" export class AvailableRasterLayers { public static EditorLayerIndex: (Feature & RasterLayerPolygon)[] = editorlayerindex.features - public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map( - (properties) => - { - type: "Feature", - properties, - geometry: BBox.global.asGeometry(), - } - ) + public static globalLayers: RasterLayerPolygon[] = globallayers.layers + .filter((properties) => properties.id !== "osm.carto" /*Added separately*/) + .map( + (properties) => + { + type: "Feature", + properties, + geometry: BBox.global.asGeometry(), + } + ) public static readonly osmCartoProperties: RasterLayerProperties = { id: "osm", name: "OpenStreetMap", @@ -74,6 +76,7 @@ export class AvailableRasterLayers { } return GeoOperations.inside(lonlat, eliPolygon) }) + matching.unshift(AvailableRasterLayers.osmCarto) matching.push(AvailableRasterLayers.maptilerDefaultLayer) matching.push(...AvailableRasterLayers.globalLayers) return matching diff --git a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts index a72306dd9..fd041335c 100644 --- a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -26,7 +26,7 @@ import predifined_filters from "../../../../assets/layers/filters/filters.json" import { TagConfigJson } from "../Json/TagConfigJson" import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson" import ValidationUtils from "./ValidationUtils" -import { RenderingSpecification } from "../../../UI/SpecialVisualization" +import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import { ConfigMeta } from "../../../UI/Studio/configMeta" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" @@ -1193,6 +1193,34 @@ class ExpandMarkerRenderings extends DesugaringStep { } } +export class AddRatingBadge extends DesugaringStep { + constructor() { + super( + "Adds the 'rating'-element if a reviews-element is used in the tagRenderings", + ["titleIcons"], + "AddRatingBadge" + ) + } + + convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { + if (!json.tagRenderings) { + return json + } + + const specialVis: Exclude[] = < + Exclude[] + >ValidationUtils.getAllSpecialVisualisations(json.tagRenderings).filter( + (rs) => typeof rs !== "string" + ) + const funcs = new Set(specialVis.map((rs) => rs.func.funcName)) + + if (funcs.has("list_reviews")) { + ;(<(string | TagRenderingConfigJson)[]>json.titleIcons).push("icons.rating") + } + return json + } +} + export class PrepareLayer extends Fuse { constructor(state: DesugaringContext) { super( @@ -1218,6 +1246,7 @@ export class PrepareLayer extends Fuse { (layer) => new Each(new PreparePointRendering(state, layer)) ), new SetDefault("titleIcons", ["icons.defaults"]), + new AddRatingBadge(), new On( "titleIcons", (layer) => diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index a8ac52374..59130c1e9 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -92,7 +92,7 @@ export class DoesImageExist extends DesugaringStep { return image } if (image.match(/[a-z]*/)) { - if (Svg.All[image + ".svg"] !== undefined) { + if (Constants.defaultPinIcons.indexOf(image) >= 0) { // This is a builtin img, e.g. 'checkmark' or 'crosshair' return image } @@ -110,7 +110,7 @@ export class DoesImageExist extends DesugaringStep { ) } else if (!this.doesPathExist(image)) { context.err( - `Image with path ${image} does not exist; it is used in ${context}.\n Check for typo's and missing directories in the path.` + `Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.` ) } else { context.err( @@ -813,6 +813,12 @@ class MiscTagRenderingChecks extends DesugaringStep { ) } + if (Object.keys(json).length === 1 && typeof json["render"] === "string") { + context.warn( + `use the content directly instead of {render: ${JSON.stringify(json["render"])}}` + ) + } + { for (const key of ["question", "questionHint", "render"]) { CheckTranslation.allowUndefined.convert(json[key], context.enter(key)) diff --git a/src/Models/ThemeConfig/Conversion/ValidationUtils.ts b/src/Models/ThemeConfig/Conversion/ValidationUtils.ts index 93c507aea..1f8bf800f 100644 --- a/src/Models/ThemeConfig/Conversion/ValidationUtils.ts +++ b/src/Models/ThemeConfig/Conversion/ValidationUtils.ts @@ -9,6 +9,12 @@ export default class ValidationUtils { renderingConfigs: (TagRenderingConfigJson | QuestionableTagRenderingConfigJson)[] ): RenderingSpecification[] { const visualisations: RenderingSpecification[] = [] + if (!Array.isArray(renderingConfigs)) { + throw ( + "Could not inspect renderingConfigs, not an array: " + + JSON.stringify(renderingConfigs) + ) + } for (const renderConfig of renderingConfigs) { visualisations.push(...ValidationUtils.getSpecialVisualisationsWithArgs(renderConfig)) } diff --git a/src/Models/ThemeConfig/PointRenderingConfig.ts b/src/Models/ThemeConfig/PointRenderingConfig.ts index 800be4d4f..70b852b5d 100644 --- a/src/Models/ThemeConfig/PointRenderingConfig.ts +++ b/src/Models/ThemeConfig/PointRenderingConfig.ts @@ -3,12 +3,10 @@ import TagRenderingConfig from "./TagRenderingConfig" import { TagsFilter } from "../../Logic/Tags/TagsFilter" import { TagUtils } from "../../Logic/Tags/TagUtils" import { Utils } from "../../Utils" -import Svg from "../../Svg" import WithContextLoader from "./WithContextLoader" import { ImmutableStore, Store } from "../../Logic/UIEventSource" import BaseUIElement from "../../UI/BaseUIElement" import { FixedUiElement } from "../../UI/Base/FixedUiElement" -import Img from "../../UI/Base/Img" import Combine from "../../UI/Base/Combine" import { VariableUiElement } from "../../UI/Base/VariableUIElement" import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" @@ -133,71 +131,23 @@ export default class PointRenderingConfig extends WithContextLoader { context + ".rotationAlignment" ) } - - /** - * Given a single HTML spec (either a single image path OR "image_path_to_known_svg:fill-colour", returns a fixedUIElement containing that - * The element will fill 100% and be positioned absolutely with top:0 and left: 0 - */ - private static FromHtmlSpec(htmlSpec: string, style: string, isBadge = false): BaseUIElement { - if (htmlSpec === undefined) { - return undefined + private static FromHtmlMulti(multiSpec: string, tags: Store>) { + const icons: IconConfig[] = [] + for (const subspec of multiSpec.split(";")) { + const [icon, color] = subspec.split(":") + icons.push(new IconConfig({ icon, color })) } - const match = htmlSpec.match(/([a-zA-Z0-9_]*):([^;]*)/) - if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { - const svg = Svg.All[match[1] + ".svg"] as string - const targetColor = match[2] - const img = new Img( - svg.replace(/(rgb\(0%,0%,0%\)|#000000|#000)/g, targetColor), - true - ).SetStyle(style) - if (isBadge) { - img.SetClass("badge") - } - return img - } else if (Svg.All[htmlSpec + ".svg"] !== undefined) { - const svg = Svg.All[htmlSpec + ".svg"] as string - const img = new Img(svg, true).SetStyle(style) - if (isBadge) { - img.SetClass("badge") - } - return img - } else { - return new FixedUiElement(``) - } - } - - private static FromHtmlMulti( - multiSpec: string, - rotation: string, - isBadge: boolean, - defaultElement: BaseUIElement = undefined, - options?: { - noFullWidth?: boolean - } - ) { - if (multiSpec === undefined) { - return defaultElement - } - const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0` - - const htmlDefs = multiSpec.trim()?.split(";") ?? [] - const elements = Utils.NoEmpty(htmlDefs).map((def) => - PointRenderingConfig.FromHtmlSpec(def, style, isBadge) + return new SvelteUIElement(DynamicMarker, { marker: icons, tags }).SetClass( + "w-full h-full block absolute top-0 left-0" ) - if (elements.length === 0) { - return defaultElement - } else { - const combine = new Combine(elements).SetClass("relative block") - if (options?.noFullWidth) { - return combine - } - combine.SetClass("w-full h-full") - return combine - } } public GetBaseIcon(tags?: Record): BaseUIElement { - return new SvelteUIElement(DynamicMarker, { config: this, tags: new ImmutableStore(tags) }) + return new SvelteUIElement(DynamicMarker, { + marker: this.marker, + rotation: this.rotation, + tags: new ImmutableStore(tags), + }) } public RenderIcon( @@ -249,9 +199,11 @@ export default class PointRenderingConfig extends WithContextLoader { anchorH = -iconH / 2 } - const icon = new SvelteUIElement(DynamicMarker, { config: this, tags }).SetClass( - "w-full h-full" - ) + const icon = new SvelteUIElement(DynamicMarker, { + marker: this.marker, + rotation: this.rotation, + tags, + }).SetClass("w-full h-full") let badges = undefined if (options?.includeBadges ?? true) { badges = this.GetBadges(tags, options?.metatags) @@ -306,9 +258,9 @@ export default class PointRenderingConfig extends WithContextLoader { } return new VariableUiElement( tags.map( - (tags) => { + (tagsData) => { const badgeElements = this.iconBadges.map((badge) => { - if (!badge.if.matchesProperties(tags)) { + if (!badge.if.matchesProperties(tagsData)) { // Doesn't match... return undefined } @@ -323,19 +275,23 @@ export default class PointRenderingConfig extends WithContextLoader { } const htmlDefs = Utils.SubstituteKeys( - badge.then.GetRenderValue(tags)?.txt, - tags + badge.then.GetRenderValue(tagsData)?.txt, + tagsData ) if (htmlDefs.startsWith("<") && htmlDefs.endsWith(">")) { // This is probably an HTML-element - return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags)) + return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tagsData)) .SetStyle("width: 1.5rem") .SetClass("block") } + + if (!htmlDefs) { + return undefined + } + const badgeElement = PointRenderingConfig.FromHtmlMulti( htmlDefs, - "0", - true + tags )?.SetClass("block relative") if (badgeElement === undefined) { return undefined diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index d6d3d15a4..424141bd4 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -19,6 +19,7 @@ import { Paragraph } from "../../UI/Base/Paragraph" import Svg from "../../Svg" import Validators, { ValidatorType } from "../../UI/InputElement/Validators" import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" +import Constants from "../Constants" export interface Icon {} @@ -362,7 +363,7 @@ export default class TagRenderingConfig { if (stripped.endsWith(".svg")) { stripped = stripped.substring(0, stripped.length - 4) } - if (Svg.All[stripped + ".svg"] !== undefined) { + if (Constants.defaultPinIcons.indexOf(stripped) >= 0) { icon = "./assets/svg/" + mapping.icon if (!icon.endsWith(".svg")) { icon += ".svg" diff --git a/src/UI/AllThemesGui.svelte b/src/UI/AllThemesGui.svelte new file mode 100644 index 000000000..beaa6442e --- /dev/null +++ b/src/UI/AllThemesGui.svelte @@ -0,0 +1,74 @@ + + +
+
+ +
+ +
+ +
+ +
+ +
+

+ +

+ + +
+ +
+ + + + +
+ +
+ + + + +
+ + +
+ v{Constants.vNumber} +
+
+ diff --git a/src/UI/AllThemesGui.ts b/src/UI/AllThemesGui.ts deleted file mode 100644 index b21d84218..000000000 --- a/src/UI/AllThemesGui.ts +++ /dev/null @@ -1,69 +0,0 @@ -import UserRelatedState from "../Logic/State/UserRelatedState" -import { FixedUiElement } from "./Base/FixedUiElement" -import Combine from "./Base/Combine" -import MoreScreen from "./BigComponents/MoreScreen" -import Translations from "./i18n/Translations" -import Constants from "../Models/Constants" -import LanguagePicker from "./LanguagePicker" -import IndexText from "./BigComponents/IndexText" -import { LoginToggle } from "./Popup/LoginButton" -import { ImmutableStore } from "../Logic/UIEventSource" -import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { QueryParameters } from "../Logic/Web/QueryParameters" -import { OsmConnectionFeatureSwitches } from "../Logic/State/FeatureSwitchState" -import { SubtleButton } from "./Base/SubtleButton" -import Svg from "../Svg" -import Link from "./Base/Link" - -export default class AllThemesGui { - setup() { - try { - const featureSwitches = new OsmConnectionFeatureSwitches() - const osmConnection = new OsmConnection({ - fakeUser: featureSwitches.featureSwitchFakeUser.data, - oauth_token: QueryParameters.GetQueryParameter( - "oauth_token", - undefined, - "Used to complete the login" - ), - }) - const state = new UserRelatedState(osmConnection) - const intro = new Combine([ - new LanguagePicker( - Translations.t.index.title.SupportedLanguages(), - state.language - ).SetClass("flex absolute top-2 right-3"), - new IndexText(), - ]) - new Combine([ - intro, - new MoreScreen(state, true), - new LoginToggle( - new Link( - new Combine([ - Svg.pencil_svg().SetClass("w-6 h-6 mr-2"), - Translations.t.general.morescreen.createYourOwnTheme, - ]).SetClass("flex p-2"), - window.location.protocol + "//" + window.location.host + "/studio.html" - ).SetClass("w-full h-fit button"), - Translations.t.index.logIn, - { - osmConnection, - featureSwitchUserbadge: new ImmutableStore(true), - } - ).SetClass("flex justify-center w-full"), - Translations.t.general.aboutMapComplete.intro.SetClass("link-underline"), - new FixedUiElement("v" + Constants.vNumber).SetClass("block"), - ]) - .SetClass("block m-5 lg:w-3/4 lg:ml-40") - .AttachTo("main") - } catch (e) { - console.error(">>>> CRITICAL", e) - new FixedUiElement( - "Seems like no layers are compiled - check the output of `npm run generate:layeroverview`. Is this visible online? Contact pietervdvn immediately!" - ) - .SetClass("alert") - .AttachTo("main") - } - } -} diff --git a/src/UI/Base/DragInvitation.svelte b/src/UI/Base/DragInvitation.svelte index c296f0ada..b2eb6c20d 100644 --- a/src/UI/Base/DragInvitation.svelte +++ b/src/UI/Base/DragInvitation.svelte @@ -5,6 +5,7 @@ */ import { Store } from "../../Logic/UIEventSource" import { onDestroy } from "svelte" + import Hand from "../../assets/svg/Hand.svelte"; let mainElem: HTMLElement export let hideSignal: Store @@ -34,7 +35,7 @@
- +
diff --git a/src/UI/Base/Dropdown.svelte b/src/UI/Base/Dropdown.svelte index 0d4767bdb..71a94a08f 100644 --- a/src/UI/Base/Dropdown.svelte +++ b/src/UI/Base/Dropdown.svelte @@ -1,14 +1,31 @@ - {value.setData(e.srcElement.value)}}> diff --git a/src/UI/Base/LoginToggle.svelte b/src/UI/Base/LoginToggle.svelte index f0dd14003..a3ac0787d 100644 --- a/src/UI/Base/LoginToggle.svelte +++ b/src/UI/Base/LoginToggle.svelte @@ -6,6 +6,7 @@ import Tr from "./Tr.svelte" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" + import Invalid from "../../assets/svg/Invalid.svelte"; export let state: { osmConnection: OsmConnection @@ -35,7 +36,7 @@ {:else if $loadingStatus === "error"}
- +
{:else if $loadingStatus === "logged-in"} diff --git a/src/UI/Base/LogoutButton.svelte b/src/UI/Base/LogoutButton.svelte new file mode 100644 index 000000000..020c411e1 --- /dev/null +++ b/src/UI/Base/LogoutButton.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/UI/Base/OpenJosm.svelte b/src/UI/Base/OpenJosm.svelte new file mode 100644 index 000000000..e6920368f --- /dev/null +++ b/src/UI/Base/OpenJosm.svelte @@ -0,0 +1,48 @@ + +{#if $showButton} + {#if $josmState === undefined} + + {:else if state === "OK"} + + {:else} + + {/if} + + +{/if} diff --git a/src/UI/Base/ShareButton.svelte b/src/UI/Base/ShareButton.svelte index 731cdeb83..48be0f1c9 100644 --- a/src/UI/Base/ShareButton.svelte +++ b/src/UI/Base/ShareButton.svelte @@ -1,6 +1,7 @@ - Svg.mapillary_black_svg().SetClass("w-12 h-12 m-2 mr-4 shrink-0")} /> +
diff --git a/src/UI/BigComponents/NewPointLocationInput.svelte b/src/UI/BigComponents/NewPointLocationInput.svelte index 5ca19ba11..f69ca6b93 100644 --- a/src/UI/BigComponents/NewPointLocationInput.svelte +++ b/src/UI/BigComponents/NewPointLocationInput.svelte @@ -16,6 +16,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { Utils } from "../../Utils" import { createEventDispatcher } from "svelte" + import Move_arrows from "../../assets/svg/Move_arrows.svelte"; /** * An advanced location input, which has support to: @@ -125,6 +126,6 @@ maxDistanceInMeters="50" > - + diff --git a/src/UI/BigComponents/OpenJosm.ts b/src/UI/BigComponents/OpenJosm.ts deleted file mode 100644 index 3eafbd2ac..000000000 --- a/src/UI/BigComponents/OpenJosm.ts +++ /dev/null @@ -1,60 +0,0 @@ -import Combine from "../Base/Combine" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import { BBox } from "../../Logic/BBox" -import Translations from "../i18n/Translations" -import { VariableUiElement } from "../Base/VariableUIElement" -import Toggle from "../Input/Toggle" -import { SubtleButton } from "../Base/SubtleButton" -import Svg from "../../Svg" -import { Utils } from "../../Utils" -import Constants from "../../Models/Constants" - -export class OpenJosm extends Combine { - public static readonly needsUrls = ["http://127.0.0.1:8111/load_and_zoom"] - constructor(osmConnection: OsmConnection, bounds: Store, iconStyle?: string) { - const t = Translations.t.general.attribution - - const josmState = new UIEventSource(undefined) - // Reset after 15s - josmState.stabilized(15000).addCallbackD(() => josmState.setData(undefined)) - - const stateIndication = new VariableUiElement( - josmState.map((state) => { - if (state === undefined) { - return undefined - } - state = state.toUpperCase() - if (state === "OK") { - return t.josmOpened.SetClass("thanks") - } - return t.josmNotOpened.SetClass("alert") - }) - ) - - const toggle = new Toggle( - new SubtleButton(Svg.josm_logo_svg().SetStyle(iconStyle), t.editJosm) - .onClick(() => { - const bbox = bounds.data - if (bbox === undefined) { - return - } - const top = bbox.getNorth() - const bottom = bbox.getSouth() - const right = bbox.getEast() - const left = bbox.getWest() - const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}` - Utils.download(josmLink) - .then((answer) => josmState.setData(answer.replace(/\n/g, "").trim())) - .catch(() => josmState.setData("ERROR")) - }) - .SetClass("w-full"), - undefined, - osmConnection.userDetails.map( - (ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible - ) - ) - - super([stateIndication, toggle]) - } -} diff --git a/src/UI/BigComponents/ThemeIntroPanel.svelte b/src/UI/BigComponents/ThemeIntroPanel.svelte index a250489b8..6e125a6ea 100644 --- a/src/UI/BigComponents/ThemeIntroPanel.svelte +++ b/src/UI/BigComponents/ThemeIntroPanel.svelte @@ -1,20 +1,21 @@ + +{#if availableLanguages?.length > 1} +
+ + + + {#if preferredFiltered} + {#each preferredFiltered as language} + + {/each} + + {/if} + + {#each availableLanguages as language} + + {/each} + + + +{/if} diff --git a/src/UI/LanguagePicker.ts b/src/UI/LanguagePicker.ts deleted file mode 100644 index 350822a1c..000000000 --- a/src/UI/LanguagePicker.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { DropDown } from "./Input/DropDown" -import Locale from "./i18n/Locale" -import BaseUIElement from "./BaseUIElement" -import native from "../assets/language_native.json" -import language_translations from "../assets/language_translations.json" -import { Translation } from "./i18n/Translation" -import Lazy from "./Base/Lazy" -import Toggle from "./Input/Toggle" -import LanguageUtils from "../Utils/LanguageUtils" -import { UIEventSource } from "../Logic/UIEventSource" -import { QueryParameters } from "../Logic/Web/QueryParameters" - -export default class LanguagePicker extends Toggle { - constructor(languages: string[], assignTo: UIEventSource) { - console.log("Constructing a language picker for languages", languages) - if ( - languages === undefined || - languages.length <= 1 || - QueryParameters.wasInitialized("language") - ) { - super(undefined, undefined, undefined) - } else { - const normalPicker = LanguagePicker.dropdownFor(languages, assignTo ?? Locale.language) - const fullPicker = new Lazy(() => - LanguagePicker.dropdownFor(allLanguages, assignTo ?? Locale.language) - ) - super(fullPicker, normalPicker, Locale.showLinkToWeblate) - const allLanguages: string[] = LanguageUtils.usedLanguagesSorted - } - } - - private static dropdownFor( - languages: string[], - assignTo: UIEventSource - ): BaseUIElement { - return new DropDown( - undefined, - languages - .filter((lang) => lang !== "_context") - .map((lang) => { - return { value: lang, shown: LanguagePicker.hybrid(lang) } - }), - assignTo - ) - } - - private static hybrid(lang: string): Translation { - const nativeText = native[lang] ?? lang - const translation = {} - const trans = language_translations[lang] - if (trans === undefined) { - return new Translation({ "*": nativeText }) - } - for (const key in trans) { - if (key.startsWith("_")) { - continue - } - const translationInKey = language_translations[lang][key] - if (nativeText.toLowerCase() === translationInKey.toLowerCase()) { - translation[key] = nativeText - } else { - translation[key] = nativeText + " (" + translationInKey + ")" - } - } - return new Translation(translation) - } -} diff --git a/src/UI/Map/DynamicMarker.svelte b/src/UI/Map/DynamicMarker.svelte index 56b7e91ec..5bce4c965 100644 --- a/src/UI/Map/DynamicMarker.svelte +++ b/src/UI/Map/DynamicMarker.svelte @@ -1,20 +1,22 @@ -{#if config !== undefined} -
- {#each icons as icon} +{#if marker && marker} +
+ {#each marker as icon} {/each}
diff --git a/src/UI/Map/Icon.svelte b/src/UI/Map/Icon.svelte index 00cef96f2..cccbef469 100644 --- a/src/UI/Map/Icon.svelte +++ b/src/UI/Map/Icon.svelte @@ -19,6 +19,9 @@ import Teardrop from "../../assets/svg/Teardrop.svelte" import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte" import Triangle from "../../assets/svg/Triangle.svelte" + import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte"; + import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte"; + import Gps_arrow from "../../assets/svg/Gps_arrow.svelte"; /** * Renders a single icon. @@ -72,6 +75,20 @@ {:else if icon === "triangle"} + {:else if icon === "brick_wall_square"} + + {:else if icon === "brick_wall_round"} + + {:else if icon === "gps_arrow"} + + {:else if icon === "checkmark"} + + {:else if icon === "help"} + + {:else if icon === "close"} + + {:else if icon === "invalid"} + {:else} {/if} diff --git a/src/UI/PlantNet/PlantNet.svelte b/src/UI/PlantNet/PlantNet.svelte index c8b3896fb..2d01a3362 100644 --- a/src/UI/PlantNet/PlantNet.svelte +++ b/src/UI/PlantNet/PlantNet.svelte @@ -12,6 +12,7 @@ import { createEventDispatcher } from "svelte" import ToSvelte from "../Base/ToSvelte.svelte" import Svg from "../../Svg" + import Plantnet_logo from "../../assets/svg/Plantnet_logo.svelte"; /** * The main entry point for the plantnet wizard @@ -142,9 +143,7 @@ {/if}
- +
diff --git a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte index bb94de893..782bfdbd4 100644 --- a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -3,109 +3,110 @@ * This component ties together all the steps that are needed to create a new point. * There are many subcomponents which help with that */ - import type { SpecialVisualizationState } from "../../SpecialVisualization" - import PresetList from "./PresetList.svelte" - import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig" - import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" - import Tr from "../../Base/Tr.svelte" - import SubtleButton from "../../Base/SubtleButton.svelte" - import FromHtml from "../../Base/FromHtml.svelte" - import Translations from "../../i18n/Translations.js" - import TagHint from "../TagHint.svelte" - import { And } from "../../../Logic/Tags/And.js" - import LoginToggle from "../../Base/LoginToggle.svelte" - import Constants from "../../../Models/Constants.js" - import FilteredLayer from "../../../Models/FilteredLayer" - import { Store, UIEventSource } from "../../../Logic/UIEventSource" - import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid" - import LoginButton from "../../Base/LoginButton.svelte" - import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte" - import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction" - import { OsmWay } from "../../../Logic/Osm/OsmObject" - import { Tag } from "../../../Logic/Tags/Tag" - import type { WayId } from "../../../Models/OsmFeature" - import Loading from "../../Base/Loading.svelte" - import type { GlobalFilter } from "../../../Models/GlobalFilter" - import { onDestroy } from "svelte" - import NextButton from "../../Base/NextButton.svelte" - import BackButton from "../../Base/BackButton.svelte" - import ToSvelte from "../../Base/ToSvelte.svelte" - import Svg from "../../../Svg" - import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte" - import { twJoin } from "tailwind-merge" + import type { SpecialVisualizationState } from "../../SpecialVisualization"; + import PresetList from "./PresetList.svelte"; + import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"; + import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; + import Tr from "../../Base/Tr.svelte"; + import SubtleButton from "../../Base/SubtleButton.svelte"; + import Translations from "../../i18n/Translations.js"; + import TagHint from "../TagHint.svelte"; + import { And } from "../../../Logic/Tags/And.js"; + import LoginToggle from "../../Base/LoginToggle.svelte"; + import Constants from "../../../Models/Constants.js"; + import FilteredLayer from "../../../Models/FilteredLayer"; + import { Store, UIEventSource } from "../../../Logic/UIEventSource"; + import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"; + import LoginButton from "../../Base/LoginButton.svelte"; + import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"; + import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"; + import { OsmWay } from "../../../Logic/Osm/OsmObject"; + import { Tag } from "../../../Logic/Tags/Tag"; + import type { WayId } from "../../../Models/OsmFeature"; + import Loading from "../../Base/Loading.svelte"; + import type { GlobalFilter } from "../../../Models/GlobalFilter"; + import { onDestroy } from "svelte"; + import NextButton from "../../Base/NextButton.svelte"; + import BackButton from "../../Base/BackButton.svelte"; + import ToSvelte from "../../Base/ToSvelte.svelte"; + import Svg from "../../../Svg"; + import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"; + import { twJoin } from "tailwind-merge"; + import Confirm from "../../../assets/svg/Confirm.svelte"; + import Close from "../../../assets/svg/Close.svelte"; - export let coordinate: { lon: number; lat: number } - export let state: SpecialVisualizationState + export let coordinate: { lon: number; lat: number }; + export let state: SpecialVisualizationState; let selectedPreset: { preset: PresetConfig layer: LayerConfig icon: string tags: Record - } = undefined - let checkedOfGlobalFilters: number = 0 - let confirmedCategory = false + } = undefined; + let checkedOfGlobalFilters: number = 0; + let confirmedCategory = false; $: if (selectedPreset === undefined) { - confirmedCategory = false - creating = false - checkedOfGlobalFilters = 0 + confirmedCategory = false; + creating = false; + checkedOfGlobalFilters = 0; } - let flayer: FilteredLayer = undefined - let layerIsDisplayed: UIEventSource | undefined = undefined - let layerHasFilters: Store | undefined = undefined - let globalFilter: UIEventSource = state.layerState.globalFilters - let _globalFilter: GlobalFilter[] = [] + let flayer: FilteredLayer = undefined; + let layerIsDisplayed: UIEventSource | undefined = undefined; + let layerHasFilters: Store | undefined = undefined; + let globalFilter: UIEventSource = state.layerState.globalFilters; + let _globalFilter: GlobalFilter[] = []; onDestroy( globalFilter.addCallbackAndRun((globalFilter) => { - console.log("Global filters are", globalFilter) - _globalFilter = globalFilter ?? [] + console.log("Global filters are", globalFilter); + _globalFilter = globalFilter ?? []; }) - ) + ); $: { - flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id) - layerIsDisplayed = flayer?.isDisplayed - layerHasFilters = flayer?.hasFilter + flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id); + layerIsDisplayed = flayer?.isDisplayed; + layerHasFilters = flayer?.hasFilter; } - const t = Translations.t.general.add + const t = Translations.t.general.add; - const zoom = state.mapProperties.zoom + const zoom = state.mapProperties.zoom; - const isLoading = state.dataIsLoading - let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined) - let snappedToObject: UIEventSource = new UIEventSource(undefined) + const isLoading = state.dataIsLoading; + let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined); + let snappedToObject: UIEventSource = new UIEventSource(undefined); // Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map - let preciseInputIsTapped = false + let preciseInputIsTapped = false; - let creating = false + let creating = false; /** * Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters. * Will delete the lastclick-location */ function abort() { - state.selectedElement.setData(undefined) + state.selectedElement.setData(undefined); // When aborted, we force the contributors to place the pin _again_ // This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map - state.lastClickObject.features.setData([]) - preciseInputIsTapped = false + state.lastClickObject.features.setData([]); + preciseInputIsTapped = false; } async function confirm() { - creating = true - const location: { lon: number; lat: number } = preciseCoordinate.data - const snapTo: WayId | undefined = snappedToObject.data + creating = true; + const location: { lon: number; lat: number } = preciseCoordinate.data; + const snapTo: WayId | undefined = snappedToObject.data; const tags: Tag[] = selectedPreset.preset.tags.concat( ..._globalFilter.map((f) => f?.onNewPoint?.tags ?? []) - ) - console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags) + ); + console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); - let snapToWay: undefined | OsmWay = undefined + let snapToWay: undefined | OsmWay = undefined; if (snapTo !== undefined && snapTo !== null) { - const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0) + const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0); if (downloaded !== "deleted") { - snapToWay = downloaded + snapToWay = downloaded; } } @@ -113,44 +114,44 @@ theme: state.layout?.id ?? "unkown", changeType: "create", snapOnto: snapToWay, - reusePointWithinMeters: 1, - }) - await state.changes.applyAction(newElementAction) - state.newFeatures.features.ping() + reusePointWithinMeters: 1 + }); + await state.changes.applyAction(newElementAction); + state.newFeatures.features.ping(); // The 'changes' should have created a new point, which added this into the 'featureProperties' - const newId = newElementAction.newElementId - console.log("Applied pending changes, fetching store for", newId) - const tagsStore = state.featureProperties.getStore(newId) + const newId = newElementAction.newElementId; + console.log("Applied pending changes, fetching store for", newId); + const tagsStore = state.featureProperties.getStore(newId); if (!tagsStore) { - console.error("Bug: no tagsStore found for", newId) + console.error("Bug: no tagsStore found for", newId); } { // Set some metainfo - const properties = tagsStore.data + const properties = tagsStore.data; if (snapTo) { // metatags (starting with underscore) are not uploaded, so we can safely mark this - delete properties["_referencing_ways"] - properties["_referencing_ways"] = `["${snapTo}"]` + delete properties["_referencing_ways"]; + properties["_referencing_ways"] = `["${snapTo}"]`; } - properties["_backend"] = state.osmConnection.Backend() - properties["_last_edit:timestamp"] = new Date().toISOString() - const userdetails = state.osmConnection.userDetails.data - properties["_last_edit:contributor"] = userdetails.name - properties["_last_edit:uid"] = "" + userdetails.uid - tagsStore.ping() + properties["_backend"] = state.osmConnection.Backend(); + properties["_last_edit:timestamp"] = new Date().toISOString(); + const userdetails = state.osmConnection.userDetails.data; + properties["_last_edit:contributor"] = userdetails.name; + properties["_last_edit:uid"] = "" + userdetails.uid; + tagsStore.ping(); } - const feature = state.indexedFeatures.featuresById.data.get(newId) - console.log("Selecting feature", feature, "and opening their popup") - abort() - state.selectedLayer.setData(selectedPreset.layer) - state.selectedElement.setData(feature) - tagsStore.ping() + const feature = state.indexedFeatures.featuresById.data.get(newId); + console.log("Selecting feature", feature, "and opening their popup"); + abort(); + state.selectedLayer.setData(selectedPreset.layer); + state.selectedElement.setData(feature); + tagsStore.ping(); } function confirmSync() { confirm() .then((_) => console.debug("New point successfully handled")) - .catch((e) => console.error("Handling the new point went wrong due to", e)) + .catch((e) => console.error("Handling the new point went wrong due to", e)); } @@ -285,7 +286,7 @@ (confirmedCategory = true)} clss="primary w-full">
- +
@@ -299,11 +300,7 @@ checkedOfGlobalFilters = checkedOfGlobalFilters + 1 }} > - + - + {:else if !creating} diff --git a/src/UI/Popup/AllTagsPanel.svelte b/src/UI/Popup/AllTagsPanel.svelte index 81bc9219e..9913416fb 100644 --- a/src/UI/Popup/AllTagsPanel.svelte +++ b/src/UI/Popup/AllTagsPanel.svelte @@ -9,6 +9,7 @@ import Lazy from "../Base/Lazy" import BaseUIElement from "../BaseUIElement" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" + import { VariableUiElement } from "../Base/VariableUIElement"; //Svelte props export let tags: UIEventSource @@ -54,13 +55,7 @@ return parts }) - let _allTags = [] - onDestroy( - allTags.addCallbackAndRunD((allTags) => { - _allTags = allTags - }) - ) - const tagsTable = new Table(["Key", "Value"], _allTags).SetClass("zebra-table break-all") + const tagsTable = new VariableUiElement(allTags.mapD(_allTags => new Table(["Key", "Value"], _allTags).SetClass("zebra-table break-all")))
diff --git a/src/UI/Popup/CreateNewNote.svelte b/src/UI/Popup/CreateNewNote.svelte index 6cd1f5153..9cc70d560 100644 --- a/src/UI/Popup/CreateNewNote.svelte +++ b/src/UI/Popup/CreateNewNote.svelte @@ -15,6 +15,8 @@ import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte" import ToSvelte from "../Base/ToSvelte.svelte" import Svg from "../../Svg" + import Layers from "../../assets/svg/Layers.svelte"; + import AddSmall from "../../assets/svg/AddSmall.svelte"; export let coordinate: UIEventSource<{ lon: number; lat: number }> export let state: SpecialVisualizationState @@ -97,7 +99,7 @@
notelayer.disableAllFilters()}> - +
@@ -126,7 +128,7 @@ {#if $comment?.length >= 3} - + {:else} @@ -143,7 +145,7 @@ - + diff --git a/src/UI/Popup/NearbyImagesCollapsed.svelte b/src/UI/Popup/NearbyImagesCollapsed.svelte index 0a315dcb3..c78917a77 100644 --- a/src/UI/Popup/NearbyImagesCollapsed.svelte +++ b/src/UI/Popup/NearbyImagesCollapsed.svelte @@ -11,6 +11,7 @@ import ToSvelte from "../Base/ToSvelte.svelte" import { XCircleIcon } from "@babeard/svelte-heroicons/solid" import exp from "constants" + import Camera_plus from "../../assets/svg/Camera_plus.svelte"; export let tags: Store export let state: SpecialVisualizationState @@ -42,7 +43,7 @@ expanded = true }} > - + {/if} diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index dc0736029..0131a20fa 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -23,6 +23,8 @@ import UserRelatedState from "../../../Logic/State/UserRelatedState" import { twJoin } from "tailwind-merge" import { TagUtils } from "../../../Logic/Tags/TagUtils" + import Search from "../../../assets/svg/Search.svelte"; + import Login from "../../../assets/svg/Login.svelte"; export let config: TagRenderingConfig export let tags: UIEventSource> @@ -211,7 +213,7 @@ {#if config.mappings?.length >= 8}
- +
{/if} @@ -318,7 +320,7 @@ state?.osmConnection?.AttemptLogin()}> - + {#if $feedback !== undefined} diff --git a/src/UI/Reviews/AllReviews.svelte b/src/UI/Reviews/AllReviews.svelte index 75d5ec63c..fc4b635d0 100644 --- a/src/UI/Reviews/AllReviews.svelte +++ b/src/UI/Reviews/AllReviews.svelte @@ -12,6 +12,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import ToSvelte from "../Base/ToSvelte.svelte" import Svg from "../../Svg" + import Mangrove_logo from "../../assets/svg/Mangrove_logo.svelte"; /** * An element showing all reviews @@ -40,7 +41,7 @@ {/if}
- +
diff --git a/src/UI/Reviews/StarElement.svelte b/src/UI/Reviews/StarElement.svelte index 7b0d8382c..60eabbdc1 100644 --- a/src/UI/Reviews/StarElement.svelte +++ b/src/UI/Reviews/StarElement.svelte @@ -2,6 +2,9 @@ import ToSvelte from "../Base/ToSvelte.svelte" import Svg from "../../Svg" import { createEventDispatcher } from "svelte" + import Star from "../../assets/svg/Star.svelte"; + import Star_half from "../../assets/svg/Star_half.svelte"; + import Star_outline from "../../assets/svg/Star_outline.svelte"; export let score: number export let cutoff: number @@ -23,10 +26,10 @@ on:mousemove={(e) => dispatch("hover", { score: getScore(e) })} > {#if score >= cutoff} - + {:else if score + 10 >= cutoff} - + {:else} - + {/if} diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 28125837c..be3ffee15 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -88,7 +88,7 @@ export interface SpecialVisualization { readonly funcName: string readonly docs: string | BaseUIElement readonly example?: string - readonly needsUrls: string[] + readonly needsUrls: string[] | ((args: string[]) => string) /** * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index cc128c4c4..c0720293a 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -45,7 +45,6 @@ import { GeoOperations } from "../Logic/GeoOperations" import CreateNewNote from "./Popup/CreateNewNote.svelte" import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" import UserProfile from "./BigComponents/UserProfile.svelte" -import LanguagePicker from "./LanguagePicker" import Link from "./Base/Link" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" @@ -59,7 +58,6 @@ import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" -import { OpenJosm } from "./BigComponents/OpenJosm" import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" import FediverseValidator from "./InputElement/Validators/FediverseValidator" import SendEmail from "./Popup/SendEmail.svelte" @@ -78,6 +76,9 @@ import Questionbox from "./Popup/TagRendering/Questionbox.svelte" import { TagUtils } from "../Logic/Tags/TagUtils" import Giggity from "./BigComponents/Giggity.svelte" import ThemeViewState from "../Models/ThemeViewState" +import LanguagePicker from "./InputElement/LanguagePicker.svelte" +import LogoutButton from "./Base/LogoutButton.svelte" +import OpenJosm from "./Base/OpenJosm.svelte" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -453,10 +454,13 @@ export default class SpecialVisualizations { needsUrls: [], docs: "A component to set the language of the user interface", constr(state: SpecialVisualizationState): BaseUIElement { - return new LanguagePicker( - state.layout.language, - state.userRelatedState.language - ) + return new SvelteUIElement(LanguagePicker, { + assignTo: state.userRelatedState.language, + availableLanguages: state.layout.language, + preferredLanguages: state.osmConnection.userDetails.map( + (ud) => ud.languages + ), + }) }, }, { @@ -465,11 +469,7 @@ export default class SpecialVisualizations { needsUrls: [Constants.osmAuthConfig.url], docs: "Shows a button where the user can log out", constr(state: SpecialVisualizationState): BaseUIElement { - return new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, { - imgSize: "w-6 h-6", - }).onClick(() => { - state.osmConnection.LogOut() - }) + return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection }) }, }, new HistogramViz(), @@ -903,10 +903,10 @@ export default class SpecialVisualizations { funcName: "open_in_josm", docs: "Opens the current view in the JOSM-editor", args: [], - needsUrls: OpenJosm.needsUrls, + needsUrls: ["http://127.0.0.1:8111/load_and_zoom"], constr: (state) => { - return new OpenJosm(state.osmConnection, state.mapProperties.bounds) + return new SvelteUIElement(OpenJosm, { state }) }, }, { @@ -1099,12 +1099,6 @@ export default class SpecialVisualizations { if (maproulette_id_key === "" || maproulette_id_key === undefined) { maproulette_id_key = "mr_taskId" } - if (Svg.All[image] !== undefined || Svg.All[image + ".svg"] !== undefined) { - if (image.endsWith(".svg")) { - image = image.substring(0, image.length - 4) - } - image = Svg[image + "_svg"]() - } const failed = new UIEventSource(false) const closeButton = new SubtleButton(image, message).OnClickWithLoading( @@ -1460,7 +1454,7 @@ export default class SpecialVisualizations { }, ], docs: "Shows events that are happening based on a Giggity URL", - needsUrls: ["*"], + needsUrls: (args) => args[0], constr( state: SpecialVisualizationState, tagSource: UIEventSource>, diff --git a/src/UI/StudioGUI.svelte b/src/UI/StudioGUI.svelte index 097576393..fb1b64753 100644 --- a/src/UI/StudioGUI.svelte +++ b/src/UI/StudioGUI.svelte @@ -1,36 +1,36 @@
@@ -40,16 +41,16 @@
@@ -58,11 +59,11 @@
@@ -81,7 +82,7 @@
@@ -105,26 +106,26 @@
diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index ac1b5d0a6..1b51c34bf 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -1,16 +1,6 @@ - - -
Title 0
-
Content 0 loaded
- -
Title 1
-
Content 1
-
+
+ No tests +
diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 34a46a699..0197ba58e 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -1,62 +1,71 @@ -
+