Refactoring: fix 'delete' and 'move'-buttons as special elements

This commit is contained in:
Pieter Vander Vennet 2023-04-14 02:42:57 +02:00
parent 466dd16568
commit 8a1f0599d9
19 changed files with 317 additions and 296 deletions

View file

@ -38,9 +38,9 @@ export class MenuState {
[], [],
(str) => MenuState._menuviewTabs.indexOf(<any>str) (str) => MenuState._menuviewTabs.indexOf(<any>str)
) )
this.themeIsOpened.addCallbackAndRun((isOpen) => { this.menuIsOpened.addCallbackAndRun((isOpen) => {
if (!isOpen) { if (!isOpen) {
this.highlightedLayerInFilters.setData(undefined) this.highlightedUserSetting.setData(undefined)
} }
}) })
this.themeViewTab.addCallbackAndRun((tab) => { this.themeViewTab.addCallbackAndRun((tab) => {

View file

@ -234,9 +234,9 @@ class ExpandTagRendering extends Conversion<
if (typeof layer.source !== "string") { if (typeof layer.source !== "string") {
if (found.condition === undefined) { if (found.condition === undefined) {
found.condition = layer.source.osmTags found.condition = layer.source["osmTags"]
} else { } else {
found.condition = { and: [found.condition, layer.source.osmTags] } found.condition = { and: [found.condition, layer.source["osmTags"]] }
} }
} }
} }
@ -436,7 +436,7 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
if (typeof json.render === "string") { if (typeof json.render === "string") {
spec = { "*": json.render } spec = { "*": json.render }
} else { } else {
spec = json.render spec = <Record<string, string>>json.render
} }
const errors: string[] = [] const errors: string[] = []
for (const key in spec) { for (const key in spec) {
@ -480,7 +480,10 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
json: LayerConfigJson, json: LayerConfigJson,
context: string context: string
): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { ): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
if (json.tagRenderings === undefined) { if (
json.tagRenderings === undefined ||
json.tagRenderings.some((tr) => tr["id"] === "leftover-questions")
) {
return { result: json } return { result: json }
} }
json = JSON.parse(JSON.stringify(json)) json = JSON.parse(JSON.stringify(json))
@ -500,7 +503,6 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
const errors: string[] = [] const errors: string[] = []
const warnings: string[] = [] const warnings: string[] = []
if (noLabels.length > 1) { if (noLabels.length > 1) {
console.log(json.tagRenderings)
errors.push( errors.push(
"At " + "At " +
context + context +
@ -572,6 +574,45 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
} }
} }
export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
constructor() {
super(
"Add some editing elements, such as the delete button or the move button if they are configured. These used to be handled by the feature info box, but this has been replaced by special visualisation elements",
[],
"AddEditingElements"
)
}
convert(
json: LayerConfigJson,
context: string
): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
json = JSON.parse(JSON.stringify(json))
if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) {
json.tagRenderings.push({
id: "split-button",
render: { "*": "{split_button()}" },
})
}
if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) {
json.tagRenderings.push({
id: "move-button",
render: { "*": "{move_button()}" },
})
}
if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "delete_button")) {
json.tagRenderings.push({
id: "delete-button",
render: { "*": "{delete_button()}" },
})
}
return { result: json }
}
}
export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[]> { export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[]> {
constructor() { constructor() {
super("Applies a rewrite", [], "ExpandRewrite") super("Applies a rewrite", [], "ExpandRewrite")
@ -1064,6 +1105,36 @@ class PreparePointRendering extends Fuse<PointRenderingConfigJson | LineRenderin
} }
} }
export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
private readonly _state: DesugaringContext
constructor(state: DesugaringContext) {
super(
"Adds a default 'minimap'-element to the tagrenderings if none of the elements define such a minimap",
["tagRenderings"],
"AddMiniMap"
)
this._state = state
}
convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } {
if (!layerConfig.tagRenderings) {
return { result: layerConfig }
}
const state = this._state
const hasMinimap = ValidationUtils.hasSpecialVisualisation(layerConfig, "minimap")
if (!hasMinimap) {
layerConfig = { ...layerConfig }
layerConfig.tagRenderings = [...layerConfig.tagRenderings]
layerConfig.tagRenderings.push(state.tagRenderings.get("minimap"))
}
return {
result: layerConfig,
}
}
}
export class PrepareLayer extends Fuse<LayerConfigJson> { export class PrepareLayer extends Fuse<LayerConfigJson> {
constructor(state: DesugaringContext) { constructor(state: DesugaringContext) {
super( super(
@ -1072,6 +1143,9 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
new On("tagRenderings", (layer) => new Concat(new ExpandTagRendering(state, layer))), new On("tagRenderings", (layer) => new Concat(new ExpandTagRendering(state, layer))),
new On("tagRenderings", new Each(new DetectInline())), new On("tagRenderings", new Each(new DetectInline())),
new AddQuestionBox(),
new AddMiniMap(state),
new AddEditingElements(),
new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
new On<(PointRenderingConfigJson | LineRenderingConfigJson)[], LayerConfigJson>( new On<(PointRenderingConfigJson | LineRenderingConfigJson)[], LayerConfigJson>(
"mapRendering", "mapRendering",

View file

@ -10,7 +10,7 @@ import {
SetDefault, SetDefault,
} from "./Conversion" } from "./Conversion"
import { LayoutConfigJson } from "../Json/LayoutConfigJson" import { LayoutConfigJson } from "../Json/LayoutConfigJson"
import { AddQuestionBox, PrepareLayer } from "./PrepareLayer" import { PrepareLayer } from "./PrepareLayer"
import { LayerConfigJson } from "../Json/LayerConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson"
import { Utils } from "../../../Utils" import { Utils } from "../../../Utils"
import Constants from "../../Constants" import Constants from "../../Constants"
@ -295,56 +295,6 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
} }
} }
export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
private readonly _state: DesugaringContext
constructor(state: DesugaringContext) {
super(
"Adds a default 'minimap'-element to the tagrenderings if none of the elements define such a minimap",
["tagRenderings"],
"AddMiniMap"
)
this._state = state
}
/**
* Returns true if this tag rendering has a minimap in some language.
* Note: this minimap can be hidden by conditions
*
* AddMiniMap.hasMinimap({render: "{minimap()}"}) // => true
* AddMiniMap.hasMinimap({render: {en: "{minimap()}"}}) // => true
* AddMiniMap.hasMinimap({render: {en: "{minimap()}", nl: "{minimap()}"}}) // => true
* AddMiniMap.hasMinimap({render: {en: "{minimap()}", nl: "No map for the dutch!"}}) // => true
* AddMiniMap.hasMinimap({render: "{minimap()}"}) // => true
* AddMiniMap.hasMinimap({render: "{minimap(18,featurelist)}"}) // => true
* AddMiniMap.hasMinimap({mappings: [{if: "xyz=abc",then: "{minimap(18,featurelist)}"}]}) // => true
* AddMiniMap.hasMinimap({render: "Some random value {key}"}) // => false
* AddMiniMap.hasMinimap({render: "Some random value {minimap}"}) // => false
*/
static hasMinimap(renderingConfig: TagRenderingConfigJson): boolean {
return ValidationUtils.getSpecialVisualisations(renderingConfig).some(
(vis) => vis.funcName === "minimap"
)
}
convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } {
const state = this._state
const hasMinimap =
layerConfig.tagRenderings?.some((tr) =>
AddMiniMap.hasMinimap(<TagRenderingConfigJson>tr)
) ?? true
if (!hasMinimap) {
layerConfig = { ...layerConfig }
layerConfig.tagRenderings = [...layerConfig.tagRenderings]
layerConfig.tagRenderings.push(state.tagRenderings.get("minimap"))
}
return {
result: layerConfig,
}
}
}
class AddContextToTranslationsInLayout extends DesugaringStep<LayoutConfigJson> { class AddContextToTranslationsInLayout extends DesugaringStep<LayoutConfigJson> {
constructor() { constructor() {
super( super(
@ -660,9 +610,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
? new Pass("AddDefaultLayers is disabled due to the set flag") ? new Pass("AddDefaultLayers is disabled due to the set flag")
: new AddDefaultLayers(state), : new AddDefaultLayers(state),
new AddDependencyLayersToTheme(state), new AddDependencyLayersToTheme(state),
new AddImportLayers(), new AddImportLayers()
new On("layers", new Each(new AddQuestionBox())),
new On("layers", new Each(new AddMiniMap(state)))
) )
} }

View file

@ -2,8 +2,22 @@ import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import { Utils } from "../../../Utils" import { Utils } from "../../../Utils"
import SpecialVisualizations from "../../../UI/SpecialVisualizations" import SpecialVisualizations from "../../../UI/SpecialVisualizations"
import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization" import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization"
import { LayerConfigJson } from "../Json/LayerConfigJson"
export default class ValidationUtils { export default class ValidationUtils {
public static hasSpecialVisualisation(
layer: LayerConfigJson,
specialVisualisation: string
): boolean {
return (
layer.tagRenderings?.some((tagRendering) =>
ValidationUtils.getSpecialVisualisations(<TagRenderingConfigJson>tagRendering).some(
(vis) => vis.funcName === specialVisualisation
)
) ?? false
)
}
/** /**
* Gives all the (function names of) used special visualisations * Gives all the (function names of) used special visualisations
* @param renderingConfig * @param renderingConfig
@ -15,6 +29,7 @@ export default class ValidationUtils {
(spec) => spec["func"] (spec) => spec["func"]
) )
} }
public static getSpecialVisualsationsWithArgs( public static getSpecialVisualsationsWithArgs(
renderingConfig: TagRenderingConfigJson renderingConfig: TagRenderingConfigJson
): RenderingSpecification[] { ): RenderingSpecification[] {

View file

@ -90,7 +90,7 @@ export default class LayerConfig extends WithContextLoader {
if (json.source === "special" || json.source === "special:library") { if (json.source === "special" || json.source === "special:library") {
this.source = null this.source = null
} else if (json.source.osmTags === undefined) { } else if (json.source["osmTags"] === undefined) {
throw ( throw (
"Layer " + "Layer " +
this.id + this.id +
@ -122,8 +122,8 @@ export default class LayerConfig extends WithContextLoader {
} }
this.syncSelection = json.syncSelection ?? "no" this.syncSelection = json.syncSelection ?? "no"
if (typeof json.source !== "string") { if (typeof json.source !== "string") {
this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 this.maxAgeOfCache = json.source["maxCacheAge"] ?? 24 * 60 * 60 * 30
const osmTags = TagUtils.Tag(json.source.osmTags, context + "source.osmTags") const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags")
if (osmTags.isNegative()) { if (osmTags.isNegative()) {
throw ( throw (
context + context +

View file

@ -248,7 +248,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
new UIEventSource<Record<string, string>>(last_click.properties) new UIEventSource<Record<string, string>>(last_click.properties)
) )
new ShowDataLayer(this.map, { new ShowDataLayer(this.map, {
features: last_click, features: new FilteringFeatureSource(last_click_layer, last_click),
doShowLayer: new ImmutableStore(true), doShowLayer: new ImmutableStore(true),
layer: last_click_layer.layerDef, layer: last_click_layer.layerDef,
selectedElement: this.selectedElement, selectedElement: this.selectedElement,

View file

@ -10,56 +10,58 @@
import Hotkeys from "../Base/Hotkeys"; import Hotkeys from "../Base/Hotkeys";
import { Geocoding } from "../../Logic/Osm/Geocoding"; import { Geocoding } from "../../Logic/Osm/Geocoding";
import { BBox } from "../../Logic/BBox"; import { BBox } from "../../Logic/BBox";
import type { SpecialVisualizationState } from "../SpecialVisualization"; import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore";
export let state: SpecialVisualizationState export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined;
export let bounds: UIEventSource<BBox> export let bounds: UIEventSource<BBox>;
export let selectedElement: UIEventSource<Feature>; export let selectedElement: UIEventSource<Feature> | undefined = undefined;
export let selectedLayer: UIEventSource<LayerConfig>; export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined;
let searchContents: string = undefined; let searchContents: string = undefined;
let isRunning: boolean = false; let isRunning: boolean = false;
let inputElement: HTMLInputElement; let inputElement: HTMLInputElement;
let feedback: string = undefined let feedback: string = undefined;
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ ctrl: "F" }, { ctrl: "F" },
Translations.t.hotkeyDocumentation.selectSearch, Translations.t.hotkeyDocumentation.selectSearch,
() => { () => {
inputElement?.focus() inputElement?.focus();
inputElement?.select() inputElement?.select();
} }
) );
async function performSearch() { async function performSearch() {
try { try {
isRunning = true; isRunning = true;
searchContents = searchContents?.trim() ?? "" searchContents = searchContents?.trim() ?? "";
if (searchContents === "") { if (searchContents === "") {
return return;
} }
const result = await Geocoding.Search(searchContents, bounds.data) const result = await Geocoding.Search(searchContents, bounds.data);
if (result.length == 0) { if (result.length == 0) {
feedback = Translations.t.search.nothing.txt feedback = Translations.t.search.nothing.txt;
return return;
} }
const poi = result[0] const poi = result[0];
const [lat0, lat1, lon0, lon1] = poi.boundingbox const [lat0, lat1, lon0, lon1] = poi.boundingbox;
bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01)) bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01));
const id = poi.osm_type + "/" + poi.osm_id if (perLayer !== undefined) {
const perLayer = state.perLayer const id = poi.osm_type + "/" + poi.osm_id;
const layers = Array.from(perLayer.values()) const layers = Array.from(perLayer?.values() ?? []);
for (const layer of layers) { for (const layer of layers) {
const found = layer.features.data.find(f => f.properties.id === id) const found = layer.features.data.find(f => f.properties.id === id);
selectedElement.setData(found) selectedElement?.setData(found);
selectedLayer.setData(layer.layer.layerDef) selectedLayer?.setData(layer.layer.layerDef);
}
} }
}catch (e) { } catch (e) {
console.error(e) console.error(e);
feedback = Translations.t.search.error.txt feedback = Translations.t.search.error.txt;
} finally { } finally {
isRunning = false; isRunning = false;
} }
@ -72,7 +74,7 @@
{#if isRunning} {#if isRunning}
<Loading>{Translations.t.general.search.searching}</Loading> <Loading>{Translations.t.general.search.searching}</Loading>
{:else if feedback !== undefined} {:else if feedback !== undefined}
<div class="alert" on:click={() => feedback = undefined}> <div class="alert" on:click={() => feedback = undefined}>
{feedback} {feedback}
</div> </div>

View file

@ -177,9 +177,7 @@ export class ImageUploadFlow extends Toggle {
) )
.onClick(() => { .onClick(() => {
console.log("Opening the license settings... ") console.log("Opening the license settings... ")
ScrollableFullScreen.collapse() state.guistate.openUsersettings("picture-license")
DefaultGuiState.state.userInfoIsOpened.setData(true)
DefaultGuiState.state.userInfoFocusedQuestion.setData("picture-license")
}) })
.SetClass("underline"), .SetClass("underline"),
]).SetStyle("font-size:small;"), ]).SetStyle("font-size:small;"),

View file

@ -4,8 +4,6 @@
import { Map as MlMap } from "maplibre-gl"; import { Map as MlMap } from "maplibre-gl";
import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor"; import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor";
import MaplibreMap from "../../Map/MaplibreMap.svelte"; import MaplibreMap from "../../Map/MaplibreMap.svelte";
import Svg from "../../../Svg";
import ToSvelte from "../../Base/ToSvelte.svelte";
import DragInvitation from "../../Base/DragInvitation.svelte"; import DragInvitation from "../../Base/DragInvitation.svelte";
/** /**
@ -14,13 +12,13 @@
export let value: UIEventSource<{lon: number, lat: number}>; export let value: UIEventSource<{lon: number, lat: number}>;
export let mapProperties: Partial<MapProperties> & { readonly location: UIEventSource<{ lon: number; lat: number }> } = undefined; 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 onCreated : (value: Store<{lon: number, lat: number}> , map: Store<MlMap>, mapProperties: MapProperties ) => void
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined); export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
let mla = new MapLibreAdaptor(map, mapProperties); let mla = new MapLibreAdaptor(map, mapProperties);
mapProperties.location.syncWith(value)
if(onCreated){ if(onCreated){
onCreated(value, map, mla) onCreated(value, map, mla)
} }

View file

@ -8,7 +8,7 @@ import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig"
import { OsmTags } from "../../Models/OsmFeature" import { OsmTags } from "../../Models/OsmFeature"
import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource"
import { BBox } from "../../Logic/BBox" import { BBox } from "../../Logic/BBox"
import { Feature } from "geojson" import { Feature, Point } from "geojson"
import ScrollableFullScreen from "../Base/ScrollableFullScreen" import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
@ -26,6 +26,7 @@ class PointRenderingLayer {
private readonly _onClick: (feature: Feature) => void private readonly _onClick: (feature: Feature) => void
private readonly _allMarkers: Map<string, Marker> = new Map<string, Marker>() private readonly _allMarkers: Map<string, Marker> = new Map<string, Marker>()
private _dirty = false private _dirty = false
constructor( constructor(
map: MlMap, map: MlMap,
features: FeatureSource, features: FeatureSource,
@ -139,6 +140,18 @@ class PointRenderingLayer {
store store
.map((tags) => this._config.rotationAlignment.GetRenderValue(tags).Subs(tags).txt) .map((tags) => this._config.rotationAlignment.GetRenderValue(tags).Subs(tags).txt)
.addCallbackAndRun((pitchAligment) => marker.setRotationAlignment(pitchAligment)) .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 return marker
} }
} }
@ -159,6 +172,7 @@ class LineRenderingLayer {
private static readonly lineConfigKeysColor = ["color", "fillColor"] as const private static readonly lineConfigKeysColor = ["color", "fillColor"] as const
private static readonly lineConfigKeysNumber = ["width", "offset"] as const private static readonly lineConfigKeysNumber = ["width", "offset"] as const
private static missingIdTriggered = false
private readonly _map: MlMap private readonly _map: MlMap
private readonly _config: LineRenderingConfig private readonly _config: LineRenderingConfig
private readonly _visibility?: Store<boolean> private readonly _visibility?: Store<boolean>
@ -167,7 +181,6 @@ class LineRenderingLayer {
private readonly _layername: string private readonly _layername: string
private readonly _listenerInstalledOn: Set<string> = new Set<string>() private readonly _listenerInstalledOn: Set<string> = new Set<string>()
private static missingIdTriggered = false
constructor( constructor(
map: MlMap, map: MlMap,
features: FeatureSource, 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" const polylayer = this._layername + "_polygon"
map.addLayer({ map.addLayer({
source: this._layername, source: this._layername,
@ -260,6 +278,10 @@ class LineRenderingLayer {
"fill-opacity": 0.1, "fill-opacity": 0.1,
}, },
}) })
map.on("click", polylayer, (e) => {
e.originalEvent["consumed"] = true
this._onClick(e.features[0])
})
this._visibility?.addCallbackAndRunD((visible) => { this._visibility?.addCallbackAndRunD((visible) => {
try { try {

View file

@ -20,10 +20,10 @@ import { RadioButton } from "../Input/RadioButton"
import { FixedInputElement } from "../Input/FixedInputElement" import { FixedInputElement } from "../Input/FixedInputElement"
import Title from "../Base/Title" import Title from "../Base/Title"
import { SubstitutedTranslation } from "../SubstitutedTranslation" import { SubstitutedTranslation } from "../SubstitutedTranslation"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import TagRenderingQuestion from "./TagRenderingQuestion" import TagRenderingQuestion from "./TagRenderingQuestion"
import { OsmId } from "../../Models/OsmFeature" import { OsmId, OsmTags } from "../../Models/OsmFeature"
import { LoginToggle } from "./LoginButton" import { LoginToggle } from "./LoginButton"
import { SpecialVisualizationState } from "../SpecialVisualization"
export default class DeleteWizard extends Toggle { 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. * 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) * (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) { constructor(
const deleteAbility = new DeleteabilityChecker(id, state, options.neededChangesets) id: OsmId,
const tagsSource = state.allElements.getEventSourceById(id) tagsSource: UIEventSource<OsmTags>,
state: SpecialVisualizationState,
options: DeleteConfig
) {
const deleteAbility = new DeleteabilityChecker(
id,
state.osmConnection,
options.neededChangesets
)
const isDeleted = new UIEventSource(false) const isDeleted = new UIEventSource(false)
const allowSoftDeletion = !!options.softDeletionTags const allowSoftDeletion = !!options.softDeletionTags
@ -62,7 +67,7 @@ export default class DeleteWizard extends Toggle {
if (selected["retagTo"] !== undefined) { if (selected["retagTo"] !== undefined) {
// no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping // 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, { actionToTake = new ChangeTagAction(id, selected["retagTo"], tagsSource.data, {
theme: state?.layoutToUse?.id ?? "unkown", theme: state?.layout?.id ?? "unkown",
changeType: "special-delete", changeType: "special-delete",
}) })
} else { } else {
@ -70,7 +75,7 @@ export default class DeleteWizard extends Toggle {
id, id,
options.softDeletionTags, options.softDeletionTags,
{ {
theme: state?.layoutToUse?.id ?? "unkown", theme: state?.layout?.id ?? "unkown",
specialMotivation: selected["deleteReason"], specialMotivation: selected["deleteReason"],
}, },
deleteAbility.canBeDeleted.data.canBeDeleted deleteAbility.canBeDeleted.data.canBeDeleted
@ -250,7 +255,7 @@ export default class DeleteWizard extends Toggle {
private static constructMultipleChoice( private static constructMultipleChoice(
config: DeleteConfig, config: DeleteConfig,
tagsSource: UIEventSource<Record<string, string>>, tagsSource: UIEventSource<Record<string, string>>,
state: FeaturePipelineState state: SpecialVisualizationState
): InputElement<{ deleteReason: string } | { retagTo: TagsFilter }> { ): InputElement<{ deleteReason: string } | { retagTo: TagsFilter }> {
const elements: InputElement<{ deleteReason: string } | { retagTo: TagsFilter }>[] = [] const elements: InputElement<{ deleteReason: string } | { retagTo: TagsFilter }>[] = []
@ -282,19 +287,13 @@ export default class DeleteWizard extends Toggle {
class DeleteabilityChecker { class DeleteabilityChecker {
public readonly canBeDeleted: UIEventSource<{ canBeDeleted?: boolean; reason: Translation }> public readonly canBeDeleted: UIEventSource<{ canBeDeleted?: boolean; reason: Translation }>
private readonly _id: string private readonly _id: OsmId
private readonly _allowDeletionAtChangesetCount: number private readonly _allowDeletionAtChangesetCount: number
private readonly _state: { private readonly _osmConnection: OsmConnection
osmConnection: OsmConnection
}
constructor( constructor(id: OsmId, osmConnection: OsmConnection, allowDeletionAtChangesetCount?: number) {
id: string,
state: { osmConnection: OsmConnection },
allowDeletionAtChangesetCount?: number
) {
this._id = id this._id = id
this._state = state this._osmConnection = osmConnection
this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE
this.canBeDeleted = new UIEventSource<{ canBeDeleted?: boolean; reason: Translation }>({ 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? // 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) { if (ud === undefined) {
return undefined return undefined
} }
@ -347,10 +346,10 @@ class DeleteabilityChecker {
// Not yet downloaded // Not yet downloaded
return null return null
} }
const userId = self._state.osmConnection.userDetails.data.uid const userId = self._osmConnection.userDetails.data.uid
return !previous.some((editor) => editor !== userId) return !previous.some((editor) => editor !== userId)
}, },
[self._state.osmConnection.userDetails] [self._osmConnection.userDetails]
) )
// User allowed OR only edited by self? // User allowed OR only edited by self?

View file

@ -17,7 +17,6 @@ import MoveWizard from "./MoveWizard"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import Lazy from "../Base/Lazy" import Lazy from "../Base/Lazy"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import { Tag } from "../../Logic/Tags/Tag"
import Svg from "../../Svg" import Svg from "../../Svg"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
@ -32,9 +31,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
setHash?: true | boolean setHash?: true | boolean
} }
) { ) {
if (state === undefined) {
throw "State is undefined!"
}
const showAllQuestions = state.featureSwitchShowAllQuestions.map( const showAllQuestions = state.featureSwitchShowAllQuestions.map(
(fsShow) => fsShow || state.showAllQuestionsAtOnce.data, (fsShow) => fsShow || state.showAllQuestionsAtOnce.data,
[state.showAllQuestionsAtOnce] [state.showAllQuestionsAtOnce]
@ -98,27 +94,11 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
private static GenerateMainContent( private static GenerateMainContent(
tags: UIEventSource<any>, tags: UIEventSource<any>,
layerConfig: LayerConfig, layerConfig: LayerConfig,
state: FeaturePipelineState, state: FeaturePipelineState
showAllQuestions?: Store<boolean>
): BaseUIElement { ): BaseUIElement {
let questionBoxes: Map<string, QuestionBox> = new Map<string, QuestionBox>() let questionBoxes: Map<string, QuestionBox> = new Map<string, QuestionBox>()
const t = Translations.t.general const t = Translations.t.general
const allGroupNames = Utils.Dedup(layerConfig.tagRenderings.map((tr) => tr.group)) 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( const withQuestion = layerConfig.tagRenderings.filter(
(tr) => tr.question !== undefined (tr) => tr.question !== undefined
@ -243,40 +223,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
editElements.push(questionBox) 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( editElements.push(
new VariableUiElement( new VariableUiElement(
state.osmConnection.userDetails 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") return new Combine(editElements).SetClass("flex flex-col")
} }
} }

View file

@ -1,29 +1,28 @@
import { SubtleButton } from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import Svg from "../../Svg" import Svg from "../../Svg"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import { UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import { VariableUiElement } from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
import { Translation } from "../i18n/Translation" import { Translation } from "../i18n/Translation"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import LocationInput from "../Input/LocationInput"
import Loc from "../../Models/Loc"
import { GeoOperations } from "../../Logic/GeoOperations" import { GeoOperations } from "../../Logic/GeoOperations"
import { OsmObject } from "../../Logic/Osm/OsmObject" import { OsmObject } from "../../Logic/Osm/OsmObject"
import { Changes } from "../../Logic/Osm/Changes"
import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction" import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import MoveConfig from "../../Models/ThemeConfig/MoveConfig" 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 ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And" import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag" import { Tag } from "../../Logic/Tags/Tag"
import { LoginToggle } from "./LoginButton" 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 { interface MoveReason {
text: Translation | string text: Translation | string
@ -43,14 +42,9 @@ export default class MoveWizard extends Toggle {
* The UI-element which helps moving a point * The UI-element which helps moving a point
*/ */
constructor( constructor(
featureToMove: any, featureToMove: Feature<Point>,
state: { tags: UIEventSource<OsmTags>,
osmConnection: OsmConnection state: SpecialVisualizationState,
featureSwitchUserbadge: UIEventSource<boolean>
changes: Changes
layoutToUse: LayoutConfig
allElements: ElementStorage
},
options: MoveConfig options: MoveConfig
) { ) {
const t = Translations.t.move const t = Translations.t.move
@ -130,56 +124,38 @@ export default class MoveWizard extends Toggle {
if (reason === undefined) { if (reason === undefined) {
return undefined return undefined
} }
const loc = new UIEventSource<Loc>({
lon: lon,
lat: lat,
zoom: reason?.startZoom ?? 16,
})
let background: string[] const mapProperties: Partial<MapProperties> = {
if (typeof reason.background == "string") { minzoom: new UIEventSource(reason.minZoom),
background = [reason.background] zoom: new UIEventSource(reason?.startZoom ?? 16),
} else { location: new UIEventSource({ lon, lat }),
background = reason.background bounds: new UIEventSource(undefined),
} }
const value = new UIEventSource<{ lon: number; lat: number }>(undefined)
const preferredBackground = AvailableBaseLayers.SelectBestLayerAccordingTo( const locationInput = new SvelteUIElement(LocationInput, {
loc, mapProperties,
new UIEventSource(background) value,
).data
const locationInput = new LocationInput({
minZoom: reason.minZoom,
centerLocation: loc,
mapBackground: new UIEventSource<BaseLayer>(preferredBackground), // We detach the layer
state: <any>state,
}) })
if (reason.lockBounds) {
locationInput.installBounds(0.05, true)
}
let searchPanel: BaseUIElement = undefined let searchPanel: BaseUIElement = undefined
if (reason.includeSearch) { if (reason.includeSearch) {
searchPanel = new SearchAndGo({ searchPanel = new SvelteUIElement(Geosearch, { bounds: mapProperties.bounds })
leafletMap: locationInput.leafletMap,
})
} }
locationInput.SetStyle("height: 17.5rem") locationInput.SetStyle("height: 17.5rem")
const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove) const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove)
confirmMove.onClick(async () => { confirmMove.onClick(async () => {
const loc = locationInput.GetValue().data const loc = value.data
await state.changes.applyAction( await state.changes.applyAction(
new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], { new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], {
reason: reason.changesetCommentValue, reason: reason.changesetCommentValue,
theme: state.layoutToUse.id, theme: state.layout.id,
}) })
) )
featureToMove.properties._lat = loc.lat featureToMove.properties._lat = loc.lat
featureToMove.properties._lon = loc.lon featureToMove.properties._lon = loc.lon
featureToMove.geometry.coordinates = [loc.lon, loc.lat]
if (reason.eraseAddressFields) { if (reason.eraseAddressFields) {
await state.changes.applyAction( await state.changes.applyAction(
new ChangeTagAction( new ChangeTagAction(
@ -193,13 +169,13 @@ export default class MoveWizard extends Toggle {
featureToMove.properties, featureToMove.properties,
{ {
changeType: "relocated", changeType: "relocated",
theme: state.layoutToUse.id, theme: state.layout.id,
} }
) )
) )
} }
state.allElements.getEventSourceById(id).ping() state.featureProperties.getStore(id).ping()
currentStep.setData("moved") currentStep.setData("moved")
}) })
const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6") const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6")
@ -209,7 +185,7 @@ export default class MoveWizard extends Toggle {
new Toggle( new Toggle(
confirmMove, confirmMove,
zoomInFurhter, zoomInFurhter,
locationInput.GetValue().map((l) => l.zoom >= 19) mapProperties.zoom.map((zoom) => zoom >= Constants.minZoomLevelToAddNewPoint)
), ),
]).SetClass("flex flex-col") ]).SetClass("flex flex-col")
}) })

View file

@ -2,33 +2,27 @@ import Toggle from "../Input/Toggle"
import Svg from "../../Svg" import Svg from "../../Svg"
import { UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import { SubtleButton } from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import Minimap from "../Base/Minimap"
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
import { GeoOperations } from "../../Logic/GeoOperations" import { GeoOperations } from "../../Logic/GeoOperations"
import { LeafletMouseEvent } from "leaflet"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import { Button } from "../Base/Button" import { Button } from "../Base/Button"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import SplitAction from "../../Logic/Osm/Actions/SplitAction" import SplitAction from "../../Logic/Osm/Actions/SplitAction"
import Title from "../Base/Title" import Title from "../Base/Title"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { BBox } from "../../Logic/BBox" import { BBox } from "../../Logic/BBox"
import split_point from "../../assets/layers/split_point/split_point.json" import split_point from "../../assets/layers/split_point/split_point.json"
import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Changes } from "../../Logic/Osm/Changes" import { Changes } from "../../Logic/Osm/Changes"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { ElementStorage } from "../../Logic/ElementStorage"
import BaseLayer from "../../Models/BaseLayer"
import FilteredLayer from "../../Models/FilteredLayer" import FilteredLayer from "../../Models/FilteredLayer"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
import ScrollableFullScreen from "../Base/ScrollableFullScreen" import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import { LoginToggle } from "./LoginButton" import { LoginToggle } from "./LoginButton"
import { SpecialVisualizationState } from "../SpecialVisualization"
export default class SplitRoadWizard extends Combine { export default class SplitRoadWizard extends Combine {
// @ts-ignore
private static splitLayerStyling = new LayerConfig( private static splitLayerStyling = new LayerConfig(
split_point, split_point,
"(BUILTIN) SplitRoadWizard.ts", "(BUILTIN) SplitRoadWizard.ts",
@ -43,22 +37,7 @@ export default class SplitRoadWizard extends Combine {
* @param id: The id of the road to remove * @param id: The id of the road to remove
* @param state: the state of the application * @param state: the state of the application
*/ */
constructor( constructor(id: string, state: SpecialVisualizationState) {
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>
}
) {
const t = Translations.t.split 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 // 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

View file

@ -26,18 +26,27 @@
})); }));
let htmlElem: HTMLElement; 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) { if (highlightedRendering) {
$: onDestroy(highlightedRendering.addCallbackAndRun(highlighted => { onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()))
console.log("Highlighted rendering is", highlighted) onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()))
if(htmlElem === undefined){
return
}
if (config.id === highlighted) {
htmlElem.classList.add("glowing-shadow");
} else {
htmlElem.classList.remove("glowing-shadow");
}
}));
} }

View file

@ -54,7 +54,7 @@ import Maproulette from "../Logic/Maproulette"
import SvelteUIElement from "./Base/SvelteUIElement" import SvelteUIElement from "./Base/SvelteUIElement"
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import QuestionViz from "./Popup/QuestionViz" import QuestionViz from "./Popup/QuestionViz"
import { Feature } from "geojson" import { Feature, Point } from "geojson"
import { GeoOperations } from "../Logic/GeoOperations" import { GeoOperations } from "../Logic/GeoOperations"
import CreateNewNote from "./Popup/CreateNewNote.svelte" import CreateNewNote from "./Popup/CreateNewNote.svelte"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
@ -76,6 +76,9 @@ import { SaveButton } from "./Popup/SaveButton"
import Lazy from "./Base/Lazy" import Lazy from "./Base/Lazy"
import { CheckBox } from "./Input/Checkboxes" import { CheckBox } from "./Input/Checkboxes"
import Slider from "./Input/Slider" 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 NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -226,6 +229,7 @@ class StealViz implements SpecialVisualization {
required: true, required: true,
}, },
] ]
constr(state: SpecialVisualizationState, featureTags, args) { constr(state: SpecialVisualizationState, featureTags, args) {
const [featureIdKey, layerAndtagRenderingIds] = args const [featureIdKey, layerAndtagRenderingIds] = args
const tagRenderings: [LayerConfig, TagRenderingConfig][] = [] const tagRenderings: [LayerConfig, TagRenderingConfig][] = []
@ -273,6 +277,7 @@ class StealViz implements SpecialVisualization {
return [layerId] return [layerId]
} }
} }
export default class SpecialVisualizations { export default class SpecialVisualizations {
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList() public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
@ -521,6 +526,74 @@ export default class SpecialVisualizations {
new HistogramViz(), new HistogramViz(),
new StealViz(), new StealViz(),
new MinimapViz(), 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 ShareLinkViz(),
new UploadToOsmViz(), new UploadToOsmViz(),
new MultiApplyViz(), new MultiApplyViz(),

View file

@ -93,7 +93,7 @@
<div class="absolute top-0 right-0 mt-4 mr-4"> <div class="absolute top-0 right-0 mt-4 mr-4">
<If condition={state.featureSwitches.featureSwitchSearch}> <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> </If>
</div> </div>

View file

@ -2,6 +2,12 @@
"id": "last_click", "id": "last_click",
"description": "This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up", "description": "This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up",
"source": "special", "source": "special",
"isShown": {
"or": [
"has_presets=yes",
"has_note_layer=yes"
]
},
"name": null, "name": null,
"titleIcons": [], "titleIcons": [],
"title": { "title": {
@ -153,4 +159,4 @@
] ]
} }
] ]
} }

View file

@ -116,7 +116,7 @@
"mappings": [ "mappings": [
{ {
"if": "theme=advertising", "if": "theme=advertising",
"then": "./assets/themes/advertising/poster_box.svg" "then": "./assets/themes/advertising/icon.svg"
}, },
{ {
"if": "theme=aed", "if": "theme=aed",