forked from MapComplete/MapComplete
SpecialVis: allow import flows to work with multiple target layers
This commit is contained in:
parent
7872f22151
commit
915cad2253
6 changed files with 349 additions and 329 deletions
|
@ -1,129 +1,135 @@
|
|||
<script lang="ts">
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Tiles } from "../../Models/TileRange"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
import ShowDataLayer from "../Map/ShowDataLayer"
|
||||
import type {
|
||||
FeatureSource,
|
||||
FeatureSourceForLayer,
|
||||
} from "../../Logic/FeatureSource/FeatureSource"
|
||||
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource"
|
||||
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { Utils } from "../../Utils"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import Move_arrows from "../../assets/svg/Move_arrows.svelte"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Tiles } from "../../Models/TileRange"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
import ShowDataLayer from "../Map/ShowDataLayer"
|
||||
import type { FeatureSource, FeatureSourceForLayer } from "../../Logic/FeatureSource/FeatureSource"
|
||||
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource"
|
||||
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { Utils } from "../../Utils"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import Move_arrows from "../../assets/svg/Move_arrows.svelte"
|
||||
|
||||
/**
|
||||
* An advanced location input, which has support to:
|
||||
* - Show more layers
|
||||
* - Snap to layers
|
||||
*
|
||||
* This one is mostly used to insert new points, including when importing
|
||||
*/
|
||||
export let state: SpecialVisualizationState
|
||||
/**
|
||||
* The start coordinate
|
||||
*/
|
||||
export let coordinate: { lon: number; lat: number }
|
||||
/**
|
||||
* An advanced location input, which has support to:
|
||||
* - Show more layers
|
||||
* - Snap to layers
|
||||
*
|
||||
* This one is mostly used to insert new points, including when importing
|
||||
*/
|
||||
export let state: SpecialVisualizationState
|
||||
/**
|
||||
* The start coordinate
|
||||
*/
|
||||
export let coordinate: { lon: number; lat: number }
|
||||
|
||||
/**
|
||||
* The center of the map at all times
|
||||
* If undefined at the beginning, 'coordinate' will be used
|
||||
*/
|
||||
export let value: UIEventSource<{ lon: number; lat: number }>
|
||||
if (value.data === undefined) {
|
||||
value.setData(coordinate)
|
||||
}
|
||||
if (coordinate === undefined) {
|
||||
coordinate = value.data
|
||||
}
|
||||
export let snapToLayers: string[] | undefined
|
||||
export let targetLayer: LayerConfig | undefined
|
||||
export let maxSnapDistance: number = undefined
|
||||
|
||||
export let snappedTo: UIEventSource<string | undefined>
|
||||
|
||||
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
|
||||
lon: number
|
||||
lat: number
|
||||
}>(undefined)
|
||||
|
||||
const dispatch = createEventDispatcher<{ click: { lon: number; lat: number } }>()
|
||||
|
||||
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16)
|
||||
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
let initialMapProperties: Partial<MapProperties> = {
|
||||
zoom: new UIEventSource<number>(19),
|
||||
maxbounds: new UIEventSource(undefined),
|
||||
/*If no snapping needed: the value is simply the map location;
|
||||
* If snapping is needed: the value will be set later on by the snapping feature source
|
||||
* */
|
||||
location:
|
||||
snapToLayers?.length > 0
|
||||
? new UIEventSource<{ lon: number; lat: number }>(coordinate)
|
||||
: value,
|
||||
bounds: new UIEventSource<BBox>(undefined),
|
||||
allowMoving: new UIEventSource<boolean>(true),
|
||||
allowZooming: new UIEventSource<boolean>(true),
|
||||
minzoom: new UIEventSource<number>(18),
|
||||
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
|
||||
}
|
||||
|
||||
if (targetLayer) {
|
||||
const featuresForLayer = state.perLayer.get(targetLayer.id)
|
||||
if (featuresForLayer) {
|
||||
new ShowDataLayer(map, {
|
||||
layer: targetLayer,
|
||||
features: featuresForLayer,
|
||||
})
|
||||
/**
|
||||
* The center of the map at all times
|
||||
* If undefined at the beginning, 'coordinate' will be used
|
||||
*/
|
||||
export let value: UIEventSource<{ lon: number; lat: number }>
|
||||
if (value.data === undefined) {
|
||||
value.setData(coordinate)
|
||||
}
|
||||
}
|
||||
|
||||
if (snapToLayers?.length > 0) {
|
||||
const snapSources: FeatureSource[] = []
|
||||
for (const layerId of snapToLayers ?? []) {
|
||||
const layer: FeatureSourceForLayer = state.perLayer.get(layerId)
|
||||
snapSources.push(layer)
|
||||
if (layer.features === undefined) {
|
||||
continue
|
||||
}
|
||||
new ShowDataLayer(map, {
|
||||
layer: layer.layer.layerDef,
|
||||
zoomToFeatures: false,
|
||||
features: layer,
|
||||
})
|
||||
if (coordinate === undefined) {
|
||||
coordinate = value.data
|
||||
}
|
||||
const snappedLocation = new SnappingFeatureSource(
|
||||
new FeatureSourceMerger(...Utils.NoNull(snapSources)),
|
||||
// We snap to the (constantly updating) map location
|
||||
initialMapProperties.location,
|
||||
{
|
||||
maxDistance: maxSnapDistance ?? 15,
|
||||
allowUnsnapped: true,
|
||||
snappedTo,
|
||||
snapLocation: value,
|
||||
}
|
||||
)
|
||||
export let snapToLayers: string[] | undefined
|
||||
export let targetLayer: LayerConfig | LayerConfig[] | undefined
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
layer: targetLayer,
|
||||
features: snappedLocation,
|
||||
let targetLayers: LayerConfig[] | undefined
|
||||
if (Array.isArray(targetLayers)) {
|
||||
targetLayers = <LayerConfig[]>targetLayer
|
||||
} else if (targetLayer) {
|
||||
targetLayers = [<LayerConfig>targetLayer]
|
||||
}
|
||||
export let maxSnapDistance: number = undefined
|
||||
|
||||
export let snappedTo: UIEventSource<string | undefined>
|
||||
|
||||
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
|
||||
lon: number
|
||||
lat: number
|
||||
}>(undefined)
|
||||
|
||||
const dispatch = createEventDispatcher<{ click: { lon: number; lat: number } }>()
|
||||
|
||||
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16)
|
||||
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
let initialMapProperties: Partial<MapProperties> = {
|
||||
zoom: new UIEventSource<number>(19),
|
||||
maxbounds: new UIEventSource(undefined),
|
||||
/*If no snapping needed: the value is simply the map location;
|
||||
* If snapping is needed: the value will be set later on by the snapping feature source
|
||||
* */
|
||||
location:
|
||||
snapToLayers?.length > 0
|
||||
? new UIEventSource<{ lon: number; lat: number }>(coordinate)
|
||||
: value,
|
||||
bounds: new UIEventSource<BBox>(undefined),
|
||||
allowMoving: new UIEventSource<boolean>(true),
|
||||
allowZooming: new UIEventSource<boolean>(true),
|
||||
minzoom: new UIEventSource<number>(18),
|
||||
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
|
||||
}
|
||||
|
||||
targetLayers?.forEach(layer => {
|
||||
const featuresForLayer = state.perLayer.get(layer.id)
|
||||
if (featuresForLayer) {
|
||||
new ShowDataLayer(map, {
|
||||
layer,
|
||||
features: featuresForLayer,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (snapToLayers?.length > 0) {
|
||||
const snapSources: FeatureSource[] = []
|
||||
for (const layerId of snapToLayers ?? []) {
|
||||
const layer: FeatureSourceForLayer = state.perLayer.get(layerId)
|
||||
snapSources.push(layer)
|
||||
if (layer.features === undefined) {
|
||||
continue
|
||||
}
|
||||
new ShowDataLayer(map, {
|
||||
layer: layer.layer.layerDef,
|
||||
zoomToFeatures: false,
|
||||
features: layer,
|
||||
})
|
||||
}
|
||||
const snappedLocation = new SnappingFeatureSource(
|
||||
new FeatureSourceMerger(...Utils.NoNull(snapSources)),
|
||||
// We snap to the (constantly updating) map location
|
||||
initialMapProperties.location,
|
||||
{
|
||||
maxDistance: maxSnapDistance ?? 15,
|
||||
allowUnsnapped: true,
|
||||
snappedTo,
|
||||
snapLocation: value,
|
||||
},
|
||||
)
|
||||
|
||||
targetLayers.forEach(layer => {
|
||||
new ShowDataLayer(map, {
|
||||
layer,
|
||||
features: snappedLocation,
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<LocationInput
|
||||
{map}
|
||||
on:click={(data) => dispatch("click", data)}
|
||||
mapProperties={initialMapProperties}
|
||||
value={preciseLocation}
|
||||
initialCoordinate={coordinate}
|
||||
maxDistanceInMeters="50"
|
||||
{map}
|
||||
mapProperties={initialMapProperties}
|
||||
maxDistanceInMeters={50}
|
||||
on:click={(data) => dispatch("click", data)}
|
||||
value={preciseLocation}
|
||||
>
|
||||
<slot name="image" slot="image">
|
||||
<Move_arrows class="h-full max-h-24" />
|
||||
|
|
|
@ -1,41 +1,57 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* The 'importflow' does some basic setup, e.g. validate that imports are allowed, that the user is logged-in, ...
|
||||
* They show some default components
|
||||
*/
|
||||
import ImportFlow from "./ImportFlow"
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||
import BackButton from "../../Base/BackButton.svelte"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import NextButton from "../../Base/NextButton.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
import { And } from "../../../Logic/Tags/And"
|
||||
import TagHint from "../TagHint.svelte"
|
||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||
import { Store } from "../../../Logic/UIEventSource"
|
||||
import Svg from "../../../Svg"
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import type { ImportFlowArguments } from "./ImportFlow"
|
||||
/**
|
||||
* The 'importflow' does some basic setup, e.g. validate that imports are allowed, that the user is logged-in, ...
|
||||
* They show some default components
|
||||
*/
|
||||
import ImportFlow from "./ImportFlow"
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||
import BackButton from "../../Base/BackButton.svelte"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import NextButton from "../../Base/NextButton.svelte"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
import { And } from "../../../Logic/Tags/And"
|
||||
import TagHint from "../TagHint.svelte"
|
||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||
import { Store } from "../../../Logic/UIEventSource"
|
||||
import Svg from "../../../Svg"
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
|
||||
export let importFlow: ImportFlow
|
||||
let state = importFlow.state
|
||||
export let importFlow: ImportFlow<ImportFlowArguments>
|
||||
let state = importFlow.state
|
||||
|
||||
export let currentFlowStep: "start" | "confirm" | "importing" | "imported" = "start"
|
||||
export let currentFlowStep: "start" | "confirm" | "importing" | "imported" = "start"
|
||||
|
||||
const isLoading = state.dataIsLoading
|
||||
const dispatch = createEventDispatcher<{ confirm }>()
|
||||
const canBeImported = importFlow.canBeImported()
|
||||
const tags: Store<TagsFilter> = importFlow.tagsToApply.map((tags) => new And(tags))
|
||||
const isLoading = state.dataIsLoading
|
||||
let dispatch = createEventDispatcher<{ confirm }>()
|
||||
let canBeImported = importFlow.canBeImported()
|
||||
let tags: Store<TagsFilter> = importFlow.tagsToApply.map((tags) => new And(tags))
|
||||
|
||||
const isDisplayed = importFlow.targetLayer.isDisplayed
|
||||
const hasFilter = importFlow.targetLayer.hasFilter
|
||||
|
||||
function abort() {
|
||||
state.selectedElement.setData(undefined)
|
||||
state.selectedLayer.setData(undefined)
|
||||
}
|
||||
let targetLayers = importFlow.targetLayer
|
||||
let filteredLayer: FilteredLayer
|
||||
let undisplayedLayer: FilteredLayer
|
||||
|
||||
function updateIsDisplayed() {
|
||||
filteredLayer = targetLayers.find(tl => tl.hasFilter.data)
|
||||
undisplayedLayer = targetLayers.find(tl => !tl.isDisplayed.data)
|
||||
}
|
||||
|
||||
updateIsDisplayed()
|
||||
|
||||
for (const tl of targetLayers) {
|
||||
onDestroy(
|
||||
tl.isDisplayed.addCallback(updateIsDisplayed),
|
||||
)
|
||||
}
|
||||
|
||||
function abort() {
|
||||
state.selectedElement.setData(undefined)
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoginToggle {state}>
|
||||
|
@ -44,13 +60,13 @@
|
|||
{#if $canBeImported.extraHelp}
|
||||
<Tr t={$canBeImported.extraHelp} />
|
||||
{/if}
|
||||
{:else if !$isDisplayed}
|
||||
{:else if undisplayedLayer !== undefined}
|
||||
<!-- Check that the layer is enabled, so that we don't add a duplicate -->
|
||||
<div class="alert flex items-center justify-center">
|
||||
<EyeOffIcon class="w-8" />
|
||||
<Tr
|
||||
t={Translations.t.general.add.layerNotEnabled.Subs({
|
||||
layer: importFlow.targetLayer.layerDef.name,
|
||||
layer: undisplayedLayer.layerDef.name,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
@ -60,7 +76,7 @@
|
|||
class="flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(importFlow.targetLayer.layerDef)
|
||||
state.guistate.openFilterView(filteredLayer.layerDef)
|
||||
}}
|
||||
>
|
||||
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
||||
|
@ -70,19 +86,19 @@
|
|||
<button
|
||||
class="primary flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
isDisplayed.setData(true)
|
||||
undisplayedLayer.isDisplayed.setData(true)
|
||||
abort()
|
||||
}}
|
||||
>
|
||||
<EyeIcon class="w-12" />
|
||||
<Tr
|
||||
t={Translations.t.general.add.enableLayer.Subs({
|
||||
name: importFlow.targetLayer.layerDef.name,
|
||||
name: undisplayedLayer.layerDef.name,
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{:else if $hasFilter}
|
||||
{:else if filteredLayer !== undefined}
|
||||
<!-- Some filters are enabled. The feature to add might already be mapped, but hidden -->
|
||||
<div class="alert flex items-center justify-center">
|
||||
<EyeOffIcon class="w-8" />
|
||||
|
@ -93,7 +109,7 @@
|
|||
class="primary flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
importFlow.targetLayer.disableAllFilters()
|
||||
filteredLayer.disableAllFilters()
|
||||
}}
|
||||
>
|
||||
<EyeOffIcon class="w-12" />
|
||||
|
@ -103,7 +119,7 @@
|
|||
class="flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
state.guistate.openFilterView(importFlow.targetLayer.layerDef)
|
||||
state.guistate.openFilterView(filteredLayer.layerDef)
|
||||
}}
|
||||
>
|
||||
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
||||
|
|
|
@ -6,7 +6,6 @@ import TagApplyButton from "../TagApplyButton"
|
|||
import { PointImportFlowArguments } from "./PointImportFlowState"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import { LayerConfigJson } from "../../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
|
@ -25,7 +24,7 @@ export class ImportFlowUtils {
|
|||
public static readonly conflationLayer = new LayerConfig(
|
||||
<LayerConfigJson>conflation_json,
|
||||
"all_known_layers",
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
public static readonly documentationGeneral = `\n\n\nNote that the contributor must zoom to at least zoomlevel 18 to be able to use this functionality.
|
||||
|
@ -67,7 +66,7 @@ ${Utils.special_visualizations_importRequirementDocs}
|
|||
*/
|
||||
public static getTagsToApply(
|
||||
originalFeatureTags: UIEventSource<any>,
|
||||
args: { tags: string }
|
||||
args: { tags: string },
|
||||
): Store<Tag[]> {
|
||||
if (originalFeatureTags === undefined) {
|
||||
return undefined
|
||||
|
@ -83,9 +82,9 @@ ${Utils.special_visualizations_importRequirementDocs}
|
|||
const items: string = originalFeatureTags.data[tags]
|
||||
console.debug(
|
||||
"The import button is using tags from properties[" +
|
||||
tags +
|
||||
"] of this object, namely ",
|
||||
items
|
||||
tags +
|
||||
"] of this object, namely ",
|
||||
items,
|
||||
)
|
||||
|
||||
if (items.startsWith("{")) {
|
||||
|
@ -108,13 +107,12 @@ ${Utils.special_visualizations_importRequirementDocs}
|
|||
* - targetLayer
|
||||
*
|
||||
* Others (e.g.: snapOnto-layers) are not to be handled here
|
||||
* @param argsRaw
|
||||
*/
|
||||
public static getLayerDependencies(argsRaw: string[], argSpec?) {
|
||||
public static getLayerDependencies(argsRaw: string[], argSpec?): string[] {
|
||||
const args: ImportFlowArguments = <any>(
|
||||
Utils.ParseVisArgs(argSpec ?? ImportFlowUtils.generalArguments, argsRaw)
|
||||
)
|
||||
return [args.targetLayer]
|
||||
return args.targetLayer.split(" ")
|
||||
}
|
||||
|
||||
public static getLayerDependenciesWithSnapOnto(
|
||||
|
@ -122,7 +120,7 @@ ${Utils.special_visualizations_importRequirementDocs}
|
|||
name: string
|
||||
defaultValue?: string
|
||||
}[],
|
||||
argsRaw: string[]
|
||||
argsRaw: string[],
|
||||
): string[] {
|
||||
const deps = ImportFlowUtils.getLayerDependencies(argsRaw, argSpec)
|
||||
const argsParsed: PointImportFlowArguments = <any>Utils.ParseVisArgs(argSpec, argsRaw)
|
||||
|
@ -130,30 +128,6 @@ ${Utils.special_visualizations_importRequirementDocs}
|
|||
deps.push(...snapOntoLayers)
|
||||
return deps
|
||||
}
|
||||
|
||||
public static buildTagSpec(
|
||||
args: ImportFlowArguments,
|
||||
tagSource: Store<Record<string, string>>
|
||||
): Store<string> {
|
||||
let tagSpec = args.tags
|
||||
return tagSource.mapD((tags) => {
|
||||
if (
|
||||
tagSpec.indexOf(" ") < 0 &&
|
||||
tagSpec.indexOf(";") < 0 &&
|
||||
tags[args.tags] !== undefined
|
||||
) {
|
||||
// This is probably a key
|
||||
tagSpec = tags[args.tags]
|
||||
console.debug(
|
||||
"The import button is using tags from properties[" +
|
||||
args.tags +
|
||||
"] of this object, namely ",
|
||||
tagSpec
|
||||
)
|
||||
}
|
||||
return tagSpec
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -164,7 +138,7 @@ ${Utils.special_visualizations_importRequirementDocs}
|
|||
export default abstract class ImportFlow<ArgT extends ImportFlowArguments> {
|
||||
public readonly state: SpecialVisualizationState
|
||||
public readonly args: ArgT
|
||||
public readonly targetLayer: FilteredLayer
|
||||
public readonly targetLayer: FilteredLayer[]
|
||||
public readonly tagsToApply: Store<Tag[]>
|
||||
protected readonly _originalFeatureTags: UIEventSource<Record<string, string>>
|
||||
|
||||
|
@ -172,13 +146,19 @@ export default abstract class ImportFlow<ArgT extends ImportFlowArguments> {
|
|||
state: SpecialVisualizationState,
|
||||
args: ArgT,
|
||||
tagsToApply: Store<Tag[]>,
|
||||
originalTags: UIEventSource<Record<string, string>>
|
||||
originalTags: UIEventSource<Record<string, string>>,
|
||||
) {
|
||||
this.state = state
|
||||
this.args = args
|
||||
this.tagsToApply = tagsToApply
|
||||
this._originalFeatureTags = originalTags
|
||||
this.targetLayer = state.layerState.filteredLayers.get(args.targetLayer)
|
||||
this.targetLayer = args.targetLayer.split(" ").map(tl => {
|
||||
let found = state.layerState.filteredLayers.get(tl)
|
||||
if (!found) {
|
||||
throw "Layer " + tl + " not found"
|
||||
}
|
||||
return found
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,7 +198,7 @@ export default abstract class ImportFlow<ArgT extends ImportFlowArguments> {
|
|||
|
||||
return undefined
|
||||
},
|
||||
[state.mapProperties.zoom, state.dataIsLoading, this._originalFeatureTags]
|
||||
[state.mapProperties.zoom, state.dataIsLoading, this._originalFeatureTags],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export class PointImportButtonViz implements SpecialVisualization {
|
|||
public readonly funcName: string
|
||||
public readonly docs: string | BaseUIElement
|
||||
public readonly example?: string
|
||||
public readonly args: { name: string; defaultValue?: string; doc: string }[]
|
||||
public readonly args: { name: string; defaultValue?: string; doc: string, split?: boolean }[]
|
||||
public needsUrls = []
|
||||
|
||||
constructor() {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
const args = importFlow.args
|
||||
|
||||
// The following variables are used for the map
|
||||
const targetLayer: LayerConfig = state.layout.layers.find((l) => l.id === args.targetLayer)
|
||||
const targetLayers: LayerConfig[] = args.targetLayer.split(" ").map(tl => state.layout.layers.find((l) => l.id === tl))
|
||||
const snapToLayers: string[] | undefined =
|
||||
args.snap_onto_layers?.split(",")?.map((l) => l.trim()) ?? []
|
||||
const maxSnapDistance: number = Number(args.max_snap_distance ?? 25) ?? 25
|
||||
|
@ -33,21 +33,20 @@
|
|||
|
||||
async function onConfirm(): Promise<void> {
|
||||
const importedId = await importFlow.onConfirm(value.data, snappedTo.data)
|
||||
state.selectedLayer.setData(targetLayer)
|
||||
state.selectedElement.setData(state.indexedFeatures.featuresById.data.get(importedId))
|
||||
}
|
||||
</script>
|
||||
|
||||
<ImportFlow {importFlow} on:confirm={onConfirm}>
|
||||
<div class="relative" slot="map">
|
||||
<div class="h-32">
|
||||
<div class="h-64">
|
||||
<NewPointLocationInput
|
||||
coordinate={startCoordinate}
|
||||
{maxSnapDistance}
|
||||
{snapToLayers}
|
||||
{snappedTo}
|
||||
{state}
|
||||
{targetLayer}
|
||||
targetLayer={targetLayers}
|
||||
{value}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -3,11 +3,7 @@ import { FixedUiElement } from "./Base/FixedUiElement"
|
|||
import BaseUIElement from "./BaseUIElement"
|
||||
import Title from "./Base/Title"
|
||||
import Table from "./Base/Table"
|
||||
import {
|
||||
RenderingSpecification,
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
} from "./SpecialVisualization"
|
||||
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
|
||||
import { HistogramViz } from "./Popup/HistogramViz"
|
||||
import { MinimapViz } from "./Popup/MinimapViz"
|
||||
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
||||
|
@ -110,7 +106,7 @@ class NearbyImageVis implements SpecialVisualization {
|
|||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const isOpen = args[0] === "open"
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
|
@ -175,7 +171,7 @@ class StealViz implements SpecialVisualization {
|
|||
selectedElement: otherFeature,
|
||||
state,
|
||||
layer,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
if (elements.length === 1) {
|
||||
|
@ -183,8 +179,8 @@ class StealViz implements SpecialVisualization {
|
|||
}
|
||||
return new Combine(elements).SetClass("flex flex-col")
|
||||
},
|
||||
[state.indexedFeatures.featuresById]
|
||||
)
|
||||
[state.indexedFeatures.featuresById],
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -223,7 +219,7 @@ export class QuestionViz implements SpecialVisualization {
|
|||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const labels = args[0]
|
||||
?.split(";")
|
||||
|
@ -277,17 +273,17 @@ export default class SpecialVisualizations {
|
|||
* templ.args[0] = "{email}"
|
||||
*/
|
||||
public static constructSpecification(
|
||||
template: string,
|
||||
extraMappings: SpecialVisualization[] = []
|
||||
template: string | { special: Record<string, string | Record<string, string>> & { type: string } },
|
||||
extraMappings: SpecialVisualization[] = [],
|
||||
): RenderingSpecification[] {
|
||||
if (template === "") {
|
||||
return []
|
||||
}
|
||||
|
||||
if (template["type"] !== undefined) {
|
||||
if (typeof template !== "string") {
|
||||
console.trace(
|
||||
"Got a non-expanded template while constructing the specification, it still has a 'special-key':",
|
||||
template
|
||||
template,
|
||||
)
|
||||
throw "Got a non-expanded template while constructing the specification"
|
||||
}
|
||||
|
@ -295,20 +291,20 @@ export default class SpecialVisualizations {
|
|||
for (const knownSpecial of allKnownSpecials) {
|
||||
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
|
||||
const matched = template.match(
|
||||
new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s")
|
||||
new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s"),
|
||||
)
|
||||
if (matched != null) {
|
||||
// We found a special component that should be brought to live
|
||||
const partBefore = SpecialVisualizations.constructSpecification(
|
||||
matched[1],
|
||||
extraMappings
|
||||
extraMappings,
|
||||
)
|
||||
const argument =
|
||||
matched[2] /* .trim() // We don't trim, as spaces might be relevant, e.g. "what is ... of {title()}"*/
|
||||
const style = matched[3]?.substring(1) ?? ""
|
||||
const partAfter = SpecialVisualizations.constructSpecification(
|
||||
matched[4],
|
||||
extraMappings
|
||||
extraMappings,
|
||||
)
|
||||
const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "")
|
||||
if (argument.length > 0) {
|
||||
|
@ -347,31 +343,31 @@ export default class SpecialVisualizations {
|
|||
viz.docs,
|
||||
viz.args.length > 0
|
||||
? new Table(
|
||||
["name", "default", "description"],
|
||||
viz.args.map((arg) => {
|
||||
let defaultArg = arg.defaultValue ?? "_undefined_"
|
||||
if (defaultArg == "") {
|
||||
defaultArg = "_empty string_"
|
||||
}
|
||||
return [arg.name, defaultArg, arg.doc]
|
||||
})
|
||||
)
|
||||
["name", "default", "description"],
|
||||
viz.args.map((arg) => {
|
||||
let defaultArg = arg.defaultValue ?? "_undefined_"
|
||||
if (defaultArg == "") {
|
||||
defaultArg = "_empty string_"
|
||||
}
|
||||
return [arg.name, defaultArg, arg.doc]
|
||||
}),
|
||||
)
|
||||
: undefined,
|
||||
new Title("Example usage of " + viz.funcName, 4),
|
||||
new FixedUiElement(
|
||||
viz.example ??
|
||||
"`{" +
|
||||
viz.funcName +
|
||||
"(" +
|
||||
viz.args.map((arg) => arg.defaultValue).join(",") +
|
||||
")}`"
|
||||
"`{" +
|
||||
viz.funcName +
|
||||
"(" +
|
||||
viz.args.map((arg) => arg.defaultValue).join(",") +
|
||||
")}`",
|
||||
).SetClass("literal-code"),
|
||||
])
|
||||
}
|
||||
|
||||
public static HelpMessage() {
|
||||
const helpTexts = SpecialVisualizations.specialVisualizations.map((viz) =>
|
||||
SpecialVisualizations.DocumentationFor(viz)
|
||||
SpecialVisualizations.DocumentationFor(viz),
|
||||
)
|
||||
|
||||
return new Combine([
|
||||
|
@ -405,10 +401,10 @@ export default class SpecialVisualizations {
|
|||
},
|
||||
},
|
||||
null,
|
||||
" "
|
||||
)
|
||||
" ",
|
||||
),
|
||||
).SetClass("code"),
|
||||
'In other words: use `{ "before": ..., "after": ..., "special": {"type": ..., "argname": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)',
|
||||
"In other words: use `{ \"before\": ..., \"after\": ..., \"special\": {\"type\": ..., \"argname\": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)",
|
||||
]).SetClass("flex flex-col"),
|
||||
...helpTexts,
|
||||
]).SetClass("flex flex-col")
|
||||
|
@ -417,20 +413,20 @@ export default class SpecialVisualizations {
|
|||
// noinspection JSUnusedGlobalSymbols
|
||||
public static renderExampleOfSpecial(
|
||||
state: SpecialVisualizationState,
|
||||
s: SpecialVisualization
|
||||
s: SpecialVisualization,
|
||||
): BaseUIElement {
|
||||
const examples =
|
||||
s.structuredExamples === undefined
|
||||
? []
|
||||
: s.structuredExamples().map((e) => {
|
||||
return s.constr(
|
||||
state,
|
||||
new UIEventSource<Record<string, string>>(e.feature.properties),
|
||||
e.args,
|
||||
e.feature,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
return s.constr(
|
||||
state,
|
||||
new UIEventSource<Record<string, string>>(e.feature.properties),
|
||||
e.args,
|
||||
e.feature,
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
return new Combine([new Title(s.funcName), s.docs, ...examples])
|
||||
}
|
||||
|
||||
|
@ -470,7 +466,7 @@ export default class SpecialVisualizations {
|
|||
assignTo: state.userRelatedState.language,
|
||||
availableLanguages: state.layout.language,
|
||||
preferredLanguages: state.osmConnection.userDetails.map(
|
||||
(ud) => ud.languages
|
||||
(ud) => ud.languages,
|
||||
),
|
||||
})
|
||||
},
|
||||
|
@ -495,7 +491,7 @@ export default class SpecialVisualizations {
|
|||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
): BaseUIElement {
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
|
@ -505,7 +501,7 @@ export default class SpecialVisualizations {
|
|||
return new SplitRoadWizard(<WayId>id, state)
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -519,7 +515,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
if (feature.geometry.type !== "Point") {
|
||||
return undefined
|
||||
|
@ -542,7 +538,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
if (!layer.deletion) {
|
||||
return undefined
|
||||
|
@ -570,7 +566,7 @@ export default class SpecialVisualizations {
|
|||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
feature: Feature,
|
||||
): BaseUIElement {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
return new SvelteUIElement(CreateNewNote, {
|
||||
|
@ -634,7 +630,7 @@ export default class SpecialVisualizations {
|
|||
.map((tags) => tags[args[0]])
|
||||
.map((wikidata) => {
|
||||
wikidata = Utils.NoEmpty(
|
||||
wikidata?.split(";")?.map((wd) => wd.trim()) ?? []
|
||||
wikidata?.split(";")?.map((wd) => wd.trim()) ?? [],
|
||||
)[0]
|
||||
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
||||
return new VariableUiElement(
|
||||
|
@ -644,9 +640,9 @@ export default class SpecialVisualizations {
|
|||
}
|
||||
const response = <WikidataResponse>e["success"]
|
||||
return Translation.fromMap(response.labels)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
),
|
||||
},
|
||||
new MapillaryLinkVis(),
|
||||
|
@ -678,7 +674,7 @@ export default class SpecialVisualizations {
|
|||
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
||||
tags,
|
||||
state,
|
||||
feature
|
||||
feature,
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -734,7 +730,7 @@ export default class SpecialVisualizations {
|
|||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
}
|
||||
},
|
||||
)
|
||||
return new SvelteUIElement(StarsBarIcon, {
|
||||
score: reviews.average,
|
||||
|
@ -767,7 +763,7 @@ export default class SpecialVisualizations {
|
|||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
}
|
||||
},
|
||||
)
|
||||
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
|
||||
},
|
||||
|
@ -799,7 +795,7 @@ export default class SpecialVisualizations {
|
|||
{
|
||||
nameKey: nameKey,
|
||||
fallbackName,
|
||||
}
|
||||
},
|
||||
)
|
||||
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
||||
},
|
||||
|
@ -857,7 +853,7 @@ export default class SpecialVisualizations {
|
|||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
const keyToUse = args[0]
|
||||
const prefix = args[1]
|
||||
|
@ -894,10 +890,10 @@ export default class SpecialVisualizations {
|
|||
return undefined
|
||||
}
|
||||
const allUnits: Unit[] = [].concat(
|
||||
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? [])
|
||||
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? []),
|
||||
)
|
||||
const unit = allUnits.filter((unit) =>
|
||||
unit.isApplicableToKey(key)
|
||||
unit.isApplicableToKey(key),
|
||||
)[0]
|
||||
if (unit === undefined) {
|
||||
return value
|
||||
|
@ -905,7 +901,7 @@ export default class SpecialVisualizations {
|
|||
const getCountry = () => tagSource.data._country
|
||||
const [v, denom] = unit.findDenomination(value, getCountry)
|
||||
return unit.asHumanLongValue(v, getCountry)
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -922,7 +918,7 @@ export default class SpecialVisualizations {
|
|||
new Combine([
|
||||
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
||||
t.downloadGeoJsonHelper.SetClass("subtle"),
|
||||
]).SetClass("flex flex-col")
|
||||
]).SetClass("flex flex-col"),
|
||||
)
|
||||
.onClick(() => {
|
||||
console.log("Exporting as Geojson")
|
||||
|
@ -935,7 +931,7 @@ export default class SpecialVisualizations {
|
|||
title + "_mapcomplete_export.geojson",
|
||||
{
|
||||
mimetype: "application/vnd.geo+json",
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
.SetClass("w-full")
|
||||
|
@ -971,7 +967,7 @@ export default class SpecialVisualizations {
|
|||
constr: (state) => {
|
||||
return new SubtleButton(
|
||||
Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
|
||||
Translations.t.general.removeLocationHistory
|
||||
Translations.t.general.removeLocationHistory,
|
||||
).onClick(() => {
|
||||
state.historicalUserLocations.features.setData([])
|
||||
state.selectedElement.setData(undefined)
|
||||
|
@ -1009,10 +1005,10 @@ export default class SpecialVisualizations {
|
|||
.filter((c) => c.text !== "")
|
||||
.map(
|
||||
(c, i) =>
|
||||
new NoteCommentElement(c, state, i, comments.length)
|
||||
)
|
||||
new NoteCommentElement(c, state, i, comments.length),
|
||||
),
|
||||
).SetClass("flex flex-col")
|
||||
})
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -1053,9 +1049,9 @@ export default class SpecialVisualizations {
|
|||
return undefined
|
||||
}
|
||||
return new SubstitutedTranslation(title, tagsSource, state).SetClass(
|
||||
"px-1"
|
||||
"px-1",
|
||||
)
|
||||
})
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -1071,8 +1067,8 @@ export default class SpecialVisualizations {
|
|||
let challenge = Stores.FromPromise(
|
||||
Utils.downloadJsonCached(
|
||||
`${Maproulette.defaultEndpoint}/challenge/${parentId}`,
|
||||
24 * 60 * 60 * 1000
|
||||
)
|
||||
24 * 60 * 60 * 1000,
|
||||
),
|
||||
)
|
||||
|
||||
return new VariableUiElement(
|
||||
|
@ -1097,7 +1093,7 @@ export default class SpecialVisualizations {
|
|||
} else {
|
||||
return [title, new List(listItems)]
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
docs: "Fetches the metadata of MapRoulette campaign that this task is part of and shows those details (namely `title`, `description` and `instruction`).\n\nThis reads the property `mr_challengeId` to detect the parent campaign.",
|
||||
|
@ -1111,15 +1107,15 @@ export default class SpecialVisualizations {
|
|||
"\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
' "id": "mark_duplicate",\n' +
|
||||
' "render": {\n' +
|
||||
' "special": {\n' +
|
||||
' "type": "maproulette_set_status",\n' +
|
||||
' "message": {\n' +
|
||||
' "en": "Mark as not found or false positive"\n' +
|
||||
" \"id\": \"mark_duplicate\",\n" +
|
||||
" \"render\": {\n" +
|
||||
" \"special\": {\n" +
|
||||
" \"type\": \"maproulette_set_status\",\n" +
|
||||
" \"message\": {\n" +
|
||||
" \"en\": \"Mark as not found or false positive\"\n" +
|
||||
" },\n" +
|
||||
' "status": "2",\n' +
|
||||
' "image": "close"\n' +
|
||||
" \"status\": \"2\",\n" +
|
||||
" \"image\": \"close\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
|
@ -1185,8 +1181,8 @@ export default class SpecialVisualizations {
|
|||
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
|
||||
return new StatisticsPanel(fsBboxed)
|
||||
},
|
||||
[state.mapProperties.bounds]
|
||||
)
|
||||
[state.mapProperties.bounds],
|
||||
),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -1252,7 +1248,7 @@ export default class SpecialVisualizations {
|
|||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
args: string[],
|
||||
): BaseUIElement {
|
||||
let [text, href, classnames, download, ariaLabel] = args
|
||||
if (download === "") {
|
||||
|
@ -1269,15 +1265,14 @@ export default class SpecialVisualizations {
|
|||
download: Utils.SubstituteKeys(download, tags),
|
||||
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
|
||||
newTab,
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "multi",
|
||||
docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering",
|
||||
|
||||
example:
|
||||
"```json\n" +
|
||||
JSON.stringify(
|
||||
|
@ -1293,7 +1288,7 @@ export default class SpecialVisualizations {
|
|||
},
|
||||
},
|
||||
null,
|
||||
" "
|
||||
" ",
|
||||
) +
|
||||
"\n```",
|
||||
args: [
|
||||
|
@ -1313,18 +1308,28 @@ export default class SpecialVisualizations {
|
|||
const translation = new Translation({ "*": tr })
|
||||
return new VariableUiElement(
|
||||
featureTags.map((tags) => {
|
||||
const properties: object[] = JSON.parse(tags[key])
|
||||
const elements = []
|
||||
for (const property of properties) {
|
||||
const subsTr = new SubstitutedTranslation(
|
||||
translation,
|
||||
new UIEventSource<any>(property),
|
||||
state
|
||||
)
|
||||
elements.push(subsTr)
|
||||
try {
|
||||
const data = tags[key]
|
||||
const properties: object[] = typeof data === "string" ? JSON.parse(tags[key]) : data
|
||||
const elements = []
|
||||
for (const property of properties) {
|
||||
const subsTr = new SubstitutedTranslation(
|
||||
translation,
|
||||
new UIEventSource<any>(property),
|
||||
state,
|
||||
)
|
||||
elements.push(subsTr)
|
||||
}
|
||||
return new List(elements)
|
||||
} catch (e) {
|
||||
console.log("Something went wrong while generating the elements for a multi", {
|
||||
e,
|
||||
tags,
|
||||
key,
|
||||
loaded: tags[key],
|
||||
})
|
||||
}
|
||||
return new List(elements)
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -1344,7 +1349,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
return new VariableUiElement(
|
||||
tagSource.map((tags) => {
|
||||
|
@ -1356,7 +1361,7 @@ export default class SpecialVisualizations {
|
|||
console.error("Cannot create a translation for", v, "due to", e)
|
||||
return JSON.stringify(v)
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -1376,7 +1381,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const key = argument[0]
|
||||
const validator = new FediverseValidator()
|
||||
|
@ -1386,14 +1391,14 @@ export default class SpecialVisualizations {
|
|||
.map((fediAccount) => {
|
||||
fediAccount = validator.reformat(fediAccount)
|
||||
const [_, username, host] = fediAccount.match(
|
||||
FediverseValidator.usernameAtServer
|
||||
FediverseValidator.usernameAtServer,
|
||||
)
|
||||
return new SvelteUIElement(Link, {
|
||||
text: fediAccount,
|
||||
url: "https://" + host + "/@" + username,
|
||||
newTab: true,
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -1413,7 +1418,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
return new FixedUiElement("{" + args[0] + "}")
|
||||
},
|
||||
|
@ -1434,7 +1439,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const key = argument[0] ?? "value"
|
||||
return new VariableUiElement(
|
||||
|
@ -1452,12 +1457,12 @@ export default class SpecialVisualizations {
|
|||
} catch (e) {
|
||||
return new FixedUiElement(
|
||||
"Could not parse this tag: " +
|
||||
JSON.stringify(value) +
|
||||
" due to " +
|
||||
e
|
||||
JSON.stringify(value) +
|
||||
" due to " +
|
||||
e,
|
||||
).SetClass("alert")
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -1478,7 +1483,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const giggityUrl = argument[0]
|
||||
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
|
||||
|
@ -1494,12 +1499,12 @@ export default class SpecialVisualizations {
|
|||
_: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const tags = (<ThemeViewState>(
|
||||
state
|
||||
)).geolocation.currentUserLocation.features.map(
|
||||
(features) => features[0]?.properties
|
||||
(features) => features[0]?.properties,
|
||||
)
|
||||
return new Combine([
|
||||
new SvelteUIElement(OrientationDebugPanel, {}),
|
||||
|
@ -1521,7 +1526,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(MarkAsFavourite, {
|
||||
tags: tagSource,
|
||||
|
@ -1541,7 +1546,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(MarkAsFavouriteMini, {
|
||||
tags: tagSource,
|
||||
|
@ -1561,7 +1566,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
||||
},
|
||||
|
@ -1576,7 +1581,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
|
@ -1598,9 +1603,9 @@ export default class SpecialVisualizations {
|
|||
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${layout}lat=${lat}&lon=${lon}&z=15` +
|
||||
`#${id}`
|
||||
return new Img(new Qr(url).toImageElement(75)).SetStyle(
|
||||
"width: 75px"
|
||||
"width: 75px",
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -1620,7 +1625,7 @@ export default class SpecialVisualizations {
|
|||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): BaseUIElement {
|
||||
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
||||
return new VariableUiElement(
|
||||
|
@ -1631,41 +1636,55 @@ export default class SpecialVisualizations {
|
|||
})
|
||||
.mapD((value) => {
|
||||
const dir = GeoOperations.bearingToHuman(
|
||||
GeoOperations.parseBearing(value)
|
||||
GeoOperations.parseBearing(value),
|
||||
)
|
||||
console.log("Human dir", dir)
|
||||
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "compare_data",
|
||||
needsUrls: (args) => args[1].split(";"),
|
||||
args:[
|
||||
args: [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
doc: "The attribute containing the url where to fetch more data"
|
||||
doc: "The attribute containing the url where to fetch more data",
|
||||
},
|
||||
{
|
||||
name:"host",
|
||||
required:true,
|
||||
doc: "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. "
|
||||
name: "host",
|
||||
required: true,
|
||||
doc: "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. ",
|
||||
},
|
||||
{
|
||||
name: "postprocessing",
|
||||
required: false,
|
||||
doc: "Apply some postprocessing. Currently, only 'velopark' is allowed as value"
|
||||
doc: "Apply some postprocessing. Currently, only 'velopark' is allowed as value",
|
||||
},
|
||||
{
|
||||
name:"readonly",
|
||||
required: false,
|
||||
doc: "If 'yes', will not show 'apply'-buttons"
|
||||
}
|
||||
],
|
||||
docs: "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM",
|
||||
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, args: string[], feature: Feature, layer: LayerConfig): BaseUIElement {
|
||||
const url = args[0]
|
||||
const postprocessVelopark = args[2] === "velopark"
|
||||
return new SvelteUIElement(ComparisonTool, {url, postprocessVelopark, state, tags: tagSource, layer, feature})
|
||||
}
|
||||
}
|
||||
const readonly = args[3] === "yes"
|
||||
return new SvelteUIElement(ComparisonTool, {
|
||||
url,
|
||||
postprocessVelopark,
|
||||
state,
|
||||
tags: tagSource,
|
||||
layer,
|
||||
feature,
|
||||
readonly
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
specialVisualizations.push(new AutoApplyButton(specialVisualizations))
|
||||
|
@ -1677,7 +1696,7 @@ export default class SpecialVisualizations {
|
|||
throw (
|
||||
"Invalid special visualisation found: funcName is undefined for " +
|
||||
invalid.map((sp) => sp.i).join(", ") +
|
||||
'. Did you perhaps type \n funcName: "funcname" // type declaration uses COLON\ninstead of:\n funcName = "funcName" // value definition uses EQUAL'
|
||||
". Did you perhaps type \n funcName: \"funcname\" // type declaration uses COLON\ninstead of:\n funcName = \"funcName\" // value definition uses EQUAL"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue