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,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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue