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">
|
<script lang="ts">
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
|
import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { Tiles } from "../../Models/TileRange"
|
import { Tiles } from "../../Models/TileRange"
|
||||||
import { Map as MlMap } from "maplibre-gl"
|
import { Map as MlMap } from "maplibre-gl"
|
||||||
import { BBox } from "../../Logic/BBox"
|
import { BBox } from "../../Logic/BBox"
|
||||||
import type { MapProperties } from "../../Models/MapProperties"
|
import type { MapProperties } from "../../Models/MapProperties"
|
||||||
import ShowDataLayer from "../Map/ShowDataLayer"
|
import ShowDataLayer from "../Map/ShowDataLayer"
|
||||||
import type {
|
import type { FeatureSource, FeatureSourceForLayer } from "../../Logic/FeatureSource/FeatureSource"
|
||||||
FeatureSource,
|
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource"
|
||||||
FeatureSourceForLayer,
|
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
|
||||||
} from "../../Logic/FeatureSource/FeatureSource"
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource"
|
import { Utils } from "../../Utils"
|
||||||
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
|
import { createEventDispatcher } from "svelte"
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
import Move_arrows from "../../assets/svg/Move_arrows.svelte"
|
||||||
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:
|
* An advanced location input, which has support to:
|
||||||
* - Show more layers
|
* - Show more layers
|
||||||
* - Snap to layers
|
* - Snap to layers
|
||||||
*
|
*
|
||||||
* This one is mostly used to insert new points, including when importing
|
* This one is mostly used to insert new points, including when importing
|
||||||
*/
|
*/
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
/**
|
/**
|
||||||
* The start coordinate
|
* The start coordinate
|
||||||
*/
|
*/
|
||||||
export let coordinate: { lon: number; lat: number }
|
export let coordinate: { lon: number; lat: number }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The center of the map at all times
|
* The center of the map at all times
|
||||||
* If undefined at the beginning, 'coordinate' will be used
|
* If undefined at the beginning, 'coordinate' will be used
|
||||||
*/
|
*/
|
||||||
export let value: UIEventSource<{ lon: number; lat: number }>
|
export let value: UIEventSource<{ lon: number; lat: number }>
|
||||||
if (value.data === undefined) {
|
if (value.data === undefined) {
|
||||||
value.setData(coordinate)
|
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
if (coordinate === undefined) {
|
||||||
|
coordinate = value.data
|
||||||
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(
|
export let snapToLayers: string[] | undefined
|
||||||
new FeatureSourceMerger(...Utils.NoNull(snapSources)),
|
export let targetLayer: LayerConfig | LayerConfig[] | undefined
|
||||||
// We snap to the (constantly updating) map location
|
|
||||||
initialMapProperties.location,
|
|
||||||
{
|
|
||||||
maxDistance: maxSnapDistance ?? 15,
|
|
||||||
allowUnsnapped: true,
|
|
||||||
snappedTo,
|
|
||||||
snapLocation: value,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
new ShowDataLayer(map, {
|
let targetLayers: LayerConfig[] | undefined
|
||||||
layer: targetLayer,
|
if (Array.isArray(targetLayers)) {
|
||||||
features: snappedLocation,
|
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>
|
</script>
|
||||||
|
|
||||||
<LocationInput
|
<LocationInput
|
||||||
{map}
|
|
||||||
on:click={(data) => dispatch("click", data)}
|
|
||||||
mapProperties={initialMapProperties}
|
|
||||||
value={preciseLocation}
|
|
||||||
initialCoordinate={coordinate}
|
initialCoordinate={coordinate}
|
||||||
maxDistanceInMeters="50"
|
{map}
|
||||||
|
mapProperties={initialMapProperties}
|
||||||
|
maxDistanceInMeters={50}
|
||||||
|
on:click={(data) => dispatch("click", data)}
|
||||||
|
value={preciseLocation}
|
||||||
>
|
>
|
||||||
<slot name="image" slot="image">
|
<slot name="image" slot="image">
|
||||||
<Move_arrows class="h-full max-h-24" />
|
<Move_arrows class="h-full max-h-24" />
|
||||||
|
|
|
@ -1,41 +1,57 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/**
|
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
|
* 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 ImportFlow from "./ImportFlow"
|
||||||
import BackButton from "../../Base/BackButton.svelte"
|
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||||
import Translations from "../../i18n/Translations"
|
import BackButton from "../../Base/BackButton.svelte"
|
||||||
import Tr from "../../Base/Tr.svelte"
|
import Translations from "../../i18n/Translations"
|
||||||
import NextButton from "../../Base/NextButton.svelte"
|
import Tr from "../../Base/Tr.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
import NextButton from "../../Base/NextButton.svelte"
|
||||||
import Loading from "../../Base/Loading.svelte"
|
import { createEventDispatcher, onDestroy } from "svelte"
|
||||||
import { And } from "../../../Logic/Tags/And"
|
import Loading from "../../Base/Loading.svelte"
|
||||||
import TagHint from "../TagHint.svelte"
|
import { And } from "../../../Logic/Tags/And"
|
||||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
import TagHint from "../TagHint.svelte"
|
||||||
import { Store } from "../../../Logic/UIEventSource"
|
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||||
import Svg from "../../../Svg"
|
import { Store } from "../../../Logic/UIEventSource"
|
||||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
import Svg from "../../../Svg"
|
||||||
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||||
|
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
|
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||||
|
|
||||||
export let importFlow: ImportFlow
|
export let importFlow: ImportFlow<ImportFlowArguments>
|
||||||
let state = importFlow.state
|
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 isLoading = state.dataIsLoading
|
||||||
const dispatch = createEventDispatcher<{ confirm }>()
|
let dispatch = createEventDispatcher<{ confirm }>()
|
||||||
const canBeImported = importFlow.canBeImported()
|
let canBeImported = importFlow.canBeImported()
|
||||||
const tags: Store<TagsFilter> = importFlow.tagsToApply.map((tags) => new And(tags))
|
let tags: Store<TagsFilter> = importFlow.tagsToApply.map((tags) => new And(tags))
|
||||||
|
|
||||||
const isDisplayed = importFlow.targetLayer.isDisplayed
|
|
||||||
const hasFilter = importFlow.targetLayer.hasFilter
|
|
||||||
|
|
||||||
function abort() {
|
let targetLayers = importFlow.targetLayer
|
||||||
state.selectedElement.setData(undefined)
|
let filteredLayer: FilteredLayer
|
||||||
state.selectedLayer.setData(undefined)
|
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>
|
</script>
|
||||||
|
|
||||||
<LoginToggle {state}>
|
<LoginToggle {state}>
|
||||||
|
@ -44,13 +60,13 @@
|
||||||
{#if $canBeImported.extraHelp}
|
{#if $canBeImported.extraHelp}
|
||||||
<Tr t={$canBeImported.extraHelp} />
|
<Tr t={$canBeImported.extraHelp} />
|
||||||
{/if}
|
{/if}
|
||||||
{:else if !$isDisplayed}
|
{:else if undisplayedLayer !== undefined}
|
||||||
<!-- Check that the layer is enabled, so that we don't add a duplicate -->
|
<!-- Check that the layer is enabled, so that we don't add a duplicate -->
|
||||||
<div class="alert flex items-center justify-center">
|
<div class="alert flex items-center justify-center">
|
||||||
<EyeOffIcon class="w-8" />
|
<EyeOffIcon class="w-8" />
|
||||||
<Tr
|
<Tr
|
||||||
t={Translations.t.general.add.layerNotEnabled.Subs({
|
t={Translations.t.general.add.layerNotEnabled.Subs({
|
||||||
layer: importFlow.targetLayer.layerDef.name,
|
layer: undisplayedLayer.layerDef.name,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,7 +76,7 @@
|
||||||
class="flex w-full gap-x-1"
|
class="flex w-full gap-x-1"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
abort()
|
abort()
|
||||||
state.guistate.openFilterView(importFlow.targetLayer.layerDef)
|
state.guistate.openFilterView(filteredLayer.layerDef)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
||||||
|
@ -70,19 +86,19 @@
|
||||||
<button
|
<button
|
||||||
class="primary flex w-full gap-x-1"
|
class="primary flex w-full gap-x-1"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
isDisplayed.setData(true)
|
undisplayedLayer.isDisplayed.setData(true)
|
||||||
abort()
|
abort()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EyeIcon class="w-12" />
|
<EyeIcon class="w-12" />
|
||||||
<Tr
|
<Tr
|
||||||
t={Translations.t.general.add.enableLayer.Subs({
|
t={Translations.t.general.add.enableLayer.Subs({
|
||||||
name: importFlow.targetLayer.layerDef.name,
|
name: undisplayedLayer.layerDef.name,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{:else if $hasFilter}
|
{:else if filteredLayer !== undefined}
|
||||||
<!-- Some filters are enabled. The feature to add might already be mapped, but hidden -->
|
<!-- Some filters are enabled. The feature to add might already be mapped, but hidden -->
|
||||||
<div class="alert flex items-center justify-center">
|
<div class="alert flex items-center justify-center">
|
||||||
<EyeOffIcon class="w-8" />
|
<EyeOffIcon class="w-8" />
|
||||||
|
@ -93,7 +109,7 @@
|
||||||
class="primary flex w-full gap-x-1"
|
class="primary flex w-full gap-x-1"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
abort()
|
abort()
|
||||||
importFlow.targetLayer.disableAllFilters()
|
filteredLayer.disableAllFilters()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EyeOffIcon class="w-12" />
|
<EyeOffIcon class="w-12" />
|
||||||
|
@ -103,7 +119,7 @@
|
||||||
class="flex w-full gap-x-1"
|
class="flex w-full gap-x-1"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
abort()
|
abort()
|
||||||
state.guistate.openFilterView(importFlow.targetLayer.layerDef)
|
state.guistate.openFilterView(filteredLayer.layerDef)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
||||||
|
|
|
@ -6,7 +6,6 @@ import TagApplyButton from "../TagApplyButton"
|
||||||
import { PointImportFlowArguments } from "./PointImportFlowState"
|
import { PointImportFlowArguments } from "./PointImportFlowState"
|
||||||
import { Translation } from "../../i18n/Translation"
|
import { Translation } from "../../i18n/Translation"
|
||||||
import Translations from "../../i18n/Translations"
|
import Translations from "../../i18n/Translations"
|
||||||
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
|
|
||||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||||
import { LayerConfigJson } from "../../../Models/ThemeConfig/Json/LayerConfigJson"
|
import { LayerConfigJson } from "../../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||||
|
@ -25,7 +24,7 @@ export class ImportFlowUtils {
|
||||||
public static readonly conflationLayer = new LayerConfig(
|
public static readonly conflationLayer = new LayerConfig(
|
||||||
<LayerConfigJson>conflation_json,
|
<LayerConfigJson>conflation_json,
|
||||||
"all_known_layers",
|
"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.
|
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(
|
public static getTagsToApply(
|
||||||
originalFeatureTags: UIEventSource<any>,
|
originalFeatureTags: UIEventSource<any>,
|
||||||
args: { tags: string }
|
args: { tags: string },
|
||||||
): Store<Tag[]> {
|
): Store<Tag[]> {
|
||||||
if (originalFeatureTags === undefined) {
|
if (originalFeatureTags === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -83,9 +82,9 @@ ${Utils.special_visualizations_importRequirementDocs}
|
||||||
const items: string = originalFeatureTags.data[tags]
|
const items: string = originalFeatureTags.data[tags]
|
||||||
console.debug(
|
console.debug(
|
||||||
"The import button is using tags from properties[" +
|
"The import button is using tags from properties[" +
|
||||||
tags +
|
tags +
|
||||||
"] of this object, namely ",
|
"] of this object, namely ",
|
||||||
items
|
items,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (items.startsWith("{")) {
|
if (items.startsWith("{")) {
|
||||||
|
@ -108,13 +107,12 @@ ${Utils.special_visualizations_importRequirementDocs}
|
||||||
* - targetLayer
|
* - targetLayer
|
||||||
*
|
*
|
||||||
* Others (e.g.: snapOnto-layers) are not to be handled here
|
* 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>(
|
const args: ImportFlowArguments = <any>(
|
||||||
Utils.ParseVisArgs(argSpec ?? ImportFlowUtils.generalArguments, argsRaw)
|
Utils.ParseVisArgs(argSpec ?? ImportFlowUtils.generalArguments, argsRaw)
|
||||||
)
|
)
|
||||||
return [args.targetLayer]
|
return args.targetLayer.split(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getLayerDependenciesWithSnapOnto(
|
public static getLayerDependenciesWithSnapOnto(
|
||||||
|
@ -122,7 +120,7 @@ ${Utils.special_visualizations_importRequirementDocs}
|
||||||
name: string
|
name: string
|
||||||
defaultValue?: string
|
defaultValue?: string
|
||||||
}[],
|
}[],
|
||||||
argsRaw: string[]
|
argsRaw: string[],
|
||||||
): string[] {
|
): string[] {
|
||||||
const deps = ImportFlowUtils.getLayerDependencies(argsRaw, argSpec)
|
const deps = ImportFlowUtils.getLayerDependencies(argsRaw, argSpec)
|
||||||
const argsParsed: PointImportFlowArguments = <any>Utils.ParseVisArgs(argSpec, argsRaw)
|
const argsParsed: PointImportFlowArguments = <any>Utils.ParseVisArgs(argSpec, argsRaw)
|
||||||
|
@ -130,30 +128,6 @@ ${Utils.special_visualizations_importRequirementDocs}
|
||||||
deps.push(...snapOntoLayers)
|
deps.push(...snapOntoLayers)
|
||||||
return deps
|
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> {
|
export default abstract class ImportFlow<ArgT extends ImportFlowArguments> {
|
||||||
public readonly state: SpecialVisualizationState
|
public readonly state: SpecialVisualizationState
|
||||||
public readonly args: ArgT
|
public readonly args: ArgT
|
||||||
public readonly targetLayer: FilteredLayer
|
public readonly targetLayer: FilteredLayer[]
|
||||||
public readonly tagsToApply: Store<Tag[]>
|
public readonly tagsToApply: Store<Tag[]>
|
||||||
protected readonly _originalFeatureTags: UIEventSource<Record<string, string>>
|
protected readonly _originalFeatureTags: UIEventSource<Record<string, string>>
|
||||||
|
|
||||||
|
@ -172,13 +146,19 @@ export default abstract class ImportFlow<ArgT extends ImportFlowArguments> {
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
args: ArgT,
|
args: ArgT,
|
||||||
tagsToApply: Store<Tag[]>,
|
tagsToApply: Store<Tag[]>,
|
||||||
originalTags: UIEventSource<Record<string, string>>
|
originalTags: UIEventSource<Record<string, string>>,
|
||||||
) {
|
) {
|
||||||
this.state = state
|
this.state = state
|
||||||
this.args = args
|
this.args = args
|
||||||
this.tagsToApply = tagsToApply
|
this.tagsToApply = tagsToApply
|
||||||
this._originalFeatureTags = originalTags
|
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
|
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 funcName: string
|
||||||
public readonly docs: string | BaseUIElement
|
public readonly docs: string | BaseUIElement
|
||||||
public readonly example?: string
|
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 = []
|
public needsUrls = []
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
const args = importFlow.args
|
const args = importFlow.args
|
||||||
|
|
||||||
// The following variables are used for the map
|
// 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 =
|
const snapToLayers: string[] | undefined =
|
||||||
args.snap_onto_layers?.split(",")?.map((l) => l.trim()) ?? []
|
args.snap_onto_layers?.split(",")?.map((l) => l.trim()) ?? []
|
||||||
const maxSnapDistance: number = Number(args.max_snap_distance ?? 25) ?? 25
|
const maxSnapDistance: number = Number(args.max_snap_distance ?? 25) ?? 25
|
||||||
|
@ -33,21 +33,20 @@
|
||||||
|
|
||||||
async function onConfirm(): Promise<void> {
|
async function onConfirm(): Promise<void> {
|
||||||
const importedId = await importFlow.onConfirm(value.data, snappedTo.data)
|
const importedId = await importFlow.onConfirm(value.data, snappedTo.data)
|
||||||
state.selectedLayer.setData(targetLayer)
|
|
||||||
state.selectedElement.setData(state.indexedFeatures.featuresById.data.get(importedId))
|
state.selectedElement.setData(state.indexedFeatures.featuresById.data.get(importedId))
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ImportFlow {importFlow} on:confirm={onConfirm}>
|
<ImportFlow {importFlow} on:confirm={onConfirm}>
|
||||||
<div class="relative" slot="map">
|
<div class="relative" slot="map">
|
||||||
<div class="h-32">
|
<div class="h-64">
|
||||||
<NewPointLocationInput
|
<NewPointLocationInput
|
||||||
coordinate={startCoordinate}
|
coordinate={startCoordinate}
|
||||||
{maxSnapDistance}
|
{maxSnapDistance}
|
||||||
{snapToLayers}
|
{snapToLayers}
|
||||||
{snappedTo}
|
{snappedTo}
|
||||||
{state}
|
{state}
|
||||||
{targetLayer}
|
targetLayer={targetLayers}
|
||||||
{value}
|
{value}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,11 +3,7 @@ import { FixedUiElement } from "./Base/FixedUiElement"
|
||||||
import BaseUIElement from "./BaseUIElement"
|
import BaseUIElement from "./BaseUIElement"
|
||||||
import Title from "./Base/Title"
|
import Title from "./Base/Title"
|
||||||
import Table from "./Base/Table"
|
import Table from "./Base/Table"
|
||||||
import {
|
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
|
||||||
RenderingSpecification,
|
|
||||||
SpecialVisualization,
|
|
||||||
SpecialVisualizationState,
|
|
||||||
} from "./SpecialVisualization"
|
|
||||||
import { HistogramViz } from "./Popup/HistogramViz"
|
import { HistogramViz } from "./Popup/HistogramViz"
|
||||||
import { MinimapViz } from "./Popup/MinimapViz"
|
import { MinimapViz } from "./Popup/MinimapViz"
|
||||||
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
||||||
|
@ -110,7 +106,7 @@ class NearbyImageVis implements SpecialVisualization {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const isOpen = args[0] === "open"
|
const isOpen = args[0] === "open"
|
||||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||||
|
@ -175,7 +171,7 @@ class StealViz implements SpecialVisualization {
|
||||||
selectedElement: otherFeature,
|
selectedElement: otherFeature,
|
||||||
state,
|
state,
|
||||||
layer,
|
layer,
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (elements.length === 1) {
|
if (elements.length === 1) {
|
||||||
|
@ -183,8 +179,8 @@ class StealViz implements SpecialVisualization {
|
||||||
}
|
}
|
||||||
return new Combine(elements).SetClass("flex flex-col")
|
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>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const labels = args[0]
|
const labels = args[0]
|
||||||
?.split(";")
|
?.split(";")
|
||||||
|
@ -277,17 +273,17 @@ export default class SpecialVisualizations {
|
||||||
* templ.args[0] = "{email}"
|
* templ.args[0] = "{email}"
|
||||||
*/
|
*/
|
||||||
public static constructSpecification(
|
public static constructSpecification(
|
||||||
template: string,
|
template: string | { special: Record<string, string | Record<string, string>> & { type: string } },
|
||||||
extraMappings: SpecialVisualization[] = []
|
extraMappings: SpecialVisualization[] = [],
|
||||||
): RenderingSpecification[] {
|
): RenderingSpecification[] {
|
||||||
if (template === "") {
|
if (template === "") {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (template["type"] !== undefined) {
|
if (typeof template !== "string") {
|
||||||
console.trace(
|
console.trace(
|
||||||
"Got a non-expanded template while constructing the specification, it still has a 'special-key':",
|
"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"
|
throw "Got a non-expanded template while constructing the specification"
|
||||||
}
|
}
|
||||||
|
@ -295,20 +291,20 @@ export default class SpecialVisualizations {
|
||||||
for (const knownSpecial of allKnownSpecials) {
|
for (const knownSpecial of allKnownSpecials) {
|
||||||
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
|
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
|
||||||
const matched = template.match(
|
const matched = template.match(
|
||||||
new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s")
|
new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s"),
|
||||||
)
|
)
|
||||||
if (matched != null) {
|
if (matched != null) {
|
||||||
// We found a special component that should be brought to live
|
// We found a special component that should be brought to live
|
||||||
const partBefore = SpecialVisualizations.constructSpecification(
|
const partBefore = SpecialVisualizations.constructSpecification(
|
||||||
matched[1],
|
matched[1],
|
||||||
extraMappings
|
extraMappings,
|
||||||
)
|
)
|
||||||
const argument =
|
const argument =
|
||||||
matched[2] /* .trim() // We don't trim, as spaces might be relevant, e.g. "what is ... of {title()}"*/
|
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 style = matched[3]?.substring(1) ?? ""
|
||||||
const partAfter = SpecialVisualizations.constructSpecification(
|
const partAfter = SpecialVisualizations.constructSpecification(
|
||||||
matched[4],
|
matched[4],
|
||||||
extraMappings
|
extraMappings,
|
||||||
)
|
)
|
||||||
const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "")
|
const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "")
|
||||||
if (argument.length > 0) {
|
if (argument.length > 0) {
|
||||||
|
@ -347,31 +343,31 @@ export default class SpecialVisualizations {
|
||||||
viz.docs,
|
viz.docs,
|
||||||
viz.args.length > 0
|
viz.args.length > 0
|
||||||
? new Table(
|
? new Table(
|
||||||
["name", "default", "description"],
|
["name", "default", "description"],
|
||||||
viz.args.map((arg) => {
|
viz.args.map((arg) => {
|
||||||
let defaultArg = arg.defaultValue ?? "_undefined_"
|
let defaultArg = arg.defaultValue ?? "_undefined_"
|
||||||
if (defaultArg == "") {
|
if (defaultArg == "") {
|
||||||
defaultArg = "_empty string_"
|
defaultArg = "_empty string_"
|
||||||
}
|
}
|
||||||
return [arg.name, defaultArg, arg.doc]
|
return [arg.name, defaultArg, arg.doc]
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
new Title("Example usage of " + viz.funcName, 4),
|
new Title("Example usage of " + viz.funcName, 4),
|
||||||
new FixedUiElement(
|
new FixedUiElement(
|
||||||
viz.example ??
|
viz.example ??
|
||||||
"`{" +
|
"`{" +
|
||||||
viz.funcName +
|
viz.funcName +
|
||||||
"(" +
|
"(" +
|
||||||
viz.args.map((arg) => arg.defaultValue).join(",") +
|
viz.args.map((arg) => arg.defaultValue).join(",") +
|
||||||
")}`"
|
")}`",
|
||||||
).SetClass("literal-code"),
|
).SetClass("literal-code"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HelpMessage() {
|
public static HelpMessage() {
|
||||||
const helpTexts = SpecialVisualizations.specialVisualizations.map((viz) =>
|
const helpTexts = SpecialVisualizations.specialVisualizations.map((viz) =>
|
||||||
SpecialVisualizations.DocumentationFor(viz)
|
SpecialVisualizations.DocumentationFor(viz),
|
||||||
)
|
)
|
||||||
|
|
||||||
return new Combine([
|
return new Combine([
|
||||||
|
@ -405,10 +401,10 @@ export default class SpecialVisualizations {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
" "
|
" ",
|
||||||
)
|
),
|
||||||
).SetClass("code"),
|
).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"),
|
]).SetClass("flex flex-col"),
|
||||||
...helpTexts,
|
...helpTexts,
|
||||||
]).SetClass("flex flex-col")
|
]).SetClass("flex flex-col")
|
||||||
|
@ -417,20 +413,20 @@ export default class SpecialVisualizations {
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
public static renderExampleOfSpecial(
|
public static renderExampleOfSpecial(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
s: SpecialVisualization
|
s: SpecialVisualization,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const examples =
|
const examples =
|
||||||
s.structuredExamples === undefined
|
s.structuredExamples === undefined
|
||||||
? []
|
? []
|
||||||
: s.structuredExamples().map((e) => {
|
: s.structuredExamples().map((e) => {
|
||||||
return s.constr(
|
return s.constr(
|
||||||
state,
|
state,
|
||||||
new UIEventSource<Record<string, string>>(e.feature.properties),
|
new UIEventSource<Record<string, string>>(e.feature.properties),
|
||||||
e.args,
|
e.args,
|
||||||
e.feature,
|
e.feature,
|
||||||
undefined
|
undefined,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return new Combine([new Title(s.funcName), s.docs, ...examples])
|
return new Combine([new Title(s.funcName), s.docs, ...examples])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,7 +466,7 @@ export default class SpecialVisualizations {
|
||||||
assignTo: state.userRelatedState.language,
|
assignTo: state.userRelatedState.language,
|
||||||
availableLanguages: state.layout.language,
|
availableLanguages: state.layout.language,
|
||||||
preferredLanguages: state.osmConnection.userDetails.map(
|
preferredLanguages: state.osmConnection.userDetails.map(
|
||||||
(ud) => ud.languages
|
(ud) => ud.languages,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -495,7 +491,7 @@ export default class SpecialVisualizations {
|
||||||
|
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource
|
tagSource
|
||||||
|
@ -505,7 +501,7 @@ export default class SpecialVisualizations {
|
||||||
return new SplitRoadWizard(<WayId>id, state)
|
return new SplitRoadWizard(<WayId>id, state)
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -519,7 +515,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
if (feature.geometry.type !== "Point") {
|
if (feature.geometry.type !== "Point") {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -542,7 +538,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
if (!layer.deletion) {
|
if (!layer.deletion) {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -570,7 +566,7 @@ export default class SpecialVisualizations {
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature
|
feature: Feature,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||||
return new SvelteUIElement(CreateNewNote, {
|
return new SvelteUIElement(CreateNewNote, {
|
||||||
|
@ -634,7 +630,7 @@ export default class SpecialVisualizations {
|
||||||
.map((tags) => tags[args[0]])
|
.map((tags) => tags[args[0]])
|
||||||
.map((wikidata) => {
|
.map((wikidata) => {
|
||||||
wikidata = Utils.NoEmpty(
|
wikidata = Utils.NoEmpty(
|
||||||
wikidata?.split(";")?.map((wd) => wd.trim()) ?? []
|
wikidata?.split(";")?.map((wd) => wd.trim()) ?? [],
|
||||||
)[0]
|
)[0]
|
||||||
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -644,9 +640,9 @@ export default class SpecialVisualizations {
|
||||||
}
|
}
|
||||||
const response = <WikidataResponse>e["success"]
|
const response = <WikidataResponse>e["success"]
|
||||||
return Translation.fromMap(response.labels)
|
return Translation.fromMap(response.labels)
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
new MapillaryLinkVis(),
|
new MapillaryLinkVis(),
|
||||||
|
@ -678,7 +674,7 @@ export default class SpecialVisualizations {
|
||||||
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
||||||
tags,
|
tags,
|
||||||
state,
|
state,
|
||||||
feature
|
feature,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -734,7 +730,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(StarsBarIcon, {
|
return new SvelteUIElement(StarsBarIcon, {
|
||||||
score: reviews.average,
|
score: reviews.average,
|
||||||
|
@ -767,7 +763,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
|
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
|
||||||
},
|
},
|
||||||
|
@ -799,7 +795,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
||||||
},
|
},
|
||||||
|
@ -857,7 +853,7 @@ export default class SpecialVisualizations {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): SvelteUIElement {
|
): SvelteUIElement {
|
||||||
const keyToUse = args[0]
|
const keyToUse = args[0]
|
||||||
const prefix = args[1]
|
const prefix = args[1]
|
||||||
|
@ -894,10 +890,10 @@ export default class SpecialVisualizations {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const allUnits: Unit[] = [].concat(
|
const allUnits: Unit[] = [].concat(
|
||||||
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? [])
|
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? []),
|
||||||
)
|
)
|
||||||
const unit = allUnits.filter((unit) =>
|
const unit = allUnits.filter((unit) =>
|
||||||
unit.isApplicableToKey(key)
|
unit.isApplicableToKey(key),
|
||||||
)[0]
|
)[0]
|
||||||
if (unit === undefined) {
|
if (unit === undefined) {
|
||||||
return value
|
return value
|
||||||
|
@ -905,7 +901,7 @@ export default class SpecialVisualizations {
|
||||||
const getCountry = () => tagSource.data._country
|
const getCountry = () => tagSource.data._country
|
||||||
const [v, denom] = unit.findDenomination(value, getCountry)
|
const [v, denom] = unit.findDenomination(value, getCountry)
|
||||||
return unit.asHumanLongValue(v, getCountry)
|
return unit.asHumanLongValue(v, getCountry)
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -922,7 +918,7 @@ export default class SpecialVisualizations {
|
||||||
new Combine([
|
new Combine([
|
||||||
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
||||||
t.downloadGeoJsonHelper.SetClass("subtle"),
|
t.downloadGeoJsonHelper.SetClass("subtle"),
|
||||||
]).SetClass("flex flex-col")
|
]).SetClass("flex flex-col"),
|
||||||
)
|
)
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
console.log("Exporting as Geojson")
|
console.log("Exporting as Geojson")
|
||||||
|
@ -935,7 +931,7 @@ export default class SpecialVisualizations {
|
||||||
title + "_mapcomplete_export.geojson",
|
title + "_mapcomplete_export.geojson",
|
||||||
{
|
{
|
||||||
mimetype: "application/vnd.geo+json",
|
mimetype: "application/vnd.geo+json",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.SetClass("w-full")
|
.SetClass("w-full")
|
||||||
|
@ -971,7 +967,7 @@ export default class SpecialVisualizations {
|
||||||
constr: (state) => {
|
constr: (state) => {
|
||||||
return new SubtleButton(
|
return new SubtleButton(
|
||||||
Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
|
Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
|
||||||
Translations.t.general.removeLocationHistory
|
Translations.t.general.removeLocationHistory,
|
||||||
).onClick(() => {
|
).onClick(() => {
|
||||||
state.historicalUserLocations.features.setData([])
|
state.historicalUserLocations.features.setData([])
|
||||||
state.selectedElement.setData(undefined)
|
state.selectedElement.setData(undefined)
|
||||||
|
@ -1009,10 +1005,10 @@ export default class SpecialVisualizations {
|
||||||
.filter((c) => c.text !== "")
|
.filter((c) => c.text !== "")
|
||||||
.map(
|
.map(
|
||||||
(c, i) =>
|
(c, i) =>
|
||||||
new NoteCommentElement(c, state, i, comments.length)
|
new NoteCommentElement(c, state, i, comments.length),
|
||||||
)
|
),
|
||||||
).SetClass("flex flex-col")
|
).SetClass("flex flex-col")
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1053,9 +1049,9 @@ export default class SpecialVisualizations {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return new SubstitutedTranslation(title, tagsSource, state).SetClass(
|
return new SubstitutedTranslation(title, tagsSource, state).SetClass(
|
||||||
"px-1"
|
"px-1",
|
||||||
)
|
)
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1071,8 +1067,8 @@ export default class SpecialVisualizations {
|
||||||
let challenge = Stores.FromPromise(
|
let challenge = Stores.FromPromise(
|
||||||
Utils.downloadJsonCached(
|
Utils.downloadJsonCached(
|
||||||
`${Maproulette.defaultEndpoint}/challenge/${parentId}`,
|
`${Maproulette.defaultEndpoint}/challenge/${parentId}`,
|
||||||
24 * 60 * 60 * 1000
|
24 * 60 * 60 * 1000,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1097,7 +1093,7 @@ export default class SpecialVisualizations {
|
||||||
} else {
|
} else {
|
||||||
return [title, new List(listItems)]
|
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.",
|
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" +
|
"\n" +
|
||||||
"```json\n" +
|
"```json\n" +
|
||||||
"{\n" +
|
"{\n" +
|
||||||
' "id": "mark_duplicate",\n' +
|
" \"id\": \"mark_duplicate\",\n" +
|
||||||
' "render": {\n' +
|
" \"render\": {\n" +
|
||||||
' "special": {\n' +
|
" \"special\": {\n" +
|
||||||
' "type": "maproulette_set_status",\n' +
|
" \"type\": \"maproulette_set_status\",\n" +
|
||||||
' "message": {\n' +
|
" \"message\": {\n" +
|
||||||
' "en": "Mark as not found or false positive"\n' +
|
" \"en\": \"Mark as not found or false positive\"\n" +
|
||||||
" },\n" +
|
" },\n" +
|
||||||
' "status": "2",\n' +
|
" \"status\": \"2\",\n" +
|
||||||
' "image": "close"\n' +
|
" \"image\": \"close\"\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
"}\n" +
|
"}\n" +
|
||||||
|
@ -1185,8 +1181,8 @@ export default class SpecialVisualizations {
|
||||||
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
|
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
|
||||||
return new StatisticsPanel(fsBboxed)
|
return new StatisticsPanel(fsBboxed)
|
||||||
},
|
},
|
||||||
[state.mapProperties.bounds]
|
[state.mapProperties.bounds],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1252,7 +1248,7 @@ export default class SpecialVisualizations {
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[]
|
args: string[],
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
let [text, href, classnames, download, ariaLabel] = args
|
let [text, href, classnames, download, ariaLabel] = args
|
||||||
if (download === "") {
|
if (download === "") {
|
||||||
|
@ -1269,15 +1265,14 @@ export default class SpecialVisualizations {
|
||||||
download: Utils.SubstituteKeys(download, tags),
|
download: Utils.SubstituteKeys(download, tags),
|
||||||
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
|
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
|
||||||
newTab,
|
newTab,
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
funcName: "multi",
|
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",
|
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:
|
example:
|
||||||
"```json\n" +
|
"```json\n" +
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
|
@ -1293,7 +1288,7 @@ export default class SpecialVisualizations {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
" "
|
" ",
|
||||||
) +
|
) +
|
||||||
"\n```",
|
"\n```",
|
||||||
args: [
|
args: [
|
||||||
|
@ -1313,18 +1308,28 @@ export default class SpecialVisualizations {
|
||||||
const translation = new Translation({ "*": tr })
|
const translation = new Translation({ "*": tr })
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
featureTags.map((tags) => {
|
featureTags.map((tags) => {
|
||||||
const properties: object[] = JSON.parse(tags[key])
|
try {
|
||||||
const elements = []
|
const data = tags[key]
|
||||||
for (const property of properties) {
|
const properties: object[] = typeof data === "string" ? JSON.parse(tags[key]) : data
|
||||||
const subsTr = new SubstitutedTranslation(
|
const elements = []
|
||||||
translation,
|
for (const property of properties) {
|
||||||
new UIEventSource<any>(property),
|
const subsTr = new SubstitutedTranslation(
|
||||||
state
|
translation,
|
||||||
)
|
new UIEventSource<any>(property),
|
||||||
elements.push(subsTr)
|
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>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource.map((tags) => {
|
tagSource.map((tags) => {
|
||||||
|
@ -1356,7 +1361,7 @@ export default class SpecialVisualizations {
|
||||||
console.error("Cannot create a translation for", v, "due to", e)
|
console.error("Cannot create a translation for", v, "due to", e)
|
||||||
return JSON.stringify(v)
|
return JSON.stringify(v)
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1376,7 +1381,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = argument[0]
|
const key = argument[0]
|
||||||
const validator = new FediverseValidator()
|
const validator = new FediverseValidator()
|
||||||
|
@ -1386,14 +1391,14 @@ export default class SpecialVisualizations {
|
||||||
.map((fediAccount) => {
|
.map((fediAccount) => {
|
||||||
fediAccount = validator.reformat(fediAccount)
|
fediAccount = validator.reformat(fediAccount)
|
||||||
const [_, username, host] = fediAccount.match(
|
const [_, username, host] = fediAccount.match(
|
||||||
FediverseValidator.usernameAtServer
|
FediverseValidator.usernameAtServer,
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(Link, {
|
return new SvelteUIElement(Link, {
|
||||||
text: fediAccount,
|
text: fediAccount,
|
||||||
url: "https://" + host + "/@" + username,
|
url: "https://" + host + "/@" + username,
|
||||||
newTab: true,
|
newTab: true,
|
||||||
})
|
})
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1413,7 +1418,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new FixedUiElement("{" + args[0] + "}")
|
return new FixedUiElement("{" + args[0] + "}")
|
||||||
},
|
},
|
||||||
|
@ -1434,7 +1439,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = argument[0] ?? "value"
|
const key = argument[0] ?? "value"
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1452,12 +1457,12 @@ export default class SpecialVisualizations {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return new FixedUiElement(
|
return new FixedUiElement(
|
||||||
"Could not parse this tag: " +
|
"Could not parse this tag: " +
|
||||||
JSON.stringify(value) +
|
JSON.stringify(value) +
|
||||||
" due to " +
|
" due to " +
|
||||||
e
|
e,
|
||||||
).SetClass("alert")
|
).SetClass("alert")
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1478,7 +1483,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const giggityUrl = argument[0]
|
const giggityUrl = argument[0]
|
||||||
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
|
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
|
||||||
|
@ -1494,12 +1499,12 @@ export default class SpecialVisualizations {
|
||||||
_: UIEventSource<Record<string, string>>,
|
_: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const tags = (<ThemeViewState>(
|
const tags = (<ThemeViewState>(
|
||||||
state
|
state
|
||||||
)).geolocation.currentUserLocation.features.map(
|
)).geolocation.currentUserLocation.features.map(
|
||||||
(features) => features[0]?.properties
|
(features) => features[0]?.properties,
|
||||||
)
|
)
|
||||||
return new Combine([
|
return new Combine([
|
||||||
new SvelteUIElement(OrientationDebugPanel, {}),
|
new SvelteUIElement(OrientationDebugPanel, {}),
|
||||||
|
@ -1521,7 +1526,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(MarkAsFavourite, {
|
return new SvelteUIElement(MarkAsFavourite, {
|
||||||
tags: tagSource,
|
tags: tagSource,
|
||||||
|
@ -1541,7 +1546,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(MarkAsFavouriteMini, {
|
return new SvelteUIElement(MarkAsFavouriteMini, {
|
||||||
tags: tagSource,
|
tags: tagSource,
|
||||||
|
@ -1561,7 +1566,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
||||||
},
|
},
|
||||||
|
@ -1576,7 +1581,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource
|
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` +
|
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${layout}lat=${lat}&lon=${lon}&z=15` +
|
||||||
`#${id}`
|
`#${id}`
|
||||||
return new Img(new Qr(url).toImageElement(75)).SetStyle(
|
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>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1631,41 +1636,55 @@ export default class SpecialVisualizations {
|
||||||
})
|
})
|
||||||
.mapD((value) => {
|
.mapD((value) => {
|
||||||
const dir = GeoOperations.bearingToHuman(
|
const dir = GeoOperations.bearingToHuman(
|
||||||
GeoOperations.parseBearing(value)
|
GeoOperations.parseBearing(value),
|
||||||
)
|
)
|
||||||
console.log("Human dir", dir)
|
console.log("Human dir", dir)
|
||||||
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
funcName: "compare_data",
|
funcName: "compare_data",
|
||||||
needsUrls: (args) => args[1].split(";"),
|
needsUrls: (args) => args[1].split(";"),
|
||||||
args:[
|
args: [
|
||||||
{
|
{
|
||||||
name: "url",
|
name: "url",
|
||||||
required: true,
|
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",
|
name: "host",
|
||||||
required:true,
|
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 '*'. "
|
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",
|
name: "postprocessing",
|
||||||
required: false,
|
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",
|
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 {
|
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, args: string[], feature: Feature, layer: LayerConfig): BaseUIElement {
|
||||||
const url = args[0]
|
const url = args[0]
|
||||||
const postprocessVelopark = args[2] === "velopark"
|
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))
|
specialVisualizations.push(new AutoApplyButton(specialVisualizations))
|
||||||
|
@ -1677,7 +1696,7 @@ export default class SpecialVisualizations {
|
||||||
throw (
|
throw (
|
||||||
"Invalid special visualisation found: funcName is undefined for " +
|
"Invalid special visualisation found: funcName is undefined for " +
|
||||||
invalid.map((sp) => sp.i).join(", ") +
|
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…
Add table
Add a link
Reference in a new issue