From 828102c547464043ceec159df8771e2a81a92b1c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 11 Oct 2021 00:54:35 +0200 Subject: [PATCH] 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