From 828102c547464043ceec159df8771e2a81a92b1c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 11 Oct 2021 00:54:35 +0200 Subject: [PATCH 1/7] First steps for a move flow --- UI/Input/LocationInput.ts | 50 ++++--- UI/Popup/MoveWizard.ts | 131 +++++++++++++++++++ assets/svg/license_info.json | 18 +++ assets/svg/move.svg | 7 + assets/svg/relocation.svg | 61 +++++++++ assets/themes/uk_addresses/uk_addresses.json | 4 +- langs/en.json | 10 ++ langs/themes/en.json | 5 - test.ts | 40 +++--- 9 files changed, 278 insertions(+), 48 deletions(-) create mode 100644 UI/Popup/MoveWizard.ts create mode 100644 assets/svg/move.svg create mode 100644 assets/svg/relocation.svg diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index db96cf7be7..7e167a74e1 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -1,7 +1,7 @@ import {InputElement} from "./InputElement"; import Loc from "../../Models/Loc"; import {UIEventSource} from "../../Logic/UIEventSource"; -import Minimap from "../Base/Minimap"; +import Minimap, {MinimapObj} from "../Base/Minimap"; import BaseLayer from "../../Models/BaseLayer"; import Combine from "../Base/Combine"; import Svg from "../../Svg"; @@ -14,8 +14,9 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import {BBox} from "../../Logic/BBox"; import {FixedUiElement} from "../Base/FixedUiElement"; import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; +import BaseUIElement from "../BaseUIElement"; -export default class LocationInput extends InputElement { +export default class LocationInput extends InputElement implements MinimapObj { private static readonly matchLayer = new LayerConfig( { @@ -41,6 +42,8 @@ export default class LocationInput extends InputElement { private readonly _snappedPointTags: any; private readonly _bounds: UIEventSource; public readonly _matching_layer: LayerConfig; + private readonly map: BaseUIElement & MinimapObj; + private readonly clickLocation: UIEventSource; constructor(options: { mapBackground?: UIEventSource, @@ -127,6 +130,18 @@ export default class LocationInput extends InputElement { } this.mapBackground = options.mapBackground ?? State.state?.backgroundLayer ?? new UIEventSource(AvailableBaseLayers.osmCarto) this.SetClass("block h-full") + + + this.clickLocation = new UIEventSource(undefined); + this.map = Minimap.createMiniMap( + { + location: this._centerLocation, + background: this.mapBackground, + attribution: this.mapBackground !== State.state?.backgroundLayer, + lastClickLocation: this.clickLocation, + bounds: this._bounds + } + ) } GetValue(): UIEventSource { @@ -139,19 +154,8 @@ export default class LocationInput extends InputElement { protected InnerConstructElement(): HTMLElement { try { - const clickLocation = new UIEventSource(undefined); - const map = Minimap.createMiniMap( - { - location: this._centerLocation, - background: this.mapBackground, - attribution: this.mapBackground !== State.state?.backgroundLayer, - lastClickLocation: clickLocation, - bounds: this._bounds - } - ) - clickLocation.addCallbackAndRunD(location => this._centerLocation.setData(location)) - - map.installBounds(0.15, true); + this.clickLocation.addCallbackAndRunD(location => this._centerLocation.setData(location)) + this.map.installBounds(0.15, true); if (this._snapTo !== undefined) { @@ -160,7 +164,7 @@ export default class LocationInput extends InputElement { features: new StaticFeatureSource(this._snapTo, true), enablePopups: false, zoomToFeatures: false, - leafletMap: map.leafletMap, + leafletMap: this.map.leafletMap, layers: State.state.filteredLayers } ) @@ -175,13 +179,13 @@ export default class LocationInput extends InputElement { features: new StaticFeatureSource(matchPoint, true), enablePopups: false, zoomToFeatures: false, - leafletMap: map.leafletMap, + leafletMap: this.map.leafletMap, layerToShow: this._matching_layer }) } this.mapBackground.map(layer => { - const leaflet = map.leafletMap.data + const leaflet = this.map.leafletMap.data if (leaflet === undefined || layer === undefined) { return; } @@ -190,7 +194,7 @@ export default class LocationInput extends InputElement { leaflet.setMinZoom(layer.max_zoom - 2) leaflet.setZoom(layer.max_zoom - 1) - }, [map.leafletMap]) + }, [this.map.leafletMap]) const animatedHand = Svg.hand_ui() .SetStyle("width: 2rem; height: unset;") @@ -209,7 +213,7 @@ export default class LocationInput extends InputElement { .SetClass("block w-0 h-0 z-10 relative") .SetStyle("left: calc(50% + 3rem); top: calc(50% + 2rem); opacity: 0.7"), - map + this.map .SetClass("z-0 relative block w-full h-full bg-gray-100") ]).ConstructElement(); @@ -219,4 +223,10 @@ export default class LocationInput extends InputElement { } } + readonly leafletMap: UIEventSource = this.map.leafletMap + + installBounds(factor: number | BBox, showRange?: boolean): void { + this.map.installBounds(factor, showRange) + } + } \ No newline at end of file diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts new file mode 100644 index 0000000000..f82e579caa --- /dev/null +++ b/UI/Popup/MoveWizard.ts @@ -0,0 +1,131 @@ +import {SubtleButton} from "../Base/SubtleButton"; +import Combine from "../Base/Combine"; +import Svg from "../../Svg"; +import {OsmConnection} from "../../Logic/Osm/OsmConnection"; +import Toggle from "../Input/Toggle"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import Translations from "../i18n/Translations"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import {Translation} from "../i18n/Translation"; +import BaseUIElement from "../BaseUIElement"; +import LocationInput from "../Input/LocationInput"; +import Loc from "../../Models/Loc"; +import {GeoOperations} from "../../Logic/GeoOperations"; + +interface MoveReason {text: Translation | string, + icon: string | BaseUIElement, + changesetCommentValue: string, + lockBounds: true | boolean, + background: undefined | "map" | "photo", + startZoom: number} + +export default class MoveWizard extends Toggle { + /** + * The UI-element which helps moving a point + */ + constructor( + featureToMove : any, + state: { + osmConnection: OsmConnection, + featureSwitchUserbadge: UIEventSource + },options?: { + reasons?: {text: Translation | string, + icon: string | BaseUIElement, + changesetCommentValue: string, + lockBounds?: true | boolean, + background?: undefined | "map" | "photo", + startZoom?: number | 15 + }[] + disableDefaultReasons?: false | boolean + + }) { + //State.state.featureSwitchUserbadge + // state = State.state + options = options ?? {} + const t = Translations.t.move + const loginButton = new Toggle( + t.loginToMove.Clone() .SetClass("btn").onClick(() => state.osmConnection.AttemptLogin()), + undefined, + state.featureSwitchUserbadge + ) + + const currentStep = new UIEventSource<"start" | "reason" | "pick_location">("start") + const moveReason = new UIEventSource(undefined) + const moveButton = new SubtleButton( + Svg.move_ui(), + t.inviteToMove.Clone() + ).onClick(() => { + currentStep.setData("reason") + }) + + + const reasons : MoveReason[] = [] + if(!options.disableDefaultReasons){ + reasons.push({ + text: t.reasonRelocation.Clone(), + icon: Svg.relocation_svg(), + changesetCommentValue: "relocated", + lockBounds: false, + background: undefined, + startZoom: 12 + }) + + reasons.push({ + text: t.reasonInaccurate.Clone(), + icon: Svg.crosshair_svg(), + changesetCommentValue: "improve_accuracy", + lockBounds: true, + background: "photo", + startZoom: 17 + }) + } + for (const reason of options.reasons ?? []) { + reasons.push({ + text: reason.text, + icon: reason.icon, + changesetCommentValue: reason.changesetCommentValue, + lockBounds: reason.lockBounds ?? true, + background: reason.background, + startZoom: reason.startZoom ?? 15 + }) + } + + const selectReason = new Combine(reasons.map(r => new SubtleButton(r.icon, r.text).onClick(() => { + moveReason.setData(r) + currentStep.setData("pick_location") + }))) + + const cancelButton = new SubtleButton(Svg.close_svg(), t.cancel).onClick(() => currentStep.setData("start")) + + + + const [lon, lat] = GeoOperations.centerpointCoordinates(featureToMove) + const locationInput = new LocationInput({ + centerLocation: new UIEventSource({ + lon: lon, + lat: lat, + zoom: moveReason.data.startZoom + }), + + }) + locationInput.SetStyle("height: 25rem") + super( + new VariableUiElement(currentStep.map(currentStep => { + switch (currentStep){ + case "start": + return moveButton; + case "reason": + return new Combine([selectReason, cancelButton]); + case "pick_location": + return new Combine([locationInput]) + } + + + + + })), + loginButton, + state.osmConnection.isLoggedIn + ) + } +} \ No newline at end of file diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index b3b653d4fc..cfbbfc32a7 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -955,6 +955,16 @@ ], "sources": [] }, + { + "path": "move.svg", + "license": "CC-BY-SA 3.0 Unported", + "authors": [ + "MGalloway (WMF)" + ], + "sources": [ + "https://commons.wikimedia.org/wiki/File:Move_icon.svg" + ] + }, { "path": "no_checkmark.svg", "license": "CC0; trivial", @@ -1137,6 +1147,14 @@ "authors": [], "sources": [] }, + { + "path": "relocation.svg", + "license": "CC0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, { "path": "ring.svg", "license": "CC0; trivial", diff --git a/assets/svg/move.svg b/assets/svg/move.svg new file mode 100644 index 0000000000..806cb7d634 --- /dev/null +++ b/assets/svg/move.svg @@ -0,0 +1,7 @@ + + + + move + + + diff --git a/assets/svg/relocation.svg b/assets/svg/relocation.svg new file mode 100644 index 0000000000..c9caaffcb4 --- /dev/null +++ b/assets/svg/relocation.svg @@ -0,0 +1,61 @@ + +image/svg+xml + + \ No newline at end of file diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index 296c125160..67cf0c6007 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -151,7 +151,9 @@ }, "freeform": { "key": "addr:housenumber", - "addExtraTags": ["nohousenumber="] + "addExtraTags": [ + "nohousenumber=" + ] }, "mappings": [ { diff --git a/langs/en.json b/langs/en.json index 9282ad4465..27496d0176 100644 --- a/langs/en.json +++ b/langs/en.json @@ -36,6 +36,16 @@ "splitTitle": "Choose on the map where to split this road", "hasBeenSplit": "This way has been split" }, + "move": { + "loginToMove": "You must be logged in to move a point", + "inviteToMove": "Move this point", + "selectReason": "Why do you move this object?", + "reasonRelocation": "The object has been relocated to a totally different location", + "reasonInaccurate": "The location of this object is inaccurate and should be moved a few meter", + "onlyPoints": "Only points can be moved", + "isWay": "This feature is a way", + "cancel": "Cancel move" + }, "delete": { "delete": "Delete", "cancel": "Cancel", diff --git a/langs/themes/en.json b/langs/themes/en.json index 9dcb4b54af..e1d8f67241 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -1362,11 +1362,6 @@ "title": { "render": "Known address" } - }, - "2": { - "title": { - "render": "{name}" - } } }, "shortDescription": "Help to build an open dataset of UK addresses", diff --git a/test.ts b/test.ts index e6f2a99662..e9b3388c97 100644 --- a/test.ts +++ b/test.ts @@ -1,26 +1,22 @@ -import FeatureInfoBox from "./UI/Popup/FeatureInfoBox"; -import {UIEventSource} from "./Logic/UIEventSource"; -import AllKnownLayers from "./Customizations/AllKnownLayers"; +import MoveWizard from "./UI/Popup/MoveWizard"; import State from "./State"; import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; +import MinimapImplementation from "./UI/Base/MinimapImplementation"; -State.state = new State(AllKnownLayouts.allKnownLayouts.get("charging_stations")) -State.state.changes.pendingChanges.setData([]) -const geojson = { - type: "Feature", - geometry: { - type: "Point", - coordinates: [51.0, 4] - }, - properties: - { - id: "node/42", - amenity: "charging_station", - } + +State.state = new State(AllKnownLayouts.allKnownLayouts.get("bookcases")) +const feature = { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [ + 4.21875, + 50.958426723359935 + ] + } } -State.state.allElements.addOrGetElement(geojson) -const tags = State.state.allElements.getEventSourceById("node/42") -new FeatureInfoBox( - tags, - AllKnownLayers.sharedLayers.get("charging_station") -).AttachTo("maindiv") \ No newline at end of file +MinimapImplementation.initialize() +new MoveWizard( + feature, + State.state).AttachTo("maindiv") \ No newline at end of file From 95867635e00651666b0fa4a7c44546eaac19643d Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 13 Oct 2021 03:09:37 +0200 Subject: [PATCH 2/7] Further work on the move flow --- Logic/Osm/Actions/ChangeLocationAction.ts | 17 ++ UI/BigComponents/SimpleAddUI.ts | 1 + UI/Input/LocationInput.ts | 16 +- UI/Popup/MoveWizard.ts | 177 ++++++++++---- assets/svg/license_info.json | 16 ++ assets/svg/move_confirm.svg | 281 +++++++++++++++++++++ assets/svg/move_not_allowed.svg | 285 ++++++++++++++++++++++ css/index-tailwind-output.css | 16 +- langs/en.json | 13 +- test.ts | 4 +- 10 files changed, 763 insertions(+), 63 deletions(-) create mode 100644 Logic/Osm/Actions/ChangeLocationAction.ts create mode 100644 assets/svg/move_confirm.svg create mode 100644 assets/svg/move_not_allowed.svg diff --git a/Logic/Osm/Actions/ChangeLocationAction.ts b/Logic/Osm/Actions/ChangeLocationAction.ts new file mode 100644 index 0000000000..56234e67e1 --- /dev/null +++ b/Logic/Osm/Actions/ChangeLocationAction.ts @@ -0,0 +1,17 @@ +import {ChangeDescription} from "./ChangeDescription"; +import OsmChangeAction from "./OsmChangeAction"; +import {Changes} from "../Changes"; + +export default class ChangeLocationAction extends OsmChangeAction { + constructor(id: string, newLonLat: [number, number], meta: { + theme: string, + reason: string + }) { + super(); + throw "TODO" + } + + protected CreateChangeDescriptions(changes: Changes): Promise { + return Promise.resolve([]); + } +} \ No newline at end of file diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index ad6a73ec52..3c39266b93 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -150,6 +150,7 @@ export default class SimpleAddUI extends Toggle { maxSnapDistance: preset.preciseInput.maxSnapDistance, bounds: mapBounds }) + preciseInput.installBounds(0.15, true) preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 7e167a74e1..53715562bf 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -43,9 +43,13 @@ export default class LocationInput extends InputElement implements MinimapO private readonly _bounds: UIEventSource; public readonly _matching_layer: LayerConfig; private readonly map: BaseUIElement & MinimapObj; + public readonly leafletMap: UIEventSource + private readonly clickLocation: UIEventSource; + private readonly _minZoom: number; constructor(options: { + minZoom?: number, mapBackground?: UIEventSource, snapTo?: UIEventSource<{ feature: any }[]>, maxSnapDistance?: number, @@ -60,6 +64,7 @@ export default class LocationInput extends InputElement implements MinimapO this._centerLocation = options.centerLocation; this._snappedPointTags = options.snappedPointTags this._bounds = options.bounds; + this._minZoom = options.minZoom if (this._snapTo === undefined) { this._value = this._centerLocation; } else { @@ -142,6 +147,7 @@ export default class LocationInput extends InputElement implements MinimapO bounds: this._bounds } ) + this.leafletMap = this.map.leafletMap } GetValue(): UIEventSource { @@ -154,10 +160,9 @@ export default class LocationInput extends InputElement implements MinimapO protected InnerConstructElement(): HTMLElement { try { + const self = this; this.clickLocation.addCallbackAndRunD(location => this._centerLocation.setData(location)) - this.map.installBounds(0.15, true); - - if (this._snapTo !== undefined) { + if (this._snapTo !== undefined) { // Show the lines to snap to new ShowDataMultiLayer({ @@ -191,7 +196,7 @@ export default class LocationInput extends InputElement implements MinimapO } leaflet.setMaxZoom(layer.max_zoom) - leaflet.setMinZoom(layer.max_zoom - 2) + leaflet.setMinZoom(self._minZoom ?? layer.max_zoom - 2) leaflet.setZoom(layer.max_zoom - 1) }, [this.map.leafletMap]) @@ -223,8 +228,7 @@ export default class LocationInput extends InputElement implements MinimapO } } - readonly leafletMap: UIEventSource = this.map.leafletMap - + installBounds(factor: number | BBox, showRange?: boolean): void { this.map.installBounds(factor, showRange) } diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index f82e579caa..b59b56bc75 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -11,45 +11,47 @@ import BaseUIElement from "../BaseUIElement"; import LocationInput from "../Input/LocationInput"; import Loc from "../../Models/Loc"; import {GeoOperations} from "../../Logic/GeoOperations"; +import {OsmObject} from "../../Logic/Osm/OsmObject"; +import {Changes} from "../../Logic/Osm/Changes"; +import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; -interface MoveReason {text: Translation | string, +interface MoveReason { + text: Translation | string, icon: string | BaseUIElement, changesetCommentValue: string, lockBounds: true | boolean, background: undefined | "map" | "photo", - startZoom: number} + startZoom: number, + minZoom: number +} export default class MoveWizard extends Toggle { /** * The UI-element which helps moving a point */ constructor( - featureToMove : any, + featureToMove: any, state: { - osmConnection: OsmConnection, - featureSwitchUserbadge: UIEventSource - },options?: { - reasons?: {text: Translation | string, - icon: string | BaseUIElement, - changesetCommentValue: string, - lockBounds?: true | boolean, - background?: undefined | "map" | "photo", - startZoom?: number | 15 - }[] - disableDefaultReasons?: false | boolean - - }) { - //State.state.featureSwitchUserbadge - // state = State.state + osmConnection: OsmConnection, + featureSwitchUserbadge: UIEventSource, + changes: Changes, + layoutToUse: LayoutConfig + }, options?: { + reasons?: MoveReason[] + disableDefaultReasons?: false | boolean + + }) { options = options ?? {} - const t = Translations.t.move + + const t = Translations.t.move const loginButton = new Toggle( - t.loginToMove.Clone() .SetClass("btn").onClick(() => state.osmConnection.AttemptLogin()), + t.loginToMove.Clone().SetClass("btn").onClick(() => state.osmConnection.AttemptLogin()), undefined, state.featureSwitchUserbadge ) - - const currentStep = new UIEventSource<"start" | "reason" | "pick_location">("start") + + const currentStep = new UIEventSource<"start" | "reason" | "pick_location" | "moved">("start") const moveReason = new UIEventSource(undefined) const moveButton = new SubtleButton( Svg.move_ui(), @@ -57,26 +59,35 @@ export default class MoveWizard extends Toggle { ).onClick(() => { currentStep.setData("reason") }) - - - const reasons : MoveReason[] = [] - if(!options.disableDefaultReasons){ + + const moveAgainButton = new SubtleButton( + Svg.move_ui(), + t.inviteToMoveAgain.Clone() + ).onClick(() => { + currentStep.setData("reason") + }) + + + const reasons: MoveReason[] = [] + if (!options.disableDefaultReasons) { reasons.push({ text: t.reasonRelocation.Clone(), icon: Svg.relocation_svg(), changesetCommentValue: "relocated", lockBounds: false, background: undefined, - startZoom: 12 + startZoom: 12, + minZoom: 6 }) - + reasons.push({ text: t.reasonInaccurate.Clone(), icon: Svg.crosshair_svg(), changesetCommentValue: "improve_accuracy", lockBounds: true, background: "photo", - startZoom: 17 + startZoom: 17, + minZoom: 16 }) } for (const reason of options.reasons ?? []) { @@ -86,46 +97,112 @@ export default class MoveWizard extends Toggle { changesetCommentValue: reason.changesetCommentValue, lockBounds: reason.lockBounds ?? true, background: reason.background, - startZoom: reason.startZoom ?? 15 + startZoom: reason.startZoom ?? 15, + minZoom: reason.minZoom }) } - + const selectReason = new Combine(reasons.map(r => new SubtleButton(r.icon, r.text).onClick(() => { moveReason.setData(r) currentStep.setData("pick_location") }))) - + const cancelButton = new SubtleButton(Svg.close_svg(), t.cancel).onClick(() => currentStep.setData("start")) - - - + + const [lon, lat] = GeoOperations.centerpointCoordinates(featureToMove) - const locationInput = new LocationInput({ - centerLocation: new UIEventSource({ + const locationInput = moveReason.map(reason => { + if (reason === undefined) { + return undefined + } + const loc = new UIEventSource({ lon: lon, lat: lat, - zoom: moveReason.data.startZoom - }), - - }) - locationInput.SetStyle("height: 25rem") - super( + zoom: reason?.startZoom ?? 16 + }) + + + const locationInput = new LocationInput({ + minZoom: reason.minZoom, + centerLocation: loc + }) + + if (reason.lockBounds) { + locationInput.installBounds(0.05, true) + } + + locationInput.SetStyle("height: 17.5rem") + + const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove) + confirmMove.onClick(() => { + state.changes.applyAction(new ChangeLocationAction(featureToMove.properties.id, [locationInput.GetValue().data.lon, locationInput.GetValue().data.lat], { + reason: Translations.WT(reason.text).textFor("en"), + theme: state.layoutToUse.icon + })) + currentStep.setData("moved") + }) + const zoomInFurhter = t.zoomInFurther.Clone().SetClass("alert block m-6") + return new Combine([ + locationInput, + new Toggle(confirmMove, zoomInFurhter, locationInput.GetValue().map(l => l.zoom >= 19)) + ]).SetClass("flex flex-col") + }); + + const dialogClasses = "p-2 md:p-4 m-2 border border-gray-400 rounded-xl flex flex-col" + + const moveFlow = new Toggle( new VariableUiElement(currentStep.map(currentStep => { - switch (currentStep){ + switch (currentStep) { case "start": return moveButton; case "reason": - return new Combine([selectReason, cancelButton]); + return new Combine([t.whyMove.Clone().SetClass("text-lg font-bold"), selectReason, cancelButton]).SetClass(dialogClasses); case "pick_location": - return new Combine([locationInput]) + return new Combine([t.moveTitle.Clone().SetClass("text-lg font-bold"), new VariableUiElement(locationInput), cancelButton]).SetClass(dialogClasses) + case "moved": + return new Combine([t.pointIsMoved.Clone().SetClass("thanks"), moveAgainButton]).SetClass("flex flex-col"); + } - - - - + + })), loginButton, state.osmConnection.isLoggedIn ) + let id = featureToMove.properties.id + const backend = state.osmConnection._oauth_config.url + if (id.startsWith(backend)) { + id = id.substring(backend.length) + } + + const moveDisallowedReason = new UIEventSource(undefined) + if (id.startsWith("way")) { + moveDisallowedReason.setData(t.isWay.Clone()) + } else if (id.startsWith("relation")) { + moveDisallowedReason.setData(t.isRelation.Clone()) + } else { + + OsmObject.DownloadReferencingWays(id).then(referencing => { + if (referencing.length > 0) { + console.log("Got a referencing way, move not allowed") + moveDisallowedReason.setData(t.partOfAWay.Clone()) + } + }) + OsmObject.DownloadReferencingRelations(id).then(partOf => { + if(partOf.length > 0){ + moveDisallowedReason.setData(t.partOfRelation.Clone()) + } + }) + } + super( + moveFlow, + new Combine([ + Svg.move_not_allowed_svg().SetStyle("height: 2rem"), + new Combine([t.cannotBeMoved.Clone(), + new VariableUiElement(moveDisallowedReason).SetClass("subtle") + ]).SetClass("flex flex-col") + ]).SetClass("flex"), + moveDisallowedReason.map(r => r === undefined) + ) } } \ No newline at end of file diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index cfbbfc32a7..ce03d2a3e7 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -965,6 +965,22 @@ "https://commons.wikimedia.org/wiki/File:Move_icon.svg" ] }, + { + "path": "move_confirm.svg", + "license": "CC0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, + { + "path": "move_not_allowed.svg", + "license": "CC0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, { "path": "no_checkmark.svg", "license": "CC0; trivial", diff --git a/assets/svg/move_confirm.svg b/assets/svg/move_confirm.svg new file mode 100644 index 0000000000..5aa8ac888d --- /dev/null +++ b/assets/svg/move_confirm.svg @@ -0,0 +1,281 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + move + + + + + + + diff --git a/assets/svg/move_not_allowed.svg b/assets/svg/move_not_allowed.svg new file mode 100644 index 0000000000..1abd15a036 --- /dev/null +++ b/assets/svg/move_not_allowed.svg @@ -0,0 +1,285 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + move + + + + + + + + diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 0549527564..e0b8bedad3 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -820,6 +820,10 @@ video { margin: 0.75rem; } +.m-6 { + margin: 1.5rem; +} + .m-4 { margin: 1rem; } @@ -892,6 +896,10 @@ video { margin-left: 0.5rem; } +.mb-4 { + margin-bottom: 1rem; +} + .mt-4 { margin-top: 1rem; } @@ -912,10 +920,6 @@ video { margin-bottom: 0.25rem; } -.mb-4 { - margin-bottom: 1rem; -} - .box-border { box-sizing: border-box; } @@ -2365,6 +2369,10 @@ li::marker { padding: 0.5rem; } + .md\:p-4 { + padding: 1rem; + } + .md\:pt-4 { padding-top: 1rem; } diff --git a/langs/en.json b/langs/en.json index 78a21bdcf4..2924b1e63d 100644 --- a/langs/en.json +++ b/langs/en.json @@ -250,11 +250,20 @@ "move": { "loginToMove": "You must be logged in to move a point", "inviteToMove": "Move this point", + "inviteToMoveAgain": "Move this point again", + "moveTitle": "Move this point", + "whyMove": "Why do you want to move this point?", + "confirmMove": "Move here", + "pointIsMoved": "The point has been moved", + "zoomInFurther": "Zoom in further to confirm this move", "selectReason": "Why do you move this object?", "reasonRelocation": "The object has been relocated to a totally different location", "reasonInaccurate": "The location of this object is inaccurate and should be moved a few meter", - "onlyPoints": "Only points can be moved", - "isWay": "This feature is a way", + "cannotBeMoved": "This feature cannot be moved.", + "isWay": "This feature is a way. Use another OpenStreetMap editor to move it.", + "isRelation": "This feature is a relation and can not be moved", + "partOfAWay": "This feature is part of another way. Use another editor to move it", + "partOfRelation": "This feature is part of a relation. Use another editor to move it", "cancel": "Cancel move" } } \ No newline at end of file diff --git a/test.ts b/test.ts index e9b3388c97..8b7d6c0651 100644 --- a/test.ts +++ b/test.ts @@ -7,7 +7,9 @@ import MinimapImplementation from "./UI/Base/MinimapImplementation"; State.state = new State(AllKnownLayouts.allKnownLayouts.get("bookcases")) const feature = { "type": "Feature", - "properties": {}, + "properties": { + id: "node/14925464" + }, "geometry": { "type": "Point", "coordinates": [ From 0a3eb966c1229773f4f3c409e9ca3aff5abb37b0 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Oct 2021 01:16:38 +0200 Subject: [PATCH 3/7] Add moveLocation change description --- Logic/Osm/Actions/ChangeDescription.ts | 2 +- Logic/Osm/Actions/ChangeLocationAction.ts | 29 +++++++++++++++++++++-- langs/themes/en.json | 5 ++++ langs/themes/nl.json | 5 ++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Logic/Osm/Actions/ChangeDescription.ts b/Logic/Osm/Actions/ChangeDescription.ts index 345087ef2a..44804bc327 100644 --- a/Logic/Osm/Actions/ChangeDescription.ts +++ b/Logic/Osm/Actions/ChangeDescription.ts @@ -16,7 +16,7 @@ export interface ChangeDescription { /** * The type of the change */ - changeType: "answer" | "create" | "split" | "delete" | string + changeType: "answer" | "create" | "split" | "delete" | "move" | string /** * THe motivation for the change, e.g. 'deleted because does not exist anymore' */ diff --git a/Logic/Osm/Actions/ChangeLocationAction.ts b/Logic/Osm/Actions/ChangeLocationAction.ts index 56234e67e1..506dc65398 100644 --- a/Logic/Osm/Actions/ChangeLocationAction.ts +++ b/Logic/Osm/Actions/ChangeLocationAction.ts @@ -3,15 +3,40 @@ import OsmChangeAction from "./OsmChangeAction"; import {Changes} from "../Changes"; export default class ChangeLocationAction extends OsmChangeAction { + private readonly _id: number; + private readonly _newLonLat: [number, number]; + private readonly _meta: { theme: string; reason: string }; + constructor(id: string, newLonLat: [number, number], meta: { theme: string, reason: string }) { super(); + if (!id.startsWith("node/")) { + throw "Invalid ID: only 'node/number' is accepted" + } + this._id = Number(id.substring("node/".length)) + this._newLonLat = newLonLat; + this._meta = meta; throw "TODO" } - protected CreateChangeDescriptions(changes: Changes): Promise { - return Promise.resolve([]); + protected async CreateChangeDescriptions(changes: Changes): Promise { + + const d: ChangeDescription = { + changes: { + lat: this._newLonLat[1], + lon: this._newLonLat[0] + }, + type: "node", + id: this._id, meta: { + changeType: "move", + theme: this._meta.theme, + specialMotivation: this._meta.reason + } + + } + + return [d] } } \ No newline at end of file diff --git a/langs/themes/en.json b/langs/themes/en.json index d5976735da..82008ccbdf 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -750,6 +750,11 @@ "override": { "name": "Streets without etymology information" } + }, + "2": { + "override": { + "name": "Parks and forests without etymology information" + } } }, "shortDescription": "What is the origin of a toponym?", diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 7d6fbf6d7c..e09f723b95 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -631,6 +631,11 @@ "override": { "name": "Straten zonder etymologische informatie" } + }, + "2": { + "override": { + "name": "Parken en bossen zonder etymologische informatie" + } } }, "shortDescription": "Wat is de oorsprong van een plaatsnaam?", From 7e2b73ac5d597eb1c96e5543568580d29792fb70 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Oct 2021 03:46:09 +0200 Subject: [PATCH 4/7] Add move option, enable move and delete option on most layers --- Logic/Osm/Actions/ChangeLocationAction.ts | 1 - Logic/Osm/OsmObject.ts | 11 +- Models/ThemeConfig/Json/LayerConfigJson.ts | 13 ++ Models/ThemeConfig/Json/MoveConfigJson.ts | 12 ++ Models/ThemeConfig/LayerConfig.ts | 50 +++++--- Models/ThemeConfig/MoveConfig.ts | 17 +++ UI/Input/LocationInput.ts | 12 +- UI/Popup/FeatureInfoBox.ts | 14 +++ UI/Popup/MoveWizard.ts | 114 ++++++++++-------- Utils.ts | 13 ++ assets/layers/bench/bench.json | 15 ++- .../bicycle_tube_vending_machine.json | 15 ++- .../layers/bike_cleaning/bike_cleaning.json | 15 ++- assets/layers/bike_parking/bike_parking.json | 15 ++- .../bike_repair_station.json | 15 ++- assets/layers/binocular/binocular.json | 13 ++ assets/layers/birdhide/birdhide.json | 14 ++- assets/layers/cafe_pub/cafe_pub.json | 8 +- .../charging_station/charging_station.json | 15 ++- .../layers/defibrillator/defibrillator.json | 15 ++- .../layers/drinking_water/drinking_water.json | 15 ++- assets/layers/food/food.json | 8 +- assets/layers/ghost_bike/ghost_bike.json | 15 ++- .../information_board/information_board.json | 17 ++- assets/layers/map/map.json | 15 ++- .../observation_tower/observation_tower.json | 6 +- assets/layers/parking/parking.json | 15 ++- assets/layers/picnic_table/picnic_table.json | 15 ++- assets/layers/toilet/toilet.json | 15 ++- assets/layers/waste_basket/waste_basket.json | 15 ++- langs/en.json | 12 +- package.json | 2 +- test.ts | 11 +- 33 files changed, 454 insertions(+), 104 deletions(-) create mode 100644 Models/ThemeConfig/Json/MoveConfigJson.ts create mode 100644 Models/ThemeConfig/MoveConfig.ts diff --git a/Logic/Osm/Actions/ChangeLocationAction.ts b/Logic/Osm/Actions/ChangeLocationAction.ts index 506dc65398..9bb53b4274 100644 --- a/Logic/Osm/Actions/ChangeLocationAction.ts +++ b/Logic/Osm/Actions/ChangeLocationAction.ts @@ -18,7 +18,6 @@ export default class ChangeLocationAction extends OsmChangeAction { this._id = Number(id.substring("node/".length)) this._newLonLat = newLonLat; this._meta = meta; - throw "TODO" } protected async CreateChangeDescriptions(changes: Changes): Promise { diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index fe4f48abcc..91b4b6e89e 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -59,14 +59,13 @@ export abstract class OsmObject { static async DownloadPropertiesOf(id: string): Promise { const splitted = id.split("/"); - const type = splitted[0]; const idN = Number(splitted[1]); if (idN < 0) { return undefined; } const url = `${OsmObject.backendURL}api/0.6/${id}`; - const rawData = await Utils.downloadJson(url) + const rawData = await Utils.downloadJsonCached(url, 1000) return rawData.elements[0].tags } @@ -80,7 +79,7 @@ export abstract class OsmObject { const full = (id.startsWith("way")) ? "/full" : ""; const url = `${OsmObject.backendURL}api/0.6/${id}${full}`; - const rawData = await Utils.downloadJson(url) + const rawData = await Utils.downloadJsonCached(url, 1000) // A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way) const parsed = OsmObject.ParseObjects(rawData.elements); // Lets fetch the object we need @@ -105,7 +104,7 @@ export abstract class OsmObject { * Beware: their geometry will be incomplete! */ public static DownloadReferencingWays(id: string): Promise { - return Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/ways`).then( + return Utils.downloadJsonCached(`${OsmObject.backendURL}api/0.6/${id}/ways`, 60 * 1000).then( data => { return data.elements.map(wayInfo => { const way = new OsmWay(wayInfo.id) @@ -121,7 +120,7 @@ export abstract class OsmObject { * Beware: their geometry will be incomplete! */ public static async DownloadReferencingRelations(id: string): Promise { - const data = await Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/relations`) + const data = await Utils.downloadJsonCached(`${OsmObject.backendURL}api/0.6/${id}/relations`, 60 * 1000) return data.elements.map(wayInfo => { const rel = new OsmRelation(wayInfo.id) rel.LoadData(wayInfo) @@ -139,7 +138,7 @@ export abstract class OsmObject { const idN = Number(splitted[1]); const src = new UIEventSource([]); OsmObject.historyCache.set(id, src); - Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${type}/${idN}/history`).then(data => { + Utils.downloadJsonCached(`${OsmObject.backendURL}api/0.6/${type}/${idN}/history`, 10 * 60 * 1000).then(data => { const elements: any[] = data.elements; const osmObjects: OsmObject[] = [] for (const element of elements) { diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts index f337b1a198..69d1b972ed 100644 --- a/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -3,6 +3,7 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; import FilterConfigJson from "./FilterConfigJson"; import {DeleteConfigJson} from "./DeleteConfigJson"; import UnitConfigJson from "./UnitConfigJson"; +import MoveConfigJson from "./MoveConfigJson"; /** * Configuration for a single layer @@ -314,6 +315,18 @@ export interface LayerConfigJson { */ deletion?: boolean | DeleteConfigJson + /** + * Indicates if a point can be moved and configures the modalities. + * + * A feature can be moved by MapComplete if: + * + * - It is a point + * - The point is _not_ part of a way or a a relation. + * + * Off by default. Can be enabled by setting this flag or by configuring. + */ + allowMove?: boolean | MoveConfigJson + /** * IF set, a 'split this road' button is shown */ diff --git a/Models/ThemeConfig/Json/MoveConfigJson.ts b/Models/ThemeConfig/Json/MoveConfigJson.ts new file mode 100644 index 0000000000..3ae9bf70c2 --- /dev/null +++ b/Models/ThemeConfig/Json/MoveConfigJson.ts @@ -0,0 +1,12 @@ +export default interface MoveConfigJson { + /** + * One default reason to move a point is to improve accuracy. + * Set to false to disable this reason + */ + enableImproveAccuracy?: true | boolean + /** + * One default reason to move a point is because it has relocated + * Set to false to disable this reason + */ + enableRelocation?: true | boolean +} \ No newline at end of file diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index a2e55d71c9..09f3f0fd25 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -19,6 +19,7 @@ import {Unit} from "../Unit"; import DeleteConfig from "./DeleteConfig"; import Svg from "../../Svg"; import Img from "../../UI/Base/Img"; +import MoveConfig from "./MoveConfig"; export default class LayerConfig { static WAYHANDLING_DEFAULT = 0; @@ -49,6 +50,7 @@ export default class LayerConfig { wayHandling: number; public readonly units: Unit[]; public readonly deletion: DeleteConfig | null; + public readonly allowMove: MoveConfig | null public readonly allowSplit: boolean presets: PresetConfig[]; @@ -67,7 +69,7 @@ export default class LayerConfig { this.id = json.id; this.allowSplit = json.allowSplit ?? false; this.name = Translations.T(json.name, context + ".name"); - this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`))) + this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`))) if (json.description !== undefined) { if (Object.keys(json.description).length === 0) { @@ -138,11 +140,11 @@ export default class LayerConfig { const key = kv.substring(0, index); const code = kv.substring(index + 1); - try{ - - new Function("feat", "return " + code + ";"); - }catch(e){ - throw `Invalid function definition: code ${code} is invalid:${e} (at ${context})` + try { + + new Function("feat", "return " + code + ";"); + } catch (e) { + throw `Invalid function definition: code ${code} is invalid:${e} (at ${context})` } @@ -155,8 +157,8 @@ export default class LayerConfig { this.minzoom = json.minzoom ?? 0; this.minzoomVisible = json.minzoomVisible ?? this.minzoom; this.wayHandling = json.wayHandling ?? 0; - if(json.presets !== undefined && json.presets?.map === undefined){ - throw "Presets should be a list of items (at "+context+")" + if (json.presets !== undefined && json.presets?.map === undefined) { + throw "Presets should be a list of items (at " + context + ")" } this.presets = (json.presets ?? []).map((pr, i) => { @@ -291,21 +293,21 @@ export default class LayerConfig { } this.tagRenderings = trs(json.tagRenderings, false); - - const missingIds = json.tagRenderings?.filter(tr => typeof tr !== "string" && tr["builtin"] === undefined && tr["id"] === undefined) ?? []; - if(missingIds.length > 0 && official){ - console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds) - throw "Missing ids in tagrenderings" - } - + const missingIds = json.tagRenderings?.filter(tr => typeof tr !== "string" && tr["builtin"] === undefined && tr["id"] === undefined) ?? []; + + if (missingIds.length > 0 && official) { + console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds) + throw "Missing ids in tagrenderings" + } + this.filters = (json.filter ?? []).map((option, i) => { return new FilterConfig(option, `${context}.filter-[${i}]`) }); - - if(json["filters"] !== undefined){ - throw "Error in "+context+": use 'filter' instead of 'filters'" - } + + if (json["filters"] !== undefined) { + throw "Error in " + context + ": use 'filter' instead of 'filters'" + } const titleIcons = []; const defaultIcons = [ @@ -369,6 +371,16 @@ export default class LayerConfig { this.deletion = new DeleteConfig(json.deletion, `${context}.deletion`); } + this.allowMove = null + if (json.allowMove === false) { + this.allowMove = null; + } else if (json.allowMove === true) { + this.allowMove = new MoveConfig({}, context + ".allowMove") + } else if (json.allowMove !== undefined && json.allowMove !== false) { + this.allowMove = new MoveConfig(json.allowMove, context + ".allowMove") + } + + if (json["showIf"] !== undefined) { throw ( "Invalid key on layerconfig " + diff --git a/Models/ThemeConfig/MoveConfig.ts b/Models/ThemeConfig/MoveConfig.ts new file mode 100644 index 0000000000..b2cb082601 --- /dev/null +++ b/Models/ThemeConfig/MoveConfig.ts @@ -0,0 +1,17 @@ +import MoveConfigJson from "./Json/MoveConfigJson"; + +export default class MoveConfig { + + public readonly enableImproveAccuracy: boolean + public readonly enableRelocation: boolean + + constructor(json: MoveConfigJson, context: string) { + this.enableImproveAccuracy = json.enableImproveAccuracy ?? true + this.enableRelocation = json.enableRelocation ?? true + if (!(this.enableRelocation || this.enableImproveAccuracy)) { + throw "At least one default move reason should be allowed (at " + context + ")" + } + } + + +} \ No newline at end of file diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 53715562bf..ccbf6ba557 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -15,6 +15,7 @@ import {BBox} from "../../Logic/BBox"; import {FixedUiElement} from "../Base/FixedUiElement"; import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; import BaseUIElement from "../BaseUIElement"; +import Toggle from "./Toggle"; export default class LocationInput extends InputElement implements MinimapObj { @@ -157,10 +158,15 @@ export default class LocationInput extends InputElement implements MinimapO IsValid(t: Loc): boolean { return t !== undefined; } - + protected InnerConstructElement(): HTMLElement { try { const self = this; + const hasMoved = new UIEventSource(false) + this.GetValue().addCallbackAndRunD(_ => { + hasMoved.setData(true) + return true; + }) this.clickLocation.addCallbackAndRunD(location => this._centerLocation.setData(location)) if (this._snapTo !== undefined) { @@ -213,8 +219,8 @@ export default class LocationInput extends InputElement implements MinimapO ]).SetClass("block w-0 h-0 z-10 relative") .SetStyle("background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%; opacity: 0.5"), - new Combine([ - animatedHand]) + new Toggle(undefined, + animatedHand, hasMoved) .SetClass("block w-0 h-0 z-10 relative") .SetStyle("left: calc(50% + 3rem); top: calc(50% + 2rem); opacity: 0.7"), diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index b7e33546ba..484a27ea22 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -17,6 +17,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import {Translation} from "../i18n/Translation"; import {Utils} from "../../Utils"; import {SubstitutedTranslation} from "../SubstitutedTranslation"; +import MoveWizard from "./MoveWizard"; export default class FeatureInfoBox extends ScrollableFullScreen { @@ -72,6 +73,19 @@ export default class FeatureInfoBox extends ScrollableFullScreen { editElements.push(questionBox); } + if(layerConfig.allowMove) { + editElements.push( + new VariableUiElement(tags.map(tags => tags.id).map(id => { + const feature = State.state.allElements.ContainingFeatures.get(id) + return new MoveWizard( + feature, + State.state, + layerConfig.allowMove + ); + }) + ) + ); + } if (layerConfig.deletion) { editElements.push( diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index b59b56bc75..6a5d4a07bf 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -15,13 +15,17 @@ import {OsmObject} from "../../Logic/Osm/OsmObject"; import {Changes} from "../../Logic/Osm/Changes"; import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import MoveConfig from "../../Models/ThemeConfig/MoveConfig"; +import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; +import {ElementStorage} from "../../Logic/ElementStorage"; interface MoveReason { text: Translation | string, + invitingText: Translation | string, icon: string | BaseUIElement, changesetCommentValue: string, lockBounds: true | boolean, - background: undefined | "map" | "photo", + background: undefined | "map" | "photo" | string | string[], startZoom: number, minZoom: number } @@ -36,13 +40,9 @@ export default class MoveWizard extends Toggle { osmConnection: OsmConnection, featureSwitchUserbadge: UIEventSource, changes: Changes, - layoutToUse: LayoutConfig - }, options?: { - reasons?: MoveReason[] - disableDefaultReasons?: false | boolean - - }) { - options = options ?? {} + layoutToUse: LayoutConfig, + allElements: ElementStorage + }, options : MoveConfig) { const t = Translations.t.move const loginButton = new Toggle( @@ -51,14 +51,53 @@ export default class MoveWizard extends Toggle { state.featureSwitchUserbadge ) + const reasons: MoveReason[] = [] + if (options.enableRelocation) { + reasons.push({ + text: t.reasons.reasonRelocation.Clone(), + invitingText: t.inviteToMove.reasonRelocation.Clone(), + icon: Svg.relocation_svg(), + changesetCommentValue: "relocated", + lockBounds: false, + background: undefined, + startZoom: 12, + minZoom: 6 + }) + } + if(options.enableImproveAccuracy){ + reasons.push({ + text: t.reasons.reasonInaccurate.Clone(), + invitingText: t.inviteToMove.reasonInaccurate, + icon: Svg.crosshair_svg(), + changesetCommentValue: "improve_accuracy", + lockBounds: true, + background: "photo", + startZoom: 17, + minZoom: 16 + }) + } + const currentStep = new UIEventSource<"start" | "reason" | "pick_location" | "moved">("start") const moveReason = new UIEventSource(undefined) - const moveButton = new SubtleButton( - Svg.move_ui(), - t.inviteToMove.Clone() - ).onClick(() => { - currentStep.setData("reason") - }) + let moveButton : BaseUIElement; + if(reasons.length === 1){ + const reason = reasons[0] + moveReason.setData(reason) + moveButton = new SubtleButton( + reason.icon, + Translations.WT(reason.invitingText).Clone() + ).onClick(() => { + currentStep.setData("pick_location") + }) + }else{ + moveButton = new SubtleButton( + Svg.move_ui(), + t.inviteToMove.generic.Clone() + ).onClick(() => { + currentStep.setData("reason") + }) + } + const moveAgainButton = new SubtleButton( Svg.move_ui(), @@ -68,40 +107,8 @@ export default class MoveWizard extends Toggle { }) - const reasons: MoveReason[] = [] - if (!options.disableDefaultReasons) { - reasons.push({ - text: t.reasonRelocation.Clone(), - icon: Svg.relocation_svg(), - changesetCommentValue: "relocated", - lockBounds: false, - background: undefined, - startZoom: 12, - minZoom: 6 - }) - - reasons.push({ - text: t.reasonInaccurate.Clone(), - icon: Svg.crosshair_svg(), - changesetCommentValue: "improve_accuracy", - lockBounds: true, - background: "photo", - startZoom: 17, - minZoom: 16 - }) - } - for (const reason of options.reasons ?? []) { - reasons.push({ - text: reason.text, - icon: reason.icon, - changesetCommentValue: reason.changesetCommentValue, - lockBounds: reason.lockBounds ?? true, - background: reason.background, - startZoom: reason.startZoom ?? 15, - minZoom: reason.minZoom - }) - } - + + const selectReason = new Combine(reasons.map(r => new SubtleButton(r.icon, r.text).onClick(() => { moveReason.setData(r) currentStep.setData("pick_location") @@ -124,7 +131,8 @@ export default class MoveWizard extends Toggle { const locationInput = new LocationInput({ minZoom: reason.minZoom, - centerLocation: loc + centerLocation: loc, + mapBackground: AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(reason.background)) }) if (reason.lockBounds) { @@ -135,10 +143,14 @@ export default class MoveWizard extends Toggle { const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove) confirmMove.onClick(() => { - state.changes.applyAction(new ChangeLocationAction(featureToMove.properties.id, [locationInput.GetValue().data.lon, locationInput.GetValue().data.lat], { + const loc = locationInput.GetValue().data + state.changes.applyAction(new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], { reason: Translations.WT(reason.text).textFor("en"), - theme: state.layoutToUse.icon + theme: state.layoutToUse.id })) + featureToMove.properties._lat = loc.lat + featureToMove.properties._lon = loc.lon + state.allElements.getEventSourceById(id).ping() currentStep.setData("moved") }) const zoomInFurhter = t.zoomInFurther.Clone().SetClass("alert block m-6") diff --git a/Utils.ts b/Utils.ts index e293e4f4b9..26c8a75341 100644 --- a/Utils.ts +++ b/Utils.ts @@ -320,6 +320,19 @@ export class Utils { ) } + private static _download_cache = new Map, timestamp: number}>() + public static async downloadJsonCached(url: string, maxCacheTimeMs: number, headers?: any): Promise { + const cached = Utils._download_cache.get(url) + if(cached !== undefined){ + if((new Date().getTime() - cached.timestamp) <= maxCacheTimeMs){ + return cached.promise + } + } + const promise = Utils.downloadJson(url, headers) + Utils._download_cache.set(url, {promise, timestamp: new Date().getTime()}) + return await promise + } + public static async downloadJson(url: string, headers?: any): Promise { const injected = Utils.injectedDownloads[url] if (injected !== undefined) { diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index af5bbd10bc..f9b94008ae 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -600,5 +600,18 @@ "preferredBackground": "photo" } } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity=bench", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json b/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json index 665a759427..360a0444ce 100644 --- a/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json +++ b/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json @@ -255,5 +255,18 @@ ], "multiAnswer": true } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/bike_cleaning/bike_cleaning.json b/assets/layers/bike_cleaning/bike_cleaning.json index 6c76d519b4..3bd07aa391 100644 --- a/assets/layers/bike_cleaning/bike_cleaning.json +++ b/assets/layers/bike_cleaning/bike_cleaning.json @@ -145,5 +145,18 @@ "roaming": false, "id": "bike_cleaning-charge" } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/bike_parking/bike_parking.json b/assets/layers/bike_parking/bike_parking.json index 049bcac9e7..2f866351e0 100644 --- a/assets/layers/bike_parking/bike_parking.json +++ b/assets/layers/bike_parking/bike_parking.json @@ -508,5 +508,18 @@ }, "id": "Cargo bike capacity?" } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/bike_repair_station/bike_repair_station.json b/assets/layers/bike_repair_station/bike_repair_station.json index cd202c6c88..29b8fe215e 100644 --- a/assets/layers/bike_repair_station/bike_repair_station.json +++ b/assets/layers/bike_repair_station/bike_repair_station.json @@ -731,5 +731,18 @@ "service:bicycle:pump=no" ] } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/binocular/binocular.json b/assets/layers/binocular/binocular.json index 46ba8fe420..3256288187 100644 --- a/assets/layers/binocular/binocular.json +++ b/assets/layers/binocular/binocular.json @@ -100,5 +100,18 @@ "amenity=binoculars" ] } + }, + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true } } \ No newline at end of file diff --git a/assets/layers/birdhide/birdhide.json b/assets/layers/birdhide/birdhide.json index 38f8e19703..da7042bd3b 100644 --- a/assets/layers/birdhide/birdhide.json +++ b/assets/layers/birdhide/birdhide.json @@ -296,5 +296,17 @@ } ] } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + } + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/cafe_pub/cafe_pub.json b/assets/layers/cafe_pub/cafe_pub.json index e5977e8bbc..a815bd6d98 100644 --- a/assets/layers/cafe_pub/cafe_pub.json +++ b/assets/layers/cafe_pub/cafe_pub.json @@ -181,5 +181,11 @@ } ] } - ] + ], + "deletion": { + "softDeletionTags": { + "and": ["amenity=","disused:amenity:={amenity}"] + } + }, + "allowMove": true } \ No newline at end of file diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 454f26ff19..23ee4411ff 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -3655,5 +3655,18 @@ ], "eraseInvalidValues": true } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/defibrillator/defibrillator.json b/assets/layers/defibrillator/defibrillator.json index 639020383a..484e6f4a5a 100644 --- a/assets/layers/defibrillator/defibrillator.json +++ b/assets/layers/defibrillator/defibrillator.json @@ -539,5 +539,18 @@ }, "id": "defibrillator-fixme" } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:emergency:=defibrillator}", + "emergency=" + ] + }, + "neededChangesets": 5 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/drinking_water/drinking_water.json b/assets/layers/drinking_water/drinking_water.json index 43af3910c7..63f4f849e8 100644 --- a/assets/layers/drinking_water/drinking_water.json +++ b/assets/layers/drinking_water/drinking_water.json @@ -166,5 +166,18 @@ }, "condition": "_closest_other_drinking_water_id~*" } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index 1bac9814d2..79b0015fcc 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -624,5 +624,11 @@ } ] } - ] + ], + "deletion": { + "softDeletionTags": { + "and": ["amenity=","disused:amenity:={amenity}"] + } + }, + "allowMove": true } \ No newline at end of file diff --git a/assets/layers/ghost_bike/ghost_bike.json b/assets/layers/ghost_bike/ghost_bike.json index 25a6097ef3..99ec27a2ad 100644 --- a/assets/layers/ghost_bike/ghost_bike.json +++ b/assets/layers/ghost_bike/ghost_bike.json @@ -187,5 +187,18 @@ }, "id": "ghost_bike-start_date" } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "razed:memorial:=ghost_bike", + "memorial=" + ] + }, + "neededChangesets": 50 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/information_board/information_board.json b/assets/layers/information_board/information_board.json index 6f240fa4fd..2634f1df31 100644 --- a/assets/layers/information_board/information_board.json +++ b/assets/layers/information_board/information_board.json @@ -53,5 +53,20 @@ "ru": "Информационный щит" } } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:tourism:=information", + "tourism=" , + "razed:information=board", + "information=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/map/map.json b/assets/layers/map/map.json index 221bfa7ef4..b0031e5732 100644 --- a/assets/layers/map/map.json +++ b/assets/layers/map/map.json @@ -227,5 +227,18 @@ } } ], - "wayHandling": 2 + "wayHandling": 2, + "deletion": { + "softDeletionTags": { + "and": [ + "razed:tourism:=information", + "tourism=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/observation_tower/observation_tower.json b/assets/layers/observation_tower/observation_tower.json index 8443b12134..d789271af8 100644 --- a/assets/layers/observation_tower/observation_tower.json +++ b/assets/layers/observation_tower/observation_tower.json @@ -180,5 +180,9 @@ ], "eraseInvalidValues": true } - ] + ], + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index 42f09f53e9..b428d5f991 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -81,5 +81,18 @@ "nl": "Voeg hier een parking voor auto's toe" } } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/picnic_table/picnic_table.json b/assets/layers/picnic_table/picnic_table.json index c414410fe6..c3c5e6e0b7 100644 --- a/assets/layers/picnic_table/picnic_table.json +++ b/assets/layers/picnic_table/picnic_table.json @@ -101,5 +101,18 @@ } } ], - "wayHandling": 1 + "wayHandling": 1, + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index d7592efc74..7b0366ca9b 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -498,5 +498,18 @@ } ] } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/assets/layers/waste_basket/waste_basket.json b/assets/layers/waste_basket/waste_basket.json index 1cd888dcd5..f187916110 100644 --- a/assets/layers/waste_basket/waste_basket.json +++ b/assets/layers/waste_basket/waste_basket.json @@ -115,5 +115,18 @@ "preferredBackground": "photo" } } - ] + ], + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity:={amenity}", + "amenity=" + ] + }, + "neededChangesets": 1 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } } \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index b94746cc36..5b0e4f150f 100644 --- a/langs/en.json +++ b/langs/en.json @@ -256,7 +256,6 @@ }, "move": { "loginToMove": "You must be logged in to move a point", - "inviteToMove": "Move this point", "inviteToMoveAgain": "Move this point again", "moveTitle": "Move this point", "whyMove": "Why do you want to move this point?", @@ -264,8 +263,15 @@ "pointIsMoved": "The point has been moved", "zoomInFurther": "Zoom in further to confirm this move", "selectReason": "Why do you move this object?", - "reasonRelocation": "The object has been relocated to a totally different location", - "reasonInaccurate": "The location of this object is inaccurate and should be moved a few meter", + "reasons": { + "reasonRelocation": "The object has been relocated to a totally different location", + "reasonInaccurate": "The location of this object is inaccurate and should be moved a few meter" + }, + "inviteToMove": { + "generic": "Move this point", + "reasonInaccurate": "Improve the accuracy of this point", + "reasonRelocation": "Move this object to a another place because it has relocated" + }, "cannotBeMoved": "This feature cannot be moved.", "isWay": "This feature is a way. Use another OpenStreetMap editor to move it.", "isRelation": "This feature is a relation and can not be moved", diff --git a/package.json b/package.json index 4f8cc5a9d6..ed6bf9570e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "homepage": "https://mapcomplete.osm.be", "main": "index.js", "scripts": { - "increase-memory": "export NODE_OPTIONS=--max_old_space_size=6182", + "increase-memory": "export NODE_OPTIONS=--max_old_space_size=8364", "start": "npm run start:prepare && npm-run-all --parallel start:parallel:*", "strt": "npm run start:prepare && npm run start:parallel:parcel", "start:prepare": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory", diff --git a/test.ts b/test.ts index 6bf4595129..0442ef0910 100644 --- a/test.ts +++ b/test.ts @@ -2,6 +2,9 @@ import MoveWizard from "./UI/Popup/MoveWizard"; import State from "./State"; import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; import MinimapImplementation from "./UI/Base/MinimapImplementation"; +import MoveConfig from "./Models/ThemeConfig/MoveConfig"; +import {FixedUiElement} from "./UI/Base/FixedUiElement"; +import Combine from "./UI/Base/Combine"; State.state = new State(AllKnownLayouts.allKnownLayouts.get("bookcases")) @@ -18,8 +21,14 @@ const feature = { ] } } +/* MinimapImplementation.initialize() new MoveWizard( feature, - State.state).AttachTo("maindiv") + State.state, + new MoveConfig({ + enableRelocation: false, + enableImproveAccuracy: true + }, "test")).AttachTo("maindiv") +*/ From c2f610df86ba30c88af69577e933b8f77cf82237 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Oct 2021 17:35:55 +0200 Subject: [PATCH 5/7] Fix out-of memory when starting parcel --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed6bf9570e..c7bb30cea6 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "start": "npm run start:prepare && npm-run-all --parallel start:parallel:*", "strt": "npm run start:prepare && npm run start:parallel:parcel", "start:prepare": "ts-node scripts/generateLayerOverview.ts --no-fail && npm run increase-memory", - "start:parallel:parcel": "parcel *.html UI/** Logic/** assets/*.json assets/svg/* assets/generated/* assets/layers/*/*.svg assets/layers/*/*.jpg assets/layers/*/*.png assets/tagRenderings/*.json assets/themes/*/*.svg assets/themes/*/*.jpg assets/themes/*/*.png vendor/* vendor/*/*", + "start:parallel:parcel": "node --max_old_space_size=8000 $(which parcel) serve *.html UI/** Logic/** assets/*.json assets/svg/* assets/generated/* assets/layers/*/*.svg assets/layers/*/*.jpg assets/layers/*/*.png assets/tagRenderings/*.json assets/themes/*/*.svg assets/themes/*/*.jpg assets/themes/*/*.png vendor/* vendor/*/*", "start:parallel:tailwindcli": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch", "generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css", "test": "ts-node test/TestAll.ts", From ff81dab04b9214e2345c92423128653161caef4d Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Oct 2021 17:38:00 +0200 Subject: [PATCH 6/7] Styling of move, delete and split-road buttons --- UI/Popup/DeleteWizard.ts | 4 ++-- UI/Popup/MoveWizard.ts | 7 ++++--- UI/Popup/SplitRoadWizard.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index fec675e84f..fb2bad1fde 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -93,12 +93,12 @@ export default class DeleteWizard extends Toggle { * The button which is shown first. Opening it will trigger the check for deletions */ const deleteButton = new SubtleButton( - Svg.delete_icon_ui().SetStyle("width: 2rem; height: 2rem;"), t.delete.Clone()).onClick( + Svg.delete_icon_ui().SetStyle("width: auto; height: 1.5rem;"), t.delete.Clone()).onClick( () => { deleteAbility.CheckDeleteability(true) confirm.setData(true); } - ).SetClass("w-1/2 float-right"); + ) const isShown = new UIEventSource(id.indexOf("-") < 0) diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index 6a5d4a07bf..4698d3743b 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -18,11 +18,12 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import MoveConfig from "../../Models/ThemeConfig/MoveConfig"; import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; import {ElementStorage} from "../../Logic/ElementStorage"; +import Img from "../Base/Img"; interface MoveReason { text: Translation | string, invitingText: Translation | string, - icon: string | BaseUIElement, + icon: BaseUIElement, changesetCommentValue: string, lockBounds: true | boolean, background: undefined | "map" | "photo" | string | string[], @@ -84,14 +85,14 @@ export default class MoveWizard extends Toggle { const reason = reasons[0] moveReason.setData(reason) moveButton = new SubtleButton( - reason.icon, + reason.icon.SetStyle("height: 1.5rem; width: auto;"), Translations.WT(reason.invitingText).Clone() ).onClick(() => { currentStep.setData("pick_location") }) }else{ moveButton = new SubtleButton( - Svg.move_ui(), + Svg.move_ui().SetStyle("height: 1.5rem; width: auto"), t.inviteToMove.generic.Clone() ).onClick(() => { currentStep.setData("reason") diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index 86e137fa3c..0127a865af 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -120,7 +120,7 @@ export default class SplitRoadWizard extends Toggle { })) // Toggle between splitmap - const splitButton = new SubtleButton(Svg.scissors_ui(), t.inviteToSplit.Clone().SetClass("text-lg font-bold")); + const splitButton = new SubtleButton(Svg.scissors_ui().SetStyle("height: 1.5rem; width: auto"), t.inviteToSplit.Clone().SetClass("text-lg font-bold")); splitButton.onClick( () => { splitClicked.setData(true) From 943c995dfdc71c76c7f9e304b1f39d745addc0d9 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 14 Oct 2021 17:39:07 +0200 Subject: [PATCH 7/7] Separate layer from artwork and shops theme, add delete and move config --- assets/layers/artwork/artwork.json | 398 +++++++++++++++++++++++++++++ assets/layers/shops/shops.json | 323 +++++++++++++++++++++++ assets/themes/artwork/artwork.json | 386 +--------------------------- assets/themes/shops/shops.json | 321 +---------------------- 4 files changed, 724 insertions(+), 704 deletions(-) create mode 100644 assets/layers/artwork/artwork.json create mode 100644 assets/layers/shops/shops.json diff --git a/assets/layers/artwork/artwork.json b/assets/layers/artwork/artwork.json new file mode 100644 index 0000000000..f8f1ddba08 --- /dev/null +++ b/assets/layers/artwork/artwork.json @@ -0,0 +1,398 @@ +{ + "id": "artwork", + "name": { + "en": "Artworks", + "nl": "Kunstwerken", + "fr": "Œuvres d'art", + "de": "Kunstwerke", + "id": "Karya seni", + "it": "Opere d’arte", + "ru": "Произведения искусства", + "es": "Obras de arte", + "ja": "美術品", + "zh_Hant": "藝術品", + "nb_NO": "Kunstverk" + }, + "source": { + "osmTags": "tourism=artwork" + }, + "title": { + "render": { + "en": "Artwork", + "nl": "Kunstwerk", + "fr": "Œuvre d'art", + "de": "Kunstwerk", + "id": "Karya seni", + "it": "Opera d’arte", + "ru": "Художественная работа", + "es": "Obra de arte", + "ja": "アートワーク", + "zh_Hant": "藝術品", + "nb_NO": "Kunstverk" + }, + "mappings": [ + { + "if": "name~*", + "then": { + "en": "Artwork {name}", + "nl": "Kunstwerk {name}", + "fr": "Œuvre d'art {name}", + "de": "Kunstwerk {name}", + "id": "Karya seni {name}", + "it": "Opera {name}", + "ru": "Художественная работа {name}", + "es": "Obra de arte {nombre}", + "ja": "アートワーク {name}", + "zh_Hant": "藝術品{name}" + } + } + ] + }, + "icon": { + "render": "./assets/themes/artwork/artwork.svg" + }, + "color": { + "render": "#0000ff" + }, + "width": { + "render": "10" + }, + "description": { + "en": "Diverse pieces of artwork", + "nl": "Verschillende soorten kunstwerken", + "fr": "Diverses œuvres d'art", + "de": "Verschiedene Kunstwerke", + "it": "Diverse opere d’arte", + "ru": "Разнообразные произведения искусства", + "es": "Diversas piezas de obras de arte", + "ja": "多様な作品", + "zh_Hant": "不同類型的藝術品" + }, + "minzoom": 12, + "wayHandling": 2, + "presets": [ + { + "tags": [ + "tourism=artwork" + ], + "title": { + "en": "Artwork", + "nl": "Kunstwerk", + "fr": "Œuvre d'art", + "de": "Kunstwerk", + "it": "Opera d’arte", + "ru": "Художественная работа", + "es": "Obra de arte", + "ja": "アートワーク", + "zh_Hant": "藝術品", + "nb_NO": "Kunstverk" + } + } + ], + "tagRenderings": [ + "images", + { + "render": { + "en": "This is a {artwork_type}", + "nl": "Dit is een {artwork_type}", + "fr": "Type d'œuvre : {artwork_type}", + "de": "Dies ist ein {artwork_type}", + "it": "Si tratta di un {artwork_type}", + "ru": "Это {artwork_type}", + "es": "Esta es un {artwork_type}", + "ja": "これは{artwork_type}です", + "zh_Hant": "這是 {artwork_type}", + "nb_NO": "Dette er et kunstverk av typen {artwork_type}" + }, + "question": { + "en": "What is the type of this artwork?", + "nl": "Wat voor soort kunstwerk is dit?", + "fr": "Quel est le type de cette œuvre d'art?", + "de": "Was ist die Art dieses Kunstwerks?", + "it": "Che tipo di opera d’arte è questo?", + "ru": "К какому типу относится эта работа?", + "es": "Cuál es el tipo de esta obra de arte?", + "ja": "この作品の種類は何ですか?", + "zh_Hant": "這是什麼類型的藝術品?", + "nb_NO": "Hvilken type kunstverk er dette?" + }, + "freeform": { + "key": "artwork_type", + "addExtraTags": [ + "fixme=Artowrk type was added with the freeform, might need another check" + ] + }, + "mappings": [ + { + "if": "artwork_type=architecture", + "then": { + "en": "Architecture", + "nl": "Architectuur", + "fr": "Architecture", + "de": "Architektur", + "it": "Architettura", + "ru": "Архитектура", + "ja": "建物", + "zh_Hant": "建築物", + "nb_NO": "Arkitektur" + } + }, + { + "if": "artwork_type=mural", + "then": { + "en": "Mural", + "nl": "Muurschildering", + "fr": "Peinture murale", + "de": "Wandbild", + "it": "Murale", + "ru": "Фреска", + "ja": "壁画", + "zh_Hant": "壁畫", + "nb_NO": "Veggmaleri" + } + }, + { + "if": "artwork_type=painting", + "then": { + "en": "Painting", + "nl": "Schilderij", + "fr": "Peinture", + "de": "Malerei", + "it": "Dipinto", + "ru": "Живопись", + "ja": "絵画", + "zh_Hant": "繪畫", + "nb_NO": "Maleri" + } + }, + { + "if": "artwork_type=sculpture", + "then": { + "en": "Sculpture", + "nl": "Beeldhouwwerk", + "fr": "Sculpture", + "de": "Skulptur", + "it": "Scultura", + "ru": "Скульптура", + "ja": "彫刻", + "zh_Hant": "雕塑", + "nb_NO": "Skulptur" + } + }, + { + "if": "artwork_type=statue", + "then": { + "en": "Statue", + "nl": "Standbeeld", + "fr": "Statue", + "de": "Statue", + "it": "Statua", + "ru": "Статуя", + "ja": "彫像", + "zh_Hant": "雕像", + "nb_NO": "Statue" + } + }, + { + "if": "artwork_type=bust", + "then": { + "en": "Bust", + "nl": "Buste", + "fr": "Buste", + "de": "Büste", + "it": "Busto", + "ru": "Бюст", + "ja": "胸像", + "zh_Hant": "半身像", + "nb_NO": "Byste" + } + }, + { + "if": "artwork_type=stone", + "then": { + "en": "Stone", + "nl": "Steen", + "fr": "Rocher", + "de": "Stein", + "it": "Masso", + "ru": "Камень", + "ja": "石", + "zh_Hant": "石頭", + "nb_NO": "Stein" + } + }, + { + "if": "artwork_type=installation", + "then": { + "en": "Installation", + "nl": "Installatie", + "fr": "Installation", + "de": "Installation", + "it": "Istallazione", + "ru": "Инсталляция", + "ja": "インスタレーション", + "zh_Hant": "安裝", + "nb_NO": "Installasjon" + } + }, + { + "if": "artwork_type=graffiti", + "then": { + "en": "Graffiti", + "nl": "Graffiti", + "fr": "Graffiti", + "de": "Graffiti", + "it": "Graffiti", + "ru": "Граффити", + "ja": "落書き", + "zh_Hant": "塗鴨", + "nb_NO": "Graffiti" + } + }, + { + "if": "artwork_type=relief", + "then": { + "en": "Relief", + "nl": "Reliëf", + "fr": "Relief", + "de": "Relief", + "it": "Rilievo", + "ru": "Рельеф", + "ja": "レリーフ", + "zh_Hant": "寬慰", + "nb_NO": "Relieff" + } + }, + { + "if": "artwork_type=azulejo", + "then": { + "en": "Azulejo (Spanish decorative tilework)", + "nl": "Azulejo (Spaanse siertegels)", + "fr": "Azulejo (faïence latine)", + "de": "Azulejo (spanische dekorative Fliesenarbeit)", + "it": "Azulejo (ornamento decorativo piastrellato spagnolo)", + "ru": "Азуле́жу (испанская роспись глазурованной керамической плитки)", + "ja": "Azulejo (スペインの装飾タイル)", + "zh_Hant": "Azulejo (西班牙雕塑作品名稱)", + "nb_NO": "Azulejo (Spansk dekorativt flisverk)" + } + }, + { + "if": "artwork_type=tilework", + "then": { + "en": "Tilework", + "nl": "Tegelwerk", + "fr": "Carrelage", + "de": "Fliesenarbeit", + "it": "Mosaico di piastrelle", + "ru": "Плитка (мозаика)", + "ja": "タイルワーク", + "zh_Hant": "瓷磚", + "nb_NO": "Flisarbeid" + } + } + ], + "id": "artwork-artwork_type" + }, + { + "question": { + "en": "Which artist created this?", + "nl": "Welke kunstenaar creëerde dit kunstwerk?", + "fr": "Quel artiste a créé cette œuvre ?", + "de": "Welcher Künstler hat das geschaffen?", + "it": "Quale artista ha creato quest’opera?", + "ru": "Какой художник создал это?", + "ja": "どのアーティストが作ったんですか?", + "zh_Hant": "創造這個的藝術家是誰?", + "nb_NO": "Hvilken artist lagde dette?" + }, + "render": { + "en": "Created by {artist_name}", + "nl": "Gecreëerd door {artist_name}", + "fr": "Créé par {artist_name}", + "de": "Erstellt von {artist_name}", + "it": "Creato da {artist_name}", + "ru": "Создано {artist_name}", + "ja": "作成者:{artist_name}", + "zh_Hant": "{artist_name} 創作", + "nb_NO": "Laget av {artist_name}" + }, + "freeform": { + "key": "artist_name" + }, + "id": "artwork-artist_name" + }, + { + "question": { + "en": "Is there a website with more information about this artwork?", + "nl": "Is er een website met meer informatie over dit kunstwerk?", + "fr": "Existe-t-il un site web où trouver plus d'informations sur cette œuvre d'art ?", + "de": "Gibt es eine Website mit weiteren Informationen über dieses Kunstwerk?", + "it": "Esiste un sito web con maggiori informazioni su quest’opera?", + "ru": "Есть ли сайт с более подробной информацией об этой работе?", + "ja": "この作品についての詳しい情報はどのウェブサイトにありますか?", + "zh_Hant": "在那個網站能夠找到更多藝術品的資訊?", + "nb_NO": "Finnes det en nettside med mer info om dette kunstverket?" + }, + "render": { + "en": "More information on this website", + "nl": "Meer informatie op deze website", + "fr": "Plus d'info sûr ce site web", + "de": "Weitere Informationen auf dieser Webseite", + "id": "Info lanjut tersedia di laman web ini.", + "it": "Ulteriori informazioni su questo sito web", + "ru": "Больше информации на этом сайте", + "ja": "Webサイトに詳細情報がある", + "zh_Hant": "這個網站有更多資訊", + "nb_NO": "Mer info er å finne på denne nettsiden" + }, + "freeform": { + "key": "website", + "type": "url" + }, + "id": "artwork-website" + }, + { + "question": { + "en": "Which Wikidata-entry corresponds with this artwork?", + "nl": "Welk Wikidata-item beschrijft dit kunstwerk?", + "fr": "Quelle entrée Wikidata correspond à cette œuvre d'art ?", + "de": "Welcher Wikidata-Eintrag entspricht diesem Kunstwerk?", + "it": "Quale elemento Wikidata corrisponde a quest’opera d’arte?", + "ru": "Какая запись в Wikidata соответсвует этой работе?", + "ja": "このアートワークに関するWikidataのエントリーはどれですか?", + "zh_Hant": "這個藝術品有那個對應的 Wikidata 項目?", + "nb_NO": "Hvilken Wikipedia-oppføring samsvarer med dette kunstverket?" + }, + "render": { + "en": "Corresponds with {wikidata}", + "nl": "Komt overeen met {wikidata}", + "fr": "Correspond à {wikidata}", + "de": "Entspricht {wikidata}", + "it": "Corrisponde a {wikidata}", + "ru": "Запись об этой работе в wikidata: {wikidata}", + "ja": "{wikidata}に関連する", + "zh_Hant": "與 {wikidata}對應", + "nb_NO": "Samsvarer med {wikidata}" + }, + "freeform": { + "key": "wikidata", + "type": "wikidata" + }, + "id": "artwork-wikidata" + } + ], + "deletion": { + "softDeletionTags": { + "and": [ + "razed:tourism=artwork", + "tourism=" + ] + }, + "neededChangesets": 5 + }, + "allowMove": { + "enableRelocation": false, + "enableImproveAccuraccy": true + } +} \ No newline at end of file diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json new file mode 100644 index 0000000000..f9cf85710f --- /dev/null +++ b/assets/layers/shops/shops.json @@ -0,0 +1,323 @@ +{ + "id": "shops", + "name": { + "en": "Shop", + "fr": "Magasin", + "ru": "Магазин", + "ja": "店", + "nl": "Winkel" + }, + "minzoom": 16, + "source": { + "osmTags": { + "and": [ + "shop~*" + ] + } + }, + "title": { + "render": { + "en": "Shop", + "fr": "Magasin", + "ru": "Магазин", + "ja": "店", + "nl": "Winkel" + }, + "mappings": [ + { + "if": { + "and": [ + "name~*" + ] + }, + "then": { + "en": "{name}", + "fr": "{name}", + "ru": "{name}", + "ja": "{name}" + } + }, + { + "if": { + "and": [ + "shop!~yes" + ] + }, + "then": { + "en": "{shop}", + "fr": "{shop}", + "ru": "{shop}", + "ja": "{shop}" + } + } + ] + }, + "description": { + "en": "A shop", + "fr": "Un magasin", + "ja": "ショップ", + "nl": "Een winkel", + "ru": "Магазин" + }, + "tagRenderings": [ + "images", + { + "question": { + "en": "What is the name of this shop?", + "fr": "Qu'est-ce que le nom de ce magasin?", + "ru": "Как называется этот магазин?", + "ja": "このお店の名前は何ですか?", + "nl": "Wat is de naam van deze winkel?" + }, + "render": "This shop is called {name}", + "freeform": { + "key": "name" + }, + "id": "shops-name" + }, + { + "render": { + "en": "This shop sells {shop}", + "fr": "Ce magasin vends {shop}", + "ja": "こちらのお店では{shop}を販売しております" + }, + "question": { + "en": "What does this shop sell?", + "fr": "Que vends ce magasin ?", + "ja": "このお店では何を売っていますか?", + "ru": "Что продаётся в этом магазине?" + }, + "freeform": { + "key": "shop" + }, + "mappings": [ + { + "if": { + "and": [ + "shop=convenience" + ] + }, + "then": { + "en": "Convenience store", + "fr": "Épicerie/superette", + "ja": "コンビニエンスストア" + } + }, + { + "if": { + "and": [ + "shop=supermarket" + ] + }, + "then": { + "en": "Supermarket", + "fr": "Supermarché", + "ru": "Супермаркет", + "ja": "スーパーマーケット", + "nl": "Supermarkt" + } + }, + { + "if": { + "and": [ + "shop=clothes" + ] + }, + "then": { + "en": "Clothing store", + "fr": "Magasin de vêtements", + "ru": "Магазин одежды", + "ja": "衣料品店" + } + }, + { + "if": { + "and": [ + "shop=hairdresser" + ] + }, + "then": { + "en": "Hairdresser", + "fr": "Coiffeur", + "ru": "Парикмахерская", + "ja": "理容師", + "nl": "Kapper" + } + }, + { + "if": { + "and": [ + "shop=bakery" + ] + }, + "then": { + "en": "Bakery", + "fr": "Boulangerie", + "ja": "ベーカリー", + "nl": "Bakkerij" + } + }, + { + "if": { + "and": [ + "shop=car_repair" + ] + }, + "then": { + "en": "Car repair (garage)", + "fr": "Garagiste", + "ja": "自動車修理(ガレージ)" + } + }, + { + "if": { + "and": [ + "shop=car" + ] + }, + "then": { + "en": "Car dealer", + "fr": "Concessionnaire", + "ru": "Автосалон", + "ja": "自動車ディーラー" + } + } + ], + "id": "shops-shop" + }, + { + "render": { + "en": "{phone}", + "fr": "{phone}", + "ca": "{phone}", + "id": "{phone}", + "ru": "{phone}", + "ja": "{phone}" + }, + "question": { + "en": "What is the phone number?", + "fr": "Quel est le numéro de téléphone ?", + "ja": "電話番号は何番ですか?", + "nl": "Wat is het telefoonnummer?", + "ru": "Какой телефон?" + }, + "freeform": { + "key": "phone", + "type": "phone" + }, + "id": "shops-phone" + }, + { + "render": { + "en": "{website}", + "fr": "{website}", + "ca": "{website}", + "id": "{website}", + "ru": "{website}", + "ja": "{website}" + }, + "question": { + "en": "What is the website of this shop?", + "fr": "Quel est le site internet de ce magasin ?", + "ja": "このお店のホームページは何ですか?", + "nl": "Wat is de website van deze winkel?", + "ru": "Какой веб-сайт у этого магазина?" + }, + "freeform": { + "key": "website", + "type": "url" + }, + "id": "shops-website" + }, + { + "render": { + "en": "{email}", + "fr": "{email}", + "id": "{email}", + "ru": "{email}", + "ja": "{email}" + }, + "question": { + "en": "What is the email address of this shop?", + "fr": "Quelle est l'adresse électronique de ce magasin ?", + "ja": "このお店のメールアドレスは何ですか?", + "ru": "Каков адрес электронной почты этого магазина?", + "nl": "Wat is het e-mailadres van deze winkel?" + }, + "freeform": { + "key": "email", + "type": "email" + }, + "id": "shops-email" + }, + { + "render": { + "en": "{opening_hours_table(opening_hours)}", + "fr": "{opening_hours_table(opening_hours)}", + "ru": "{opening_hours_table(opening_hours)}", + "ja": "{opening_hours_table(opening_hours)}" + }, + "question": { + "en": "What are the opening hours of this shop?", + "fr": "Quels sont les horaires d'ouverture de ce magasin ?", + "ja": "この店の営業時間は何時から何時までですか?", + "nl": "Wat zijn de openingsuren van deze winkel?", + "ru": "Каковы часы работы этого магазина?" + }, + "freeform": { + "key": "opening_hours", + "type": "opening_hours" + }, + "id": "shops-opening_hours" + }, + "questions", + "reviews" + ], + "icon": { + "render": "./assets/themes/shops/shop.svg" + }, + "iconOverlays": [ + { + "if": "opening_hours~*", + "then": "isOpen", + "badge": true + } + ], + "width": { + "render": "8" + }, + "iconSize": { + "render": "40,40,center" + }, + "color": { + "render": "#00f" + }, + "presets": [ + { + "tags": [ + "shop=yes" + ], + "title": { + "en": "Shop", + "fr": "Magasin", + "ru": "Магазин", + "ja": "店", + "nl": "Winkel" + }, + "description": { + "en": "Add a new shop", + "fr": "Ajouter un nouveau magasin", + "ru": "Добавить новый магазин", + "ja": "新しい店を追加する", + "nl": "Voeg een nieuwe winkel toe" + } + } + ], + "wayHandling": 2, + "deletion": { + "softDeletionTags": { + "and": ["amenity=","disused:amenity:={amenity}"] + } + }, + "allowMove": true +} \ No newline at end of file diff --git a/assets/themes/artwork/artwork.json b/assets/themes/artwork/artwork.json index c03d7c023c..d1f692549c 100644 --- a/assets/themes/artwork/artwork.json +++ b/assets/themes/artwork/artwork.json @@ -49,390 +49,6 @@ "startLat": 0, "startLon": 0, "layers": [ - { - "id": "artwork", - "name": { - "en": "Artworks", - "nl": "Kunstwerken", - "fr": "Œuvres d'art", - "de": "Kunstwerke", - "id": "Karya seni", - "it": "Opere d’arte", - "ru": "Произведения искусства", - "es": "Obras de arte", - "ja": "美術品", - "zh_Hant": "藝術品", - "nb_NO": "Kunstverk" - }, - "source": { - "osmTags": "tourism=artwork" - }, - "title": { - "render": { - "en": "Artwork", - "nl": "Kunstwerk", - "fr": "Œuvre d'art", - "de": "Kunstwerk", - "id": "Karya seni", - "it": "Opera d’arte", - "ru": "Художественная работа", - "es": "Obra de arte", - "ja": "アートワーク", - "zh_Hant": "藝術品", - "nb_NO": "Kunstverk" - }, - "mappings": [ - { - "if": "name~*", - "then": { - "en": "Artwork {name}", - "nl": "Kunstwerk {name}", - "fr": "Œuvre d'art {name}", - "de": "Kunstwerk {name}", - "id": "Karya seni {name}", - "it": "Opera {name}", - "ru": "Художественная работа {name}", - "es": "Obra de arte {nombre}", - "ja": "アートワーク {name}", - "zh_Hant": "藝術品{name}" - } - } - ] - }, - "icon": { - "render": "./assets/themes/artwork/artwork.svg" - }, - "color": { - "render": "#0000ff" - }, - "width": { - "render": "10" - }, - "description": { - "en": "Diverse pieces of artwork", - "nl": "Verschillende soorten kunstwerken", - "fr": "Diverses œuvres d'art", - "de": "Verschiedene Kunstwerke", - "it": "Diverse opere d’arte", - "ru": "Разнообразные произведения искусства", - "es": "Diversas piezas de obras de arte", - "ja": "多様な作品", - "zh_Hant": "不同類型的藝術品" - }, - "minzoom": 12, - "wayHandling": 2, - "presets": [ - { - "tags": [ - "tourism=artwork" - ], - "title": { - "en": "Artwork", - "nl": "Kunstwerk", - "fr": "Œuvre d'art", - "de": "Kunstwerk", - "it": "Opera d’arte", - "ru": "Художественная работа", - "es": "Obra de arte", - "ja": "アートワーク", - "zh_Hant": "藝術品", - "nb_NO": "Kunstverk" - } - } - ], - "tagRenderings": [ - "images", - { - "render": { - "en": "This is a {artwork_type}", - "nl": "Dit is een {artwork_type}", - "fr": "Type d'œuvre : {artwork_type}", - "de": "Dies ist ein {artwork_type}", - "it": "Si tratta di un {artwork_type}", - "ru": "Это {artwork_type}", - "es": "Esta es un {artwork_type}", - "ja": "これは{artwork_type}です", - "zh_Hant": "這是 {artwork_type}", - "nb_NO": "Dette er et kunstverk av typen {artwork_type}" - }, - "question": { - "en": "What is the type of this artwork?", - "nl": "Wat voor soort kunstwerk is dit?", - "fr": "Quel est le type de cette œuvre d'art?", - "de": "Was ist die Art dieses Kunstwerks?", - "it": "Che tipo di opera d’arte è questo?", - "ru": "К какому типу относится эта работа?", - "es": "Cuál es el tipo de esta obra de arte?", - "ja": "この作品の種類は何ですか?", - "zh_Hant": "這是什麼類型的藝術品?", - "nb_NO": "Hvilken type kunstverk er dette?" - }, - "freeform": { - "key": "artwork_type", - "addExtraTags": [ - "fixme=Artowrk type was added with the freeform, might need another check" - ] - }, - "mappings": [ - { - "if": "artwork_type=architecture", - "then": { - "en": "Architecture", - "nl": "Architectuur", - "fr": "Architecture", - "de": "Architektur", - "it": "Architettura", - "ru": "Архитектура", - "ja": "建物", - "zh_Hant": "建築物", - "nb_NO": "Arkitektur" - } - }, - { - "if": "artwork_type=mural", - "then": { - "en": "Mural", - "nl": "Muurschildering", - "fr": "Peinture murale", - "de": "Wandbild", - "it": "Murale", - "ru": "Фреска", - "ja": "壁画", - "zh_Hant": "壁畫", - "nb_NO": "Veggmaleri" - } - }, - { - "if": "artwork_type=painting", - "then": { - "en": "Painting", - "nl": "Schilderij", - "fr": "Peinture", - "de": "Malerei", - "it": "Dipinto", - "ru": "Живопись", - "ja": "絵画", - "zh_Hant": "繪畫", - "nb_NO": "Maleri" - } - }, - { - "if": "artwork_type=sculpture", - "then": { - "en": "Sculpture", - "nl": "Beeldhouwwerk", - "fr": "Sculpture", - "de": "Skulptur", - "it": "Scultura", - "ru": "Скульптура", - "ja": "彫刻", - "zh_Hant": "雕塑", - "nb_NO": "Skulptur" - } - }, - { - "if": "artwork_type=statue", - "then": { - "en": "Statue", - "nl": "Standbeeld", - "fr": "Statue", - "de": "Statue", - "it": "Statua", - "ru": "Статуя", - "ja": "彫像", - "zh_Hant": "雕像", - "nb_NO": "Statue" - } - }, - { - "if": "artwork_type=bust", - "then": { - "en": "Bust", - "nl": "Buste", - "fr": "Buste", - "de": "Büste", - "it": "Busto", - "ru": "Бюст", - "ja": "胸像", - "zh_Hant": "半身像", - "nb_NO": "Byste" - } - }, - { - "if": "artwork_type=stone", - "then": { - "en": "Stone", - "nl": "Steen", - "fr": "Rocher", - "de": "Stein", - "it": "Masso", - "ru": "Камень", - "ja": "石", - "zh_Hant": "石頭", - "nb_NO": "Stein" - } - }, - { - "if": "artwork_type=installation", - "then": { - "en": "Installation", - "nl": "Installatie", - "fr": "Installation", - "de": "Installation", - "it": "Istallazione", - "ru": "Инсталляция", - "ja": "インスタレーション", - "zh_Hant": "安裝", - "nb_NO": "Installasjon" - } - }, - { - "if": "artwork_type=graffiti", - "then": { - "en": "Graffiti", - "nl": "Graffiti", - "fr": "Graffiti", - "de": "Graffiti", - "it": "Graffiti", - "ru": "Граффити", - "ja": "落書き", - "zh_Hant": "塗鴨", - "nb_NO": "Graffiti" - } - }, - { - "if": "artwork_type=relief", - "then": { - "en": "Relief", - "nl": "Reliëf", - "fr": "Relief", - "de": "Relief", - "it": "Rilievo", - "ru": "Рельеф", - "ja": "レリーフ", - "zh_Hant": "寬慰", - "nb_NO": "Relieff" - } - }, - { - "if": "artwork_type=azulejo", - "then": { - "en": "Azulejo (Spanish decorative tilework)", - "nl": "Azulejo (Spaanse siertegels)", - "fr": "Azulejo (faïence latine)", - "de": "Azulejo (spanische dekorative Fliesenarbeit)", - "it": "Azulejo (ornamento decorativo piastrellato spagnolo)", - "ru": "Азуле́жу (испанская роспись глазурованной керамической плитки)", - "ja": "Azulejo (スペインの装飾タイル)", - "zh_Hant": "Azulejo (西班牙雕塑作品名稱)", - "nb_NO": "Azulejo (Spansk dekorativt flisverk)" - } - }, - { - "if": "artwork_type=tilework", - "then": { - "en": "Tilework", - "nl": "Tegelwerk", - "fr": "Carrelage", - "de": "Fliesenarbeit", - "it": "Mosaico di piastrelle", - "ru": "Плитка (мозаика)", - "ja": "タイルワーク", - "zh_Hant": "瓷磚", - "nb_NO": "Flisarbeid" - } - } - ], - "id": "artwork-artwork_type" - }, - { - "question": { - "en": "Which artist created this?", - "nl": "Welke kunstenaar creëerde dit kunstwerk?", - "fr": "Quel artiste a créé cette œuvre ?", - "de": "Welcher Künstler hat das geschaffen?", - "it": "Quale artista ha creato quest’opera?", - "ru": "Какой художник создал это?", - "ja": "どのアーティストが作ったんですか?", - "zh_Hant": "創造這個的藝術家是誰?", - "nb_NO": "Hvilken artist lagde dette?" - }, - "render": { - "en": "Created by {artist_name}", - "nl": "Gecreëerd door {artist_name}", - "fr": "Créé par {artist_name}", - "de": "Erstellt von {artist_name}", - "it": "Creato da {artist_name}", - "ru": "Создано {artist_name}", - "ja": "作成者:{artist_name}", - "zh_Hant": "{artist_name} 創作", - "nb_NO": "Laget av {artist_name}" - }, - "freeform": { - "key": "artist_name" - }, - "id": "artwork-artist_name" - }, - { - "question": { - "en": "Is there a website with more information about this artwork?", - "nl": "Is er een website met meer informatie over dit kunstwerk?", - "fr": "Existe-t-il un site web où trouver plus d'informations sur cette œuvre d'art ?", - "de": "Gibt es eine Website mit weiteren Informationen über dieses Kunstwerk?", - "it": "Esiste un sito web con maggiori informazioni su quest’opera?", - "ru": "Есть ли сайт с более подробной информацией об этой работе?", - "ja": "この作品についての詳しい情報はどのウェブサイトにありますか?", - "zh_Hant": "在那個網站能夠找到更多藝術品的資訊?", - "nb_NO": "Finnes det en nettside med mer info om dette kunstverket?" - }, - "render": { - "en": "More information on this website", - "nl": "Meer informatie op deze website", - "fr": "Plus d'info sûr ce site web", - "de": "Weitere Informationen auf dieser Webseite", - "id": "Info lanjut tersedia di laman web ini.", - "it": "Ulteriori informazioni su questo sito web", - "ru": "Больше информации на этом сайте", - "ja": "Webサイトに詳細情報がある", - "zh_Hant": "這個網站有更多資訊", - "nb_NO": "Mer info er å finne på denne nettsiden" - }, - "freeform": { - "key": "website", - "type": "url" - }, - "id": "artwork-website" - }, - { - "question": { - "en": "Which Wikidata-entry corresponds with this artwork?", - "nl": "Welk Wikidata-item beschrijft dit kunstwerk?", - "fr": "Quelle entrée Wikidata correspond à cette œuvre d'art ?", - "de": "Welcher Wikidata-Eintrag entspricht diesem Kunstwerk?", - "it": "Quale elemento Wikidata corrisponde a quest’opera d’arte?", - "ru": "Какая запись в Wikidata соответсвует этой работе?", - "ja": "このアートワークに関するWikidataのエントリーはどれですか?", - "zh_Hant": "這個藝術品有那個對應的 Wikidata 項目?", - "nb_NO": "Hvilken Wikipedia-oppføring samsvarer med dette kunstverket?" - }, - "render": { - "en": "Corresponds with {wikidata}", - "nl": "Komt overeen met {wikidata}", - "fr": "Correspond à {wikidata}", - "de": "Entspricht {wikidata}", - "it": "Corrisponde a {wikidata}", - "ru": "Запись об этой работе в wikidata: {wikidata}", - "ja": "{wikidata}に関連する", - "zh_Hant": "與 {wikidata}對應", - "nb_NO": "Samsvarer med {wikidata}" - }, - "freeform": { - "key": "wikidata", - "type": "wikidata" - }, - "id": "artwork-wikidata" - } - ] - } + "artwork" ] } \ No newline at end of file diff --git a/assets/themes/shops/shops.json b/assets/themes/shops/shops.json index bae4c3cbd6..ee02f6e59f 100644 --- a/assets/themes/shops/shops.json +++ b/assets/themes/shops/shops.json @@ -37,323 +37,6 @@ "widenFactor": 3, "socialImage": "", "layers": [ - { - "id": "shops", - "name": { - "en": "Shop", - "fr": "Magasin", - "ru": "Магазин", - "ja": "店", - "nl": "Winkel" - }, - "minzoom": 16, - "source": { - "osmTags": { - "and": [ - "shop~*" - ] - } - }, - "title": { - "render": { - "en": "Shop", - "fr": "Magasin", - "ru": "Магазин", - "ja": "店", - "nl": "Winkel" - }, - "mappings": [ - { - "if": { - "and": [ - "name~*" - ] - }, - "then": { - "en": "{name}", - "fr": "{name}", - "ru": "{name}", - "ja": "{name}" - } - }, - { - "if": { - "and": [ - "shop!~yes" - ] - }, - "then": { - "en": "{shop}", - "fr": "{shop}", - "ru": "{shop}", - "ja": "{shop}" - } - } - ] - }, - "description": { - "en": "A shop", - "fr": "Un magasin", - "ja": "ショップ", - "nl": "Een winkel", - "ru": "Магазин" - }, - "tagRenderings": [ - "images", - { - "question": { - "en": "What is the name of this shop?", - "fr": "Qu'est-ce que le nom de ce magasin?", - "ru": "Как называется этот магазин?", - "ja": "このお店の名前は何ですか?", - "nl": "Wat is de naam van deze winkel?" - }, - "render": "This shop is called {name}", - "freeform": { - "key": "name" - }, - "id": "shops-name" - }, - { - "render": { - "en": "This shop sells {shop}", - "fr": "Ce magasin vends {shop}", - "ja": "こちらのお店では{shop}を販売しております" - }, - "question": { - "en": "What does this shop sell?", - "fr": "Que vends ce magasin ?", - "ja": "このお店では何を売っていますか?", - "ru": "Что продаётся в этом магазине?" - }, - "freeform": { - "key": "shop" - }, - "mappings": [ - { - "if": { - "and": [ - "shop=convenience" - ] - }, - "then": { - "en": "Convenience store", - "fr": "Épicerie/superette", - "ja": "コンビニエンスストア" - } - }, - { - "if": { - "and": [ - "shop=supermarket" - ] - }, - "then": { - "en": "Supermarket", - "fr": "Supermarché", - "ru": "Супермаркет", - "ja": "スーパーマーケット", - "nl": "Supermarkt" - } - }, - { - "if": { - "and": [ - "shop=clothes" - ] - }, - "then": { - "en": "Clothing store", - "fr": "Magasin de vêtements", - "ru": "Магазин одежды", - "ja": "衣料品店" - } - }, - { - "if": { - "and": [ - "shop=hairdresser" - ] - }, - "then": { - "en": "Hairdresser", - "fr": "Coiffeur", - "ru": "Парикмахерская", - "ja": "理容師", - "nl": "Kapper" - } - }, - { - "if": { - "and": [ - "shop=bakery" - ] - }, - "then": { - "en": "Bakery", - "fr": "Boulangerie", - "ja": "ベーカリー", - "nl": "Bakkerij" - } - }, - { - "if": { - "and": [ - "shop=car_repair" - ] - }, - "then": { - "en": "Car repair (garage)", - "fr": "Garagiste", - "ja": "自動車修理(ガレージ)" - } - }, - { - "if": { - "and": [ - "shop=car" - ] - }, - "then": { - "en": "Car dealer", - "fr": "Concessionnaire", - "ru": "Автосалон", - "ja": "自動車ディーラー" - } - } - ], - "id": "shops-shop" - }, - { - "render": { - "en": "{phone}", - "fr": "{phone}", - "ca": "{phone}", - "id": "{phone}", - "ru": "{phone}", - "ja": "{phone}" - }, - "question": { - "en": "What is the phone number?", - "fr": "Quel est le numéro de téléphone ?", - "ja": "電話番号は何番ですか?", - "nl": "Wat is het telefoonnummer?", - "ru": "Какой телефон?" - }, - "freeform": { - "key": "phone", - "type": "phone" - }, - "id": "shops-phone" - }, - { - "render": { - "en": "{website}", - "fr": "{website}", - "ca": "{website}", - "id": "{website}", - "ru": "{website}", - "ja": "{website}" - }, - "question": { - "en": "What is the website of this shop?", - "fr": "Quel est le site internet de ce magasin ?", - "ja": "このお店のホームページは何ですか?", - "nl": "Wat is de website van deze winkel?", - "ru": "Какой веб-сайт у этого магазина?" - }, - "freeform": { - "key": "website", - "type": "url" - }, - "id": "shops-website" - }, - { - "render": { - "en": "{email}", - "fr": "{email}", - "id": "{email}", - "ru": "{email}", - "ja": "{email}" - }, - "question": { - "en": "What is the email address of this shop?", - "fr": "Quelle est l'adresse électronique de ce magasin ?", - "ja": "このお店のメールアドレスは何ですか?", - "ru": "Каков адрес электронной почты этого магазина?", - "nl": "Wat is het e-mailadres van deze winkel?" - }, - "freeform": { - "key": "email", - "type": "email" - }, - "id": "shops-email" - }, - { - "render": { - "en": "{opening_hours_table(opening_hours)}", - "fr": "{opening_hours_table(opening_hours)}", - "ru": "{opening_hours_table(opening_hours)}", - "ja": "{opening_hours_table(opening_hours)}" - }, - "question": { - "en": "What are the opening hours of this shop?", - "fr": "Quels sont les horaires d'ouverture de ce magasin ?", - "ja": "この店の営業時間は何時から何時までですか?", - "nl": "Wat zijn de openingsuren van deze winkel?", - "ru": "Каковы часы работы этого магазина?" - }, - "freeform": { - "key": "opening_hours", - "type": "opening_hours" - }, - "id": "shops-opening_hours" - }, - "questions", - "reviews" - ], - "icon": { - "render": "./assets/themes/shops/shop.svg" - }, - "iconOverlays": [ - { - "if": "opening_hours~*", - "then": "isOpen", - "badge": true - } - ], - "width": { - "render": "8" - }, - "iconSize": { - "render": "40,40,center" - }, - "color": { - "render": "#00f" - }, - "presets": [ - { - "tags": [ - "shop=yes" - ], - "title": { - "en": "Shop", - "fr": "Magasin", - "ru": "Магазин", - "ja": "店", - "nl": "Winkel" - }, - "description": { - "en": "Add a new shop", - "fr": "Ajouter un nouveau magasin", - "ru": "Добавить новый магазин", - "ja": "新しい店を追加する", - "nl": "Voeg een nieuwe winkel toe" - } - } - ], - "wayHandling": 2 - } - ], - "roamingRenderings": [] +"shops" + ] } \ No newline at end of file