forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
f0823f4c4d
524 changed files with 18747 additions and 8546 deletions
|
|
@ -28,12 +28,14 @@
|
|||
import { onDestroy } from "svelte"
|
||||
import NextButton from "../../Base/NextButton.svelte"
|
||||
import BackButton from "../../Base/BackButton.svelte"
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||
import Svg from "../../../Svg"
|
||||
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
import Confirm from "../../../assets/svg/Confirm.svelte"
|
||||
import Close from "../../../assets/svg/Close.svelte"
|
||||
import Layers from "../../../assets/svg/Layers.svelte"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
|
||||
export let coordinate: { lon: number; lat: number }
|
||||
export let state: SpecialVisualizationState
|
||||
|
|
@ -41,8 +43,9 @@
|
|||
let selectedPreset: {
|
||||
preset: PresetConfig
|
||||
layer: LayerConfig
|
||||
icon: string
|
||||
tags: Record<string, string>
|
||||
icon: BaseUIElement
|
||||
tags: Record<string, string>,
|
||||
text: Translation
|
||||
} = undefined
|
||||
let checkedOfGlobalFilters: number = 0
|
||||
let confirmedCategory = false
|
||||
|
|
@ -142,7 +145,6 @@
|
|||
const feature = state.indexedFeatures.featuresById.data.get(newId)
|
||||
console.log("Selecting feature", feature, "and opening their popup")
|
||||
abort()
|
||||
state.selectedLayer.setData(selectedPreset.layer)
|
||||
state.selectedElement.setData(feature)
|
||||
tagsStore.ping()
|
||||
}
|
||||
|
|
@ -198,7 +200,7 @@
|
|||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
>
|
||||
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
||||
<Layers class="w-12"/>
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
</button>
|
||||
|
||||
|
|
@ -239,7 +241,7 @@
|
|||
state.guistate.openFilterView(selectedPreset.layer)
|
||||
}}
|
||||
>
|
||||
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
|
||||
<Layers class="w-12"/>
|
||||
<Tr t={Translations.t.general.add.openLayerControl} />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -251,8 +253,6 @@
|
|||
/>
|
||||
</h2>
|
||||
|
||||
<Tr t={Translations.t.general.add.confirmIntro} />
|
||||
|
||||
{#if selectedPreset.preset.description}
|
||||
<Tr t={selectedPreset.preset.description} />
|
||||
{/if}
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
|
||||
<NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full">
|
||||
<div slot="image" class="relative">
|
||||
<ToSvelte construct={selectedPreset.icon} />
|
||||
<ToSvelte construct={selectedPreset.icon}/>
|
||||
<Confirm class="absolute bottom-0 right-0 h-4 w-4" />
|
||||
</div>
|
||||
<div class="w-full">
|
||||
|
|
@ -303,7 +303,7 @@
|
|||
<Tr
|
||||
slot="message"
|
||||
t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({
|
||||
preset: selectedPreset.preset,
|
||||
preset: selectedPreset.text
|
||||
})}
|
||||
/>
|
||||
</SubtleButton>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@
|
|||
import { ImmutableStore } from "../../../Logic/UIEventSource"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import FromHtml from "../../Base/FromHtml.svelte"
|
||||
import NextButton from "../../Base/NextButton.svelte"
|
||||
import { UIElement } from "../../UIElement"
|
||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
import Combine from "../../Base/Combine"
|
||||
|
||||
/**
|
||||
* This component lists all the presets and allows the user to select one
|
||||
|
|
@ -24,6 +23,10 @@
|
|||
preset: PresetConfig
|
||||
layer: LayerConfig
|
||||
text: Translation
|
||||
/**
|
||||
* Same as `this.preset.description.firstSentence()`
|
||||
*/
|
||||
description: Translation,
|
||||
icon: BaseUIElement
|
||||
tags: Record<string, string>
|
||||
}[] = []
|
||||
|
|
@ -37,7 +40,7 @@
|
|||
"Not showing presets for layer",
|
||||
flayer.layerDef.id,
|
||||
"as not displayed and featureSwitchFilter.data is set",
|
||||
state.featureSwitches.featureSwitchFilter.data
|
||||
state.featureSwitches.featureSwitchFilter.data,
|
||||
)
|
||||
// ...and we cannot enable the layer control -> we skip, as these presets can never be shown anyway
|
||||
continue
|
||||
|
|
@ -52,9 +55,9 @@
|
|||
for (const preset of layer.presets) {
|
||||
const tags = TagUtils.KVtoProperties(preset.tags ?? [])
|
||||
|
||||
const icon: BaseUIElement = layer.mapRendering[0]
|
||||
.RenderIcon(new ImmutableStore<any>(tags))
|
||||
.html.SetClass("w-12 h-12 block relative mr-4")
|
||||
const markers = layer.mapRendering.map((mr, i) => mr.RenderIcon(new ImmutableStore<any>(tags), {noSize: i == 0})
|
||||
.html.SetClass(i == 0 ? "w-full h-full" : ""))
|
||||
const icon: BaseUIElement = new Combine(markers.map(m => new Combine([m]).SetClass("absolute top-0 left-0 w-full h-full flex justify-around items-center"))).SetClass("w-12 h-12 block relative mr-4")
|
||||
|
||||
const description = preset.description?.FirstSentence()
|
||||
|
||||
|
|
@ -66,7 +69,7 @@
|
|||
tags,
|
||||
text: Translations.t.general.add.addNew.Subs(
|
||||
{ category: preset.title },
|
||||
preset.title["context"]
|
||||
preset.title["context"],
|
||||
),
|
||||
}
|
||||
presets.push(simplified)
|
||||
|
|
@ -74,7 +77,13 @@
|
|||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
select: { preset: PresetConfig; layer: LayerConfig; icon: string; tags: Record<string, string> }
|
||||
select: {
|
||||
preset: PresetConfig;
|
||||
layer: LayerConfig;
|
||||
icon: BaseUIElement;
|
||||
tags: Record<string, string>,
|
||||
text: Translation
|
||||
}
|
||||
}>()
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import all_languages from "../../assets/language_translations.json"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
|
||||
export class AllLanguagesSelector extends SearchablePillsSelector<string> {
|
||||
constructor(options?: {
|
||||
mode?: "select-many" | "select-one"
|
||||
currentCountry?: Store<string>
|
||||
supportedLanguages?: Record<string, string> & { _meta?: { countries?: string[] } }
|
||||
}) {
|
||||
const possibleValues: {
|
||||
show: BaseUIElement
|
||||
value: string
|
||||
mainTerm: Record<string, string>
|
||||
searchTerms?: Record<string, string[]>
|
||||
hasPriority?: Store<boolean>
|
||||
}[] = []
|
||||
|
||||
const langs = options?.supportedLanguages ?? all_languages
|
||||
for (const ln in langs) {
|
||||
let languageInfo: Record<string, string> & { _meta?: { countries: string[] } } =
|
||||
all_languages[ln]
|
||||
const countries = languageInfo._meta?.countries?.map((c) => c.toLowerCase())
|
||||
languageInfo = { ...languageInfo }
|
||||
delete languageInfo._meta
|
||||
const term = {
|
||||
show: new Translation(languageInfo),
|
||||
value: ln,
|
||||
mainTerm: languageInfo,
|
||||
searchTerms: { "*": [ln] },
|
||||
hasPriority:
|
||||
countries === undefined
|
||||
? undefined
|
||||
: options?.currentCountry?.map(
|
||||
(country) => countries?.indexOf(country.toLowerCase()) >= 0
|
||||
),
|
||||
}
|
||||
possibleValues.push(term)
|
||||
}
|
||||
super(possibleValues, {
|
||||
mode: options?.mode ?? "select-many",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +1,100 @@
|
|||
<script lang="ts">
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Table from "../Base/Table"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import SimpleMetaTaggers from "../../Logic/SimpleMetaTagger"
|
||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import { onDestroy } from "svelte"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import Lazy from "../Base/Lazy"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
|
||||
//Svelte props
|
||||
export let tags: UIEventSource<any>
|
||||
export let state: { layoutToUse: LayoutConfig } = undefined
|
||||
export let tags: UIEventSource<Record<string, any>>
|
||||
export let tagKeys = tags.map(tgs => Object.keys(tgs))
|
||||
|
||||
const calculatedTags = [].concat(
|
||||
...(state?.layoutToUse?.layers?.map((l) => l.calculatedTags?.map((c) => c[0]) ?? []) ?? [])
|
||||
)
|
||||
export let layer: LayerConfig | undefined = undefined
|
||||
|
||||
const allTags = tags.mapD((tags) => {
|
||||
const parts: (string | BaseUIElement)[][] = []
|
||||
for (const key in tags) {
|
||||
let v = tags[key]
|
||||
if (v === "") {
|
||||
v = "<b>empty string</b>"
|
||||
}
|
||||
parts.push([key, v ?? "<b>undefined</b>"])
|
||||
/**
|
||||
* The names (keys) of the calculated tags. Each will normally start with an underscore (but in rare cases not)
|
||||
*/
|
||||
let calculatedTags: string[] = []
|
||||
for (const calculated of layer?.calculatedTags ?? []) {
|
||||
if(calculated){
|
||||
continue
|
||||
}
|
||||
const name = calculated[0]
|
||||
calculatedTags.push(name)
|
||||
}
|
||||
let knownValues: Store<string[]> = tags.map(tags => Object.keys(tags))
|
||||
|
||||
for (const key of calculatedTags) {
|
||||
const value = tags[key]
|
||||
if (value === undefined) {
|
||||
continue
|
||||
}
|
||||
let type = ""
|
||||
if (typeof value !== "string") {
|
||||
type = " <i>" + typeof value + "</i>"
|
||||
}
|
||||
parts.push(["<i>" + key + "</i>", value])
|
||||
}
|
||||
const metaKeys: string[] = [].concat(...SimpleMetaTaggers.metatags.map(k => k.keys))
|
||||
let allCalculatedTags = new Set<string>([...calculatedTags, ...metaKeys])
|
||||
|
||||
for (const metatag of SimpleMetaTaggers.metatags.filter((mt) => mt.isLazy)) {
|
||||
const title = "<i>" + metatag.keys.join(";") + "</i> (lazy)"
|
||||
const toggleState = new UIEventSource(false)
|
||||
const toggle: BaseUIElement = new Toggle(
|
||||
new Lazy(() => new FixedUiElement(metatag.keys.map((key) => tags[key]).join(";"))),
|
||||
new FixedUiElement("Evaluate").onClick(() => toggleState.setData(true)),
|
||||
toggleState
|
||||
)
|
||||
parts.push([title, toggle])
|
||||
}
|
||||
|
||||
return parts
|
||||
})
|
||||
|
||||
const tagsTable = new VariableUiElement(
|
||||
allTags.mapD((_allTags) =>
|
||||
new Table(["Key", "Value"], _allTags).SetClass("zebra-table break-all")
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<ToSvelte construct={tagsTable} />
|
||||
<table class="zebra-table break-all">
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Normal tags</th>
|
||||
</tr>
|
||||
{#each $tagKeys as key}
|
||||
{#if !allCalculatedTags.has(key)}
|
||||
<tr>
|
||||
<td>{key}</td>
|
||||
<td>
|
||||
{#if $tags[key] === undefined}
|
||||
<i>undefined</i>
|
||||
{:else if $tags[key] === ""}
|
||||
<i>Empty string</i>
|
||||
{:else}
|
||||
{$tags[key]}
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
{/each}
|
||||
<tr>
|
||||
<th colspan="2">Calculated tags</th>
|
||||
</tr>
|
||||
{#if calculatedTags.length === 0}
|
||||
<tr>
|
||||
<td colspan="2"><i>This layer does not use calculated tags</i></td>
|
||||
</tr>
|
||||
{/if}
|
||||
{#each calculatedTags as key}
|
||||
<tr>
|
||||
<td>{key}</td>
|
||||
<td>
|
||||
{#if $tags[key] === undefined}
|
||||
<i>undefined</i>
|
||||
{:else if $tags[key] === ""}
|
||||
<i>Empty string</i>
|
||||
{:else if $tags[key] !== "string"}
|
||||
<span class="literal-code">{$tags[key]}</span>
|
||||
<i>{typeof $tags[key]}</i>
|
||||
{:else}
|
||||
{$tags[key]}
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<tr>
|
||||
<th colspan="2">Metatags tags</th>
|
||||
</tr>
|
||||
{#each metaKeys as key}
|
||||
<tr>
|
||||
<td>{key}</td>
|
||||
<td>
|
||||
{#if $knownValues.indexOf(key) < 0 }
|
||||
<button class="small" on:click={_ => {console.log($tags[key])}}>Evaluate</button>
|
||||
{:else if !$tags[key] === undefined}
|
||||
<i>Undefined</i>
|
||||
{:else if $tags[key] === ""}
|
||||
<i>Empty string</i>
|
||||
{:else}
|
||||
{$tags[key]}
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@
|
|||
import type { Feature } from "geojson"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import type { UploadableTag } from "../../../Logic/Tags/TagUtils"
|
||||
import OsmChangeAction from "../../../Logic/Osm/Actions/OsmChangeAction"
|
||||
import DeleteAction from "../../../Logic/Osm/Actions/DeleteAction"
|
||||
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
|
||||
|
|
@ -43,7 +42,7 @@
|
|||
|
||||
const t = Translations.t.delete
|
||||
|
||||
let selectedTags: TagsFilter
|
||||
let selectedTags: UploadableTag
|
||||
let changedProperties = undefined
|
||||
$: changedProperties = TagUtils.changeAsProperties(selectedTags?.asChange(tags?.data ?? {}) ?? [])
|
||||
let isHardDelete = undefined
|
||||
|
|
@ -66,7 +65,7 @@
|
|||
theme: state?.layout?.id ?? "unknown",
|
||||
specialMotivation: deleteReason,
|
||||
},
|
||||
canBeDeleted.data
|
||||
canBeDeleted.data,
|
||||
)
|
||||
} else {
|
||||
// no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import Histogram from "../BigComponents/Histogram"
|
||||
import { Feature } from "geojson"
|
||||
import Constants from "../../Models/Constants"
|
||||
|
||||
export class HistogramViz implements SpecialVisualization {
|
||||
funcName = "histogram"
|
||||
|
|
|
|||
|
|
@ -86,8 +86,7 @@ export default class ConflateImportButtonViz implements SpecialVisualization, Au
|
|||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
feature: Feature
|
||||
): BaseUIElement {
|
||||
const canBeImported =
|
||||
feature.geometry.type === "LineString" ||
|
||||
|
|
|
|||
|
|
@ -1,42 +1,58 @@
|
|||
<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 Confirm from "../../../assets/svg/Confirm.svelte"
|
||||
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"
|
||||
import Confirm from "../../../assets/svg/Confirm.svelte"
|
||||
|
||||
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}>
|
||||
|
|
@ -45,13 +61,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>
|
||||
|
|
@ -61,7 +77,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")} />
|
||||
|
|
@ -71,19 +87,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" />
|
||||
|
|
@ -94,7 +110,7 @@
|
|||
class="primary flex w-full gap-x-1"
|
||||
on:click={() => {
|
||||
abort()
|
||||
importFlow.targetLayer.disableAllFilters()
|
||||
filteredLayer.disableAllFilters()
|
||||
}}
|
||||
>
|
||||
<EyeOffIcon class="w-12" />
|
||||
|
|
@ -104,7 +120,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() {
|
||||
|
|
@ -51,8 +51,7 @@ export class PointImportButtonViz implements SpecialVisualization {
|
|||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
feature: Feature
|
||||
): BaseUIElement {
|
||||
if (feature.geometry.type !== "Point") {
|
||||
return Translations.t.general.add.import.wrongType.SetClass("alert")
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,254 +0,0 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import all_languages from "../../assets/language_translations.json"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import Combine from "../Base/Combine"
|
||||
import Title from "../Base/Title"
|
||||
import Lazy from "../Base/Lazy"
|
||||
import { SubstitutedTranslation } from "../SubstitutedTranslation"
|
||||
import List from "../Base/List"
|
||||
import { AllLanguagesSelector } from "./AllLanguagesSelector"
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { And } from "../../Logic/Tags/And"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import { EditButton, SaveButton } from "./SaveButton"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import { Feature } from "geojson"
|
||||
|
||||
export class LanguageElement implements SpecialVisualization {
|
||||
funcName: string = "language_chooser"
|
||||
needsUrls = []
|
||||
|
||||
docs: string | BaseUIElement =
|
||||
"The language element allows to show and pick all known (modern) languages. The key can be set"
|
||||
|
||||
args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
|
||||
{
|
||||
name: "key",
|
||||
required: true,
|
||||
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked ",
|
||||
},
|
||||
{
|
||||
name: "question",
|
||||
required: true,
|
||||
doc: "What to ask if no questions are known",
|
||||
},
|
||||
{
|
||||
name: "render_list_item",
|
||||
doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).",
|
||||
defaultValue: "{language()}",
|
||||
},
|
||||
{
|
||||
name: "render_single_language",
|
||||
doc: "What will be shown if the feature only supports a single language",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "render_all",
|
||||
doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
|
||||
defaultValue: "{list()}",
|
||||
},
|
||||
{
|
||||
name: "no_known_languages",
|
||||
doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead",
|
||||
},
|
||||
{
|
||||
name: "mode",
|
||||
doc: "If one or many languages can be selected. Should be 'multi' or 'single'",
|
||||
defaultValue: "multi",
|
||||
},
|
||||
]
|
||||
|
||||
example: `
|
||||
\`\`\`json
|
||||
{"special":
|
||||
"type": "language_chooser",
|
||||
"key": "school:language",
|
||||
"question": {"en": "What are the main (and administrative) languages spoken in this school?"},
|
||||
"render_single_language": {"en": "{language()} is spoken on this school"},
|
||||
"render_list_item": {"en": "{language()}"},
|
||||
"render_all": {"en": "The following languages are spoken here:{list()}"}
|
||||
"mode":"multi"
|
||||
}
|
||||
\`\`\`
|
||||
`
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
): BaseUIElement {
|
||||
let [key, question, item_render, single_render, all_render, on_no_known_languages, mode] =
|
||||
argument
|
||||
if (mode === undefined || mode.length == 0) {
|
||||
mode = "multi"
|
||||
}
|
||||
if (item_render === undefined || item_render.trim() === "") {
|
||||
item_render = "{language()}"
|
||||
}
|
||||
if (all_render === undefined || all_render.length == 0) {
|
||||
all_render = "{list()}"
|
||||
}
|
||||
if (mode !== "single" && mode !== "multi") {
|
||||
throw (
|
||||
"Error while calling language_chooser: mode must be either 'single' or 'multi' but it is " +
|
||||
mode
|
||||
)
|
||||
}
|
||||
if (single_render.indexOf("{language()") < 0) {
|
||||
throw (
|
||||
"Error while calling language_chooser: render_single_language must contain '{language()}' but it is " +
|
||||
single_render
|
||||
)
|
||||
}
|
||||
if (item_render.indexOf("{language()") < 0) {
|
||||
throw (
|
||||
"Error while calling language_chooser: render_list_item must contain '{language()}' but it is " +
|
||||
item_render
|
||||
)
|
||||
}
|
||||
if (all_render.indexOf("{list()") < 0) {
|
||||
throw "Error while calling language_chooser: render_all must contain '{list()}'"
|
||||
}
|
||||
|
||||
const prefix = key + ":"
|
||||
const foundLanguages = tagSource.map((tags) => {
|
||||
const foundLanguages: string[] = []
|
||||
for (const k in tags) {
|
||||
const v = tags[k]
|
||||
if (v !== "yes") {
|
||||
continue
|
||||
}
|
||||
if (k.startsWith(prefix)) {
|
||||
foundLanguages.push(k.substring(prefix.length))
|
||||
}
|
||||
}
|
||||
return foundLanguages
|
||||
})
|
||||
const forceInputMode = new UIEventSource(false)
|
||||
const inputEl = new Lazy(() => {
|
||||
const selector = new AllLanguagesSelector({
|
||||
mode: mode === "single" ? "select-one" : "select-many",
|
||||
currentCountry: tagSource.map((tgs) => tgs["_country"]),
|
||||
})
|
||||
const cancelButton = Toggle.If(forceInputMode, () =>
|
||||
Translations.t.general.cancel
|
||||
.Clone()
|
||||
.SetClass("btn btn-secondary")
|
||||
.onClick(() => forceInputMode.setData(false))
|
||||
)
|
||||
|
||||
const saveButton = new SaveButton(
|
||||
selector.GetValue().map((lngs) => (lngs.length > 0 ? "true" : undefined)),
|
||||
state
|
||||
).onClick(() => {
|
||||
const selectedLanguages = selector.GetValue().data
|
||||
const currentLanguages = foundLanguages.data
|
||||
const selection: Tag[] = selectedLanguages.map((ln) => new Tag(prefix + ln, "yes"))
|
||||
|
||||
for (const currentLanguage of currentLanguages) {
|
||||
if (selectedLanguages.indexOf(currentLanguage) >= 0) {
|
||||
continue
|
||||
}
|
||||
// Erase language that is not spoken anymore
|
||||
selection.push(new Tag(prefix + currentLanguage, ""))
|
||||
}
|
||||
|
||||
if (state.featureSwitchIsTesting.data) {
|
||||
for (const tag of selection) {
|
||||
tagSource.data[tag.key] = tag.value
|
||||
}
|
||||
tagSource.ping()
|
||||
} else {
|
||||
;(state?.changes)
|
||||
.applyAction(
|
||||
new ChangeTagAction(
|
||||
tagSource.data.id,
|
||||
new And(selection),
|
||||
tagSource.data,
|
||||
{
|
||||
theme: state?.layout?.id ?? "unkown",
|
||||
changeType: "answer",
|
||||
}
|
||||
)
|
||||
)
|
||||
.then((_) => {
|
||||
console.log("Tagchanges applied")
|
||||
})
|
||||
}
|
||||
forceInputMode.setData(false)
|
||||
})
|
||||
|
||||
return new Combine([
|
||||
new Title(question),
|
||||
selector,
|
||||
new Combine([cancelButton, saveButton]).SetClass("flex justify-end"),
|
||||
]).SetClass("flex flex-col question disable-links")
|
||||
})
|
||||
|
||||
const editButton = new EditButton(state.osmConnection, () => forceInputMode.setData(true))
|
||||
|
||||
return new VariableUiElement(
|
||||
foundLanguages.map(
|
||||
(foundLanguages) => {
|
||||
if (forceInputMode.data) {
|
||||
return inputEl
|
||||
}
|
||||
|
||||
if (foundLanguages.length === 0) {
|
||||
// No languages found - we show the question and the input element
|
||||
if (
|
||||
on_no_known_languages !== undefined &&
|
||||
on_no_known_languages.length > 0
|
||||
) {
|
||||
return new Combine([on_no_known_languages, editButton]).SetClass(
|
||||
"flex justify-end"
|
||||
)
|
||||
}
|
||||
return inputEl
|
||||
}
|
||||
|
||||
let rendered: BaseUIElement
|
||||
if (foundLanguages.length === 1) {
|
||||
const ln = foundLanguages[0]
|
||||
let mapping = new Map<string, BaseUIElement>()
|
||||
mapping.set("language", new Translation(all_languages[ln]))
|
||||
rendered = new SubstitutedTranslation(
|
||||
new Translation({ "*": single_render }, undefined),
|
||||
tagSource,
|
||||
state,
|
||||
mapping
|
||||
)
|
||||
} else {
|
||||
let mapping = new Map<string, BaseUIElement>()
|
||||
const languagesList = new List(
|
||||
foundLanguages.map((ln) => {
|
||||
let mappingLn = new Map<string, BaseUIElement>()
|
||||
mappingLn.set("language", new Translation(all_languages[ln]))
|
||||
return new SubstitutedTranslation(
|
||||
new Translation({ "*": item_render }, undefined),
|
||||
tagSource,
|
||||
state,
|
||||
mappingLn
|
||||
)
|
||||
})
|
||||
)
|
||||
mapping.set("list", languagesList)
|
||||
rendered = new SubstitutedTranslation(
|
||||
new Translation({ "*": all_render }, undefined),
|
||||
tagSource,
|
||||
state,
|
||||
mapping
|
||||
)
|
||||
}
|
||||
return new Combine([rendered, editButton]).SetClass("flex justify-between")
|
||||
},
|
||||
[forceInputMode]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
46
src/UI/Popup/LanguageElement/LanguageAnswer.svelte
Normal file
46
src/UI/Popup/LanguageElement/LanguageAnswer.svelte
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<script lang="ts">
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import SpecialTranslation from "../TagRendering/SpecialTranslation.svelte"
|
||||
import { Translation, TypedTranslation } from "../../i18n/Translation"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import type { Feature } from "geojson"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import * as all_languages from "../../../assets/language_translations.json"
|
||||
|
||||
/**
|
||||
* Visualizes a list of the known languages
|
||||
*/
|
||||
export let languages: Store<string[]>
|
||||
export let single_render: string
|
||||
export let item_render: string
|
||||
export let render_all: string // Should contain one `{list()}`
|
||||
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let feature: Feature
|
||||
export let layer: LayerConfig | undefined
|
||||
|
||||
let [beforeListing, afterListing] = (render_all ?? "{list()}").split("{list()}")
|
||||
</script>
|
||||
|
||||
{#if $languages.length === 1}
|
||||
<SpecialTranslation {state} {tags} {feature} {layer}
|
||||
t={new TypedTranslation({"*": single_render}).PartialSubsTr(
|
||||
"language()",
|
||||
new Translation(all_languages[$languages[0]], undefined)
|
||||
)}/>
|
||||
{:else}
|
||||
{beforeListing}
|
||||
<ul>
|
||||
{#each $languages as language}
|
||||
<li>
|
||||
<SpecialTranslation {state} {tags} {feature} {layer} t={
|
||||
new TypedTranslation({"*": item_render}).PartialSubsTr("language()",
|
||||
new Translation(all_languages[language], undefined) )}
|
||||
/>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{afterListing}
|
||||
{/if}
|
||||
69
src/UI/Popup/LanguageElement/LanguageElement.svelte
Normal file
69
src/UI/Popup/LanguageElement/LanguageElement.svelte
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import LanguageQuestion from "./LanguageQuestion.svelte"
|
||||
import LanguageAnswer from "./LanguageAnswer.svelte"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import type { Feature } from "geojson"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import EditButton from "../TagRendering/EditButton.svelte"
|
||||
|
||||
export let key: string
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let feature: Feature
|
||||
export let layer: LayerConfig | undefined
|
||||
|
||||
export let question: string
|
||||
export let on_no_known_languages: string = undefined
|
||||
export let single_render: string
|
||||
export let item_render: string
|
||||
export let render_all: string // Should contain one `{list()}`
|
||||
let prefix = key + ":"
|
||||
let foundLanguages = tags.map((tags) => {
|
||||
const foundLanguages: string[] = []
|
||||
for (const k in tags) {
|
||||
const v = tags[k]
|
||||
if (v !== "yes") {
|
||||
continue
|
||||
}
|
||||
if (k.startsWith(prefix)) {
|
||||
foundLanguages.push(k.substring(prefix.length))
|
||||
}
|
||||
}
|
||||
return foundLanguages
|
||||
})
|
||||
|
||||
const forceInputMode = new UIEventSource(false)
|
||||
|
||||
</script>
|
||||
|
||||
{#if $foundLanguages.length === 0 && on_no_known_languages && !$forceInputMode}
|
||||
<div class="p-1 flex items-center justify-between low-interaction rounded">
|
||||
<div>
|
||||
{on_no_known_languages}
|
||||
</div>
|
||||
<EditButton on:click={_ => forceInputMode.setData(true)} />
|
||||
</div>
|
||||
{:else if $forceInputMode || $foundLanguages.length === 0}
|
||||
<LanguageQuestion {question} {foundLanguages} {prefix} {state} {tags} {feature} {layer}
|
||||
on:save={_ => forceInputMode.setData(false)}>
|
||||
<span slot="cancel-button">
|
||||
{#if $forceInputMode}
|
||||
<button on:click={_ => forceInputMode.setData(false)}>
|
||||
<Tr t={Translations.t.general.cancel} />
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
</LanguageQuestion>
|
||||
{:else}
|
||||
<div class="p-2 flex items-center justify-between low-interaction rounded">
|
||||
<div>
|
||||
<LanguageAnswer {single_render} {item_render} {render_all} languages={foundLanguages} {state} {tags} { feature}
|
||||
{layer} />
|
||||
</div>
|
||||
<EditButton on:click={_ => forceInputMode.setData(true)} />
|
||||
</div>
|
||||
{/if}
|
||||
107
src/UI/Popup/LanguageElement/LanguageElement.ts
Normal file
107
src/UI/Popup/LanguageElement/LanguageElement.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
||||
import { Feature } from "geojson"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import { default as LanguageElementSvelte } from "./LanguageElement.svelte"
|
||||
|
||||
export class LanguageElement implements SpecialVisualization {
|
||||
funcName: string = "language_chooser"
|
||||
needsUrls = []
|
||||
|
||||
docs: string | BaseUIElement =
|
||||
"The language element allows to show and pick all known (modern) languages. The key can be set"
|
||||
|
||||
args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
|
||||
{
|
||||
name: "key",
|
||||
required: true,
|
||||
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked ",
|
||||
},
|
||||
{
|
||||
name: "question",
|
||||
required: true,
|
||||
doc: "What to ask if no questions are known",
|
||||
},
|
||||
{
|
||||
name: "render_list_item",
|
||||
doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).",
|
||||
defaultValue: "{language()}",
|
||||
},
|
||||
{
|
||||
name: "render_single_language",
|
||||
doc: "What will be shown if the feature only supports a single language",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "render_all",
|
||||
doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
|
||||
defaultValue: "{list()}",
|
||||
},
|
||||
{
|
||||
name: "no_known_languages",
|
||||
doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead",
|
||||
},
|
||||
]
|
||||
|
||||
example: `
|
||||
\`\`\`json
|
||||
{"special":
|
||||
"type": "language_chooser",
|
||||
"key": "school:language",
|
||||
"question": {"en": "What are the main (and administrative) languages spoken in this school?"},
|
||||
"render_single_language": {"en": "{language()} is spoken on this school"},
|
||||
"render_list_item": {"en": "{language()}"},
|
||||
"render_all": {"en": "The following languages are spoken here:{list()}"}
|
||||
}
|
||||
\`\`\`
|
||||
`
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
let [key, question, item_render, single_render, all_render, on_no_known_languages] =
|
||||
argument
|
||||
if (item_render === undefined || item_render.trim() === "") {
|
||||
item_render = "{language()}"
|
||||
}
|
||||
if (all_render === undefined || all_render.length == 0) {
|
||||
all_render = "{list()}"
|
||||
}
|
||||
if (single_render.indexOf("{language()") < 0) {
|
||||
throw (
|
||||
"Error while calling language_chooser: render_single_language must contain '{language()}' but it is " +
|
||||
single_render
|
||||
)
|
||||
}
|
||||
if (item_render.indexOf("{language()") < 0) {
|
||||
throw (
|
||||
"Error while calling language_chooser: render_list_item must contain '{language()}' but it is " +
|
||||
item_render
|
||||
)
|
||||
}
|
||||
if (all_render.indexOf("{list()") < 0) {
|
||||
throw "Error while calling language_chooser: render_all must contain '{list()}'"
|
||||
}
|
||||
if (on_no_known_languages === "") {
|
||||
on_no_known_languages = undefined
|
||||
}
|
||||
|
||||
return new SvelteUIElement(LanguageElementSvelte, {
|
||||
key,
|
||||
tags: tagSource,
|
||||
state,
|
||||
feature,
|
||||
layer,
|
||||
question,
|
||||
on_no_known_languages,
|
||||
single_render,
|
||||
item_render,
|
||||
})
|
||||
}
|
||||
}
|
||||
133
src/UI/Popup/LanguageElement/LanguageOptions.svelte
Normal file
133
src/UI/Popup/LanguageElement/LanguageOptions.svelte
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<script lang="ts">/**
|
||||
* An input element which allows to select one or more langauges
|
||||
*/
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import all_languages from "../../../assets/language_translations.json"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import Translations from "../../i18n/Translations.js"
|
||||
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Locale from "../../i18n/Locale"
|
||||
|
||||
/**
|
||||
* Will contain one or more ISO-language codes
|
||||
*/
|
||||
export let selectedLanguages: UIEventSource<string[]>
|
||||
/**
|
||||
* The country (countries) that the point lies in.
|
||||
* Note that a single place might be claimed by multiple countries
|
||||
*/
|
||||
export let countries: Set<string>
|
||||
let searchValue: UIEventSource<string> = new UIEventSource<string>("")
|
||||
let searchLC = searchValue.mapD(search => search.toLowerCase())
|
||||
const knownLanguagecodes = Object.keys(all_languages)
|
||||
let probableLanguages = []
|
||||
let isChecked = {}
|
||||
for (const lng of knownLanguagecodes) {
|
||||
const lngInfo = all_languages[lng]
|
||||
if (lngInfo._meta?.countries?.some(l => countries.has(l))) {
|
||||
probableLanguages.push(lng)
|
||||
}
|
||||
isChecked[lng] = false
|
||||
}
|
||||
let newlyChecked: UIEventSource<string[]> = new UIEventSource<string[]>([])
|
||||
|
||||
function update(isChecked: Record<string, boolean>) {
|
||||
const currentlyChecked = new Set<string>(selectedLanguages.data)
|
||||
const languages: string[] = []
|
||||
for (const lng in isChecked) {
|
||||
if (isChecked[lng]) {
|
||||
languages.push(lng)
|
||||
if (!currentlyChecked.has(lng)) {
|
||||
newlyChecked.data.push(lng)
|
||||
newlyChecked.ping()
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedLanguages.setData(languages)
|
||||
}
|
||||
function matchesSearch(lng: string, searchLc: string | undefined): boolean {
|
||||
if(!searchLc){
|
||||
return
|
||||
}
|
||||
if(lng.indexOf(searchLc) >= 0){
|
||||
return true
|
||||
}
|
||||
|
||||
const languageInfo = all_languages[lng]
|
||||
const native : string = languageInfo[lng]?.toLowerCase()
|
||||
if(native?.indexOf(searchLc) >= 0){
|
||||
return true
|
||||
}
|
||||
const current : string = languageInfo[Locale.language.data]?.toLowerCase()
|
||||
if(current?.indexOf(searchLc) >= 0){
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
function onEnter(){
|
||||
// we select the first match which is not yet checked
|
||||
for (const lng of knownLanguagecodes) {
|
||||
if(lng === searchLC.data){
|
||||
isChecked[lng] = true
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
for (const lng of knownLanguagecodes) {
|
||||
if(matchesSearch(lng, searchLC.data)){
|
||||
isChecked[lng] = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
$: {
|
||||
update(isChecked)
|
||||
}
|
||||
searchValue.addCallback(_ => {
|
||||
newlyChecked.setData([])
|
||||
})
|
||||
</script>
|
||||
<form on:submit|preventDefault={() => onEnter()}>
|
||||
|
||||
{#each probableLanguages as lng}
|
||||
|
||||
<label class="no-image-background flex items-center gap-1">
|
||||
<input bind:checked={isChecked[lng]} type="checkbox" />
|
||||
<Tr t={new Translation(all_languages[lng])} />
|
||||
<span class="subtle">({lng})</span>
|
||||
</label>
|
||||
|
||||
{/each}
|
||||
|
||||
<label class="block relative neutral-label m-4 mx-16">
|
||||
<SearchIcon class="w-6 h-6 absolute right-0" />
|
||||
<input bind:value={$searchValue} type="text" />
|
||||
<Tr t={Translations.t.general.useSearch} />
|
||||
</label>
|
||||
|
||||
<div class="overflow-auto" style="max-height: 25vh">
|
||||
{#each knownLanguagecodes as lng}
|
||||
{#if (isChecked[lng]) && $newlyChecked.indexOf(lng) < 0 && probableLanguages.indexOf(lng) < 0}
|
||||
<label class="no-image-background flex items-center gap-1">
|
||||
<input bind:checked={isChecked[lng]} type="checkbox" />
|
||||
<Tr t={new Translation(all_languages[lng])} />
|
||||
<span class="subtle">({lng})</span>
|
||||
</label>
|
||||
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#each knownLanguagecodes as lng}
|
||||
{#if $searchLC.length > 0 && matchesSearch(lng, $searchLC) && (!isChecked[lng] || $newlyChecked.indexOf(lng) >= 0) && probableLanguages.indexOf(lng) < 0}
|
||||
<label class="no-image-background flex items-center gap-1">
|
||||
<input bind:checked={isChecked[lng]} type="checkbox" />
|
||||
<Tr t={new Translation(all_languages[lng])} />
|
||||
<span class="subtle">({lng})</span>
|
||||
</label>
|
||||
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</form>
|
||||
87
src/UI/Popup/LanguageElement/LanguageQuestion.svelte
Normal file
87
src/UI/Popup/LanguageElement/LanguageQuestion.svelte
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<script lang="ts">/**
|
||||
* The 'languageQuestion' is a special element which asks about the (possible) languages of a feature
|
||||
* (e.g. which speech output an ATM has, in what language(s) the braille writing is or what languages are spoken at a school)
|
||||
*
|
||||
* This is written into a `key`.
|
||||
*
|
||||
*/
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import SpecialTranslation from "../TagRendering/SpecialTranslation.svelte"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import type { Store } from "../../../Logic/UIEventSource"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
|
||||
import type { Feature } from "geojson"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import LanguageOptions from "./LanguageOptions.svelte"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { Tag } from "../../../Logic/Tags/Tag"
|
||||
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { And } from "../../../Logic/Tags/And"
|
||||
|
||||
export let question: string
|
||||
export let prefix: string
|
||||
|
||||
export let foundLanguages: Store<string[]>
|
||||
export let state: SpecialVisualizationState
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let feature: Feature
|
||||
export let layer: LayerConfig | undefined
|
||||
let dispatch = createEventDispatcher<{ save }>()
|
||||
|
||||
let selectedLanguages: UIEventSource<string[]> = new UIEventSource<string[]>([])
|
||||
let countries: Store<Set<string>> = tags.mapD(tags => new Set<string>(tags["_country"]?.toUpperCase()?.split(";") ?? []))
|
||||
async function applySelectedLanguages() {
|
||||
const selectedLngs = selectedLanguages.data
|
||||
const selection: Tag[] = selectedLanguages.data.map((ln) => new Tag(prefix + ln, "yes"))
|
||||
if (selection.length === 0) {
|
||||
return
|
||||
}
|
||||
const currentLanguages = foundLanguages.data
|
||||
|
||||
for (const currentLanguage of currentLanguages) {
|
||||
if (selectedLngs.indexOf(currentLanguage) >= 0) {
|
||||
continue
|
||||
}
|
||||
// Erase languages that are not spoken anymore
|
||||
selection.push(new Tag(prefix + currentLanguage, ""))
|
||||
}
|
||||
|
||||
if (state === undefined || state?.featureSwitchIsTesting?.data) {
|
||||
for (const tag of selection) {
|
||||
tags.data[tag.key] = tag.value
|
||||
}
|
||||
tags.ping()
|
||||
} else if (state.changes) {
|
||||
await state.changes
|
||||
.applyAction(
|
||||
new ChangeTagAction(
|
||||
tags.data.id,
|
||||
new And(selection),
|
||||
tags.data,
|
||||
{
|
||||
theme: state?.layout?.id ?? "unkown",
|
||||
changeType: "answer",
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
dispatch("save")
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col disable-links interactive border-interactive p-2">
|
||||
<div class="interactive justify-between pt-1 font-bold">
|
||||
<SpecialTranslation {feature} {layer} {state} t={new Translation({"*":question})} {tags} />
|
||||
</div>
|
||||
<LanguageOptions {selectedLanguages} countries={$countries}/>
|
||||
|
||||
<div class="flex justify-end flex-wrap-reverse w-full">
|
||||
<slot name="cancel-button"></slot>
|
||||
<button class="primary" class:disabled={$selectedLanguages.length === 0} on:click={_ => applySelectedLanguages()}>
|
||||
<Tr t={Translations.t.general.save} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -6,7 +6,6 @@ import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
|
|||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MaplibreMap from "../Map/MaplibreMap.svelte"
|
||||
import ShowDataLayer from "../Map/ShowDataLayer"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
|
||||
|
|
@ -32,7 +31,7 @@ export class MinimapViz implements SpecialVisualization {
|
|||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
feature: Feature
|
||||
) {
|
||||
if (state === undefined || feature === undefined) {
|
||||
return undefined
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
import Geosearch from "../BigComponents/Geosearch.svelte"
|
||||
import If from "../Base/If.svelte"
|
||||
import Constants from "../../Models/Constants"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
|
||||
|
|
@ -49,7 +50,7 @@
|
|||
let notAllowed = moveWizardState.moveDisallowedReason
|
||||
let currentMapProperties: MapProperties = undefined
|
||||
</script>
|
||||
|
||||
<LoginToggle {state}>
|
||||
{#if moveWizardState.reasons.length > 0}
|
||||
{#if $notAllowed}
|
||||
<div class="m-2 flex rounded-lg bg-gray-200 p-2">
|
||||
|
|
@ -165,3 +166,4 @@
|
|||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import { Tag } from "../../Logic/Tags/Tag"
|
|||
import { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { Feature, Point } from "geojson"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import Confirm from "../../assets/svg/Confirm.svelte"
|
||||
import Relocation from "../../assets/svg/Relocation.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
|
||||
export interface MoveReason {
|
||||
text: Translation | string
|
||||
|
|
@ -62,7 +62,7 @@ export class MoveWizardState {
|
|||
reasons.push({
|
||||
text: t.reasons.reasonInaccurate,
|
||||
invitingText: t.inviteToMove.reasonInaccurate,
|
||||
icon: new SvelteUIElement(Confirm),
|
||||
icon: new SvelteUIElement(Location),
|
||||
changesetCommentValue: "improve_accuracy",
|
||||
lockBounds: true,
|
||||
includeSearch: false,
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ export class AddNoteCommentViz implements SpecialVisualization {
|
|||
|
||||
public constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
tags: UIEventSource<Record<string, string>>
|
||||
) {
|
||||
return new SvelteUIElement(AddNoteComment, { state, tags })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import BaseUIElement from "../../BaseUIElement"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import { Utils } from "../../../Utils"
|
||||
import Svg from "../../../Svg"
|
||||
import Img from "../../Base/Img"
|
||||
import { SubtleButton } from "../../Base/SubtleButton"
|
||||
import Toggle from "../../Input/Toggle"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import Combine from "../../Base/Combine"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
import Svg from "../../../Svg"
|
||||
import Link from "../../Base/Link"
|
||||
import { FixedUiElement } from "../../Base/FixedUiElement"
|
||||
import Translations from "../../i18n/Translations"
|
||||
|
|
@ -11,6 +10,10 @@ import { Stores, UIEventSource } from "../../../Logic/UIEventSource"
|
|||
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
|
||||
import { VariableUiElement } from "../../Base/VariableUIElement"
|
||||
import { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
||||
import Note from "../../../assets/svg/Note.svelte"
|
||||
import Resolved from "../../../assets/svg/Resolved.svelte"
|
||||
import Speech_bubble from "../../../assets/svg/Speech_bubble.svelte"
|
||||
|
||||
export default class NoteCommentElement extends Combine {
|
||||
constructor(
|
||||
|
|
@ -32,11 +35,11 @@ export default class NoteCommentElement extends Combine {
|
|||
|
||||
let actionIcon: BaseUIElement
|
||||
if (comment.action === "opened" || comment.action === "reopened") {
|
||||
actionIcon = Svg.note_svg()
|
||||
actionIcon = new SvelteUIElement(Note)
|
||||
} else if (comment.action === "closed") {
|
||||
actionIcon = Svg.resolved_svg()
|
||||
actionIcon = new SvelteUIElement(Resolved)
|
||||
} else {
|
||||
actionIcon = Svg.speech_bubble_svg()
|
||||
actionIcon = new SvelteUIElement(Speech_bubble)
|
||||
}
|
||||
|
||||
let user: BaseUIElement
|
||||
|
|
@ -72,6 +75,7 @@ export default class NoteCommentElement extends Combine {
|
|||
const extension = link.substring(lastDotIndex + 1, link.length)
|
||||
return Utils.imageExtensions.has(extension)
|
||||
})
|
||||
.filter(link => !link.startsWith("https://wiki.openstreetmap.org/wiki/File:"))
|
||||
let imagesEl: BaseUIElement = undefined
|
||||
if (images.length > 0) {
|
||||
const imageEls = images.map((i) =>
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
import { Store } from "../../Logic/UIEventSource"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import Combine from "../Base/Combine"
|
||||
import Svg from "../../Svg"
|
||||
import { LoginToggle } from "./LoginButton"
|
||||
|
||||
export class EditButton extends Toggle {
|
||||
constructor(osmConnection: OsmConnection, onClick: () => void) {
|
||||
super(
|
||||
new Combine([Svg.pencil_svg()])
|
||||
.SetClass("block relative h-10 w-10 p-2 float-right")
|
||||
.SetStyle("border: 1px solid black; border-radius: 0.7em")
|
||||
.onClick(onClick),
|
||||
undefined,
|
||||
osmConnection.isLoggedIn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class SaveButton extends LoginToggle {
|
||||
constructor(
|
||||
value: Store<any>,
|
||||
state: {
|
||||
readonly osmConnection?: OsmConnection
|
||||
readonly featureSwitchUserbadge?: Store<boolean>
|
||||
},
|
||||
textEnabled?: BaseUIElement,
|
||||
textDisabled?: BaseUIElement
|
||||
) {
|
||||
if (value === undefined) {
|
||||
throw "No event source for savebutton, something is wrong"
|
||||
}
|
||||
|
||||
const isSaveable = value.map((v) => v !== false && (v ?? "") !== "")
|
||||
|
||||
const saveEnabled = (textEnabled ?? Translations.t.general.save.Clone()).SetClass(`btn`)
|
||||
const saveDisabled = (textDisabled ?? Translations.t.general.save.Clone()).SetClass(
|
||||
`btn btn-disabled`
|
||||
)
|
||||
|
||||
const save = new Toggle(saveEnabled, saveDisabled, isSaveable)
|
||||
super(save, Translations.t.general.loginToStart, state)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import Envelope from "../../assets/svg/Envelope.svelte"
|
||||
|
||||
export let tags: Store<OsmTags>
|
||||
export let args: string[]
|
||||
|
|
@ -14,6 +15,6 @@
|
|||
</script>
|
||||
|
||||
<a class="button flex w-full items-center" href={url} style="margin-left: 0">
|
||||
<ToSvelte construct={Svg.envelope_svg().SetClass("w-8 h-8 mr-4 shrink-0")} />
|
||||
<Envelope class="w-8 h-8 mr-4 shrink-0"/>
|
||||
{button_text}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import Toggle from "../Input/Toggle"
|
||||
import Svg from "../../Svg"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { SubtleButton } from "../Base/SubtleButton"
|
||||
import Combine from "../Base/Combine"
|
||||
|
|
@ -19,6 +18,7 @@ import { Changes } from "../../Logic/Osm/Changes"
|
|||
import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import OsmObjectDownloader from "../../Logic/Osm/OsmObjectDownloader"
|
||||
import Scissors from "../../assets/svg/Scissors.svelte"
|
||||
|
||||
export default class SplitRoadWizard extends Combine {
|
||||
public dialogIsOpened: UIEventSource<boolean>
|
||||
|
|
@ -26,8 +26,8 @@ export default class SplitRoadWizard extends Combine {
|
|||
/**
|
||||
* A UI Element used for splitting roads
|
||||
*
|
||||
* @param id: The id of the road to remove
|
||||
* @param state: the state of the application
|
||||
* @param id The id of the road to remove
|
||||
* @param state the state of the application
|
||||
*/
|
||||
constructor(
|
||||
id: WayId,
|
||||
|
|
@ -68,7 +68,7 @@ export default class SplitRoadWizard extends Combine {
|
|||
|
||||
// Toggle between splitmap
|
||||
const splitButton = new SubtleButton(
|
||||
Svg.scissors_svg().SetStyle("height: 1.5rem; width: auto"),
|
||||
new SvelteUIElement(Scissors).SetClass("h-6 w-6"),
|
||||
new Toggle(
|
||||
t.splitAgain.Clone().SetClass("text-lg font-bold"),
|
||||
t.inviteToSplit.Clone().SetClass("text-lg font-bold"),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
|
|||
import { Feature } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import Maproulette from "../../Logic/Maproulette"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import Icon from "../Map/Icon.svelte"
|
||||
import { Map } from "maplibre-gl"
|
||||
|
||||
export default class TagApplyButton implements AutoAction, SpecialVisualization {
|
||||
public readonly funcName = "tag_apply"
|
||||
|
|
@ -45,9 +48,9 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
|
|||
doc: "If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element",
|
||||
},
|
||||
{
|
||||
name: "maproulette_task_id",
|
||||
name: "maproulette_id",
|
||||
defaultValue: undefined,
|
||||
doc: "If specified, this maproulette-challenge will be closed when the tags are applied",
|
||||
doc: "If specified, this maproulette-challenge will be closed when the tags are applied. This should be the ID of the task, _not_ the task_id.",
|
||||
},
|
||||
]
|
||||
public readonly example =
|
||||
|
|
@ -81,7 +84,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
|
|||
for (const [key, value] of tgsSpec) {
|
||||
if (value.indexOf("$") >= 0) {
|
||||
let parts = value.split("$")
|
||||
// THe first of the split won't start with a '$', so no substitution needed
|
||||
// The first item of the split won't start with a '$', so no substitution needed
|
||||
let actualValue = parts[0]
|
||||
parts.shift()
|
||||
|
||||
|
|
@ -111,7 +114,6 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
|
|||
|
||||
while (spec.length > 0) {
|
||||
const [part] = spec.match(/((\\;)|[^;])*/)
|
||||
console.log("Spec is", part, spec)
|
||||
spec = spec.substring(part.length + 1) // +1 to remove the pending ';' as well
|
||||
const kv = part.split("=").map((s) => s.trim().replace("\\;", ";"))
|
||||
if (kv.length == 2) {
|
||||
|
|
@ -133,12 +135,8 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
|
|||
}
|
||||
|
||||
public async applyActionOn(
|
||||
feature: Feature,
|
||||
state: {
|
||||
layout: LayoutConfig
|
||||
changes: Changes
|
||||
indexedFeatures: IndexedFeatureSource
|
||||
},
|
||||
_: Feature,
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<any>,
|
||||
args: string[]
|
||||
): Promise<void> {
|
||||
|
|
@ -156,14 +154,22 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
|
|||
}
|
||||
)
|
||||
await state.changes.applyAction(changeAction)
|
||||
try {
|
||||
state.selectedElement.setData(state.indexedFeatures.featuresById.data.get(targetId))
|
||||
}catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
const maproulette_id_key = args[4]
|
||||
if (maproulette_id_key) {
|
||||
const maproulette_id = Number(tags.data[maproulette_id_key])
|
||||
await Maproulette.singleton.closeTask(maproulette_id, Maproulette.STATUS_FIXED, {
|
||||
const maproulette_id = tags.data[ maproulette_id_key]
|
||||
const maproulette_feature= state.indexedFeatures.featuresById.data.get(
|
||||
maproulette_id)
|
||||
const maproulette_task_id = Number(maproulette_feature.properties.mr_taskId)
|
||||
await Maproulette.singleton.closeTask(maproulette_task_id, Maproulette.STATUS_FIXED, {
|
||||
comment: "Tags are copied onto " + targetId + " with MapComplete",
|
||||
})
|
||||
tags.data["mr_taskStatus"] = "Fixed"
|
||||
tags.ping()
|
||||
maproulette_feature.properties["mr_taskStatus"] = "Fixed"
|
||||
state.featureProperties.getStore(maproulette_id).ping()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,6 +186,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
|
|||
if (image === "" || image === "undefined") {
|
||||
image = undefined
|
||||
}
|
||||
|
||||
const targetIdKey = args[3]
|
||||
const t = Translations.t.general.apply_button
|
||||
|
||||
|
|
@ -195,9 +202,9 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
|
|||
})
|
||||
).SetClass("subtle")
|
||||
const self = this
|
||||
const applied = new UIEventSource(tags?.data?.["mr_taskStatus"] !== "Created") // This will default to 'false' for non-maproulette challenges
|
||||
const applied = new UIEventSource(tags?.data?.["mr_taskStatus"] !== undefined && tags?.data?.["mr_taskStatus"] !== "Created") // This will default to 'false' for non-maproulette challenges
|
||||
const applyButton = new SubtleButton(
|
||||
image,
|
||||
new SvelteUIElement(Icon, {icon: image}),
|
||||
new Combine([msg, tagsExplanation]).SetClass("flex flex-col")
|
||||
).onClick(async () => {
|
||||
applied.setData(true)
|
||||
|
|
|
|||
23
src/UI/Popup/TagRendering/EditButton.svelte
Normal file
23
src/UI/Popup/TagRendering/EditButton.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { PencilAltIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { ariaLabel } from "../../../Utils/ariaLabel.js";
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
|
||||
/**
|
||||
* A small, round button with an edit-icon (and aria-labels etc)
|
||||
*/
|
||||
/**
|
||||
* What arialabel to apply onto this button?
|
||||
*/
|
||||
export let arialabel : Translation = undefined;
|
||||
export let ariaLabelledBy: string = undefined
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click
|
||||
class="secondary h-8 w-8 shrink-0 self-start rounded-full p-1"
|
||||
aria-labelledby={arialabel === undefined ? ariaLabelledBy : undefined}
|
||||
use:ariaLabel={arialabel}
|
||||
>
|
||||
<PencilAltIcon />
|
||||
</button>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
|
||||
export let value: UIEventSource<string>
|
||||
export let unvalidatedText: UIEventSource<string> = new UIEventSource<string>(value.data)
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
||||
|
|
@ -64,6 +65,7 @@
|
|||
type={config.freeform.type}
|
||||
{placeholder}
|
||||
{value}
|
||||
{unvalidatedText}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
|
@ -74,5 +76,6 @@
|
|||
{value}
|
||||
{state}
|
||||
on:submit
|
||||
{unvalidatedText}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import type { Feature } from "geojson"
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { onDestroy } from "svelte"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
|
|
@ -24,7 +23,7 @@
|
|||
throw "Config is undefined in tagRenderingAnswer"
|
||||
}
|
||||
let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD((tags) =>
|
||||
Utils.NoNull(config?.GetRenderValues(tags))
|
||||
Utils.NoNull(config?.GetRenderValues(tags)),
|
||||
)
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
import { Utils } from "../../../Utils"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { ariaLabel } from "../../../Utils/ariaLabel"
|
||||
import EditButton from "./EditButton.svelte"
|
||||
import EditItemButton from "../../Studio/EditItemButton.svelte"
|
||||
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
|
@ -63,7 +65,6 @@
|
|||
if (config.id === highlighted) {
|
||||
htmlElem.classList.add("glowing-shadow")
|
||||
htmlElem.tabIndex = -1
|
||||
console.log("Scrolling to", htmlElem)
|
||||
htmlElem.scrollIntoView({ behavior: "smooth" })
|
||||
Utils.focusOnFocusableChild(htmlElem)
|
||||
} else {
|
||||
|
|
@ -88,6 +89,7 @@
|
|||
{state}
|
||||
{layer}
|
||||
on:saved={() => (editMode = false)}
|
||||
allowDeleteOfFreeform={true}
|
||||
>
|
||||
<button
|
||||
slot="cancel"
|
||||
|
|
@ -102,16 +104,12 @@
|
|||
{:else}
|
||||
<div class="low-interaction flex items-center justify-between overflow-hidden rounded px-2">
|
||||
<TagRenderingAnswer id={answerId} {config} {tags} {selectedElement} {state} {layer} />
|
||||
<button
|
||||
<EditButton
|
||||
arialabel={config.editButtonAriaLabel}
|
||||
ariaLabelledBy={answerId}
|
||||
on:click={() => {
|
||||
editMode = true
|
||||
}}
|
||||
class="secondary h-8 w-8 shrink-0 self-start rounded-full p-1"
|
||||
aria-labelledby={config.editButtonAriaLabel === undefined ? answerId : undefined}
|
||||
use:ariaLabel={config.editButtonAriaLabel}
|
||||
>
|
||||
<PencilAltIcon />
|
||||
</button>
|
||||
editMode = true
|
||||
}}/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
{#if mapping.icon !== undefined}
|
||||
<div class="inline-flex items-center">
|
||||
<Icon icon={mapping.icon} clss={twJoin(`mapping-icon-${mapping.iconClass}`, "mr-2")} />
|
||||
<Icon icon={mapping.icon} clss={twJoin(`mapping-icon-${mapping.iconClass ?? "small"}`, "mr-2")} />
|
||||
<SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement} />
|
||||
</div>
|
||||
{:else if mapping.then !== undefined}
|
||||
|
|
|
|||
|
|
@ -22,17 +22,23 @@
|
|||
import { Unit } from "../../../Models/Unit"
|
||||
import UserRelatedState from "../../../Logic/State/UserRelatedState"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
import type { UploadableTag } from "../../../Logic/Tags/TagUtils"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
|
||||
import Search from "../../../assets/svg/Search.svelte"
|
||||
import Login from "../../../assets/svg/Login.svelte"
|
||||
import { placeholder } from "../../../Utils/placeholder"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { Tag } from "../../../Logic/Tags/Tag"
|
||||
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let selectedElement: Feature
|
||||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig | undefined
|
||||
export let selectedTags: TagsFilter = undefined
|
||||
export let selectedTags: UploadableTag = undefined
|
||||
|
||||
export let allowDeleteOfFreeform: boolean = false
|
||||
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||
|
||||
|
|
@ -40,13 +46,15 @@
|
|||
|
||||
// Will be bound if a freeform is available
|
||||
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
|
||||
let freeformInputUnvalidated = new UIEventSource<string>(freeformInput.data)
|
||||
|
||||
let selectedMapping: number = undefined
|
||||
/**
|
||||
* A list of booleans, used if multiAnswer is set
|
||||
*/
|
||||
let checkedMappings: boolean[]
|
||||
|
||||
let mappings: Mapping[] = config?.mappings
|
||||
let mappings: Mapping[] = config?.mappings ?? []
|
||||
let searchTerm: UIEventSource<string> = new UIEventSource("")
|
||||
|
||||
let dispatch = createEventDispatcher<{
|
||||
|
|
@ -128,7 +136,6 @@
|
|||
}
|
||||
|
||||
freeformInput.addCallbackAndRun((freeformValue) => {
|
||||
console.log("FreeformValue:", freeformValue)
|
||||
if (!mappings || mappings?.length == 0 || config.freeform?.key === undefined) {
|
||||
return
|
||||
}
|
||||
|
|
@ -146,20 +153,25 @@
|
|||
}
|
||||
})
|
||||
$: {
|
||||
try {
|
||||
selectedTags = config?.constructChangeSpecification(
|
||||
$freeformInput,
|
||||
selectedMapping,
|
||||
checkedMappings,
|
||||
tags.data,
|
||||
)
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e)
|
||||
selectedTags = undefined
|
||||
if (allowDeleteOfFreeform && $freeformInput === undefined && $freeformInputUnvalidated === "" && (mappings?.length ?? 0) === 0) {
|
||||
selectedTags = new Tag(config.freeform.key, "")
|
||||
} else {
|
||||
|
||||
try {
|
||||
selectedTags = config?.constructChangeSpecification(
|
||||
$freeformInput,
|
||||
selectedMapping,
|
||||
checkedMappings,
|
||||
tags.data,
|
||||
)
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e)
|
||||
selectedTags = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onSave(e) {
|
||||
function onSave(_ = undefined) {
|
||||
if (selectedTags === undefined) {
|
||||
return
|
||||
}
|
||||
|
|
@ -198,9 +210,9 @@
|
|||
|
||||
function onInputKeypress(e: KeyboardEvent) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onSave(e)
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onSave()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,136 +243,139 @@
|
|||
<fieldset>
|
||||
|
||||
<legend>
|
||||
<div class="interactive sticky top-0 justify-between pt-1 font-bold" style="z-index: 11">
|
||||
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
|
||||
</div>
|
||||
<div class="interactive sticky top-0 justify-between pt-1 font-bold" style="z-index: 11">
|
||||
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
|
||||
</div>
|
||||
|
||||
{#if config.questionhint}
|
||||
<div class="max-h-60 overflow-y-auto">
|
||||
<SpecialTranslation
|
||||
t={config.questionhint}
|
||||
{tags}
|
||||
{state}
|
||||
{layer}
|
||||
feature={selectedElement}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if config.questionhint}
|
||||
<div class="max-h-60 overflow-y-auto">
|
||||
<SpecialTranslation
|
||||
t={config.questionhint}
|
||||
{tags}
|
||||
{state}
|
||||
{layer}
|
||||
feature={selectedElement}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</legend>
|
||||
|
||||
{#if config.mappings?.length >= 8}
|
||||
<div class="sticky flex w-full" aria-hidden="true">
|
||||
<Search class="h-6 w-6" />
|
||||
<input
|
||||
type="text"
|
||||
bind:value={$searchTerm}
|
||||
class="w-full"
|
||||
use:placeholder={Translations.t.general.searchAnswer}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if config.mappings?.length >= 8}
|
||||
<div class="sticky flex w-full" aria-hidden="true">
|
||||
<Search class="h-6 w-6" />
|
||||
<input
|
||||
type="text"
|
||||
bind:value={$searchTerm}
|
||||
class="w-full"
|
||||
use:placeholder={Translations.t.general.searchAnswer}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if config.freeform?.key && !(mappings?.length > 0)}
|
||||
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
|
||||
<FreeformInput
|
||||
{config}
|
||||
{#if config.freeform?.key && !(mappings?.length > 0)}
|
||||
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
|
||||
<FreeformInput
|
||||
{config}
|
||||
{tags}
|
||||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
{:else if mappings !== undefined && !config.multiAnswer}
|
||||
<!-- Simple radiobuttons as mapping -->
|
||||
<div class="flex flex-col">
|
||||
{#each config.mappings as mapping, i (mapping.then)}
|
||||
<!-- Even though we have a list of 'mappings' already, we still iterate over the list as to keep the original indices-->
|
||||
<TagRenderingMappingInput
|
||||
{mapping}
|
||||
{tags}
|
||||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
{:else if mappings !== undefined && !config.multiAnswer}
|
||||
<!-- Simple radiobuttons as mapping -->
|
||||
<div class="flex flex-col">
|
||||
{#each config.mappings as mapping, i (mapping.then)}
|
||||
<!-- Even though we have a list of 'mappings' already, we still iterate over the list as to keep the original indices-->
|
||||
<TagRenderingMappingInput
|
||||
{mapping}
|
||||
{tags}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{layer}
|
||||
{searchTerm}
|
||||
mappingIsSelected={selectedMapping === i}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={selectedMapping}
|
||||
name={"mappings-radio-" + config.id}
|
||||
value={i}
|
||||
on:keypress={(e) => onInputKeypress(e)}
|
||||
/>
|
||||
</TagRenderingMappingInput>
|
||||
{/each}
|
||||
{#if config.freeform?.key}
|
||||
<label class="flex gap-x-1">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={selectedMapping}
|
||||
name={"mappings-radio-" + config.id}
|
||||
value={config.mappings?.length}
|
||||
on:keypress={(e) => onInputKeypress(e)}
|
||||
/>
|
||||
<FreeformInput
|
||||
{config}
|
||||
{tags}
|
||||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
on:selected={() => (selectedMapping = config.mappings?.length)}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if mappings !== undefined && config.multiAnswer}
|
||||
<!-- Multiple answers can be chosen: checkboxes -->
|
||||
<div class="flex flex-col">
|
||||
{#each config.mappings as mapping, i (mapping.then)}
|
||||
<TagRenderingMappingInput
|
||||
{mapping}
|
||||
{tags}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{layer}
|
||||
{searchTerm}
|
||||
mappingIsSelected={checkedMappings[i]}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={"mappings-checkbox-" + config.id + "-" + i}
|
||||
bind:checked={checkedMappings[i]}
|
||||
on:keypress={(e) => onInputKeypress(e)}
|
||||
/>
|
||||
</TagRenderingMappingInput>
|
||||
{/each}
|
||||
{#if config.freeform?.key}
|
||||
<label class="flex gap-x-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
name={"mappings-checkbox-" + config.id + "-" + config.mappings?.length}
|
||||
bind:checked={checkedMappings[config.mappings.length]}
|
||||
on:keypress={(e) => onInputKeypress(e)}
|
||||
/>
|
||||
<FreeformInput
|
||||
{config}
|
||||
{tags}
|
||||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
{selectedElement}
|
||||
{layer}
|
||||
{searchTerm}
|
||||
mappingIsSelected={selectedMapping === i}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={selectedMapping}
|
||||
name={"mappings-radio-" + config.id}
|
||||
value={i}
|
||||
on:keypress={(e) => onInputKeypress(e)}
|
||||
/>
|
||||
</TagRenderingMappingInput>
|
||||
{/each}
|
||||
{#if config.freeform?.key}
|
||||
<label class="flex gap-x-1">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={selectedMapping}
|
||||
name={"mappings-radio-" + config.id}
|
||||
value={config.mappings?.length}
|
||||
on:keypress={(e) => onInputKeypress(e)}
|
||||
/>
|
||||
<FreeformInput
|
||||
{config}
|
||||
{tags}
|
||||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
on:selected={() => (selectedMapping = config.mappings?.length)}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if mappings !== undefined && config.multiAnswer}
|
||||
<!-- Multiple answers can be chosen: checkboxes -->
|
||||
<div class="flex flex-col">
|
||||
{#each config.mappings as mapping, i (mapping.then)}
|
||||
<TagRenderingMappingInput
|
||||
{mapping}
|
||||
{tags}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{layer}
|
||||
{searchTerm}
|
||||
mappingIsSelected={checkedMappings[i]}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={"mappings-checkbox-" + config.id + "-" + i}
|
||||
bind:checked={checkedMappings[i]}
|
||||
on:keypress={(e) => onInputKeypress(e)}
|
||||
/>
|
||||
</TagRenderingMappingInput>
|
||||
{/each}
|
||||
{#if config.freeform?.key}
|
||||
<label class="flex gap-x-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
name={"mappings-checkbox-" + config.id + "-" + config.mappings?.length}
|
||||
bind:checked={checkedMappings[config.mappings.length]}
|
||||
on:keypress={(e) => onInputKeypress(e)}
|
||||
/>
|
||||
<FreeformInput
|
||||
{config}
|
||||
{tags}
|
||||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<LoginToggle {state}>
|
||||
<Loading slot="loading" />
|
||||
<SubtleButton slot="not-logged-in" on:click={() => state?.osmConnection?.AttemptLogin()}>
|
||||
|
|
@ -379,12 +394,19 @@
|
|||
<!-- TagRenderingQuestion-buttons -->
|
||||
<slot name="cancel" />
|
||||
<slot name="save-button" {selectedTags}>
|
||||
<button
|
||||
on:click={onSave}
|
||||
class={twJoin(selectedTags === undefined ? "disabled" : "button-shadow", "primary")}
|
||||
>
|
||||
<Tr t={Translations.t.general.save} />
|
||||
</button>
|
||||
{#if allowDeleteOfFreeform && (mappings?.length ?? 0) === 0 && $freeformInput === undefined && $freeformInputUnvalidated === ""}
|
||||
<button class="primary flex" on:click|stopPropagation|preventDefault={onSave}>
|
||||
<TrashIcon class="w-6 h-6 text-red-500" />
|
||||
<Tr t={Translations.t.general.eraseValue}/>
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
on:click={onSave}
|
||||
class={twJoin(selectedTags === undefined ? "disabled" : "button-shadow", "primary")}
|
||||
>
|
||||
<Tr t={Translations.t.general.save} />
|
||||
</button>
|
||||
{/if}
|
||||
</slot>
|
||||
</div>
|
||||
{#if UserRelatedState.SHOW_TAGS_VALUES.indexOf($showTags) >= 0 || ($showTags === "" && numberOfCs >= Constants.userJourney.tagsVisibleAt) || $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue