forked from MapComplete/MapComplete
Refactoring: fix 'delete' and 'move'-buttons as special elements
This commit is contained in:
parent
466dd16568
commit
8a1f0599d9
19 changed files with 317 additions and 296 deletions
|
@ -10,56 +10,58 @@
|
|||
import Hotkeys from "../Base/Hotkeys";
|
||||
import { Geocoding } from "../../Logic/Osm/Geocoding";
|
||||
import { BBox } from "../../Logic/BBox";
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
|
||||
import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore";
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let bounds: UIEventSource<BBox>
|
||||
export let selectedElement: UIEventSource<Feature>;
|
||||
export let selectedLayer: UIEventSource<LayerConfig>;
|
||||
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined;
|
||||
export let bounds: UIEventSource<BBox>;
|
||||
export let selectedElement: UIEventSource<Feature> | undefined = undefined;
|
||||
export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined;
|
||||
|
||||
let searchContents: string = undefined;
|
||||
|
||||
let isRunning: boolean = false;
|
||||
|
||||
let inputElement: HTMLInputElement;
|
||||
|
||||
let feedback: string = undefined
|
||||
|
||||
let feedback: string = undefined;
|
||||
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ ctrl: "F" },
|
||||
Translations.t.hotkeyDocumentation.selectSearch,
|
||||
() => {
|
||||
inputElement?.focus()
|
||||
inputElement?.select()
|
||||
inputElement?.focus();
|
||||
inputElement?.select();
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
async function performSearch() {
|
||||
try {
|
||||
isRunning = true;
|
||||
searchContents = searchContents?.trim() ?? ""
|
||||
searchContents = searchContents?.trim() ?? "";
|
||||
if (searchContents === "") {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const result = await Geocoding.Search(searchContents, bounds.data)
|
||||
const result = await Geocoding.Search(searchContents, bounds.data);
|
||||
if (result.length == 0) {
|
||||
feedback = Translations.t.search.nothing.txt
|
||||
return
|
||||
feedback = Translations.t.search.nothing.txt;
|
||||
return;
|
||||
}
|
||||
const poi = result[0]
|
||||
const [lat0, lat1, lon0, lon1] = poi.boundingbox
|
||||
bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01))
|
||||
const id = poi.osm_type + "/" + poi.osm_id
|
||||
const perLayer = state.perLayer
|
||||
const layers = Array.from(perLayer.values())
|
||||
for (const layer of layers) {
|
||||
const found = layer.features.data.find(f => f.properties.id === id)
|
||||
selectedElement.setData(found)
|
||||
selectedLayer.setData(layer.layer.layerDef)
|
||||
|
||||
const poi = result[0];
|
||||
const [lat0, lat1, lon0, lon1] = poi.boundingbox;
|
||||
bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01));
|
||||
if (perLayer !== undefined) {
|
||||
const id = poi.osm_type + "/" + poi.osm_id;
|
||||
const layers = Array.from(perLayer?.values() ?? []);
|
||||
for (const layer of layers) {
|
||||
const found = layer.features.data.find(f => f.properties.id === id);
|
||||
selectedElement?.setData(found);
|
||||
selectedLayer?.setData(layer.layer.layerDef);
|
||||
|
||||
}
|
||||
}
|
||||
}catch (e) {
|
||||
console.error(e)
|
||||
feedback = Translations.t.search.error.txt
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
feedback = Translations.t.search.error.txt;
|
||||
} finally {
|
||||
isRunning = false;
|
||||
}
|
||||
|
@ -72,7 +74,7 @@
|
|||
|
||||
{#if isRunning}
|
||||
<Loading>{Translations.t.general.search.searching}</Loading>
|
||||
{:else if feedback !== undefined}
|
||||
{:else if feedback !== undefined}
|
||||
<div class="alert" on:click={() => feedback = undefined}>
|
||||
{feedback}
|
||||
</div>
|
||||
|
|
|
@ -177,9 +177,7 @@ export class ImageUploadFlow extends Toggle {
|
|||
)
|
||||
.onClick(() => {
|
||||
console.log("Opening the license settings... ")
|
||||
ScrollableFullScreen.collapse()
|
||||
DefaultGuiState.state.userInfoIsOpened.setData(true)
|
||||
DefaultGuiState.state.userInfoFocusedQuestion.setData("picture-license")
|
||||
state.guistate.openUsersettings("picture-license")
|
||||
})
|
||||
.SetClass("underline"),
|
||||
]).SetStyle("font-size:small;"),
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
import { Map as MlMap } from "maplibre-gl";
|
||||
import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor";
|
||||
import MaplibreMap from "../../Map/MaplibreMap.svelte";
|
||||
import Svg from "../../../Svg";
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte";
|
||||
import DragInvitation from "../../Base/DragInvitation.svelte";
|
||||
|
||||
/**
|
||||
|
@ -14,13 +12,13 @@
|
|||
export let value: UIEventSource<{lon: number, lat: number}>;
|
||||
export let mapProperties: Partial<MapProperties> & { readonly location: UIEventSource<{ lon: number; lat: number }> } = undefined;
|
||||
/**
|
||||
* Called when setup is done, cna be used to add layrs to the map
|
||||
* Called when setup is done, can be used to add more layers to the map
|
||||
*/
|
||||
export let onCreated : (value: Store<{lon: number, lat: number}> , map: Store<MlMap>, mapProperties: MapProperties ) => void
|
||||
|
||||
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
|
||||
let mla = new MapLibreAdaptor(map, mapProperties);
|
||||
|
||||
mapProperties.location.syncWith(value)
|
||||
if(onCreated){
|
||||
onCreated(value, map, mla)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig"
|
|||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import { Feature } from "geojson"
|
||||
import { Feature, Point } from "geojson"
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
|
||||
import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig"
|
||||
import { Utils } from "../../Utils"
|
||||
|
@ -26,6 +26,7 @@ class PointRenderingLayer {
|
|||
private readonly _onClick: (feature: Feature) => void
|
||||
private readonly _allMarkers: Map<string, Marker> = new Map<string, Marker>()
|
||||
private _dirty = false
|
||||
|
||||
constructor(
|
||||
map: MlMap,
|
||||
features: FeatureSource,
|
||||
|
@ -139,6 +140,18 @@ class PointRenderingLayer {
|
|||
store
|
||||
.map((tags) => this._config.rotationAlignment.GetRenderValue(tags).Subs(tags).txt)
|
||||
.addCallbackAndRun((pitchAligment) => marker.setRotationAlignment(pitchAligment))
|
||||
if (feature.geometry.type === "Point") {
|
||||
// When the tags get 'pinged', check that the location didn't change
|
||||
store.addCallbackAndRunD(() => {
|
||||
// Check if the location is still the same
|
||||
const oldLoc = marker.getLngLat()
|
||||
const newloc = (<Point>feature.geometry).coordinates
|
||||
if (newloc[0] === oldLoc.lng && newloc[1] === oldLoc.lat) {
|
||||
return
|
||||
}
|
||||
marker.setLngLat({ lon: newloc[0], lat: newloc[1] })
|
||||
})
|
||||
}
|
||||
return marker
|
||||
}
|
||||
}
|
||||
|
@ -159,6 +172,7 @@ class LineRenderingLayer {
|
|||
|
||||
private static readonly lineConfigKeysColor = ["color", "fillColor"] as const
|
||||
private static readonly lineConfigKeysNumber = ["width", "offset"] as const
|
||||
private static missingIdTriggered = false
|
||||
private readonly _map: MlMap
|
||||
private readonly _config: LineRenderingConfig
|
||||
private readonly _visibility?: Store<boolean>
|
||||
|
@ -167,7 +181,6 @@ class LineRenderingLayer {
|
|||
private readonly _layername: string
|
||||
private readonly _listenerInstalledOn: Set<string> = new Set<string>()
|
||||
|
||||
private static missingIdTriggered = false
|
||||
constructor(
|
||||
map: MlMap,
|
||||
features: FeatureSource,
|
||||
|
@ -248,6 +261,11 @@ class LineRenderingLayer {
|
|||
},
|
||||
})
|
||||
|
||||
map.on("click", linelayer, (e) => {
|
||||
console.log("Click", e)
|
||||
e.originalEvent["consumed"] = true
|
||||
this._onClick(e.features[0])
|
||||
})
|
||||
const polylayer = this._layername + "_polygon"
|
||||
map.addLayer({
|
||||
source: this._layername,
|
||||
|
@ -260,6 +278,10 @@ class LineRenderingLayer {
|
|||
"fill-opacity": 0.1,
|
||||
},
|
||||
})
|
||||
map.on("click", polylayer, (e) => {
|
||||
e.originalEvent["consumed"] = true
|
||||
this._onClick(e.features[0])
|
||||
})
|
||||
|
||||
this._visibility?.addCallbackAndRunD((visible) => {
|
||||
try {
|
||||
|
|
|
@ -20,10 +20,10 @@ import { RadioButton } from "../Input/RadioButton"
|
|||
import { FixedInputElement } from "../Input/FixedInputElement"
|
||||
import Title from "../Base/Title"
|
||||
import { SubstitutedTranslation } from "../SubstitutedTranslation"
|
||||
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
|
||||
import TagRenderingQuestion from "./TagRenderingQuestion"
|
||||
import { OsmId } from "../../Models/OsmFeature"
|
||||
import { OsmId, OsmTags } from "../../Models/OsmFeature"
|
||||
import { LoginToggle } from "./LoginButton"
|
||||
import { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
|
||||
export default class DeleteWizard extends Toggle {
|
||||
/**
|
||||
|
@ -41,13 +41,18 @@ export default class DeleteWizard extends Toggle {
|
|||
* Ideal for the case of "THIS PATH IS ON MY GROUND AND SHOULD BE DELETED IMMEDIATELY OR I WILL GET MY LAWYER" but to mark it as private instead.
|
||||
* (Note that _delete_reason is used as trigger to do actual deletion - setting such a tag WILL delete from the database with that as changeset comment)
|
||||
*
|
||||
* @param id: The id of the element to remove
|
||||
* @param state: the state of the application
|
||||
* @param options softDeletionTags: the tags to apply if the user doesn't have permission to delete, e.g. 'disused:amenity=public_bookcase', 'amenity='. After applying, the element should not be picked up on the map anymore. If undefined, the wizard will only show up if the point can be (hard) deleted
|
||||
*/
|
||||
constructor(id: OsmId, state: FeaturePipelineState, options: DeleteConfig) {
|
||||
const deleteAbility = new DeleteabilityChecker(id, state, options.neededChangesets)
|
||||
const tagsSource = state.allElements.getEventSourceById(id)
|
||||
constructor(
|
||||
id: OsmId,
|
||||
tagsSource: UIEventSource<OsmTags>,
|
||||
state: SpecialVisualizationState,
|
||||
options: DeleteConfig
|
||||
) {
|
||||
const deleteAbility = new DeleteabilityChecker(
|
||||
id,
|
||||
state.osmConnection,
|
||||
options.neededChangesets
|
||||
)
|
||||
|
||||
const isDeleted = new UIEventSource(false)
|
||||
const allowSoftDeletion = !!options.softDeletionTags
|
||||
|
@ -62,7 +67,7 @@ export default class DeleteWizard extends Toggle {
|
|||
if (selected["retagTo"] !== undefined) {
|
||||
// no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping
|
||||
actionToTake = new ChangeTagAction(id, selected["retagTo"], tagsSource.data, {
|
||||
theme: state?.layoutToUse?.id ?? "unkown",
|
||||
theme: state?.layout?.id ?? "unkown",
|
||||
changeType: "special-delete",
|
||||
})
|
||||
} else {
|
||||
|
@ -70,7 +75,7 @@ export default class DeleteWizard extends Toggle {
|
|||
id,
|
||||
options.softDeletionTags,
|
||||
{
|
||||
theme: state?.layoutToUse?.id ?? "unkown",
|
||||
theme: state?.layout?.id ?? "unkown",
|
||||
specialMotivation: selected["deleteReason"],
|
||||
},
|
||||
deleteAbility.canBeDeleted.data.canBeDeleted
|
||||
|
@ -250,7 +255,7 @@ export default class DeleteWizard extends Toggle {
|
|||
private static constructMultipleChoice(
|
||||
config: DeleteConfig,
|
||||
tagsSource: UIEventSource<Record<string, string>>,
|
||||
state: FeaturePipelineState
|
||||
state: SpecialVisualizationState
|
||||
): InputElement<{ deleteReason: string } | { retagTo: TagsFilter }> {
|
||||
const elements: InputElement<{ deleteReason: string } | { retagTo: TagsFilter }>[] = []
|
||||
|
||||
|
@ -282,19 +287,13 @@ export default class DeleteWizard extends Toggle {
|
|||
|
||||
class DeleteabilityChecker {
|
||||
public readonly canBeDeleted: UIEventSource<{ canBeDeleted?: boolean; reason: Translation }>
|
||||
private readonly _id: string
|
||||
private readonly _id: OsmId
|
||||
private readonly _allowDeletionAtChangesetCount: number
|
||||
private readonly _state: {
|
||||
osmConnection: OsmConnection
|
||||
}
|
||||
private readonly _osmConnection: OsmConnection
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
state: { osmConnection: OsmConnection },
|
||||
allowDeletionAtChangesetCount?: number
|
||||
) {
|
||||
constructor(id: OsmId, osmConnection: OsmConnection, allowDeletionAtChangesetCount?: number) {
|
||||
this._id = id
|
||||
this._state = state
|
||||
this._osmConnection = osmConnection
|
||||
this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE
|
||||
|
||||
this.canBeDeleted = new UIEventSource<{ canBeDeleted?: boolean; reason: Translation }>({
|
||||
|
@ -324,7 +323,7 @@ class DeleteabilityChecker {
|
|||
}
|
||||
|
||||
// Does the currently logged in user have enough experience to delete this point?
|
||||
const deletingPointsOfOtherAllowed = this._state.osmConnection.userDetails.map((ud) => {
|
||||
const deletingPointsOfOtherAllowed = this._osmConnection.userDetails.map((ud) => {
|
||||
if (ud === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -347,10 +346,10 @@ class DeleteabilityChecker {
|
|||
// Not yet downloaded
|
||||
return null
|
||||
}
|
||||
const userId = self._state.osmConnection.userDetails.data.uid
|
||||
const userId = self._osmConnection.userDetails.data.uid
|
||||
return !previous.some((editor) => editor !== userId)
|
||||
},
|
||||
[self._state.osmConnection.userDetails]
|
||||
[self._osmConnection.userDetails]
|
||||
)
|
||||
|
||||
// User allowed OR only edited by self?
|
||||
|
|
|
@ -17,7 +17,6 @@ import MoveWizard from "./MoveWizard"
|
|||
import Toggle from "../Input/Toggle"
|
||||
import Lazy from "../Base/Lazy"
|
||||
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import Svg from "../../Svg"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
||||
|
@ -32,9 +31,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
|||
setHash?: true | boolean
|
||||
}
|
||||
) {
|
||||
if (state === undefined) {
|
||||
throw "State is undefined!"
|
||||
}
|
||||
const showAllQuestions = state.featureSwitchShowAllQuestions.map(
|
||||
(fsShow) => fsShow || state.showAllQuestionsAtOnce.data,
|
||||
[state.showAllQuestionsAtOnce]
|
||||
|
@ -98,27 +94,11 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
|||
private static GenerateMainContent(
|
||||
tags: UIEventSource<any>,
|
||||
layerConfig: LayerConfig,
|
||||
state: FeaturePipelineState,
|
||||
showAllQuestions?: Store<boolean>
|
||||
state: FeaturePipelineState
|
||||
): BaseUIElement {
|
||||
let questionBoxes: Map<string, QuestionBox> = new Map<string, QuestionBox>()
|
||||
const t = Translations.t.general
|
||||
const allGroupNames = Utils.Dedup(layerConfig.tagRenderings.map((tr) => tr.group))
|
||||
if (state?.featureSwitchUserbadge?.data ?? true) {
|
||||
const questionSpecs = layerConfig.tagRenderings.filter((tr) => tr.id === "questions")
|
||||
for (const groupName of allGroupNames) {
|
||||
const questions = layerConfig.tagRenderings.filter((tr) => tr.group === groupName)
|
||||
const questionSpec = questionSpecs.filter((tr) => tr.group === groupName)[0]
|
||||
const questionBox = new QuestionBox(state, {
|
||||
tagsSource: tags,
|
||||
tagRenderings: questions,
|
||||
units: layerConfig.units,
|
||||
showAllQuestionsAtOnce:
|
||||
questionSpec?.freeform?.helperArgs["showAllQuestions"] ?? showAllQuestions,
|
||||
})
|
||||
questionBoxes.set(groupName, questionBox)
|
||||
}
|
||||
}
|
||||
|
||||
const withQuestion = layerConfig.tagRenderings.filter(
|
||||
(tr) => tr.question !== undefined
|
||||
|
@ -243,40 +223,6 @@ 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.allElements.ContainingFeatures.get(id)
|
||||
if (feature === undefined) {
|
||||
return "This feature is not register in the state.allElements and cannot be moved"
|
||||
}
|
||||
return new MoveWizard(feature, state, layerConfig.allowMove)
|
||||
})
|
||||
).SetClass("text-base")
|
||||
)
|
||||
}
|
||||
|
||||
if (layerConfig.deletion) {
|
||||
editElements.push(
|
||||
new VariableUiElement(
|
||||
tags
|
||||
.map((tags) => tags.id)
|
||||
.map((id) => new DeleteWizard(id, state, layerConfig.deletion))
|
||||
).SetClass("text-base")
|
||||
)
|
||||
}
|
||||
|
||||
if (layerConfig.allowSplit) {
|
||||
editElements.push(
|
||||
new VariableUiElement(
|
||||
tags.map((tags) => tags.id).map((id) => new SplitRoadWizard(id, state))
|
||||
).SetClass("text-base")
|
||||
)
|
||||
}
|
||||
|
||||
editElements.push(
|
||||
new VariableUiElement(
|
||||
state.osmConnection.userDetails
|
||||
|
@ -302,30 +248,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
|||
)
|
||||
)
|
||||
|
||||
editElements.push(
|
||||
Toggle.If(state.featureSwitchIsDebugging, () => {
|
||||
const config_all_tags: TagRenderingConfig = new TagRenderingConfig(
|
||||
{ render: "{all_tags()}" },
|
||||
""
|
||||
)
|
||||
const config_download: TagRenderingConfig = new TagRenderingConfig(
|
||||
{ render: "{export_as_geojson()}" },
|
||||
""
|
||||
)
|
||||
const config_id: TagRenderingConfig = new TagRenderingConfig(
|
||||
{ render: "{open_in_iD()}" },
|
||||
""
|
||||
)
|
||||
|
||||
return new Combine([
|
||||
new TagRenderingAnswer(tags, config_all_tags, state),
|
||||
new TagRenderingAnswer(tags, config_download, state),
|
||||
new TagRenderingAnswer(tags, config_id, state),
|
||||
"This is layer " + layerConfig.id,
|
||||
])
|
||||
})
|
||||
)
|
||||
|
||||
return new Combine(editElements).SetClass("flex flex-col")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,28 @@
|
|||
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 { ElementStorage } from "../../Logic/ElementStorage"
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"
|
||||
import BaseLayer from "../../Models/BaseLayer"
|
||||
import SearchAndGo from "../BigComponents/SearchAndGo"
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { And } from "../../Logic/Tags/And"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import { LoginToggle } from "./LoginButton"
|
||||
import { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { Feature, Point } from "geojson"
|
||||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import { MapProperties } from "../../Models/MapProperties"
|
||||
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
|
||||
import Geosearch from "../BigComponents/Geosearch.svelte"
|
||||
import Constants from "../../Models/Constants"
|
||||
|
||||
interface MoveReason {
|
||||
text: Translation | string
|
||||
|
@ -43,14 +42,9 @@ 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
|
||||
},
|
||||
featureToMove: Feature<Point>,
|
||||
tags: UIEventSource<OsmTags>,
|
||||
state: SpecialVisualizationState,
|
||||
options: MoveConfig
|
||||
) {
|
||||
const t = Translations.t.move
|
||||
|
@ -130,56 +124,38 @@ export default class MoveWizard extends Toggle {
|
|||
if (reason === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const loc = new UIEventSource<Loc>({
|
||||
lon: lon,
|
||||
lat: lat,
|
||||
zoom: reason?.startZoom ?? 16,
|
||||
})
|
||||
|
||||
let background: string[]
|
||||
if (typeof reason.background == "string") {
|
||||
background = [reason.background]
|
||||
} else {
|
||||
background = reason.background
|
||||
const mapProperties: Partial<MapProperties> = {
|
||||
minzoom: new UIEventSource(reason.minZoom),
|
||||
zoom: new UIEventSource(reason?.startZoom ?? 16),
|
||||
location: new UIEventSource({ lon, lat }),
|
||||
bounds: new UIEventSource(undefined),
|
||||
}
|
||||
|
||||
const preferredBackground = AvailableBaseLayers.SelectBestLayerAccordingTo(
|
||||
loc,
|
||||
new UIEventSource(background)
|
||||
).data
|
||||
|
||||
const locationInput = new LocationInput({
|
||||
minZoom: reason.minZoom,
|
||||
centerLocation: loc,
|
||||
mapBackground: new UIEventSource<BaseLayer>(preferredBackground), // We detach the layer
|
||||
state: <any>state,
|
||||
const value = new UIEventSource<{ lon: number; lat: number }>(undefined)
|
||||
const locationInput = new SvelteUIElement(LocationInput, {
|
||||
mapProperties,
|
||||
value,
|
||||
})
|
||||
|
||||
if (reason.lockBounds) {
|
||||
locationInput.installBounds(0.05, true)
|
||||
}
|
||||
|
||||
let searchPanel: BaseUIElement = undefined
|
||||
if (reason.includeSearch) {
|
||||
searchPanel = new SearchAndGo({
|
||||
leafletMap: locationInput.leafletMap,
|
||||
})
|
||||
searchPanel = new SvelteUIElement(Geosearch, { bounds: mapProperties.bounds })
|
||||
}
|
||||
|
||||
locationInput.SetStyle("height: 17.5rem")
|
||||
|
||||
const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove)
|
||||
confirmMove.onClick(async () => {
|
||||
const loc = locationInput.GetValue().data
|
||||
const loc = value.data
|
||||
await state.changes.applyAction(
|
||||
new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], {
|
||||
reason: reason.changesetCommentValue,
|
||||
theme: state.layoutToUse.id,
|
||||
theme: state.layout.id,
|
||||
})
|
||||
)
|
||||
featureToMove.properties._lat = loc.lat
|
||||
featureToMove.properties._lon = loc.lon
|
||||
|
||||
featureToMove.geometry.coordinates = [loc.lon, loc.lat]
|
||||
if (reason.eraseAddressFields) {
|
||||
await state.changes.applyAction(
|
||||
new ChangeTagAction(
|
||||
|
@ -193,13 +169,13 @@ export default class MoveWizard extends Toggle {
|
|||
featureToMove.properties,
|
||||
{
|
||||
changeType: "relocated",
|
||||
theme: state.layoutToUse.id,
|
||||
theme: state.layout.id,
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
state.allElements.getEventSourceById(id).ping()
|
||||
state.featureProperties.getStore(id).ping()
|
||||
currentStep.setData("moved")
|
||||
})
|
||||
const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6")
|
||||
|
@ -209,7 +185,7 @@ export default class MoveWizard extends Toggle {
|
|||
new Toggle(
|
||||
confirmMove,
|
||||
zoomInFurhter,
|
||||
locationInput.GetValue().map((l) => l.zoom >= 19)
|
||||
mapProperties.zoom.map((zoom) => zoom >= Constants.minZoomLevelToAddNewPoint)
|
||||
),
|
||||
]).SetClass("flex flex-col")
|
||||
})
|
||||
|
|
|
@ -2,33 +2,27 @@ import Toggle from "../Input/Toggle"
|
|||
import Svg from "../../Svg"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { SubtleButton } from "../Base/SubtleButton"
|
||||
import Minimap from "../Base/Minimap"
|
||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import { LeafletMouseEvent } from "leaflet"
|
||||
import Combine from "../Base/Combine"
|
||||
import { Button } from "../Base/Button"
|
||||
import Translations from "../i18n/Translations"
|
||||
import SplitAction from "../../Logic/Osm/Actions/SplitAction"
|
||||
import Title from "../Base/Title"
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
||||
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import split_point from "../../assets/layers/split_point/split_point.json"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { Changes } from "../../Logic/Osm/Changes"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { ElementStorage } from "../../Logic/ElementStorage"
|
||||
import BaseLayer from "../../Models/BaseLayer"
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
|
||||
import { LoginToggle } from "./LoginButton"
|
||||
import { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
|
||||
export default class SplitRoadWizard extends Combine {
|
||||
// @ts-ignore
|
||||
private static splitLayerStyling = new LayerConfig(
|
||||
split_point,
|
||||
"(BUILTIN) SplitRoadWizard.ts",
|
||||
|
@ -43,22 +37,7 @@ export default class SplitRoadWizard extends Combine {
|
|||
* @param id: The id of the road to remove
|
||||
* @param state: the state of the application
|
||||
*/
|
||||
constructor(
|
||||
id: string,
|
||||
state: {
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
backgroundLayer: UIEventSource<BaseLayer>
|
||||
featureSwitchIsTesting: UIEventSource<boolean>
|
||||
featureSwitchIsDebugging: UIEventSource<boolean>
|
||||
featureSwitchShowAllQuestions: UIEventSource<boolean>
|
||||
osmConnection: OsmConnection
|
||||
featureSwitchUserbadge: UIEventSource<boolean>
|
||||
changes: Changes
|
||||
layoutToUse: LayoutConfig
|
||||
allElements: ElementStorage
|
||||
selectedElement: UIEventSource<any>
|
||||
}
|
||||
) {
|
||||
constructor(id: string, state: SpecialVisualizationState) {
|
||||
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
|
||||
|
|
|
@ -26,18 +26,27 @@
|
|||
}));
|
||||
|
||||
let htmlElem: HTMLElement;
|
||||
const _htmlElement = new UIEventSource<HTMLElement>(undefined);
|
||||
$: _htmlElement.setData(htmlElem);
|
||||
|
||||
function setHighlighting() {
|
||||
if (highlightedRendering === undefined) {
|
||||
return;
|
||||
}
|
||||
if (htmlElem === undefined) {
|
||||
return;
|
||||
}
|
||||
const highlighted = highlightedRendering.data;
|
||||
if (config.id === highlighted) {
|
||||
htmlElem.classList.add("glowing-shadow");
|
||||
} else {
|
||||
htmlElem.classList.remove("glowing-shadow");
|
||||
}
|
||||
}
|
||||
|
||||
if (highlightedRendering) {
|
||||
$: onDestroy(highlightedRendering.addCallbackAndRun(highlighted => {
|
||||
console.log("Highlighted rendering is", highlighted)
|
||||
if(htmlElem === undefined){
|
||||
return
|
||||
}
|
||||
if (config.id === highlighted) {
|
||||
htmlElem.classList.add("glowing-shadow");
|
||||
} else {
|
||||
htmlElem.classList.remove("glowing-shadow");
|
||||
}
|
||||
}));
|
||||
onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()))
|
||||
onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ import Maproulette from "../Logic/Maproulette"
|
|||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
||||
import QuestionViz from "./Popup/QuestionViz"
|
||||
import { Feature } from "geojson"
|
||||
import { Feature, Point } from "geojson"
|
||||
import { GeoOperations } from "../Logic/GeoOperations"
|
||||
import CreateNewNote from "./Popup/CreateNewNote.svelte"
|
||||
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
|
||||
|
@ -76,6 +76,9 @@ import { SaveButton } from "./Popup/SaveButton"
|
|||
import Lazy from "./Base/Lazy"
|
||||
import { CheckBox } from "./Input/Checkboxes"
|
||||
import Slider from "./Input/Slider"
|
||||
import DeleteWizard from "./Popup/DeleteWizard"
|
||||
import { OsmId, OsmTags } from "../Models/OsmFeature"
|
||||
import MoveWizard from "./Popup/MoveWizard"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualization {
|
||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||
|
@ -226,6 +229,7 @@ class StealViz implements SpecialVisualization {
|
|||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
constr(state: SpecialVisualizationState, featureTags, args) {
|
||||
const [featureIdKey, layerAndtagRenderingIds] = args
|
||||
const tagRenderings: [LayerConfig, TagRenderingConfig][] = []
|
||||
|
@ -273,6 +277,7 @@ class StealViz implements SpecialVisualization {
|
|||
return [layerId]
|
||||
}
|
||||
}
|
||||
|
||||
export default class SpecialVisualizations {
|
||||
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
|
||||
|
||||
|
@ -521,6 +526,74 @@ export default class SpecialVisualizations {
|
|||
new HistogramViz(),
|
||||
new StealViz(),
|
||||
new MinimapViz(),
|
||||
{
|
||||
funcName: "split_button",
|
||||
docs: "Adds a button which allows to split a way",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
return new VariableUiElement(
|
||||
// TODO
|
||||
tagSource
|
||||
.map((tags) => tags.id)
|
||||
.map((id) => new FixedUiElement("TODO: enable splitting")) // new SplitRoadWizard(id, state))
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "move_button",
|
||||
docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
if (feature.geometry.type !== "Point") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return new MoveWizard(
|
||||
<Feature<Point>>feature,
|
||||
<UIEventSource<OsmTags>>tagSource,
|
||||
state,
|
||||
layer.allowMove
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "delete_button",
|
||||
docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config",
|
||||
args: [],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
.map((tags) => tags.id)
|
||||
.map(
|
||||
(id) =>
|
||||
new DeleteWizard(
|
||||
<OsmId>id,
|
||||
<UIEventSource<OsmTags>>tagSource,
|
||||
state,
|
||||
layer.deletion // Reading the configuration from the layerconfig is a bit cheating and should be factored out
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
},
|
||||
new ShareLinkViz(),
|
||||
new UploadToOsmViz(),
|
||||
new MultiApplyViz(),
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
|
||||
<div class="absolute top-0 right-0 mt-4 mr-4">
|
||||
<If condition={state.featureSwitches.featureSwitchSearch}>
|
||||
<Geosearch bounds={state.mapProperties.bounds} {selectedElement} {selectedLayer} {state}></Geosearch>
|
||||
<Geosearch bounds={state.mapProperties.bounds} {selectedElement} {selectedLayer} perLayer={state.perLayer}></Geosearch>
|
||||
</If>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue