Merge branch 'feature/move-flox' into develop

This commit is contained in:
Pieter Vander Vennet 2021-10-14 17:39:16 +02:00
commit 43f73947c9
47 changed files with 2139 additions and 786 deletions

View file

@ -153,6 +153,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;")

View file

@ -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,10 @@ 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";
import Toggle from "./Toggle";
export default class LocationInput extends InputElement<Loc> {
export default class LocationInput extends InputElement<Loc> implements MinimapObj {
private static readonly matchLayer = new LayerConfig(
{
@ -41,8 +43,14 @@ export default class LocationInput extends InputElement<Loc> {
private readonly _snappedPointTags: any;
private readonly _bounds: UIEventSource<BBox>;
public readonly _matching_layer: LayerConfig;
private readonly map: BaseUIElement & MinimapObj;
public readonly leafletMap: UIEventSource<any>
private readonly clickLocation: UIEventSource<Loc>;
private readonly _minZoom: number;
constructor(options: {
minZoom?: number,
mapBackground?: UIEventSource<BaseLayer>,
snapTo?: UIEventSource<{ feature: any }[]>,
maxSnapDistance?: number,
@ -57,6 +65,7 @@ export default class LocationInput extends InputElement<Loc> {
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 {
@ -127,6 +136,19 @@ export default class LocationInput extends InputElement<Loc> {
}
this.mapBackground = options.mapBackground ?? State.state?.backgroundLayer ?? new UIEventSource(AvailableBaseLayers.osmCarto)
this.SetClass("block h-full")
this.clickLocation = new UIEventSource<Loc>(undefined);
this.map = Minimap.createMiniMap(
{
location: this._centerLocation,
background: this.mapBackground,
attribution: this.mapBackground !== State.state?.backgroundLayer,
lastClickLocation: this.clickLocation,
bounds: this._bounds
}
)
this.leafletMap = this.map.leafletMap
}
GetValue(): UIEventSource<Loc> {
@ -136,31 +158,24 @@ export default class LocationInput extends InputElement<Loc> {
IsValid(t: Loc): boolean {
return t !== undefined;
}
protected InnerConstructElement(): HTMLElement {
try {
const clickLocation = new UIEventSource<Loc>(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);
if (this._snapTo !== undefined) {
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) {
// Show the lines to snap to
new ShowDataMultiLayer({
features: new StaticFeatureSource(this._snapTo, true),
enablePopups: false,
zoomToFeatures: false,
leafletMap: map.leafletMap,
leafletMap: this.map.leafletMap,
layers: State.state.filteredLayers
}
)
@ -175,22 +190,22 @@ export default class LocationInput extends InputElement<Loc> {
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;
}
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)
}, [map.leafletMap])
}, [this.map.leafletMap])
const animatedHand = Svg.hand_ui()
.SetStyle("width: 2rem; height: unset;")
@ -204,12 +219,12 @@ export default class LocationInput extends InputElement<Loc> {
]).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"),
map
this.map
.SetClass("z-0 relative block w-full h-full bg-gray-100")
]).ConstructElement();
@ -219,4 +234,9 @@ export default class LocationInput extends InputElement<Loc> {
}
}
installBounds(factor: number | BBox, showRange?: boolean): void {
this.map.installBounds(factor, showRange)
}
}

View file

@ -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<boolean>(id.indexOf("-") < 0)

View file

@ -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(

221
UI/Popup/MoveWizard.ts Normal file
View file

@ -0,0 +1,221 @@
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";
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";
import Img from "../Base/Img";
interface MoveReason {
text: Translation | string,
invitingText: Translation | string,
icon: BaseUIElement,
changesetCommentValue: string,
lockBounds: true | boolean,
background: undefined | "map" | "photo" | string | string[],
startZoom: number,
minZoom: number
}
export default class MoveWizard extends Toggle {
/**
* The UI-element which helps moving a point
*/
constructor(
featureToMove: any,
state: {
osmConnection: OsmConnection,
featureSwitchUserbadge: UIEventSource<boolean>,
changes: Changes,
layoutToUse: LayoutConfig,
allElements: ElementStorage
}, options : MoveConfig) {
const t = Translations.t.move
const loginButton = new Toggle(
t.loginToMove.Clone().SetClass("btn").onClick(() => state.osmConnection.AttemptLogin()),
undefined,
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<MoveReason>(undefined)
let moveButton : BaseUIElement;
if(reasons.length === 1){
const reason = reasons[0]
moveReason.setData(reason)
moveButton = new SubtleButton(
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().SetStyle("height: 1.5rem; width: auto"),
t.inviteToMove.generic.Clone()
).onClick(() => {
currentStep.setData("reason")
})
}
const moveAgainButton = new SubtleButton(
Svg.move_ui(),
t.inviteToMoveAgain.Clone()
).onClick(() => {
currentStep.setData("reason")
})
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 = moveReason.map(reason => {
if (reason === undefined) {
return undefined
}
const loc = new UIEventSource<Loc>({
lon: lon,
lat: lat,
zoom: reason?.startZoom ?? 16
})
const locationInput = new LocationInput({
minZoom: reason.minZoom,
centerLocation: loc,
mapBackground: AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(reason.background))
})
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(() => {
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.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")
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) {
case "start":
return moveButton;
case "reason":
return new Combine([t.whyMove.Clone().SetClass("text-lg font-bold"), selectReason, cancelButton]).SetClass(dialogClasses);
case "pick_location":
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<BaseUIElement>(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)
)
}
}

View file

@ -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)