diff --git a/.github/workflows/deploy_hosted.yml b/.github/workflows/deploy_hosted.yml new file mode 100644 index 000000000..ca25e6aa8 --- /dev/null +++ b/.github/workflows/deploy_hosted.yml @@ -0,0 +1,55 @@ +name: Deploy develop on dev.mapcomplete.org +on: + push + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: install deps + run: npm ci + shell: bash + + - name: create generated dir + run: mkdir ./assets/generated + shell: bash + + - name: create dependencies + run: npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:service-worker; npm run download:editor-layer-index + shell: bash + + - name: sync translations + run: npm run generate:translations + shell: bash + + - name: run tests + run: | + pwd + ls + npm run test + shell: bash + + - name: Prepare deploy + run: npm run prepare-deploy + shell: bash + + + + - name: Upload artefact + env: + SSH_KEY: ${{ secrets.HETZNER_KEY }} + run: | + mkdir .ssh + echo $SSH_KEY > .ssh/id_ed25519 + scp dist/* pietervdvn@hosted.mapcomplete.org:/root/public/${{ github.ref_name }} + + diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index 964643eac..f011e4cda 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -3232,6 +3232,18 @@ } } ] + }, + { + "id": "name", + "question":{ + "en": "What is the name of this place?" + }, + "render": { + "*": "{name}" + }, + "freeform": { + "key": "name" + } } ], "allowMove": false, @@ -3247,4 +3259,4 @@ } } ] -} \ No newline at end of file +} diff --git a/assets/layers/usertouched/usertouched.json b/assets/layers/usertouched/usertouched.json new file mode 100644 index 000000000..f22e5e104 --- /dev/null +++ b/assets/layers/usertouched/usertouched.json @@ -0,0 +1,70 @@ +{ + "id": "usertouched", + "description": { + "en": "Special layer showing all items which were changed by a certain user" + }, + "name": { + "en": "Changed by user" + }, + "title": { + "render": { + "en": "Changed by user" + } + }, + "source": "special", + "tagRenderings": [ + { + "id": "test", + "render": { + "en": "Changed by user" + } + }, + "all_tags" + ], + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "iconSize": "15,15", + "marker": [ + { + "icon": "circle", + "color": "#aaa" + }, + { + "icon": "ring", + "color": "#000" + } + ] + } + ], + "lineRendering": [ + { + "color": "black", + "width": 3, + "fillColor": "#00000000" + }, + { + "color": "#cccccccc", + "width": { + "render": 0, + "mappings": [ + { + "if": { + "or": [ + "_geometry:type=Polygon", + "_geometry:type=MultiPolygon" + ] + }, + "then": 20 + } + ] + }, + "offset": 15, + "fillColor": "#00000000" + } + ], + "allowMove": false +} diff --git a/assets/themes/inspector/inspector.json b/assets/themes/inspector/inspector.json new file mode 100644 index 000000000..9a531f3d8 --- /dev/null +++ b/assets/themes/inspector/inspector.json @@ -0,0 +1,13 @@ +{ + "id": "inspector", + "title": { + "en": "Inspect changes from a single user" + }, + "description": { + "en": "A theme to inspect what a single user did in the past" + }, + "icon": "./assets/svg/add.svg", + "layers": [ + "usertouched" + ] +} diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index f06464705..9cc62392c 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -4,26 +4,20 @@ "en": "Changes made with MapComplete", "de": "Änderungen mit MapComplete", "cs": "Změny provedené pomocí MapComplete", - "es": "Cambios realizados con MapComplete", - "fr": "Modifications faites avec MapComplete", - "nl": "Wijzigingen gemaakt met MapComplete" + "es": "Cambios realizados con MapComplete" }, "shortDescription": { "en": "Shows changes made by MapComplete", "de": "Zeigt die von MapComplete vorgenommenen Änderungen an", "cs": "Zobrazuje změny provedené nástrojem MapComplete", - "es": "Muestra los cambios realizados por MapComplete", - "fr": "Afficher les modifications faites avec MapComplete", - "nl": "Toont wijzigingen gemaakt met MapComplete" + "es": "Muestra los cambios realizados por MapComplete" }, "description": { "en": "This maps shows all the changes made with MapComplete", "de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen", "es": "Este mapa muestra todos los cambios realizados con MapComplete", "pl": "Ta mapa pokazuje wszystkie zmiany wprowadzone za pomocą MapComplete", - "cs": "Tyto mapy zobrazují všechny změny provedené pomocí MapComplete", - "fr": "Cette carte montre tous les changements effectués avec MapComplete", - "nl": "Deze kaarten tonen alle wijzigingen die zijn gemaakt met MapComplete" + "cs": "Tyto mapy zobrazují všechny změny provedené pomocí MapComplete" }, "icon": "./assets/svg/logo.svg", "hideFromOverview": true, @@ -36,10 +30,7 @@ "name": { "en": "Changeset centers", "de": "Changeset-Zentren", - "es": "Centros de conjuntos de cambios", - "fr": "Centre du groupe de modifications", - "nl": "Changeset centra", - "cs": "Changeset centra" + "es": "Centros de conjuntos de cambios" }, "minzoom": 0, "source": { @@ -52,16 +43,14 @@ "en": "Changeset for {theme}", "de": "Änderungssatz für {theme}", "cs": "Sada změn pro {theme}", - "es": "Conjunto de cambios para {theme}", - "nl": "Changeset voor {theme}" + "es": "Conjunto de cambios para {theme}" } }, "description": { "en": "Shows all MapComplete changes", "de": "Zeigt alle MapComplete-Änderungen", "es": "Muestra todos los cambios de MapComplete", - "cs": "Zobrazí všechny změny MapComplete", - "nl": "Toon alle MapComplete-wijzigingen" + "cs": "Zobrazí všechny změny MapComplete" }, "tagRenderings": [ { @@ -70,8 +59,7 @@ "en": "Changeset {id}", "de": "Änderungssatz {id}", "cs": "Sada změn {id}", - "es": "Conjunto de cambios {id}", - "nl": "Changeset {id}" + "es": "Conjunto de cambios {id}" } }, { @@ -80,8 +68,7 @@ "en": "What contributor did make this change?", "de": "Wer hat zu dieser Änderung beigetragen?", "cs": "Který přispěvatel provedl tuto změnu?", - "es": "¿Qué colaborador realizó este cambio?", - "nl": "Welke bijdrager maakte deze verandering?" + "es": "¿Qué colaborador realizó este cambio?" }, "freeform": { "key": "user" @@ -90,9 +77,7 @@ "en": "Change made by {user}", "de": "Änderung vorgenommen von {user}", "cs": "Změna provedena uživatelem {user}", - "es": "Cambio realizado por {user}", - "fr": "Modification faite par {user}", - "nl": "Wijziging aangebracht door {user}" + "es": "Cambio realizado por {user}" } }, { @@ -101,8 +86,7 @@ "en": "What theme was used to make this change?", "de": "Welches Thema wurde für diese Änderung verwendet?", "cs": "Jaký motiv byl použit k provedení této změny?", - "es": "¿Qué tema se utilizó para realizar este cambio?", - "nl": "Welk thema werd gebruikt voor deze wijziging?" + "es": "¿Qué tema se utilizó para realizar este cambio?" }, "freeform": { "key": "theme" @@ -110,9 +94,7 @@ "render": { "en": "Change with theme {theme}", "de": "Änderung mit Thema {theme}", - "es": "Cambio con el tema {theme}", - "nl": "Verander met thema {theme}", - "cs": "Změna pomocí tématu {theme}" + "es": "Cambio con el tema {theme}" } }, { @@ -124,15 +106,13 @@ "en": "What locale (language) was this change made in?", "de": "In welcher Sprache (Locale) wurde diese Änderung vorgenommen?", "cs": "V jakém prostředí (jazyce) byla tato změna provedena?", - "es": "¿En qué configuración regional (idioma) se realizó este cambio?", - "nl": "In welke 'locale' (taal) is deze wijziging gemaakt?" + "es": "¿En qué configuración regional (idioma) se realizó este cambio?" }, "render": { "en": "User locale is {locale}", "de": "Die Benutzersprache ist {locale}", "cs": "Uživatelské prostředí je {locale}", - "es": "Configuración regional del usuario es {locale}", - "nl": "De gebruikerstaal (locale) is {locale}" + "es": "Configuración regional del usuario es {locale}" } }, { @@ -141,15 +121,13 @@ "en": "Change with with {host}", "de": "Änderung mit {host}", "cs": "Změnit pomocí {host}", - "es": "Cambio realizado con {host}", - "nl": "Gewijzigd met {host}" + "es": "Cambio realizado con {host}" }, "question": { "en": "What host (website) was this change made with?", "de": "Bei welchem Host (Website) wurde diese Änderung vorgenommen?", "cs": "U jakého hostitele (webové stránky) byla tato změna provedena?", - "es": "¿Con qué anfitrión (sitio web) se realizó este cambio?", - "nl": "Met welke host (website) is deze wijziging gemaakt?" + "es": "¿Con qué anfitrión (sitio web) se realizó este cambio?" }, "freeform": { "key": "host" @@ -173,17 +151,13 @@ "en": "What version of MapComplete was used to make this change?", "de": "Welche Version von MapComplete wurde verwendet, um diese Änderung vorzunehmen?", "cs": "Jaká verze aplikace MapComplete byla použita k provedení této změny?", - "es": "¿Qué versión de MapComplete se utilizó para realizar este cambio?", - "fr": "Quelle version de MapCompletee a été utilisée pour faire cette modification ?", - "nl": "Welke versie van MapComplete is gebruikt voor deze wijziging?" + "es": "¿Qué versión de MapComplete se utilizó para realizar este cambio?" }, "render": { "en": "Made with {editor}", "de": "Erstellt mit {editor}", "cs": "Vytvořeno pomocí {editor}", - "es": "Hecho con {editor}", - "fr": "Fait avec {editor}", - "nl": "Gemaakt met {editor}" + "es": "Hecho con {editor}" }, "freeform": { "key": "editor" @@ -378,8 +352,8 @@ "then": "./assets/layers/entrance/entrance.svg" }, { - "if": "theme=insects", - "then": "./assets/layers/insect_hotel/insect_hotel.svg" + "if": "theme=inspector", + "then": "./assets/svg/add.svg" }, { "if": "theme=items_with_image", @@ -589,9 +563,7 @@ "de": "Themenname enthält {search}", "es": "El nombre del tema contiene {search}", "pl": "Nazwa tematu zawiera {search}", - "cs": "Název obsahuje {search}", - "fr": "Le nom du thème contient {search}", - "nl": "Themanaam bevat {search}" + "cs": "Název obsahuje {search}" } } ] @@ -610,9 +582,7 @@ "en": "Themename does not contain {search}", "de": "Themename enthält nicht {search}", "es": "El nombre del tema no contiene {search}", - "cs": "Název motivu neobsahuje {search}", - "fr": "Le nom du thème ne contient pas {search}", - "nl": "Themanaam bevat geen {search}" + "cs": "Název motivu neobsahuje {search}" } } ] @@ -631,9 +601,7 @@ "en": "Made by contributor {search}", "de": "Erstellt von Mitwirkendem {search}", "es": "Hecho por el colaborador {search}", - "cs": "Vytvořeno přispěvatelem {search}", - "fr": "Fait par le·a contributeur·trice {search}", - "nl": "Toegevoegd door {search}" + "cs": "Vytvořeno přispěvatelem {search}" } } ] @@ -652,9 +620,7 @@ "en": "Not made by contributor {search}", "de": "Nicht erstellt von Mitwirkendem {search}", "es": "No hecho por el colaborador {search}", - "cs": "Nevytvořeno přispěvatelem {search}", - "fr": "Pas fait par le·a contributeur·trice {search}", - "nl": "Niet toegevoegd door {search}" + "cs": "Nevytvořeno přispěvatelem {search}" } } ] @@ -674,9 +640,7 @@ "en": "Made before {search}", "de": "Erstellt vor {search}", "es": "Hecho antes de {search}", - "cs": "Vytvořeno před {search}", - "fr": "Fait avant {search}", - "nl": "Toegevoegd vóór {search}" + "cs": "Vytvořeno před {search}" } } ] @@ -696,9 +660,7 @@ "en": "Made after {search}", "de": "Erstellt nach {search}", "es": "Hecho después de {search}", - "cs": "Vytvořeno po {search}", - "fr": "Fait après {search}", - "nl": "Toegevoegd na {search}" + "cs": "Vytvořeno po {search}" } } ] @@ -717,9 +679,7 @@ "en": "User language (iso-code) {search}", "de": "Benutzersprache (ISO-Code) {search}", "es": "Idioma del usuario (código ISO) {search}", - "cs": "Jazyk uživatele (iso-kód) {search}", - "fr": "Langage utilisateur (code iso) {search}", - "nl": "Gebruikerstaal (iso-code) {search}" + "cs": "Jazyk uživatele (iso-kód) {search}" } } ] @@ -738,8 +698,7 @@ "en": "Made with host {search}", "de": "Erstellt mit Host {search}", "cs": "Vytvořeno pomocí hostitele {search}", - "es": "Hecho con el anfitrión {search}", - "nl": "Gemaakt met {search}" + "es": "Hecho con el anfitrión {search}" } } ] @@ -753,8 +712,7 @@ "en": "Changeset added at least one image", "de": "Changeset hat mindestens ein Bild hinzugefügt", "cs": "Sada změn přidala alespoň jeden obrázek", - "es": "El conjunto de cambios agregó al menos una imagen", - "nl": "Changeset voegde minstens één afbeelding toe" + "es": "El conjunto de cambios agregó al menos una imagen" } } ] @@ -768,8 +726,7 @@ "en": "Exclude GRB theme", "de": "GRB-Thema ausschließen", "cs": "Vyloučit motiv GRB", - "es": "Excluir el tema GRB", - "nl": "GRB-thema uitsluiten" + "es": "Excluir el tema GRB" } } ] @@ -783,8 +740,7 @@ "en": "Exclude etymology theme", "de": "Etymologie-Thema ausschließen", "es": "Excluir el tema de etimología", - "cs": "Vyloučit etymologii tématu", - "nl": "Thema etymologie uitsluiten" + "cs": "Vyloučit etymologii tématu" } } ] @@ -802,9 +758,7 @@ "en": "More statistics can be found here", "de": "Weitere Statistiken findest du hier", "cs": "Další statistiky najdete zde", - "es": "Puedes encontrar más estadísticas aquí", - "fr": "Plus de statistiques peuvent être trouvées ici", - "nl": "Meer statistieken vind je hier" + "es": "Puedes encontrar más estadísticas aquí" } }, { diff --git a/langs/en.json b/langs/en.json index a48943fc9..b3e98862d 100644 --- a/langs/en.json +++ b/langs/en.json @@ -675,6 +675,9 @@ "pickTheme": "Pick a theme below to get started.", "title": "MapComplete" }, + "inspector": { + "menu": "Inspect a contributor" + }, "move": { "cancel": "Select a different reason", "cannotBeMoved": "This feature cannot be moved.", diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 74cfa9a43..a61aab92b 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -643,11 +643,15 @@ class LayerOverviewUtils extends Script { LayerOverviewUtils.layerPath + sharedLayerPath.substring(sharedLayerPath.lastIndexOf("/")) if (!forceReload && !this.shouldBeUpdated(sharedLayerPath, targetPath)) { - const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8")) - sharedLayers.set(sharedLayer.id, sharedLayer) - skippedLayers.push(sharedLayer.id) - ScriptUtils.erasableLog("Loaded " + sharedLayer.id) - continue + try{ + const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8")) + sharedLayers.set(sharedLayer.id, sharedLayer) + skippedLayers.push(sharedLayer.id) + ScriptUtils.erasableLog("Loaded " + sharedLayer.id) + continue + }catch (e) { + throw "Could not parse "+targetPath+" : "+e + } } } diff --git a/src/Logic/ImageProviders/Panoramax.ts b/src/Logic/ImageProviders/Panoramax.ts index c02667adc..c3ef71522 100644 --- a/src/Logic/ImageProviders/Panoramax.ts +++ b/src/Logic/ImageProviders/Panoramax.ts @@ -12,7 +12,7 @@ import Panoramax_bw from "../../assets/svg/Panoramax_bw.svelte" import Link from "../../UI/Base/Link" export default class PanoramaxImageProvider extends ImageProvider { - public static readonly singleton = new PanoramaxImageProvider() + public static readonly singleton: PanoramaxImageProvider = new PanoramaxImageProvider() private static readonly xyz = new PanoramaxXYZ() private static defaultPanoramax = new AuthorizedPanoramax( Constants.panoramax.url, @@ -126,7 +126,11 @@ export default class PanoramaxImageProvider extends ImageProvider { if (!Panoramax.isId(value)) { return undefined } - return [await this.getInfoFor(value).then((r) => this.featureToImage(r))] + return [await this.getInfo(value)] + } + + public async getInfo(hash: string): Promise { + return await this.getInfoFor(hash).then((r) => this.featureToImage(r)) } getRelevantUrls(tags: Record, prefixes: string[]): Store { diff --git a/src/Logic/Osm/OsmObjectDownloader.ts b/src/Logic/Osm/OsmObjectDownloader.ts index d9afcace4..b0e9f9cec 100644 --- a/src/Logic/Osm/OsmObjectDownloader.ts +++ b/src/Logic/Osm/OsmObjectDownloader.ts @@ -14,7 +14,7 @@ export default class OsmObjectDownloader { readonly isUploading: Store } private readonly backend: string - private historyCache = new Map>() + private historyCache = new Map>() constructor( backend: string = "https://api.openstreetmap.org", @@ -75,49 +75,51 @@ export default class OsmObjectDownloader { return await this.applyPendingChanges(obj) } - public DownloadHistory(id: NodeId): UIEventSource - - public DownloadHistory(id: WayId): UIEventSource - - public DownloadHistory(id: RelationId): UIEventSource - - public DownloadHistory(id: OsmId): UIEventSource - - public DownloadHistory(id: string): UIEventSource { - if (this.historyCache.has(id)) { - return this.historyCache.get(id) - } + private async _downloadHistoryUncached(id: string): Promise { const splitted = id.split("/") const type = splitted[0] const idN = Number(splitted[1]) - const src = new UIEventSource([]) - this.historyCache.set(id, src) - Utils.downloadJsonCached( + const data = await Utils.downloadJsonCached( `${this.backend}api/0.6/${type}/${idN}/history`, 10 * 60 * 1000 - ).then((data) => { - const elements: any[] = data.elements - const osmObjects: OsmObject[] = [] - for (const element of elements) { - let osmObject: OsmObject = null - element.nodes = [] - switch (type) { - case "node": - osmObject = new OsmNode(idN, element) - break - case "way": - osmObject = new OsmWay(idN, element) - break - case "relation": - osmObject = new OsmRelation(idN, element) - break - } - osmObject?.SaveExtraData(element, []) - osmObjects.push(osmObject) + ) + const elements: [] = data["elements"] + const osmObjects: OsmObject[] = [] + for (const element of elements) { + let osmObject: OsmObject = null + element["nodes"] = [] + switch (type) { + case "node": + osmObject = new OsmNode(idN, element) + break + case "way": + osmObject = new OsmWay(idN, element) + break + case "relation": + osmObject = new OsmRelation(idN, element) + break } - src.setData(osmObjects) - }) - return src + osmObject?.SaveExtraData(element, []) + osmObjects.push(osmObject) + } + return osmObjects + } + + public downloadHistory(id: NodeId): Promise + + public downloadHistory(id: WayId): Promise + + public downloadHistory(id: RelationId): Promise + + public downloadHistory(id: OsmId): Promise + + public async downloadHistory(id: string): Promise { + if (this.historyCache.has(id)) { + return this.historyCache.get(id) + } + const promise = this._downloadHistoryUncached(id) + this.historyCache.set(id, promise) + return promise } /** diff --git a/src/Logic/Osm/Overpass.ts b/src/Logic/Osm/Overpass.ts index 31f9b79cd..8c890db0b 100644 --- a/src/Logic/Osm/Overpass.ts +++ b/src/Logic/Osm/Overpass.ts @@ -26,7 +26,10 @@ export class Overpass { ) { this._timeout = timeout ?? new ImmutableStore(90) this._interpreterUrl = interpreterUrl - const optimized = filter.optimize() + if (filter === undefined && !extraScripts) { + throw "Filter is undefined. This is probably a bug. Alternatively, pass an 'extraScript'" + } + const optimized = filter?.optimize() if (optimized === true || optimized === false) { throw "Invalid filter: optimizes to true of false" } @@ -85,7 +88,7 @@ export class Overpass { * new Overpass(new Tag("key","value"), [], "").buildScript("{{bbox}}") // => `[out:json][timeout:90]{{bbox}};(nwr["key"="value"];);out body;out meta;>;out skel qt;` */ public buildScript(bbox: string, postCall: string = "", pretty = false): string { - const filters = this._filter.asOverpass() + const filters = this._filter?.asOverpass() ?? [] let filter = "" for (const filterOr of filters) { if (pretty) { @@ -97,12 +100,13 @@ export class Overpass { } } for (const extraScript of this._extraScripts) { - filter += "(" + extraScript + ");" + filter += extraScript } return `[out:json][timeout:${this._timeout.data}]${bbox};(${filter});out body;${ this._includeMeta ? "out meta;" : "" }>;out skel qt;` } + /** * Constructs the actual script to execute on Overpass with geocoding * 'PostCall' can be used to set an extra range, see 'AsOverpassTurboLink' diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index 77cfe209f..43f40558f 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -727,6 +727,7 @@ export class UIEventSource extends Store implements Writable { } /** + * Parse the number and round to the nearest int * * @param source * UIEventSource.asInt(new UIEventSource("123")).data // => 123 diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index 3f6f5c943..916a1836c 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -41,6 +41,7 @@ export default class Constants { "usersettings", "icons", "filters", + "usertouched" ] as const /** * Layer IDs of layers which have special properties through built-in hooks diff --git a/src/Models/ThemeConfig/ThemeConfig.ts b/src/Models/ThemeConfig/ThemeConfig.ts index 9917007ea..d91b9525c 100644 --- a/src/Models/ThemeConfig/ThemeConfig.ts +++ b/src/Models/ThemeConfig/ThemeConfig.ts @@ -306,7 +306,7 @@ export default class ThemeConfig implements ThemeInformation { return { untranslated, total } } - public getMatchingLayer(tags: Record): LayerConfig | undefined { + public getMatchingLayer(tags: Record, blacklistLayers?: Set): LayerConfig | undefined { if (tags === undefined) { return undefined } @@ -314,6 +314,9 @@ export default class ThemeConfig implements ThemeInformation { return this.getLayer("current_view") } for (const layer of this.layers) { + if(blacklistLayers?.has(layer.id)){ + continue + } if (!layer.source) { if (layer.isShown?.matchesProperties(tags)) { return layer diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index ed077ac27..8fece162b 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -1061,7 +1061,7 @@ export default class ThemeViewState implements SpecialVisualizationState { /** * Searches the appropriate layer - will first try if a special layer matches; if not, a normal layer will be used by delegating to the theme */ - public getMatchingLayer(properties: Record) { + public getMatchingLayer(properties: Record): LayerConfig | undefined { const id = properties.id if (id.startsWith("summary_")) { diff --git a/src/UI/Base/SelectedElementPanel.svelte b/src/UI/Base/SelectedElementPanel.svelte index 617801eb1..d570b741f 100644 --- a/src/UI/Base/SelectedElementPanel.svelte +++ b/src/UI/Base/SelectedElementPanel.svelte @@ -3,24 +3,15 @@ import type { Feature } from "geojson" import SelectedElementView from "../BigComponents/SelectedElementView.svelte" import SelectedElementTitle from "../BigComponents/SelectedElementTitle.svelte" - import UserRelatedState from "../../Logic/State/UserRelatedState" - import { LastClickFeatureSource } from "../../Logic/FeatureSource/Sources/LastClickFeatureSource" import Loading from "./Loading.svelte" import { onDestroy } from "svelte" - import LayerConfig from "../../Models/ThemeConfig/LayerConfig" - import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" - import ThemeViewState from "../../Models/ThemeViewState" export let state: SpecialVisualizationState export let selected: Feature let tags = state.featureProperties.getStore(selected.properties.id) export let absolute = true - function getLayer(properties: Record): LayerConfig { - return state.getMatchingLayer(properties) - } - - let layer = getLayer(selected.properties) + let layer = state.getMatchingLayer(selected.properties) let stillMatches = tags.map( (tags) => !layer?.source?.osmTags || layer?.source?.osmTags?.matchesProperties(tags) diff --git a/src/UI/BigComponents/MenuDrawer.svelte b/src/UI/BigComponents/MenuDrawer.svelte index a78388db2..3017462dd 100644 --- a/src/UI/BigComponents/MenuDrawer.svelte +++ b/src/UI/BigComponents/MenuDrawer.svelte @@ -279,6 +279,11 @@ + + + + + diff --git a/src/UI/BigComponents/SelectedElementTitle.svelte b/src/UI/BigComponents/SelectedElementTitle.svelte index 898f0437b..4a759362f 100644 --- a/src/UI/BigComponents/SelectedElementTitle.svelte +++ b/src/UI/BigComponents/SelectedElementTitle.svelte @@ -1,23 +1,21 @@ +{#if $allDiffs === undefined} + +{:else if $addedImages.length === 0} + No images added by this contributor +{:else} +
+ {#each $addedImages as imgDiff} +
+ +
+ {/each} +
+{/if} diff --git a/src/UI/History/AggregateView.svelte b/src/UI/History/AggregateView.svelte new file mode 100644 index 000000000..22bd1d822 --- /dev/null +++ b/src/UI/History/AggregateView.svelte @@ -0,0 +1,105 @@ + + +{#if allHistories === undefined} + +{:else if $allDiffs !== undefined} + {#each $mergedCount as diff} +

+ {#if diff.tr} + + {:else} + {diff.key} + {/if} +

+ + + + +
    + {#each diff.values as value} +
  • + {value.value} + {#if value.count > 1} + - {value.count} + {/if} +
  • + {/each} +
+
+ {/each} +{/if} diff --git a/src/UI/History/AttributedPanoramaxImage.svelte b/src/UI/History/AttributedPanoramaxImage.svelte new file mode 100644 index 000000000..83463ada6 --- /dev/null +++ b/src/UI/History/AttributedPanoramaxImage.svelte @@ -0,0 +1,13 @@ + + +{#if $image !== undefined} + +{/if} diff --git a/src/UI/History/History.svelte b/src/UI/History/History.svelte new file mode 100644 index 000000000..5cfa6b908 --- /dev/null +++ b/src/UI/History/History.svelte @@ -0,0 +1,106 @@ + + +{#if !$allGeometry || !ignoreLayersIfNoChanges.has($lastStep?.layer?.id)} + {#if $lastStep?.layer} +
+

+
+ +
+ +

+
+ {/if} + + {#if !$filteredHistory} + Loading history... + {:else if $filteredHistory.length === 0} + + {:else} + + {#each $filteredHistory as { step, layer }} + + {#if step.version === 1} + + + + + + {/if} + {#if HistoryUtils.tagHistoryDiff(step, $fullHistory).length === 0} + + + + + {:else} + {#each HistoryUtils.tagHistoryDiff(step, $fullHistory) as diff} + + + + {#if diff.oldValue === undefined} + + + {:else if diff.value === undefined } + + + {:else} + + + {/if} + + + + {/each} + {/if} + {/each} +
+

+

+
{step.version}{layer?.id ?? "Unknown layer"}{diff.key}{diff.value}{diff.key} {diff.value}{diff.key} {diff.oldValue} → {diff.value}
+ {/if} +{/if} diff --git a/src/UI/History/HistoryUtils.ts b/src/UI/History/HistoryUtils.ts new file mode 100644 index 000000000..c85aaeff2 --- /dev/null +++ b/src/UI/History/HistoryUtils.ts @@ -0,0 +1,51 @@ +import * as all_layers from "../../assets/generated/themes/personal.json" +import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" +import { OsmObject } from "../../Logic/Osm/OsmObject" + +export class HistoryUtils { + + public static readonly personalTheme = new ThemeConfig( all_layers, true) + private static ignoredLayers = new Set(["fixme"]) + public static determineLayer(properties: Record){ + return this.personalTheme.getMatchingLayer(properties, this.ignoredLayers) + } + + public static tagHistoryDiff(step: OsmObject, history: OsmObject[]): { + key: string, + value?: string, + oldValue?: string, + step: OsmObject + }[] { + const previous = history[step.version - 2] + if (!previous) { + return Object.keys(step.tags).filter(key => !key.startsWith("_") && key !== "id").map(key => ({ + key, value: step.tags[key], step + })) + } + const previousTags = previous.tags + return Object.keys(step.tags).filter(key => !key.startsWith("_") ) + .map(key => { + const value = step.tags[key] + const oldValue = previousTags[key] + return { + key, value, oldValue, step + } + }).filter(ch => ch.oldValue !== ch.value) + } + + public static fullHistoryDiff(histories: OsmObject[][], onlyShowUsername?: Set){ + const allDiffs: {key: string, oldValue?: string, value?: string}[] = [].concat(...histories.map( + history => { + const filtered = history.filter(step => !onlyShowUsername || onlyShowUsername?.has(step.tags["_last_edit:contributor"] )) + const diffs: { + key: string; + value?: string; + oldValue?: string + }[][] = filtered.map(step => HistoryUtils.tagHistoryDiff(step, history)) + return [].concat(...diffs) + } + )) + return allDiffs + } + +} diff --git a/src/UI/History/PreviouslySpiedUsers.svelte b/src/UI/History/PreviouslySpiedUsers.svelte new file mode 100644 index 000000000..4bb757b43 --- /dev/null +++ b/src/UI/History/PreviouslySpiedUsers.svelte @@ -0,0 +1,107 @@ + + + + + + + + + + + {#each $inspectedContributors as c} + + + + + + + {/each} +
+ + + + + + + Remove
+ + + {c.visitedTime} + + + + remove(c.name)} /> +
+ + + +
Labels
+ {#if $labels.length === 0} + No labels + {:else} + {#each $labels as label} +
{label} + +
+ {/each} + {/if} +
+
Create a new label
+ + +
+
+
diff --git a/src/UI/Image/AttributedImage.svelte b/src/UI/Image/AttributedImage.svelte index 02a871249..546de016d 100644 --- a/src/UI/Image/AttributedImage.svelte +++ b/src/UI/Image/AttributedImage.svelte @@ -28,22 +28,24 @@ export let imgClass: string = undefined export let state: SpecialVisualizationState = undefined export let attributionFormat: "minimal" | "medium" | "large" = "medium" - export let previewedImage: UIEventSource + export let previewedImage: UIEventSource = undefined export let canZoom = previewedImage !== undefined let loaded = false let showBigPreview = new UIEventSource(false) onDestroy( showBigPreview.addCallbackAndRun((shown) => { if (!shown) { - previewedImage.set(undefined) + previewedImage?.set(undefined) } }) ) + if(previewedImage){ onDestroy( previewedImage.addCallbackAndRun((previewedImage) => { showBigPreview.set(previewedImage?.id === image.id) }) ) + } function highlight(entered: boolean = true) { if (!entered) { @@ -82,7 +84,7 @@ class="normal-background" on:click={() => { console.log("Closing") - previewedImage.set(undefined) + previewedImage?.set(undefined) }} /> @@ -124,7 +126,7 @@ {#if canZoom && loaded}
previewedImage.set(image)} + on:click={() => previewedImage?.set(image)} >
diff --git a/src/UI/InspectorGUI.svelte b/src/UI/InspectorGUI.svelte new file mode 100644 index 000000000..ea4457688 --- /dev/null +++ b/src/UI/InspectorGUI.svelte @@ -0,0 +1,227 @@ + + +
+ +
+ +

+ +

+ load()} /> + {#if loadingData} + + {:else} + + {/if} + + + + +
+ +
+ + + + +
+ + {#if mode === "map"} + {#if $selectedElement !== undefined} + + + {/if} + +
+ +
+ {:else if mode === "table"} +
+ {#each $featuresStore as f} + + {/each} +
+ {:else if mode === "aggregate"} +
+ +
+ {:else if mode === "images"} +
+ +
+ {/if} +
+ + +
Earlier inspected constributors
+ { + username.set(e.detail); load();showPreviouslyVisited.set(false) + }} /> +
diff --git a/src/UI/InspectorGUI.ts b/src/UI/InspectorGUI.ts new file mode 100644 index 000000000..d26f12987 --- /dev/null +++ b/src/UI/InspectorGUI.ts @@ -0,0 +1,5 @@ +import InspectorGUI from "./InspectorGUI.svelte" + +new InspectorGUI({ + target: document.getElementById("main"), +}) diff --git a/src/UI/Popup/DeleteFlow/DeleteFlowState.ts b/src/UI/Popup/DeleteFlow/DeleteFlowState.ts index 9bab13c12..97bd82876 100644 --- a/src/UI/Popup/DeleteFlow/DeleteFlowState.ts +++ b/src/UI/Popup/DeleteFlow/DeleteFlowState.ts @@ -97,7 +97,7 @@ export class DeleteFlowState { if (allByMyself.data === null && useTheInternet) { // We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above const hist = this.objectDownloader - .DownloadHistory(id) + .downloadHistory(id) .map((versions) => versions.map((version) => Number(version.tags["_last_edit:contributor:uid"]) diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 424049720..2accea518 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -87,7 +87,7 @@ export interface SpecialVisualizationState { readonly geocodedImages: UIEventSource readonly searchState: SearchState - getMatchingLayer(properties: Record) + getMatchingLayer(properties: Record): LayerConfig | undefined showCurrentLocationOn(map: Store): ShowDataLayer reportError(message: string | Error | XMLHttpRequest, extramessage?: string): Promise