Further development of split-road feature; refactoring of change-handling

This commit is contained in:
Pieter Vander Vennet 2021-07-15 20:47:28 +02:00
parent dc4cdda3b5
commit 96ecded0b9
37 changed files with 967 additions and 568 deletions

View file

@ -39,6 +39,7 @@ export default class Minimap extends BaseUIElement {
div.style.width = "100%"
div.style.minWidth = "40px"
div.style.minHeight = "40px"
div.style.position = "relative"
const wrapper = document.createElement("div")
wrapper.appendChild(div)
const self = this;

View file

@ -20,6 +20,7 @@ import LocationInput from "../Input/LocationInput";
import {InputElement} from "../Input/InputElement";
import Loc from "../../Models/Loc";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction";
/*
* The SimpleAddUI is a single panel, which can have multiple states:
@ -61,11 +62,6 @@ export default class SimpleAddUI extends Toggle {
const selectedPreset = new UIEventSource<PresetInfo>(undefined);
isShown.addCallback(_ => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened
function createNewPoint(tags: any[], location: { lat: number, lon: number }) {
let feature = State.state.changes.createElement(tags, location.lat, location.lon);
State.state.selectedElement.setData(feature);
}
const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset)
const addUi = new VariableUiElement(
@ -75,7 +71,9 @@ export default class SimpleAddUI extends Toggle {
}
return SimpleAddUI.CreateConfirmButton(preset,
(tags, location) => {
createNewPoint(tags, location)
let changes =
State.state.changes.applyAction(new CreateNewNodeAction(tags, location.lat, location.lon))
State.state.selectedElement.setData(changes.newFeatures[0]);
selectedPreset.setData(undefined)
}, () => {
selectedPreset.setData(undefined)

View file

@ -5,6 +5,7 @@ import Combine from "../Base/Combine";
import State from "../../State";
import Svg from "../../Svg";
import {Tag} from "../../Logic/Tags/Tag";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
export default class DeleteImage extends Toggle {
@ -15,14 +16,17 @@ export default class DeleteImage extends Toggle {
.SetClass("rounded-full p-1")
.SetStyle("color:white;background:#ff8c8c")
.onClick(() => {
State.state?.changes?.addTag(tags.data.id, new Tag(key, oldValue), tags);
State.state?.changes?.
applyAction(new ChangeTagAction(tags.data.id, new Tag(key, oldValue), tags.data))
});
const deleteButton = Translations.t.image.doDelete.Clone()
.SetClass("block w-full pl-4 pr-4")
.SetStyle("color:white;background:#ff8c8c; border-top-left-radius:30rem; border-top-right-radius: 30rem;")
.onClick(() => {
State.state?.changes?.addTag(tags.data.id, new Tag(key, ""), tags);
State.state?.changes?.applyAction(
new ChangeTagAction( tags.data.id, new Tag(key, ""), tags.data)
)
});
const cancelButton = Translations.t.general.cancel.Clone().SetClass("bg-white pl-4 pr-4").SetStyle("border-bottom-left-radius:30rem; border-bottom-right-radius: 30rem;");

View file

@ -11,6 +11,7 @@ import FileSelectorButton from "../Input/FileSelectorButton";
import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader";
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
export class ImageUploadFlow extends Toggle {
@ -28,7 +29,10 @@ export class ImageUploadFlow extends Toggle {
key = imagePrefix + ":" + freeIndex;
}
console.log("Adding image:" + key, url);
State.state.changes.addTag(tags.id, new Tag(key, url), tagsSource);
State.state.changes
.applyAction(new ChangeTagAction(
tags.id, new Tag(key, url), tagsSource.data
))
})

View file

@ -3,7 +3,7 @@ import State from "../../State";
import Toggle from "../Input/Toggle";
import Translations from "../i18n/Translations";
import Svg from "../../Svg";
import DeleteAction from "../../Logic/Osm/DeleteAction";
import DeleteAction from "../../Logic/Osm/Actions/DeleteAction";
import {Tag} from "../../Logic/Tags/Tag";
import {UIEventSource} from "../../Logic/UIEventSource";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
@ -19,6 +19,7 @@ import {Changes} from "../../Logic/Osm/Changes";
import {And} from "../../Logic/Tags/And";
import Constants from "../../Models/Constants";
import DeleteConfig from "../../Customizations/JSON/DeleteConfig";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
export default class DeleteWizard extends Toggle {
/**
@ -58,7 +59,9 @@ export default class DeleteWizard extends Toggle {
})
}
(State.state?.changes ?? new Changes())
.addTag(id, new And(tagsToApply.map(kv => new Tag(kv.k, kv.v))), tagsSource);
.applyAction(new ChangeTagAction(
id, new And(tagsToApply.map(kv => new Tag(kv.k, kv.v))), tagsSource.data
))
}
function doDelete(selected: TagsFilter) {

View file

@ -13,6 +13,7 @@ import SharedTagRenderings from "../../Customizations/SharedTagRenderings";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import DeleteWizard from "./DeleteWizard";
import SplitRoadWizard from "./SplitRoadWizard";
export default class FeatureInfoBox extends ScrollableFullScreen {
@ -66,10 +67,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
renderings.push(questionBox);
}
const hasMinimap = layerConfig.tagRenderings.some(tr => tr.hasMinimap())
if (!hasMinimap) {
renderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap")))
}
if (layerConfig.deletion) {
renderings.push(
@ -81,6 +78,19 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
))
}
if (layerConfig.allowSplit) {
renderings.push(
new VariableUiElement(tags.map(tags => tags.id).map(id =>
new SplitRoadWizard(id))
))
}
const hasMinimap = layerConfig.tagRenderings.some(tr => tr.hasMinimap())
if (!hasMinimap) {
renderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap")))
}
renderings.push(
new VariableUiElement(
State.state.osmConnection.userDetails

View file

@ -11,7 +11,9 @@ import Combine from "../Base/Combine";
import {Button} from "../Base/Button";
import Translations from "../i18n/Translations";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import SplitAction from "../../Logic/Osm/SplitAction";
import SplitAction from "../../Logic/Osm/Actions/SplitAction";
import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject";
import Title from "../Base/Title";
export default class SplitRoadWizard extends Toggle {
private static splitLayout = new UIEventSource(SplitRoadWizard.GetSplitLayout())
@ -26,24 +28,25 @@ export default class SplitRoadWizard extends Toggle {
const t = Translations.t.split;
// Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
const splitPoints = new UIEventSource<{feature: any, freshness: Date}[]>([]);
const splitPoints = new UIEventSource<{ feature: any, freshness: Date }[]>([]);
const hasBeenSplit = new UIEventSource(false)
// Toggle variable between show split button and map
const splitClicked = new UIEventSource<boolean>(false);
// Minimap on which you can select the points to be splitted
const miniMap = new Minimap({background: State.state.backgroundLayer});
miniMap.SetStyle("width: 100%; height: 50rem;");
const miniMap = new Minimap({background: State.state.backgroundLayer, allowMoving: false});
miniMap.SetStyle("width: 100%; height: 24rem;");
// Define how a cut is displayed on the map
// Load the road with given id on the minimap
const roadElement = State.state.allElements.ContainingFeatures.get(id)
const splitAction = new SplitAction(roadElement)
const roadEventSource = new UIEventSource([{feature: roadElement, freshness: new Date()}]);
// Datalayer displaying the road and the cut points (if any)
new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true);
new ShowDataLayer(splitPoints, miniMap.leafletMap, SplitRoadWizard.splitLayout, false, false)
new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true, "splitRoadWay");
new ShowDataLayer(splitPoints, miniMap.leafletMap, SplitRoadWizard.splitLayout, false, false, "splitRoad: splitpoints")
/**
* Handles a click on the overleaf map.
@ -60,7 +63,7 @@ export default class SplitRoadWizard extends Toggle {
// let the state remember the point, to be able to retrieve it later by id
State.state.allElements.addOrGetElement(pointOnRoad);
// Add it to the list of all points and notify observers
splitPoints.data.push({feature: pointOnRoad, freshness: new Date()}); // show the point on the data layer
splitPoints.ping(); // not updated using .setData, so manually ping observers
@ -73,7 +76,7 @@ export default class SplitRoadWizard extends Toggle {
}))
// Toggle between splitmap
const splitButton = new SubtleButton(Svg.scissors_ui(), "Split road");
const splitButton = new SubtleButton(Svg.scissors_ui(), t.inviteToSplit.Clone());
splitButton.onClick(
() => {
splitClicked.setData(true)
@ -83,45 +86,63 @@ export default class SplitRoadWizard extends Toggle {
// Only show the splitButton if logged in, else show login prompt
const splitToggle = new Toggle(
splitButton,
t.loginToSplit.Clone().onClick(State.state.osmConnection.AttemptLogin),
t.loginToSplit.Clone().onClick(() => State.state.osmConnection.AttemptLogin()),
State.state.osmConnection.isLoggedIn)
// Save button
const saveButton = new Button("Split here", () => splitAction.DoSplit(splitPoints.data));
const saveButton = new Button(t.split.Clone(), () => {
hasBeenSplit.setData(true)
OsmObject.DownloadObject(id).addCallbackAndRunD(way => {
OsmObject.DownloadReferencingRelations(id).addCallbackAndRunD(
partOf => {
const splitAction = new SplitAction(
<OsmWay>way, way.asGeoJson(), partOf, splitPoints.data.map(ff => ff.feature)
)
State.state.changes.applyAction(splitAction)
}
)
}
)
});
saveButton.SetClass("block btn btn-primary");
const disabledSaveButton = new Button("Split here", undefined);
disabledSaveButton.SetClass("block btn btn-disabled");
// Only show the save button if there are split points defined
const saveToggle = new Toggle(disabledSaveButton, saveButton, splitPoints.map((data) => data.length === 0))
const cancelButton = new Button("Cancel", () => {
const cancelButton = new Button(Translations.t.general.cancel.Clone(), () => {
splitClicked.setData(false);
splitPoints.setData([]);
splitClicked.setData(false)
});
cancelButton.SetClass("block btn btn-secondary");
const splitTitle = t.splitTitle;
const mapView = new Combine([splitTitle, miniMap, cancelButton, saveToggle]);
super(mapView, splitToggle, splitClicked);
const splitTitle = new Title(t.splitTitle);
const mapView = new Combine([splitTitle, miniMap, new Combine([cancelButton, saveToggle])]);
mapView.SetClass("question")
const confirm = new Toggle(mapView, splitToggle, splitClicked);
super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit)
}
private static GetSplitLayout(): LayoutConfig {
return new LayoutConfig({
maintainer: "mapcomplete",
language: [],
language: ["en"],
startLon: 0,
startLat: 0,
description: undefined,
description: "Split points visualisations - built in at SplitRoadWizard.ts",
icon: "", startZoom: 0,
title: "Split locations",
version: "",
id: "splitpositions",
layers: [{id: "splitpositions", source: {osmTags: "_cutposition=yes"}, icon: "./assets/svg/plus.svg"}]
}, true, "split road wizard layout")
}, true, "(BUILTIN) SplitRoadWizard.ts")
}
}

View file

@ -25,6 +25,7 @@ import BaseUIElement from "../BaseUIElement";
import {DropDown} from "../Input/DropDown";
import {Unit} from "../../Customizations/JSON/Denomination";
import InputElementWrapper from "../Input/InputElementWrapper";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
/**
* Shows the question element.
@ -56,7 +57,9 @@ export default class TagRenderingQuestion extends Combine {
const selection = inputElement.GetValue().data;
if (selection) {
(State.state?.changes ?? new Changes())
.addTag(tags.data.id, selection, tags);
.applyAction(new ChangeTagAction(
tags.data.id, selection, tags.data
))
}
if (options.afterSave) {

View file

@ -22,7 +22,8 @@ export default class ShowDataLayer {
leafletMap: UIEventSource<L.Map>,
layoutToUse: UIEventSource<LayoutConfig>,
enablePopups = true,
zoomToFeatures = false) {
zoomToFeatures = false,
name?:string) {
this._leafletMap = leafletMap;
this._enablePopups = enablePopups;
this._features = features;
@ -60,6 +61,7 @@ export default class ShowDataLayer {
}
const allFeats = features.data.map(ff => ff.feature);
console.log("Rendering ",allFeats, "features at layer ", name)
geoLayer = self.CreateGeojsonLayer();
for (const feat of allFeats) {
if (feat === undefined) {
@ -85,9 +87,6 @@ export default class ShowDataLayer {
console.error(e)
}
}
State.state.selectedElement.ping();
}
features.addCallback(() => update());

View file

@ -126,6 +126,7 @@ export default class SpecialVisualizations {
// This is a list of values
idList = JSON.parse(value)
}
for (const id of idList) {
features.push({
freshness: new Date(),