forked from MapComplete/MapComplete
Merge branches
This commit is contained in:
commit
bae90d50bc
304 changed files with 49983 additions and 31589 deletions
|
|
@ -100,7 +100,7 @@
|
|||
<input
|
||||
autofocus
|
||||
bind:value={$themeSearchText}
|
||||
class="mr-4 w-full"
|
||||
class="mr-4 w-full outline-none"
|
||||
id="theme-search"
|
||||
type="search"
|
||||
use:placeholder={tr.searchForATheme}
|
||||
|
|
|
|||
49
src/UI/Base/CloseAnimation.svelte
Normal file
49
src/UI/Base/CloseAnimation.svelte
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts">
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { onDestroy, onMount } from "svelte"
|
||||
|
||||
let elem: HTMLElement
|
||||
let targetOuter: HTMLElement
|
||||
export let isOpened: Store<boolean>
|
||||
export let moveTo: Store<HTMLElement>
|
||||
|
||||
export let debug : string
|
||||
function copySizeOf(htmlElem: HTMLElement) {
|
||||
const target = htmlElem.getBoundingClientRect()
|
||||
elem.style.left = target.x + "px"
|
||||
elem.style.top = target.y + "px"
|
||||
elem.style.width = target.width + "px"
|
||||
elem.style.height = target.height + "px"
|
||||
}
|
||||
|
||||
function animate(opened: boolean) {
|
||||
const moveToElem = moveTo.data
|
||||
console.log("Animating", debug," to", opened)
|
||||
if (opened) {
|
||||
copySizeOf(targetOuter)
|
||||
elem.style.background = "var(--background-color)"
|
||||
} else if (moveToElem !== undefined) {
|
||||
copySizeOf(moveToElem)
|
||||
elem.style.background = "#ffffff00"
|
||||
} else {
|
||||
elem.style.left = "0px"
|
||||
elem.style.top = "0px"
|
||||
elem.style.background = "#ffffff00"
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(isOpened.addCallback(opened => animate(opened)))
|
||||
onMount(() => requestAnimationFrame(() => animate(isOpened.data)))
|
||||
|
||||
|
||||
</script>
|
||||
<div class={"absolute bottom-0 right-0 h-full w-screen p-4 md:p-6 pointer-events-none invisible"}>
|
||||
<div class="content h-full" bind:this={targetOuter} style="background: red" />
|
||||
</div>
|
||||
|
||||
<div bind:this={elem} class="pointer-events-none absolute bottom-0 right-0 low-interaction rounded-2xl"
|
||||
style="transition: all 0.5s ease-out, background-color 1.4s ease-out; background: var(--background-color);">
|
||||
<!-- Classes should be the same as the 'floatoaver' -->
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
*/
|
||||
const dispatch = createEventDispatcher<{ close }>()
|
||||
|
||||
export let extraClasses = "p-4 md:p-6"
|
||||
</script>
|
||||
|
||||
<!-- Draw the background over the total screen -->
|
||||
|
|
@ -24,7 +23,7 @@
|
|||
/>
|
||||
<!-- draw a _second_ absolute div, placed using 'bottom' which will be above the navigation bar on mobile browsers -->
|
||||
<div
|
||||
class={twMerge("absolute bottom-0 right-0 h-full w-screen", extraClasses)}
|
||||
class={"absolute bottom-0 right-0 h-full w-screen p-4 md:p-6"}
|
||||
style="z-index: 21"
|
||||
use:trapFocus
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { twJoin } from "tailwind-merge"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
|
||||
/**
|
||||
* A round button with an icon and possible a small text, which hovers above the map
|
||||
|
|
@ -12,9 +12,15 @@
|
|||
export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1"
|
||||
export let enabled: Store<boolean> = new ImmutableStore(true)
|
||||
export let arialabel: Translation = undefined
|
||||
export let htmlElem: UIEventSource<HTMLElement> = undefined
|
||||
let _htmlElem : HTMLElement
|
||||
$: {
|
||||
htmlElem?.setData(_htmlElem)
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
bind:this={_htmlElem}
|
||||
on:click={(e) => dispatch("click", e)}
|
||||
on:keydown
|
||||
use:ariaLabel={arialabel}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import Combine from "./Combine"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import Title from "./Title"
|
||||
import List from "./List"
|
||||
import Link from "./Link"
|
||||
import { marked } from "marked"
|
||||
import { parse as parse_html } from "node-html-parser"
|
||||
import {default as turndown} from "turndown"
|
||||
import { default as turndown } from "turndown"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export default class TableOfContents {
|
||||
|
|
@ -56,7 +53,7 @@ export default class TableOfContents {
|
|||
const htmlSource = <string>marked.parse(md)
|
||||
const el = parse_html(htmlSource)
|
||||
const structure = TableOfContents.generateStructure(<any>el)
|
||||
let firstTitle = structure[1]
|
||||
const firstTitle = structure[1]
|
||||
let minDepth = undefined
|
||||
do {
|
||||
minDepth = Math.min(...structure.map(s => s.depth))
|
||||
|
|
@ -81,7 +78,7 @@ export default class TableOfContents {
|
|||
let topLevelCount = 0
|
||||
for (const el of structure) {
|
||||
const depthDiff = el.depth - minDepth
|
||||
let link = `[${el.title}](#${TableOfContents.asLinkableId(el.title)})`
|
||||
const link = `[${el.title}](#${TableOfContents.asLinkableId(el.title)})`
|
||||
if (depthDiff === 0) {
|
||||
topLevelCount++
|
||||
toc += `${topLevelCount}. ${link}\n`
|
||||
|
|
@ -91,16 +88,14 @@ export default class TableOfContents {
|
|||
}
|
||||
|
||||
const heading = Utils.Times(() => "#", firstTitle.depth)
|
||||
toc = heading +" Table of contents\n\n"+toc
|
||||
toc = heading + " Table of contents\n\n" + toc
|
||||
|
||||
const original = el.outerHTML
|
||||
const firstTitleIndex = original.indexOf(firstTitle.el.outerHTML)
|
||||
const tocHtml = (<string>marked.parse(toc))
|
||||
const withToc = original.substring(0, firstTitleIndex) + tocHtml + original.substring(firstTitleIndex)
|
||||
const firstTitleIndex = md.indexOf(firstTitle.title)
|
||||
|
||||
const htmlToMd = new turndown()
|
||||
return htmlToMd.turndown(withToc)
|
||||
const intro = md.substring(0, firstTitleIndex)
|
||||
const splitPoint = intro.lastIndexOf("\n")
|
||||
|
||||
return md.substring(0, splitPoint) + toc + md.substring(splitPoint)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
export let state: SpecialVisualizationState
|
||||
let theme = state.layout?.id ?? ""
|
||||
let config: ExtraLinkConfig = state.layout.extraLink
|
||||
console.log(">>>",config)
|
||||
const isIframe = window !== window.top
|
||||
let basepath = window.location.host
|
||||
let showWelcomeMessageSwitch = state.featureSwitches.featureSwitchWelcomeMessage
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@
|
|||
{:else}
|
||||
<input
|
||||
type="search"
|
||||
class="w-full"
|
||||
class="w-full outline-none"
|
||||
bind:this={inputElement}
|
||||
on:keypress={(keypr) => {
|
||||
feedback = undefined
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
* Even though the component is very small, it gets its own class as it is often reused
|
||||
*/
|
||||
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import Translations from "../i18n/Translations"
|
||||
import MapControlButton from "../Base/MapControlButton.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
|
@ -16,11 +15,13 @@
|
|||
export let state: ThemeViewState
|
||||
export let map: Store<MlMap> = undefined
|
||||
export let hideTooltip = false
|
||||
export let htmlElem : UIEventSource<HTMLElement> = undefined
|
||||
</script>
|
||||
|
||||
<MapControlButton
|
||||
arialabel={Translations.t.general.labels.background}
|
||||
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}
|
||||
{htmlElem}
|
||||
>
|
||||
<StyleLoadingIndicator map={map ?? state.map} rasterLayer={state.mapProperties.rasterLayer}>
|
||||
<Square3Stack3dIcon class="h-6 w-6" />
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
<script lang="ts">
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState"
|
||||
|
||||
const t = Translations.t.privacy
|
||||
export let state: SpecialVisualizationState
|
||||
const usersettings = UserRelatedState.usersettingsConfig
|
||||
const editPrivacy = usersettings.tagRenderings.find(tr => tr.id === "more_privacy")
|
||||
const isLoggedIn = state.osmConnection.isLoggedIn
|
||||
</script>
|
||||
|
||||
<div class="link-underline flex flex-col">
|
||||
|
|
@ -20,7 +27,41 @@
|
|||
<h3>
|
||||
<Tr t={t.editingTitle} />
|
||||
</h3>
|
||||
<Tr t={t.editing} />
|
||||
<Tr t={t.editingIntro} />
|
||||
<Tr t={t.editingIntro} />
|
||||
<ul>
|
||||
<li>
|
||||
<Tr t={t.items.changesYouMake} />
|
||||
</li>
|
||||
<li>
|
||||
<Tr t={t.items.username} />
|
||||
</li>
|
||||
<li>
|
||||
<Tr t={t.items.date} />
|
||||
</li>
|
||||
<li>
|
||||
<Tr t={t.items.theme} />
|
||||
</li>
|
||||
<li>
|
||||
<Tr t={t.items.language} />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
{#if $isLoggedIn}
|
||||
<TagRenderingEditable config={editPrivacy} selectedElement={{
|
||||
type: "Feature",
|
||||
properties: { id: "settings" },
|
||||
geometry: { type: "Point", coordinates: [0, 0] },
|
||||
}}
|
||||
{state}
|
||||
tags={state.userRelatedState.preferencesAsTags} />
|
||||
{:else}
|
||||
<Tr t={t.items.distanceIndicator} />
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Tr t={t.editingOutro} />
|
||||
|
||||
<h3>
|
||||
<Tr t={t.miscCookiesTitle} />
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
import Add from "../../assets/svg/Add.svelte"
|
||||
import Location_refused from "../../assets/svg/Location_refused.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
|
||||
|
||||
/**
|
||||
* The theme introduction panel
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
<div class="flex h-full flex-col justify-between">
|
||||
<div>
|
||||
<!-- Intro, description, ... -->
|
||||
|
||||
<Tr t={layout.description} />
|
||||
<Tr t={Translations.t.general.welcomeExplanation.general} />
|
||||
{#if layout.layers.some((l) => l.presets?.length > 0)}
|
||||
|
|
|
|||
|
|
@ -109,16 +109,17 @@
|
|||
<div class="low-interaction border-interactive p-1">
|
||||
{#if !readonly}
|
||||
<Tr t={t.loadedFrom.Subs({ url: sourceUrl, source: sourceUrl })} />
|
||||
<h3>
|
||||
<Tr t={t.conflicting.title} />
|
||||
</h3>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col" class:gap-y-8={!readonly}>
|
||||
{#if !readonly}
|
||||
<Tr t={t.conflicting.intro} />
|
||||
{/if}
|
||||
|
||||
{#if $different.length > 0}
|
||||
{#if !readonly}
|
||||
<h3>
|
||||
<Tr t={t.conflicting.title} />
|
||||
</h3>
|
||||
<Tr t={t.conflicting.intro} />
|
||||
{/if}
|
||||
{#each $different as key (key)}
|
||||
<div class="mx-2 rounded-2xl">
|
||||
<ComparisonAction
|
||||
|
|
@ -135,6 +136,13 @@
|
|||
{/if}
|
||||
|
||||
{#if $missing.length > 0}
|
||||
{#if !readonly}
|
||||
<h3 class="m-0">
|
||||
<Tr t={t.missing.title} />
|
||||
</h3>
|
||||
|
||||
<Tr t={t.missing.intro} />
|
||||
{/if}
|
||||
{#if currentStep === "init"}
|
||||
{#each $missing as key (key)}
|
||||
<div class:glowing-shadow={applyAllHovered} class="mx-2 rounded-2xl">
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@
|
|||
addExtraTags = helperArgs[1].split(";")
|
||||
}
|
||||
|
||||
const path = `${key}s/${maintag.split("=")[0]}/${maintag.split("=")[1]}`
|
||||
const [k, v] = maintag.split("=")
|
||||
|
||||
let items: NSIItem[] = NameSuggestionIndex.getSuggestionsFor(path, feature.properties["_country"], GeoOperations.centerpointCoordinates(feature))
|
||||
let items: NSIItem[] = NameSuggestionIndex.getSuggestionsFor(key, k, v, feature.properties["_country"], GeoOperations.centerpointCoordinates(feature))
|
||||
|
||||
let selectedItem: NSIItem = items.find((item) => item.tags[key] === value.data)
|
||||
|
||||
|
|
@ -113,10 +113,10 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
|
||||
{#if items?.length >= 0}
|
||||
<div>
|
||||
<div class="normal-background my-2 flex w-5/6 justify-between rounded-full pl-2">
|
||||
<!-- TODO translate placeholder -->
|
||||
<input type="text" placeholder="Filter entries" bind:value={filter} class="outline-none" />
|
||||
<SearchIcon aria-hidden="true" class="h-6 w-6 self-end" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
|
||||
import SlopeInput from "./Helpers/SlopeInput.svelte"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import NameSuggestionIndexInput from "./Helpers/NameSuggestionIndexInput.svelte"
|
||||
|
||||
export let type: ValidatorType
|
||||
export let value: UIEventSource<string | object>
|
||||
|
|
@ -26,6 +27,9 @@
|
|||
export let feature: Feature
|
||||
export let args: (string | number | boolean)[] = undefined
|
||||
export let state: SpecialVisualizationState
|
||||
export let helperArgs: (string | number | boolean)[]
|
||||
export let key: string
|
||||
export let extraTags: UIEventSource<Record<string, string>>
|
||||
|
||||
let properties = { feature, args: args ?? [] }
|
||||
</script>
|
||||
|
|
@ -50,4 +54,6 @@
|
|||
<SlopeInput {value} {feature} {state} />
|
||||
{:else if type === "wikidata"}
|
||||
<ToSvelte construct={() => InputHelpers.constructWikidataHelper(value, properties)} />
|
||||
{:else if type === "nsi"}
|
||||
<NameSuggestionIndexInput {value} {feature} {helperArgs} {key} {extraTags} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import TagValidator from "./Validators/TagValidator"
|
|||
import IdValidator from "./Validators/IdValidator"
|
||||
import SlopeValidator from "./Validators/SlopeValidator"
|
||||
import VeloparkValidator from "./Validators/VeloparkValidator"
|
||||
import NameSuggestionIndexValidator from "./Validators/NameSuggestionIndexValidator"
|
||||
import CurrencyValidator from "./Validators/CurrencyValidator"
|
||||
|
||||
export type ValidatorType = (typeof Validators.availableTypes)[number]
|
||||
|
|
@ -61,6 +62,7 @@ export default class Validators {
|
|||
"id",
|
||||
"slope",
|
||||
"velopark",
|
||||
"nsi",
|
||||
"currency"
|
||||
] as const
|
||||
|
||||
|
|
@ -91,6 +93,7 @@ export default class Validators {
|
|||
new IdValidator(),
|
||||
new SlopeValidator(),
|
||||
new VeloparkValidator(),
|
||||
new NameSuggestionIndexValidator(),
|
||||
new CurrencyValidator()
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import Title from "../../Base/Title"
|
||||
import Combine from "../../Base/Combine"
|
||||
import { Validator } from "../Validator"
|
||||
import Table from "../../Base/Table"
|
||||
|
||||
export default class NameSuggestionIndexValidator extends Validator {
|
||||
constructor() {
|
||||
super(
|
||||
"nsi",
|
||||
new Combine([
|
||||
"Gives a list of possible suggestions for a brand or operator tag.",
|
||||
new Title("Helper arguments"),
|
||||
new Table(
|
||||
["name", "doc"],
|
||||
[
|
||||
[
|
||||
"options",
|
||||
new Combine([
|
||||
"A JSON-object of type `{ main: string, key: string }`. ",
|
||||
new Table(
|
||||
["subarg", "doc"],
|
||||
[
|
||||
[
|
||||
"main",
|
||||
"The main tag to give suggestions for, e.g. `amenity=restaurant`.",
|
||||
],
|
||||
[
|
||||
"addExtraTags",
|
||||
"Extra tags to add to the suggestions, e.g. `nobrand=yes`.",
|
||||
],
|
||||
]
|
||||
),
|
||||
]),
|
||||
],
|
||||
]
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import { Stores, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
let isLoading = false
|
||||
export let map: UIEventSource<MlMap>
|
||||
export let map: Store<MlMap>
|
||||
/**
|
||||
* Optional. Only used for the 'global' change indicator so that it won't spin on pan/zoom but only when a change _actually_ occured
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ class ApplyButton extends UIElement {
|
|||
}
|
||||
|
||||
export default class AutoApplyButton implements SpecialVisualization {
|
||||
public readonly docs: BaseUIElement
|
||||
public readonly docs: string
|
||||
public readonly funcName: string = "auto_apply"
|
||||
public readonly needsUrls = []
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ export default class AutoApplyButton implements SpecialVisualization {
|
|||
"Then, use a calculated tag on the host feature to determine the overlapping object ids",
|
||||
"At last, add this component",
|
||||
]),
|
||||
])
|
||||
]).AsMarkdown()
|
||||
}
|
||||
|
||||
constr(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
export let unvalidatedText: UIEventSource<string> = new UIEventSource<string>(value.data)
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let extraTags: UIEventSource<Record<string, string>>
|
||||
|
||||
export let feature: Feature = undefined
|
||||
export let state: SpecialVisualizationState
|
||||
|
|
@ -27,6 +28,8 @@
|
|||
inline = false
|
||||
inline = config.freeform?.inline
|
||||
}
|
||||
let helperArgs = config.freeform?.helperArgs
|
||||
let key = config.freeform?.key
|
||||
|
||||
const dispatch = createEventDispatcher<{ selected }>()
|
||||
export let feedback: UIEventSource<Translation>
|
||||
|
|
@ -75,6 +78,9 @@
|
|||
type={config.freeform.type}
|
||||
{value}
|
||||
{state}
|
||||
{helperArgs}
|
||||
{key}
|
||||
{extraTags}
|
||||
on:submit
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
console.log("Applying questions to ask")
|
||||
const qta = questionsToAsk.data
|
||||
firstQuestion.setData(undefined)
|
||||
allQuestionsToAsk.setData([])
|
||||
//allQuestionsToAsk.setData([])
|
||||
await Utils.awaitAnimationFrame()
|
||||
firstQuestion.setData(qta[0])
|
||||
allQuestionsToAsk.setData(qta)
|
||||
|
|
|
|||
|
|
@ -30,15 +30,18 @@
|
|||
import { placeholder } from "../../../Utils/placeholder"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { Tag } from "../../../Logic/Tags/Tag"
|
||||
import { And } from "../../../Logic/Tags/And"
|
||||
import { get } from "svelte/store"
|
||||
import Markdown from "../../Base/Markdown.svelte"
|
||||
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
||||
export let selectedElement: Feature
|
||||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig | undefined
|
||||
export let selectedTags: UploadableTag = undefined
|
||||
export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({})
|
||||
|
||||
export let allowDeleteOfFreeform: boolean = false
|
||||
|
||||
|
|
@ -69,6 +72,8 @@
|
|||
/**
|
||||
* Prepares and fills the checkedMappings
|
||||
*/
|
||||
console.log("Initing ", config.id)
|
||||
|
||||
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig): void {
|
||||
mappings = confg.mappings?.filter((m) => {
|
||||
if (typeof m.hideInAnswer === "boolean") {
|
||||
|
|
@ -76,8 +81,10 @@
|
|||
}
|
||||
return !m.hideInAnswer.matchesProperties(tgs)
|
||||
})
|
||||
selectedMapping = mappings?.findIndex(mapping => mapping.if.matchesProperties(tgs) || mapping.alsoShowIf?.matchesProperties(tgs))
|
||||
if(selectedMapping < 0){
|
||||
selectedMapping = mappings?.findIndex(
|
||||
(mapping) => mapping.if.matchesProperties(tgs) || mapping.alsoShowIf?.matchesProperties(tgs)
|
||||
)
|
||||
if (selectedMapping < 0) {
|
||||
selectedMapping = undefined
|
||||
}
|
||||
// We received a new config -> reinit
|
||||
|
|
@ -102,7 +109,7 @@
|
|||
seenFreeforms.push(newProps[confg.freeform.key])
|
||||
}
|
||||
return matches
|
||||
})
|
||||
}),
|
||||
]
|
||||
|
||||
if (tgs !== undefined && confg.freeform) {
|
||||
|
|
@ -133,15 +140,35 @@
|
|||
freeformInput.set(undefined)
|
||||
}
|
||||
feedback.setData(undefined)
|
||||
|
||||
|
||||
}
|
||||
|
||||
$: {
|
||||
// Even though 'config' is not declared as a store, Svelte uses it as one to update the component
|
||||
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
|
||||
initialize($tags, config)
|
||||
}
|
||||
let usedKeys: string[] = config.usedTags().flatMap((t) => t.usedKeys())
|
||||
/**
|
||||
* The 'minimalTags' is a subset of the tags of the feature, only containing the values relevant for this object.
|
||||
* The main goal is to be stable and only 'ping' when an actual change is relevant
|
||||
*/
|
||||
let minimalTags = new UIEventSource<Record<string, string>>(undefined)
|
||||
tags.addCallbackAndRunD((tags) => {
|
||||
const previousMinimal = minimalTags.data
|
||||
const newMinimal: Record<string, string> = {}
|
||||
let somethingChanged = previousMinimal === undefined
|
||||
for (const key of usedKeys) {
|
||||
const newValue = tags[key]
|
||||
somethingChanged ||= previousMinimal?.[key] !== newValue
|
||||
if (newValue !== undefined && newValue !== null) {
|
||||
newMinimal[key] = newValue
|
||||
}
|
||||
}
|
||||
if (somethingChanged) {
|
||||
console.log("Updating minimal tags to", newMinimal, "of", config.id)
|
||||
minimalTags.setData(newMinimal)
|
||||
}
|
||||
})
|
||||
|
||||
minimalTags.addCallbackAndRunD((tgs) => {
|
||||
initialize(tgs, config)
|
||||
})
|
||||
|
||||
onDestroy(
|
||||
freeformInput.subscribe((freeformValue) => {
|
||||
if (!mappings || mappings?.length == 0 || config.freeform?.key === undefined) {
|
||||
|
|
@ -178,23 +205,50 @@
|
|||
checkedMappings,
|
||||
tags.data
|
||||
)
|
||||
if(state.featureSwitches.featureSwitchIsDebugging.data){
|
||||
console.log("Constructing change spec from", {freeform: $freeformInput, selectedMapping, checkedMappings, currentTags: tags.data}, " --> ", selectedTags)
|
||||
if (featureSwitchIsDebugging?.data) {
|
||||
console.log(
|
||||
"Constructing change spec from",
|
||||
{
|
||||
freeform: $freeformInput,
|
||||
selectedMapping,
|
||||
checkedMappings,
|
||||
currentTags: tags.data,
|
||||
},
|
||||
" --> ",
|
||||
selectedTags
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e)
|
||||
selectedTags = undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (extraTags.data) {
|
||||
// Map the extraTags into an array of Tag objects
|
||||
const extraTagsArray = Object.entries(extraTags.data).map(([k, v]) => new Tag(k, v))
|
||||
|
||||
// Check the type of selectedTags
|
||||
if (selectedTags instanceof Tag) {
|
||||
// Re-define selectedTags as an And
|
||||
selectedTags = new And([selectedTags, ...extraTagsArray])
|
||||
} else if (selectedTags instanceof And) {
|
||||
// Add the extraTags to the existing And
|
||||
selectedTags = new And([...selectedTags.and, ...extraTagsArray])
|
||||
} else {
|
||||
console.error("selectedTags is not of type Tag or And")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onSave(_ = undefined) {
|
||||
if (selectedTags === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (layer === undefined || (layer?.source === null && layer.id !== "favourite")) {
|
||||
/**
|
||||
* This is a special, priviliged layer.
|
||||
* This is a special, privileged layer.
|
||||
* We simply apply the tags onto the records
|
||||
*/
|
||||
const kv = selectedTags.asChange(tags.data)
|
||||
|
|
@ -213,7 +267,7 @@
|
|||
dispatch("saved", { config, applied: selectedTags })
|
||||
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
|
||||
theme: tags.data["_orig_theme"] ?? state.layout.id,
|
||||
changeType: "answer"
|
||||
changeType: "answer",
|
||||
})
|
||||
freeformInput.set(undefined)
|
||||
selectedMapping = undefined
|
||||
|
|
@ -266,7 +320,7 @@
|
|||
|
||||
{#if config.questionhint}
|
||||
{#if config.questionHintIsMd}
|
||||
<Markdown srcWritable={ config.questionhint.current} />
|
||||
<Markdown srcWritable={config.questionhint.current} />
|
||||
{:else}
|
||||
<div class="max-h-60 overflow-y-auto">
|
||||
<SpecialTranslation
|
||||
|
|
@ -277,7 +331,7 @@
|
|||
feature={selectedElement}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</legend>
|
||||
|
||||
|
|
@ -301,6 +355,7 @@
|
|||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
{extraTags}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
|
|
@ -344,6 +399,7 @@
|
|||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
{extraTags}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
|
|
@ -388,6 +444,7 @@
|
|||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
{extraTags}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,15 @@
|
|||
import { Utils } from "../Utils"
|
||||
import Add from "../assets/svg/Add.svelte"
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
|
||||
import type { SpecialVisualizationState } from "./SpecialVisualization"
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||
|
||||
const osmConnection = new OsmConnection()
|
||||
let state: SpecialVisualizationState = {
|
||||
osmConnection,
|
||||
userRelatedState: new UserRelatedState(osmConnection)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-screen flex-col overflow-hidden px-4">
|
||||
|
|
@ -17,7 +26,7 @@
|
|||
<LanguagePicker availableLanguages={Translations.t.privacy.intro.SupportedLanguages()} />
|
||||
</div>
|
||||
<div class="h-full overflow-auto border border-gray-500 p-4">
|
||||
<PrivacyPolicy />
|
||||
<PrivacyPolicy {state} />
|
||||
</div>
|
||||
<a class="button flex" href={Utils.HomepageLink()}>
|
||||
<Add class="h-6 w-6" />
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||
import BaseUIElement from "./BaseUIElement"
|
||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
||||
import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
|
||||
import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
||||
import { Changes } from "../Logic/Osm/Changes"
|
||||
import { ExportableMap, MapProperties } from "../Models/MapProperties"
|
||||
import LayerState from "../Logic/State/LayerState"
|
||||
import { Feature, Geometry, Point } from "geojson"
|
||||
import { Feature, Geometry, Point, Polygon } from "geojson"
|
||||
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
|
||||
import { MangroveIdentity } from "../Logic/Web/MangroveReviews"
|
||||
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
|
||||
|
|
@ -61,8 +61,10 @@ export interface SpecialVisualizationState {
|
|||
|
||||
readonly selectedElement: UIEventSource<Feature>
|
||||
|
||||
readonly currentView: FeatureSource<Feature<Polygon>>
|
||||
readonly favourites: FavouritesFeatureSource
|
||||
|
||||
|
||||
/**
|
||||
* If data is currently being fetched from external sources
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -11,7 +11,7 @@
|
|||
export let configs: ConfigMeta[]
|
||||
export let title: string | undefined = undefined
|
||||
|
||||
export let path: (string | number)[] = []
|
||||
export let path: readonly (string | number)[] = []
|
||||
|
||||
let expertMode = state.expertMode
|
||||
let configsNoHidden = configs.filter((schema) => schema.hints?.group !== "hidden")
|
||||
|
|
@ -21,9 +21,9 @@
|
|||
</script>
|
||||
|
||||
{#if configs === undefined}
|
||||
Bug: 'Region' received 'undefined'
|
||||
Bug: 'Region' received 'undefined' at {path.join(".")}
|
||||
{:else if configs.length === 0}
|
||||
Bug: Region received empty list as configuration
|
||||
Bug: Region received empty list as configuration at {path.join(".")}
|
||||
{:else if title}
|
||||
<div class="flex w-full flex-col">
|
||||
<h3>{title}</h3>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import type { ConfigMeta } from "./configMeta"
|
||||
import type {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson,
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
|
|
@ -59,8 +59,8 @@
|
|||
labelMapping = {
|
||||
if: "value=" + label,
|
||||
then: {
|
||||
en: "Builtin collection <b>" + label + "</b>:",
|
||||
},
|
||||
en: "Builtin collection <b>" + label + "</b>:"
|
||||
}
|
||||
}
|
||||
perLabel[label] = labelMapping
|
||||
mappingsBuiltin.push(labelMapping)
|
||||
|
|
@ -72,14 +72,14 @@
|
|||
mappingsBuiltin.push({
|
||||
if: "value=" + tr["id"],
|
||||
then: {
|
||||
en: "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>",
|
||||
},
|
||||
en: "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson>{
|
||||
question: "Which builtin element should be shown?",
|
||||
mappings: mappingsBuiltin,
|
||||
mappings: mappingsBuiltin
|
||||
})
|
||||
|
||||
const tags = new UIEventSource({ value })
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
"condition",
|
||||
"metacondition",
|
||||
"mappings",
|
||||
"icon",
|
||||
"icon"
|
||||
])
|
||||
const ignored = new Set(["labels", "description", "classes"])
|
||||
|
||||
|
|
@ -196,7 +196,10 @@
|
|||
<h3>Text field and input element configuration</h3>
|
||||
<div class="border-l border-dashed border-gray-800 pl-2">
|
||||
<SchemaBasedField {state} path={[...path, "render"]} schema={topLevelItems["render"]} />
|
||||
<Region {state} {path} configs={freeformSchema} />
|
||||
{#if freeformSchema?.length > 0}
|
||||
<!-- In read-only cases, (e.g. popup title) there will be no freeform-schema to set and thus freeformSchema will be undefined -->
|
||||
<Region {state} {path} configs={freeformSchema} />
|
||||
{/if}
|
||||
<SchemaBasedField {state} path={[...path, "icon"]} schema={topLevelItems["icon"]} />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
EyeIcon,
|
||||
HeartIcon,
|
||||
MenuIcon,
|
||||
XCircleIcon,
|
||||
XCircleIcon
|
||||
} from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Tr from "./Base/Tr.svelte"
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
|
||||
|
|
@ -73,6 +73,8 @@
|
|||
import { BBox } from "../Logic/BBox"
|
||||
import ReviewsOverview from "./Reviews/ReviewsOverview.svelte"
|
||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton.svelte"
|
||||
import CloseAnimation from "./Base/CloseAnimation.svelte"
|
||||
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
|
||||
|
||||
export let state: ThemeViewState
|
||||
let layout = state.layout
|
||||
|
|
@ -138,7 +140,7 @@
|
|||
const bottomRight = mlmap.unproject([rect.right, rect.bottom])
|
||||
const bbox = new BBox([
|
||||
[topLeft.lng, topLeft.lat],
|
||||
[bottomRight.lng, bottomRight.lat],
|
||||
[bottomRight.lng, bottomRight.lat]
|
||||
])
|
||||
state.visualFeedbackViewportBounds.setData(bbox)
|
||||
}
|
||||
|
|
@ -151,7 +153,7 @@
|
|||
})
|
||||
let featureSwitches: FeatureSwitchState = state.featureSwitches
|
||||
let availableLayers = state.availableLayers
|
||||
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
|
||||
let currentViewLayer: LayerConfig = layout.layers.find((l) => l.id === "current_view")
|
||||
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
|
||||
let rasterLayerName =
|
||||
rasterLayer.data?.properties?.name ??
|
||||
|
|
@ -183,6 +185,22 @@
|
|||
const animation = mlmap.keyboard?.keydown(e)
|
||||
animation?.cameraAnimation(mlmap)
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for the animations
|
||||
*/
|
||||
let openMapButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
let openMenuButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
let openCurrentViewLayerButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
let _openNewElementButton: HTMLButtonElement
|
||||
let openNewElementButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
|
||||
$: {
|
||||
openNewElementButton.setData(_openNewElementButton)
|
||||
}
|
||||
let openFilterButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
let openBackgroundButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
|
||||
</script>
|
||||
|
||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
||||
|
|
@ -228,6 +246,7 @@
|
|||
<MapControlButton
|
||||
on:click={() => state.guistate.themeIsOpened.setData(true)}
|
||||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openMapButton}
|
||||
>
|
||||
<div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2">
|
||||
<img
|
||||
|
|
@ -245,15 +264,17 @@
|
|||
arialabel={Translations.t.general.labels.menu}
|
||||
on:click={() => state.guistate.menuIsOpened.setData(true)}
|
||||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openMenuButton}
|
||||
>
|
||||
<MenuIcon class="h-8 w-8 cursor-pointer" />
|
||||
</MapControlButton>
|
||||
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
|
||||
<MapControlButton
|
||||
on:click={() => {
|
||||
state.selectedElement.setData(state.currentView.features?.data?.[0])
|
||||
state.selectCurrentView()
|
||||
}}
|
||||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openCurrentViewLayerButton}
|
||||
>
|
||||
<ToSvelte
|
||||
construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")}
|
||||
|
|
@ -287,6 +308,7 @@
|
|||
<button
|
||||
class="pointer-events-auto w-fit low-interaction"
|
||||
class:disabled={$currentZoom < Constants.minZoomLevelToAddNewPoint}
|
||||
bind:this={_openNewElementButton}
|
||||
on:click={() => {
|
||||
state.openNewDialog()
|
||||
}}
|
||||
|
|
@ -310,12 +332,13 @@
|
|||
arialabel={Translations.t.general.labels.filter}
|
||||
on:click={() => state.guistate.openFilterView()}
|
||||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openFilterButton}
|
||||
>
|
||||
<Filter class="h-6 w-6" />
|
||||
</MapControlButton>
|
||||
</If>
|
||||
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
||||
<OpenBackgroundSelectorButton hideTooltip={true} {state} />
|
||||
<OpenBackgroundSelectorButton hideTooltip={true} {state} htmlElem={openBackgroundButton} />
|
||||
</If>
|
||||
<a
|
||||
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
|
||||
|
|
@ -638,7 +661,7 @@
|
|||
<Tr t={Translations.t.privacy.title} />
|
||||
</h2>
|
||||
<div class="overflow-auto p-4">
|
||||
<PrivacyPolicy />
|
||||
<PrivacyPolicy {state}/>
|
||||
</div>
|
||||
</div>
|
||||
</FloatOver>
|
||||
|
|
@ -657,3 +680,11 @@
|
|||
</div>
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
|
||||
<CloseAnimation isOpened={state.guistate.themeIsOpened} moveTo={openMapButton} debug="theme"/>
|
||||
<CloseAnimation isOpened={state.guistate.menuIsOpened} moveTo={openMenuButton} debug="menu"/>
|
||||
<CloseAnimation isOpened={selectedLayer.map(sl => (sl !== undefined && sl === currentViewLayer))} moveTo={openCurrentViewLayerButton} debug="currentViewLayer"/>
|
||||
<CloseAnimation isOpened={selectedElement.map(sl =>{ console.log("SE is", sl); return sl !== undefined && sl?.properties?.id === LastClickFeatureSource.newPointElementId })} moveTo={openNewElementButton} debug="newElement"/>
|
||||
<CloseAnimation isOpened={state.guistate.filtersPanelIsOpened} moveTo={openFilterButton} debug="filter"/>
|
||||
<CloseAnimation isOpened={state.guistate.backgroundLayerSelectionIsOpened} moveTo={openBackgroundButton} debug="bg"/>
|
||||
|
|
|
|||
|
|
@ -70,8 +70,7 @@ export default class Locale {
|
|||
}
|
||||
|
||||
if (!Utils.runningFromConsole) {
|
||||
// @ts-ignore
|
||||
window.setLanguage = function (language: string) {
|
||||
window["setLanguage"] = function (language: string) {
|
||||
source.setData(language)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue