UI: fix #1723
This commit is contained in:
parent
8d694e30d2
commit
48216c1a7f
2 changed files with 183 additions and 181 deletions
|
@ -841,6 +841,10 @@ video {
|
||||||
margin-right: 3rem;
|
margin-right: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-4 {
|
.mt-4 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -877,10 +881,6 @@ video {
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mb-4 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-1 {
|
.ml-1 {
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1784,6 +1784,10 @@ video {
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pt-1 {
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,202 +1,200 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource"
|
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||||
import Tr from "../../Base/Tr.svelte"
|
import Tr from "../../Base/Tr.svelte"
|
||||||
import type { Feature } from "geojson"
|
import type { Feature } from "geojson"
|
||||||
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig"
|
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig"
|
||||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
|
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
|
||||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||||
import FreeformInput from "./FreeformInput.svelte"
|
import FreeformInput from "./FreeformInput.svelte"
|
||||||
import Translations from "../../i18n/Translations.js"
|
import Translations from "../../i18n/Translations.js"
|
||||||
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
|
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
|
||||||
import { createEventDispatcher, onDestroy } from "svelte"
|
import { createEventDispatcher, onDestroy } from "svelte"
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||||
import SpecialTranslation from "./SpecialTranslation.svelte"
|
import SpecialTranslation from "./SpecialTranslation.svelte"
|
||||||
import TagHint from "../TagHint.svelte"
|
import TagHint from "../TagHint.svelte"
|
||||||
import LoginToggle from "../../Base/LoginToggle.svelte"
|
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||||
import SubtleButton from "../../Base/SubtleButton.svelte"
|
import SubtleButton from "../../Base/SubtleButton.svelte"
|
||||||
import Loading from "../../Base/Loading.svelte"
|
import Loading from "../../Base/Loading.svelte"
|
||||||
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"
|
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"
|
||||||
import { Translation } from "../../i18n/Translation"
|
import { Translation } from "../../i18n/Translation"
|
||||||
import Constants from "../../../Models/Constants"
|
import Constants from "../../../Models/Constants"
|
||||||
import { Unit } from "../../../Models/Unit"
|
import { Unit } from "../../../Models/Unit"
|
||||||
import UserRelatedState from "../../../Logic/State/UserRelatedState"
|
import UserRelatedState from "../../../Logic/State/UserRelatedState"
|
||||||
import { twJoin } from "tailwind-merge"
|
import { twJoin } from "tailwind-merge"
|
||||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||||
import Search from "../../../assets/svg/Search.svelte";
|
import Search from "../../../assets/svg/Search.svelte"
|
||||||
import Login from "../../../assets/svg/Login.svelte";
|
import Login from "../../../assets/svg/Login.svelte"
|
||||||
|
|
||||||
export let config: TagRenderingConfig
|
export let config: TagRenderingConfig
|
||||||
export let tags: UIEventSource<Record<string, string>>
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
export let selectedElement: Feature
|
export let selectedElement: Feature
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let layer: LayerConfig | undefined
|
export let layer: LayerConfig | undefined
|
||||||
|
|
||||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||||
|
|
||||||
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||||
|
|
||||||
// Will be bound if a freeform is available
|
// Will be bound if a freeform is available
|
||||||
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
|
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
|
||||||
let selectedMapping: number = undefined
|
let selectedMapping: number = undefined
|
||||||
let checkedMappings: boolean[]
|
let checkedMappings: boolean[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares and fills the checkedMappings
|
* Prepares and fills the checkedMappings
|
||||||
*/
|
*/
|
||||||
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
|
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
|
||||||
mappings = confg.mappings?.filter((m) => {
|
mappings = confg.mappings?.filter((m) => {
|
||||||
if (typeof m.hideInAnswer === "boolean") {
|
if (typeof m.hideInAnswer === "boolean") {
|
||||||
return !m.hideInAnswer
|
return !m.hideInAnswer
|
||||||
}
|
}
|
||||||
return !m.hideInAnswer.matchesProperties(tgs)
|
return !m.hideInAnswer.matchesProperties(tgs)
|
||||||
})
|
})
|
||||||
// We received a new config -> reinit
|
// We received a new config -> reinit
|
||||||
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||||
|
|
||||||
if (
|
if (
|
||||||
confg.mappings?.length > 0 &&
|
confg.mappings?.length > 0 &&
|
||||||
confg.multiAnswer &&
|
confg.multiAnswer &&
|
||||||
(checkedMappings === undefined ||
|
(checkedMappings === undefined ||
|
||||||
checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
|
checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
|
||||||
) {
|
) {
|
||||||
const seenFreeforms = []
|
const seenFreeforms = []
|
||||||
TagUtils.FlattenMultiAnswer()
|
TagUtils.FlattenMultiAnswer()
|
||||||
checkedMappings = [
|
checkedMappings = [
|
||||||
...confg.mappings.map((mapping) => {
|
...confg.mappings.map((mapping) => {
|
||||||
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs)
|
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs)
|
||||||
if (matches && confg.freeform) {
|
if (matches && confg.freeform) {
|
||||||
const newProps = TagUtils.changeAsProperties(mapping.if.asChange())
|
const newProps = TagUtils.changeAsProperties(mapping.if.asChange())
|
||||||
seenFreeforms.push(newProps[confg.freeform.key])
|
seenFreeforms.push(newProps[confg.freeform.key])
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
if (tgs !== undefined && confg.freeform) {
|
if (tgs !== undefined && confg.freeform) {
|
||||||
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? []
|
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? []
|
||||||
for (const seenFreeform of seenFreeforms) {
|
for (const seenFreeform of seenFreeforms) {
|
||||||
if (!seenFreeform) {
|
if (!seenFreeform) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const index = unseenFreeformValues.indexOf(seenFreeform)
|
const index = unseenFreeformValues.indexOf(seenFreeform)
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
unseenFreeformValues.splice(index, 1)
|
unseenFreeformValues.splice(index, 1)
|
||||||
|
}
|
||||||
|
// TODO this has _to much_ values
|
||||||
|
freeformInput.setData(unseenFreeformValues.join(";"))
|
||||||
|
checkedMappings.push(unseenFreeformValues.length > 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO this has _to much_ values
|
if (confg.freeform?.key) {
|
||||||
freeformInput.setData(unseenFreeformValues.join(";"))
|
if (!confg.multiAnswer) {
|
||||||
checkedMappings.push(unseenFreeformValues.length > 0)
|
// Somehow, setting multi-answer freeform values is broken if this is not set
|
||||||
}
|
freeformInput.setData(tgs[confg.freeform.key])
|
||||||
}
|
}
|
||||||
if (confg.freeform?.key) {
|
|
||||||
if (!confg.multiAnswer) {
|
|
||||||
// Somehow, setting multi-answer freeform values is broken if this is not set
|
|
||||||
freeformInput.setData(tgs[confg.freeform.key])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
freeformInput.setData(undefined)
|
|
||||||
}
|
|
||||||
feedback.setData(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
$: {
|
|
||||||
// Even though 'config' is not declared as a store, Svelte uses it as one to update the component
|
|
||||||
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
|
|
||||||
initialize($tags, config)
|
|
||||||
}
|
|
||||||
export let selectedTags: TagsFilter = undefined
|
|
||||||
|
|
||||||
let mappings: Mapping[] = config?.mappings
|
|
||||||
let searchTerm: UIEventSource<string> = new UIEventSource("")
|
|
||||||
|
|
||||||
$: {
|
|
||||||
try {
|
|
||||||
selectedTags = config?.constructChangeSpecification(
|
|
||||||
$freeformInput,
|
|
||||||
selectedMapping,
|
|
||||||
checkedMappings,
|
|
||||||
tags.data
|
|
||||||
)
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Could not calculate changeSpecification:", e)
|
|
||||||
selectedTags = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let dispatch = createEventDispatcher<{
|
|
||||||
saved: {
|
|
||||||
config: TagRenderingConfig
|
|
||||||
applied: TagsFilter
|
|
||||||
}
|
|
||||||
}>()
|
|
||||||
|
|
||||||
function onSave() {
|
|
||||||
if (selectedTags === undefined) {
|
|
||||||
console.log("SelectedTags is undefined, ignoring 'onSave'-event")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (layer === undefined || layer?.source === null) {
|
|
||||||
/**
|
|
||||||
* This is a special, priviliged layer.
|
|
||||||
* We simply apply the tags onto the records
|
|
||||||
*/
|
|
||||||
const kv = selectedTags.asChange(tags.data)
|
|
||||||
for (const { k, v } of kv) {
|
|
||||||
if (v === undefined || v === "") {
|
|
||||||
delete tags.data[k]
|
|
||||||
} else {
|
} else {
|
||||||
tags.data[k] = v
|
freeformInput.setData(undefined)
|
||||||
}
|
}
|
||||||
}
|
feedback.setData(undefined)
|
||||||
tags.ping()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch("saved", { config, applied: selectedTags })
|
$: {
|
||||||
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
|
// Even though 'config' is not declared as a store, Svelte uses it as one to update the component
|
||||||
theme: state.layout.id,
|
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
|
||||||
changeType: "answer",
|
initialize($tags, config)
|
||||||
})
|
}
|
||||||
freeformInput.setData(undefined)
|
export let selectedTags: TagsFilter = undefined
|
||||||
selectedMapping = undefined
|
|
||||||
selectedTags = undefined
|
|
||||||
|
|
||||||
change
|
let mappings: Mapping[] = config?.mappings
|
||||||
.CreateChangeDescriptions()
|
let searchTerm: UIEventSource<string> = new UIEventSource("")
|
||||||
.then((changes) => state.changes.applyChanges(changes))
|
|
||||||
.catch(console.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)
|
$: {
|
||||||
let featureSwitchIsDebugging =
|
try {
|
||||||
state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
selectedTags = config?.constructChangeSpecification(
|
||||||
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined)
|
$freeformInput,
|
||||||
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0
|
selectedMapping,
|
||||||
let question = config.question
|
checkedMappings,
|
||||||
$: question = config.question
|
tags.data,
|
||||||
if (state?.osmConnection) {
|
)
|
||||||
onDestroy(
|
} catch (e) {
|
||||||
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
|
console.error("Could not calculate changeSpecification:", e)
|
||||||
numberOfCs = ud.csCount
|
selectedTags = undefined
|
||||||
})
|
}
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
let dispatch = createEventDispatcher<{
|
||||||
|
saved: {
|
||||||
|
config: TagRenderingConfig
|
||||||
|
applied: TagsFilter
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function onSave() {
|
||||||
|
if (selectedTags === undefined) {
|
||||||
|
console.log("SelectedTags is undefined, ignoring 'onSave'-event")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (layer === undefined || layer?.source === null) {
|
||||||
|
/**
|
||||||
|
* This is a special, priviliged layer.
|
||||||
|
* We simply apply the tags onto the records
|
||||||
|
*/
|
||||||
|
const kv = selectedTags.asChange(tags.data)
|
||||||
|
for (const { k, v } of kv) {
|
||||||
|
if (v === undefined || v === "") {
|
||||||
|
delete tags.data[k]
|
||||||
|
} else {
|
||||||
|
tags.data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tags.ping()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch("saved", { config, applied: selectedTags })
|
||||||
|
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
|
||||||
|
theme: state.layout.id,
|
||||||
|
changeType: "answer",
|
||||||
|
})
|
||||||
|
freeformInput.setData(undefined)
|
||||||
|
selectedMapping = undefined
|
||||||
|
selectedTags = undefined
|
||||||
|
|
||||||
|
change
|
||||||
|
.CreateChangeDescriptions()
|
||||||
|
.then((changes) => state.changes.applyChanges(changes))
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)
|
||||||
|
let featureSwitchIsDebugging =
|
||||||
|
state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
||||||
|
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined)
|
||||||
|
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0
|
||||||
|
let question = config.question
|
||||||
|
$: question = config.question
|
||||||
|
if (state?.osmConnection) {
|
||||||
|
onDestroy(
|
||||||
|
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
|
||||||
|
numberOfCs = ud.csCount
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if question !== undefined}
|
{#if question !== undefined}
|
||||||
<div
|
<div
|
||||||
class="interactive border-interactive relative flex flex-col overflow-y-auto p-1 px-2"
|
class="interactive border-interactive relative flex flex-col overflow-y-auto px-2"
|
||||||
style="max-height: 85vh"
|
style="max-height: 75vh"
|
||||||
>
|
>
|
||||||
<div class="sticky top-0" style="z-index: 11">
|
<div class="sticky top-0 interactive pt-1 flex justify-between" style="z-index: 11">
|
||||||
<div class="interactive sticky top-0 flex justify-between">
|
|
||||||
<span class="font-bold">
|
<span class="font-bold">
|
||||||
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
|
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
|
||||||
</span>
|
</span>
|
||||||
<slot name="upper-right" />
|
<slot name="upper-right" />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if config.questionhint}
|
{#if config.questionhint}
|
||||||
|
@ -213,7 +211,7 @@
|
||||||
|
|
||||||
{#if config.mappings?.length >= 8}
|
{#if config.mappings?.length >= 8}
|
||||||
<div class="sticky flex w-full">
|
<div class="sticky flex w-full">
|
||||||
<Search class="h-6 w-6"/>
|
<Search class="h-6 w-6" />
|
||||||
<input type="text" bind:value={$searchTerm} class="w-full" />
|
<input type="text" bind:value={$searchTerm} class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
Loading…
Reference in a new issue