forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
7c5170da15
193 changed files with 4011 additions and 4462 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}>()
|
||||
|
|
|
|||
|
|
@ -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] === ""}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{/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
|
||||
{/if}
|
||||
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
||||
<span class="subtle">{config.id}</span>
|
||||
{/if}
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
<slot name="under-buttons" />
|
||||
</LoginToggle>
|
||||
</fieldset>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue