Merge develop

This commit is contained in:
Pieter Vander Vennet 2024-02-21 19:17:42 +01:00
commit 7c5170da15
193 changed files with 4011 additions and 4462 deletions

View file

@ -44,7 +44,7 @@
preset: PresetConfig
layer: LayerConfig
icon: BaseUIElement
tags: Record<string, string>,
tags: Record<string, string>
text: Translation
} = undefined
let checkedOfGlobalFilters: number = 0
@ -200,7 +200,7 @@
state.guistate.openFilterView(selectedPreset.layer)
}}
>
<Layers class="w-12"/>
<Layers class="w-12" />
<Tr t={Translations.t.general.add.openLayerControl} />
</button>
@ -241,7 +241,7 @@
state.guistate.openFilterView(selectedPreset.layer)
}}
>
<Layers class="w-12"/>
<Layers class="w-12" />
<Tr t={Translations.t.general.add.openLayerControl} />
</button>
</div>
@ -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.text
preset: selectedPreset.text,
})}
/>
</SubtleButton>

View file

@ -26,7 +26,7 @@
/**
* Same as `this.preset.description.firstSentence()`
*/
description: Translation,
description: Translation
icon: BaseUIElement
tags: Record<string, string>
}[] = []
@ -40,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
@ -55,9 +55,18 @@
for (const preset of layer.presets) {
const tags = TagUtils.KVtoProperties(preset.tags ?? [])
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 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()
@ -69,7 +78,7 @@
tags,
text: Translations.t.general.add.addNew.Subs(
{ category: preset.title },
preset.title["context"],
preset.title["context"]
),
}
presets.push(simplified)
@ -78,10 +87,10 @@
const dispatch = createEventDispatcher<{
select: {
preset: PresetConfig;
layer: LayerConfig;
icon: BaseUIElement;
tags: Record<string, string>,
preset: PresetConfig
layer: LayerConfig
icon: BaseUIElement
tags: Record<string, string>
text: Translation
}
}>()

View file

@ -4,7 +4,7 @@
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
export let tags: UIEventSource<Record<string, any>>
export let tagKeys = tags.map(tgs => Object.keys(tgs))
export let tagKeys = tags.map(tgs => tgs === undefined ? [] : Object.keys(tgs))
export let layer: LayerConfig | undefined = undefined
@ -13,17 +13,16 @@
*/
let calculatedTags: string[] = []
for (const calculated of layer?.calculatedTags ?? []) {
if(calculated){
if (calculated) {
continue
}
const name = calculated[0]
calculatedTags.push(name)
}
let knownValues: Store<string[]> = tags.map(tags => Object.keys(tags))
let knownValues: Store<string[]> = tags.map((tags) => Object.keys(tags))
const metaKeys: string[] = [].concat(...SimpleMetaTaggers.metatags.map(k => k.keys))
const metaKeys: string[] = [].concat(...SimpleMetaTaggers.metatags.map((k) => k.keys))
let allCalculatedTags = new Set<string>([...calculatedTags, ...metaKeys])
</script>
<section>
@ -84,8 +83,15 @@
<tr>
<td>{key}</td>
<td>
{#if $knownValues.indexOf(key) < 0 }
<button class="small" on:click={_ => {console.log($tags[key])}}>Evaluate</button>
{#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] === ""}

View file

@ -10,7 +10,7 @@
import type { Feature } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
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"
@ -65,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

View file

@ -53,7 +53,6 @@ export default class ConflateImportFlowState extends ImportFlow<ConflateFlowArgu
const action = this.action
await this.state.changes.applyAction(action)
const newId = action.newElementId ?? action.mainObjectId
this.state.selectedLayer.setData(this.targetLayer.layerDef)
this.state.selectedElement.setData(this.state.indexedFeatures.featuresById.data.get(newId))
}

View file

@ -1,58 +1,55 @@
<script lang="ts">
import type { ImportFlowArguments } from "./ImportFlow"
/**
* The 'importflow' does some basic setup, e.g. validate that imports are allowed, that the user is logged-in, ...
* They show some default components
*/
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"
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<ImportFlowArguments>
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
let dispatch = createEventDispatcher<{ confirm }>()
let canBeImported = importFlow.canBeImported()
let 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))
let targetLayers = importFlow.targetLayer
let filteredLayer: FilteredLayer
let undisplayedLayer: FilteredLayer
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)
}
function updateIsDisplayed() {
filteredLayer = targetLayers.find(tl => tl.hasFilter.data)
undisplayedLayer = targetLayers.find(tl => !tl.isDisplayed.data)
}
updateIsDisplayed()
updateIsDisplayed()
for (const tl of targetLayers) {
onDestroy(
tl.isDisplayed.addCallback(updateIsDisplayed),
)
}
for (const tl of targetLayers) {
onDestroy(tl.isDisplayed.addCallback(updateIsDisplayed))
}
function abort() {
state.selectedElement.setData(undefined)
}
function abort() {
state.selectedElement.setData(undefined)
}
</script>
<LoginToggle {state}>
@ -160,7 +157,7 @@
{#if importFlow.args.icon}
<img src={importFlow.args.icon} />
{:else}
<Confirm class="w-8 h-8 pr-4"/>
<Confirm class="h-8 w-8 pr-4" />
{/if}
</span>
<slot name="confirm-text">

View file

@ -24,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.
@ -66,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
@ -82,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("{")) {
@ -120,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)
@ -146,13 +146,13 @@ 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 = args.targetLayer.split(" ").map(tl => {
this.targetLayer = args.targetLayer.split(" ").map((tl) => {
let found = state.layerState.filteredLayers.get(tl)
if (!found) {
throw "Layer " + tl + " not found"
@ -198,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]
)
}
}

View file

@ -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, split?: boolean }[]
public readonly args: { name: string; defaultValue?: string; doc: string; split?: boolean }[]
public needsUrls = []
constructor() {

View file

@ -13,7 +13,9 @@
const args = importFlow.args
// The following variables are used for the map
const targetLayers: LayerConfig[] = args.targetLayer.split(" ").map(tl => state.layout.layers.find((l) => l.id === tl))
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

View file

@ -126,7 +126,6 @@ export default class WayImportFlowState extends ImportFlow<WayImportFlowArgument
const action = this.action
await this.state.changes.applyAction(action)
const newId = action.newElementId ?? action.mainObjectId
this.state.selectedLayer.setData(this.targetLayer.layerDef)
this.state.selectedElement.setData(this.state.indexedFeatures.featuresById.data.get(newId))
}

View file

@ -25,19 +25,30 @@
</script>
{#if $languages.length === 1}
<SpecialTranslation {state} {tags} {feature} {layer}
t={new TypedTranslation({"*": single_render}).PartialSubsTr(
"language()",
new Translation(all_languages[$languages[0]], undefined)
)}/>
<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) )}
<SpecialTranslation
{state}
{tags}
{feature}
{layer}
t={new TypedTranslation({ "*": item_render }).PartialSubsTr(
"language()",
new Translation(all_languages[language], undefined)
)}
/>
</li>
{/each}

View file

@ -37,33 +37,48 @@
})
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 class="low-interaction flex items-center justify-between rounded p-1">
<div>
{on_no_known_languages}
</div>
<EditButton on:click={_ => forceInputMode.setData(true)} />
<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)}>
<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 $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 class="low-interaction flex items-center justify-between rounded p-2">
<div>
<LanguageAnswer {single_render} {item_render} {render_all} languages={foundLanguages} {state} {tags} { feature}
{layer} />
<LanguageAnswer
{single_render}
{item_render}
{render_all}
languages={foundLanguages}
{state}
{tags}
{feature}
{layer}
/>
</div>
<EditButton on:click={_ => forceInputMode.setData(true)} />
<EditButton on:click={(_) => forceInputMode.setData(true)} />
</div>
{/if}

View file

@ -1,121 +1,119 @@
<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"
<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)
/**
* 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
}
isChecked[lng] = false
}
let newlyChecked: UIEventSource<string[]> = new UIEventSource<string[]>([])
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()
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
}
}
}
selectedLanguages.setData(languages)
}
function matchesSearch(lng: string, searchLc: string | undefined): boolean {
if(!searchLc){
return
$: {
update(isChecked)
}
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([])
})
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" />
<label class="neutral-label relative m-4 mx-16 block">
<SearchIcon class="absolute right-0 h-6 w-6" />
<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}
{#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}
@ -126,7 +124,6 @@ searchValue.addCallback(_ => {
<Tr t={new Translation(all_languages[lng])} />
<span class="subtle">({lng})</span>
</label>
{/if}
{/each}
</div>

View file

@ -1,86 +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"
<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"
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 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 }>()
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
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
}
// Erase languages that are not spoken anymore
selection.push(new Tag(prefix + currentLanguage, ""))
}
const currentLanguages = foundLanguages.data
if (state === undefined || state?.featureSwitchIsTesting?.data) {
for (const tag of selection) {
tags.data[tag.key] = tag.value
for (const currentLanguage of currentLanguages) {
if (selectedLngs.indexOf(currentLanguage) >= 0) {
continue
}
// Erase languages that are not spoken anymore
selection.push(new Tag(prefix + currentLanguage, ""))
}
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",
},
),
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")
}
dispatch("save")
}
</script>
<div class="flex flex-col disable-links interactive border-interactive p-2">
<div class="disable-links interactive border-interactive flex flex-col p-2">
<div class="interactive justify-between pt-1 font-bold">
<SpecialTranslation {feature} {layer} {state} t={new Translation({"*":question})} {tags} />
<SpecialTranslation {feature} {layer} {state} t={new Translation({ "*": question })} {tags} />
</div>
<LanguageOptions {selectedLanguages} countries={$countries}/>
<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()}>
<div class="flex w-full flex-wrap-reverse justify-end">
<slot name="cancel-button" />
<button
class="primary"
class:disabled={$selectedLanguages.length === 0}
on:click={(_) => applySelectedLanguages()}
>
<Tr t={Translations.t.general.save} />
</button>
</div>

View file

@ -50,120 +50,121 @@
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">
<Move_not_allowed class="m-2 h-8 w-8" />
<div class="flex flex-col">
<Tr t={t.cannotBeMoved} />
<Tr t={$notAllowed} />
{#if moveWizardState.reasons.length > 0}
{#if $notAllowed}
<div class="m-2 flex rounded-lg bg-gray-200 p-2">
<Move_not_allowed class="m-2 h-8 w-8" />
<div class="flex flex-col">
<Tr t={t.cannotBeMoved} />
<Tr t={$notAllowed} />
</div>
</div>
</div>
{:else if currentStep === "start"}
{#if moveWizardState.reasons.length === 1}
<button
class="flex"
on:click={() => {
reason.setData(moveWizardState.reasons[0])
currentStep = "pick_location"
}}
>
<ToSvelte
construct={moveWizardState.reasons[0].icon.SetStyle("height: 1.5rem; width: 1.5rem;")}
/>
<Tr t={Translations.T(moveWizardState.reasons[0].invitingText)} />
</button>
{:else}
<button
class="flex"
on:click={() => {
currentStep = "reason"
}}
>
<Move class="h-6 w-6" />
<Tr t={t.inviteToMove.generic} />
</button>
{/if}
{:else if currentStep === "reason"}
<div class="interactive border-interactive flex flex-col p-2">
<Tr cls="text-lg font-bold" t={t.whyMove} />
{#each moveWizardState.reasons as reasonSpec}
{:else if currentStep === "start"}
{#if moveWizardState.reasons.length === 1}
<button
class="flex"
on:click={() => {
reason.setData(reasonSpec)
reason.setData(moveWizardState.reasons[0])
currentStep = "pick_location"
}}
>
<ToSvelte construct={reasonSpec.icon.SetClass("w-16 h-16 pr-2")} />
<Tr t={Translations.T(reasonSpec.text)} />
<ToSvelte
construct={moveWizardState.reasons[0].icon.SetStyle("height: 1.5rem; width: 1.5rem;")}
/>
<Tr t={Translations.T(moveWizardState.reasons[0].invitingText)} />
</button>
{/each}
</div>
{:else if currentStep === "pick_location"}
<div class="border-interactive interactive flex flex-col p-2">
<Tr cls="text-lg font-bold" t={t.moveTitle} />
<div class="relative h-64 w-full">
<LocationInput
mapProperties={(currentMapProperties = initMapProperties())}
value={newLocation}
initialCoordinate={{ lon, lat }}
/>
<div class="absolute bottom-0 left-0">
<OpenBackgroundSelectorButton {state} />
</div>
</div>
{#if $reason.includeSearch}
<Geosearch bounds={currentMapProperties.bounds} clearAfterView={false} />
{/if}
<div class="flex flex-wrap">
<If
condition={currentMapProperties.zoom.mapD(
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint
)}
>
<button
class="primary flex w-full"
on:click={() => {
moveWizardState.moveFeature(newLocation.data, reason.data, featureToMove)
currentStep = "moved"
}}
>
<Move class="mr-2 h-6 w-6" />
<Tr t={t.confirmMove} />
</button>
<div slot="else" class="alert">
<Tr t={t.zoomInFurther} />
</div>
</If>
{:else}
<button
class="w-full"
class="flex"
on:click={() => {
currentStep = "start"
currentStep = "reason"
}}
>
<XCircleIcon class="mr-2 h-6 w-6" />
<Tr t={t.cancel} />
<Move class="h-6 w-6" />
<Tr t={t.inviteToMove.generic} />
</button>
{/if}
{:else if currentStep === "reason"}
<div class="interactive border-interactive flex flex-col p-2">
<Tr cls="text-lg font-bold" t={t.whyMove} />
{#each moveWizardState.reasons as reasonSpec}
<button
on:click={() => {
reason.setData(reasonSpec)
currentStep = "pick_location"
}}
>
<ToSvelte construct={reasonSpec.icon.SetClass("w-16 h-16 pr-2")} />
<Tr t={Translations.T(reasonSpec.text)} />
</button>
{/each}
</div>
{:else if currentStep === "pick_location"}
<div class="border-interactive interactive flex flex-col p-2">
<Tr cls="text-lg font-bold" t={t.moveTitle} />
<div class="relative h-64 w-full">
<LocationInput
mapProperties={(currentMapProperties = initMapProperties())}
value={newLocation}
initialCoordinate={{ lon, lat }}
/>
<div class="absolute bottom-0 left-0">
<OpenBackgroundSelectorButton {state} />
</div>
</div>
{#if $reason.includeSearch}
<Geosearch bounds={currentMapProperties.bounds} clearAfterView={false} />
{/if}
<div class="flex flex-wrap">
<If
condition={currentMapProperties.zoom.mapD(
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint
)}
>
<button
class="primary flex w-full"
on:click={() => {
moveWizardState.moveFeature(newLocation.data, reason.data, featureToMove)
currentStep = "moved"
}}
>
<Move class="mr-2 h-6 w-6" />
<Tr t={t.confirmMove} />
</button>
<div slot="else" class="alert">
<Tr t={t.zoomInFurther} />
</div>
</If>
<button
class="w-full"
on:click={() => {
currentStep = "start"
}}
>
<XCircleIcon class="mr-2 h-6 w-6" />
<Tr t={t.cancel} />
</button>
</div>
</div>
{:else if currentStep === "moved"}
<div class="flex flex-col">
<Tr cls="thanks" t={t.pointIsMoved} />
<button
on:click={() => {
currentStep = "reason"
}}
>
<Move class="h-6 w-6 pr-2" />
<Tr t={t.inviteToMoveAgain} />
</button>
</div>
</div>
{:else if currentStep === "moved"}
<div class="flex flex-col">
<Tr cls="thanks" t={t.pointIsMoved} />
<button
on:click={() => {
currentStep = "reason"
}}
>
<Move class="h-6 w-6 pr-2" />
<Tr t={t.inviteToMoveAgain} />
</button>
</div>
{/if}
{/if}
{/if}
</LoginToggle>

View file

@ -16,10 +16,7 @@ export class AddNoteCommentViz implements SpecialVisualization {
},
]
public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>
) {
public constr(state: SpecialVisualizationState, tags: UIEventSource<Record<string, string>>) {
return new SvelteUIElement(AddNoteComment, { state, tags })
}
}

View file

@ -75,7 +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:"))
.filter((link) => !link.startsWith("https://wiki.openstreetmap.org/wiki/File:"))
let imagesEl: BaseUIElement = undefined
if (images.length > 0) {
const imageEls = images.map((i) =>

View file

@ -15,6 +15,6 @@
</script>
<a class="button flex w-full items-center" href={url} style="margin-left: 0">
<Envelope class="w-8 h-8 mr-4 shrink-0"/>
<Envelope class="mr-4 h-8 w-8 shrink-0" />
{button_text}
</a>

View file

@ -156,14 +156,13 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
await state.changes.applyAction(changeAction)
try {
state.selectedElement.setData(state.indexedFeatures.featuresById.data.get(targetId))
}catch (e) {
} catch (e) {
console.error(e)
}
const maproulette_id_key = args[4]
if (maproulette_id_key) {
const maproulette_id = tags.data[ maproulette_id_key]
const maproulette_feature= state.indexedFeatures.featuresById.data.get(
maproulette_id)
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",
@ -202,9 +201,12 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
})
).SetClass("subtle")
const self = this
const applied = new UIEventSource(tags?.data?.["mr_taskStatus"] !== undefined && 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(
new SvelteUIElement(Icon, {icon: image}),
new SvelteUIElement(Icon, { icon: image }),
new Combine([msg, tagsExplanation]).SetClass("flex flex-col")
).onClick(async () => {
applied.setData(true)

View file

@ -1,16 +1,16 @@
<script lang="ts">
import { PencilAltIcon } from "@rgossiaux/svelte-heroicons/solid";
import { ariaLabel } from "../../../Utils/ariaLabel.js";
import { Translation } from "../../i18n/Translation"
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
/**
* 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

View file

@ -23,12 +23,12 @@
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>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties($tags))}
<div {id} class={twMerge("link-underline inline-block w-full", config?.classes , extraClasses)}>
<div {id} class={twMerge("link-underline inline-block w-full", config?.classes, extraClasses)}>
{#if $trs.length === 1}
<TagRenderingMapping mapping={$trs[0]} {tags} {state} {selectedElement} {layer} />
{/if}

View file

@ -34,7 +34,7 @@
onDestroy(
tags.addCallbackD((tags) => {
editMode = !config.IsKnown(tags)
}),
})
)
}
@ -104,12 +104,13 @@
{:else}
<div class="low-interaction flex items-center justify-between overflow-hidden rounded px-2">
<TagRenderingAnswer id={answerId} {config} {tags} {selectedElement} {state} {layer} />
<EditButton
<EditButton
arialabel={config.editButtonAriaLabel}
ariaLabelledBy={answerId}
on:click={() => {
editMode = true
}}/>
editMode = true
}}
/>
</div>
{/if}
{:else}

View file

@ -29,7 +29,10 @@
{#if mapping.icon !== undefined}
<div class="inline-flex items-center">
<Icon icon={mapping.icon} clss={twJoin(`mapping-icon-${mapping.iconClass ?? "small"}`, "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}

View file

@ -47,7 +47,7 @@
// 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
@ -153,16 +153,20 @@
}
})
$: {
if (allowDeleteOfFreeform && $freeformInput === undefined && $freeformInputUnvalidated === "" && (mappings?.length ?? 0) === 0) {
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,
tags.data
)
} catch (e) {
console.error("Could not calculate changeSpecification:", e)
@ -227,21 +231,19 @@
onDestroy(
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
numberOfCs = ud.csCount
}),
})
)
}
</script>
{#if question !== undefined}
<div class="relative">
<form
class="interactive border-interactive relative flex flex-col overflow-y-auto px-2"
style="max-height: 75vh"
on:submit|preventDefault={() => onSave()}
>
<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} />
@ -396,13 +398,16 @@
<slot name="save-button" {selectedTags}>
{#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}/>
<TrashIcon class="h-6 w-6 text-red-500" />
<Tr t={Translations.t.general.eraseValue} />
</button>
{:else}
<button
on:click={onSave}
class={twJoin(selectedTags === undefined ? "disabled" : "button-shadow", "primary")}
class={twJoin(
selectedTags === undefined ? "disabled" : "button-shadow",
"primary"
)}
>
<Tr t={Translations.t.general.save} />
</button>
@ -410,23 +415,21 @@
</slot>
</div>
{#if UserRelatedState.SHOW_TAGS_VALUES.indexOf($showTags) >= 0 || ($showTags === "" && numberOfCs >= Constants.userJourney.tagsVisibleAt) || $featureSwitchIsTesting || $featureSwitchIsDebugging}
<span class="flex flex-wrap justify-between">
<TagHint {state} tags={selectedTags} currentProperties={$tags} />
<span class="flex flex-wrap">
{#if $featureSwitchIsTesting}
Testmode &nbsp;
{/if}
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
<span class="subtle">{config.id}</span>
{/if}
</span>
</span>
<span class="flex flex-wrap justify-between">
<TagHint {state} tags={selectedTags} currentProperties={$tags} />
<span class="flex flex-wrap">
{#if $featureSwitchIsTesting}
Testmode &nbsp;
{/if}
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
<span class="subtle">{config.id}</span>
{/if}
</span>
</span>
{/if}
<slot name="under-buttons" />
</LoginToggle>
</fieldset>
</form>
</div>
{/if}

View file

@ -23,7 +23,7 @@ export class UploadToOsmViz implements SpecialVisualization {
const locations = state.historicalUserLocations.features.data
return new SvelteUIElement(UploadTraceToOsmUI, {
state,
trace: (title: string) => GeoOperations.toGpx(locations, title)
trace: (title: string) => GeoOperations.toGpx(locations, title),
})
}
}