Accessibility: improve keyboard only flow (see #1181); remove some legacy use of Svelte

This commit is contained in:
Pieter Vander Vennet 2023-12-06 17:27:30 +01:00
parent d1a6c11513
commit 4ee83cfe5c
35 changed files with 613 additions and 683 deletions

View file

@ -36,7 +36,9 @@
dispatcher("submit", e.dataTransfer.files)
}}
>
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")} for={"fileinput" + id}>
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")}
tabindex="0" for={"fileinput" + id}
on:click={() => {console.log("Clicked", inputElement); inputElement.click()}}>
<slot />
</label>
<input

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from "svelte"
import { createEventDispatcher, onMount } from "svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { twMerge } from "tailwind-merge"
@ -9,6 +9,12 @@
const dispatch = createEventDispatcher<{ close }>()
export let extraClasses = "p-4 md:p-6"
let mainContent: HTMLElement
onMount(() => {
console.log("Mounting floatover")
mainContent?.focus()
})
</script>
<div
@ -18,7 +24,7 @@
dispatch("close")
}}
>
<div class="content normal-background" on:click|stopPropagation={() => {}}>
<div bind:this={mainContent} class="content normal-background" on:click|stopPropagation={() => {}}>
<div class="h-full rounded-xl">
<slot />
</div>

View file

@ -1,25 +1,36 @@
<script lang="ts">
import { createEventDispatcher } from "svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { createEventDispatcher, onMount } from "svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { Utils } from "../../Utils";
/**
* The slotted element will be shown on the right side
*/
const dispatch = createEventDispatcher<{ close }>()
const dispatch = createEventDispatcher<{ close }>();
let mainContent: HTMLElement;
onMount(() => {
window.setTimeout(
() => Utils.focusOnFocusableChild(mainContent), 250
)
})
</script>
<div
bind:this={mainContent}
class="absolute top-0 right-0 h-screen w-full overflow-y-auto drop-shadow-2xl md:w-6/12 lg:w-5/12 xl:w-4/12"
style="max-width: 100vw; max-height: 100vh"
>
<div class="normal-background m-0 flex flex-col">
<slot name="close-button">
<div
<button
class="absolute right-10 top-10 h-8 w-8 cursor-pointer"
on:click={() => dispatch("close")}
>
<XCircleIcon />
</div>
</button>
</slot>
<slot />
</div>

View file

@ -30,7 +30,7 @@
}
</script>
<div class="tabbedgroup flex h-full w-full">
<div class="tabbedgroup flex h-full w-full focusable">
<TabGroup
class="flex h-full w-full flex-col"
defaultIndex={1}

View file

@ -19,6 +19,7 @@
href={LinkToWeblate.hrefToWeblate($language, context)}
target="_blank"
class="weblate-link mx-1"
tabindex="-1"
>
<Translate class="font-gray" />
</a>
@ -27,6 +28,7 @@
href={LinkToWeblate.hrefToWeblate($language, context)}
class="weblate-link hidden-on-mobile mx-1"
target="_blank"
tabindex="-1"
>
<Translate class="font-gray inline-block" />
</a>

View file

@ -1,7 +1,6 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg.js"
import Translations from "../i18n/Translations"
@ -15,7 +14,6 @@
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined
export let bounds: UIEventSource<BBox>
export let selectedElement: UIEventSource<Feature> | undefined = undefined
export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined
export let clearAfterView: boolean = true
@ -34,8 +32,11 @@
let feedback: string = undefined
Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
feedback = undefined
requestAnimationFrame(() => {
inputElement?.focus()
inputElement?.select()
})
})
const dispatch = createEventDispatcher<{ searchCompleted; searchIsValid: boolean }>()
@ -73,8 +74,12 @@
const layers = Array.from(perLayer?.values() ?? [])
for (const layer of layers) {
const found = layer.features.data.find((f) => f.properties.id === id)
selectedElement?.setData(found)
selectedLayer?.setData(layer.layer.layerDef)
if (found === undefined) {
continue;
}
selectedElement?.setData(found);
console.log("Found an element that probably matches:", selectedElement?.data);
break;
}
}
if (clearAfterView) {

View file

@ -1,35 +1,25 @@
<script lang="ts">
import type { Feature } from "geojson"
import { UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
import { onDestroy } from "svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import type { Feature } from "geojson";
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let selectedElement: Feature
export let tags: UIEventSource<Record<string, string>>
export let state: SpecialVisualizationState;
export let layer: LayerConfig;
export let selectedElement: Feature;
let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(selectedElement.properties.id);
$: {
tags = state.featureProperties.getStore(selectedElement.properties.id);
}
let _tags: Record<string, string>
onDestroy(
tags.addCallbackAndRun((tags) => {
_tags = tags
})
)
let _metatags: Record<string, string>
onDestroy(
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
_metatags = tags
})
)
let metatags: Store<Record<string, string>> = state.userRelatedState.preferencesAsTags;
</script>
{#if _tags._deleted === "yes"}
{#if $tags._deleted === "yes"}
<Tr t={Translations.t.delete.isDeleted} />
{:else}
<div
@ -44,7 +34,7 @@
class="no-weblate title-icons links-as-button mr-2 flex flex-row flex-wrap items-center gap-x-0.5 p-1 pt-0.5 sm:pt-1"
>
{#each layer.titleIcons as titleIconConfig}
{#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ..._metatags, ..._tags } ) ?? true) && titleIconConfig.IsKnown(_tags)}
{#if (titleIconConfig.condition?.matchesProperties($tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({ ...$metatags, ...$tags }) ?? true) && titleIconConfig.IsKnown($tags)}
<div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}>
<TagRenderingAnswer
config={titleIconConfig}
@ -59,15 +49,15 @@
{/each}
</div>
</div>
<XCircleIcon
class="h-8 w-8 cursor-pointer"
on:click={() => state.selectedElement.setData(undefined)}
/>
<button on:click={() => state.selectedElement.setData(undefined)} class="border-none p-0">
<XCircleIcon class="h-8 w-8" />
</button>
</div>
{/if}
<style>
:global(.title-icons a) {
display: block !important;
}
:global(.title-icons a) {
display: block !important;
}
</style>

View file

@ -1,19 +1,23 @@
<script lang="ts">
import type { Feature } from "geojson"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
import { onDestroy } from "svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let selectedElement: Feature
export let tags: UIEventSource<Record<string, string>>
export let highlightedRendering: UIEventSource<string> = undefined
let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(selectedElement.properties.id)
$: {
tags = state.featureProperties.getStore(selectedElement.properties.id)
}
let _metatags: Record<string, string>
onDestroy(
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
@ -21,20 +25,12 @@
})
)
let knownTagRenderings = layer.tagRenderings.filter(
let knownTagRenderings: Store<TagRenderingConfig[]> = tags.mapD(tgs => layer.tagRenderings.filter(
(config) =>
(config.condition?.matchesProperties($tags) ?? true) &&
config.metacondition?.matchesProperties({ ...$tags, ..._metatags } ?? true) &&
config.IsKnown($tags)
)
$: {
knownTagRenderings = layer.tagRenderings.filter(
(config) =>
(config.condition?.matchesProperties($tags) ?? true) &&
config.metacondition?.matchesProperties({ ...$tags, ..._metatags } ?? true) &&
config.IsKnown($tags)
)
}
(config.condition?.matchesProperties(tgs) ?? true) &&
config.metacondition?.matchesProperties({ ...tgs, ..._metatags } ?? true) &&
config.IsKnown(tgs)
))
</script>
{#if $tags._deleted === "yes"}
@ -43,8 +39,8 @@
<Tr t={Translations.t.general.returnToTheMap} />
</button>
{:else}
<div class="flex h-full flex-col gap-y-2 overflow-y-auto p-1 px-2">
{#each knownTagRenderings as config (config.id)}
<div class="flex h-full flex-col gap-y-2 overflow-y-auto p-1 px-2 focusable" tabindex="-1">
{#each $knownTagRenderings as config (config.id)}
<TagRenderingEditable
{tags}
{config}
@ -52,7 +48,7 @@
{selectedElement}
{layer}
{highlightedRendering}
clss={knownTagRenderings.length === 1 ? "h-full" : "tr-length-" + knownTagRenderings.length}
clss={$knownTagRenderings.length === 1 ? "h-full" : "tr-length-" + $knownTagRenderings.length}
/>
{/each}
</div>

View file

@ -23,7 +23,6 @@
export let state: ThemeViewState
let layout = state.layout
let selectedElement = state.selectedElement
let selectedLayer = state.selectedLayer
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
let searchEnabled = false
@ -116,7 +115,6 @@
}}
perLayer={state.perLayer}
{selectedElement}
{selectedLayer}
{triggerSearch}
/>
</div>

View file

@ -27,7 +27,6 @@
}
function select() {
state.selectedLayer.setData(favConfig);
state.selectedElement.setData(feature);
center();
}

View file

@ -13,11 +13,12 @@
}
let imgEl: HTMLImageElement
export let imgClass: string = undefined
</script>
<div class="relative">
<img bind:this={imgEl} src={image.url} on:error={(event) => {
<img bind:this={imgEl} src={image.url} class={imgClass ?? ""} on:error={(event) => {
if(fallbackImage){
imgEl.src = fallbackImage
}

View file

@ -7,9 +7,10 @@ import ImageAttribution from "./ImageAttribution.svelte"
import ImagePreview from "./ImagePreview.svelte"
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
import { Utils } from "../../Utils"
import { twMerge } from "tailwind-merge";
export let image: ProvidedImage
export let clss: string = undefined
async function download() {
const response = await fetch(image.url)
const blob = await response.blob()
@ -20,7 +21,7 @@ async function download() {
</script>
<div class="w-full h-full relative">
<div class={twMerge("w-full h-full relative", clss)}>
<div class="absolute top-0 left-0 w-full h-full overflow-hidden">
<ImagePreview image={image} />
</div>

View file

@ -13,7 +13,7 @@
$: {
if (panzoomEl) {
panzoomInstance = panzoom(panzoomEl, { bounds: true,
boundsPadding: 1,
boundsPadding: 0.49,
minZoom: 1,
maxZoom: 25,
initialZoom: 1.2

View file

@ -64,7 +64,9 @@
</script>
<div class="flex w-fit shrink-0 flex-col">
<AttributedImage image={providedImage} />
<div on:click={() => state.previewedImage.setData(providedImage)}>
<AttributedImage image={providedImage} imgClass="max-h-64 w-auto"/>
</div>
{#if linkable}
<label>
<input bind:checked={isLinked} type="checkbox" />

View file

@ -1,52 +1,49 @@
<script lang="ts">
import { Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import NearbyImages from "./NearbyImages.svelte"
import Svg from "../../Svg"
import ToSvelte from "../Base/ToSvelte.svelte"
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
import exp from "constants"
import Camera_plus from "../../assets/svg/Camera_plus.svelte"
import LoginToggle from "../Base/LoginToggle.svelte"
import { Store } from "../../Logic/UIEventSource";
import type { OsmTags } from "../../Models/OsmFeature";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import type { Feature } from "geojson";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import NearbyImages from "./NearbyImages.svelte";
import { XCircleIcon } from "@babeard/svelte-heroicons/solid";
import Camera_plus from "../../assets/svg/Camera_plus.svelte";
import LoginToggle from "../Base/LoginToggle.svelte";
export let tags: Store<OsmTags>
export let state: SpecialVisualizationState
export let lon: number
export let lat: number
export let feature: Feature
export let tags: Store<OsmTags>;
export let state: SpecialVisualizationState;
export let lon: number;
export let lat: number;
export let feature: Feature;
export let linkable: boolean = true
export let layer: LayerConfig
const t = Translations.t.image.nearby
export let linkable: boolean = true;
export let layer: LayerConfig;
const t = Translations.t.image.nearby;
let expanded = false
let expanded = false;
</script>
<LoginToggle {state}>
{#if expanded}
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable}>
<XCircleIcon
slot="corner"
class="h-6 w-6 cursor-pointer"
on:click={() => {
{#if expanded}
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable}>
<button slot="corner"
class="h-6 w-6 cursor-pointer no-image-background p-0 border-none"
on:click={() => {
expanded = false
}}
/>
</NearbyImages>
{:else}
<button
class="flex w-full items-center"
on:click={() => {
}}>
<XCircleIcon />
</button>
</NearbyImages>
{:else}
<button
class="flex w-full items-center"
on:click={() => {
expanded = true
}}
>
<Camera_plus class="mr-2 block h-8 w-8 p-1" />
<Tr t={t.seeNearby} />
</button>
{/if}
>
<Camera_plus class="mr-2 block h-8 w-8 p-1" />
<Tr t={t.seeNearby} />
</button>
{/if}
</LoginToggle>

View file

@ -56,9 +56,9 @@
multiple={true}
on:submit={(e) => handleFiles(e.detail)}
>
<div class="flex items-center">
<div class="flex items-center" >
{#if image !== undefined}
<img src={image} />
<img src={image} aria-hidden="true" />
{:else}
<Camera_plus class="block h-12 w-12 p-1 text-4xl" />
{/if}
@ -70,15 +70,15 @@
</div>
</FileSelector>
<div class="text-sm">
<Tr t={t.respectPrivacy} />
<a
class="cursor-pointer"
<button
class="link small "
on:click={() => {
state.guistate.openUsersettings("picture-license")
}}
>
<Tr t={t.currentLicense.Subs({ license: $licenseStore })} />
</a>
</button>
<Tr t={t.respectPrivacy} />
</div>
</div>
</LoginToggle>

View file

@ -302,19 +302,23 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
rescaleIcons: number,
pixelRatio: number
) {
const marker = element
const style = marker.style.transform
let x = marker.getBoundingClientRect().x
let y = marker.getBoundingClientRect().y
marker.style.transform = ""
const style = element.style.transform
let x = element.getBoundingClientRect().x
let y = element.getBoundingClientRect().y
element.style.transform = ""
const offset = style.match(/translate\(([-0-9]+)%, ?([-0-9]+)%\)/)
const w = marker.style.width
const w = element.style.width
const h = element.style.height
// Force a wider view for icon badges
marker.style.width = marker.getBoundingClientRect().width * 4 + "px"
const svgSource = await htmltoimage.toSvg(marker)
element.style.width = element.getBoundingClientRect().width * 4 + "px"
element.style.height = element.getBoundingClientRect().height + "px"
const svgSource = await htmltoimage.toSvg(element)
const img = await MapLibreAdaptor.createImage(svgSource)
marker.style.width = w
element.style.width = w
element.style.height = h
if (offset && rescaleIcons !== 1) {
const [_, __, relYStr] = offset
const relY = Number(relYStr)

View file

@ -410,8 +410,13 @@ class LineRenderingLayer {
this._listenerInstalledOn.add(id)
tags.addCallbackAndRunD((properties) => {
// Make sure to use 'getSource' here, the layer names are different!
if (map.getSource(this._layername) === undefined) {
return true
try {
if (map.getSource(this._layername) === undefined) {
return true
}
} catch (e) {
console.debug("Could not fetch source for", this._layername)
return
}
map.setFeatureState(
{ source: this._layername, id },

View file

@ -5,14 +5,13 @@
import TagRenderingMapping from "./TagRenderingMapping.svelte"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import type { Feature } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource"
import { Store, UIEventSource } from "../../../Logic/UIEventSource";
import { onDestroy } from "svelte"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { twMerge } from "tailwind-merge"
export let tags: UIEventSource<Record<string, string> | undefined>
let _tags: Record<string, string>
let trs: { then: Translation; icon?: string; iconClass?: string }[]
export let state: SpecialVisualizationState
export let selectedElement: Feature
@ -23,22 +22,18 @@
if (config === undefined) {
throw "Config is undefined in tagRenderingAnswer"
}
onDestroy(
tags.addCallbackAndRun((tags) => {
_tags = tags
trs = Utils.NoNull(config?.GetRenderValues(_tags))
})
)
let trs : Store<{then: Translation, icon?: string, iconClass?: string}[]> = tags.mapD(tags => Utils.NoNull(config?.GetRenderValues(tags)))
</script>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))}
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties($tags))}
<div class={twMerge("link-underline inline-block w-full", config?.classes, extraClasses)}>
{#if trs.length === 1}
<TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer} />
{#if $trs.length === 1}
<TagRenderingMapping mapping={$trs[0]} {tags} {state} {selectedElement} {layer} />
{/if}
{#if trs.length > 1}
{#if $trs.length > 1}
<ul>
{#each trs as mapping}
{#each $trs as mapping}
<li>
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer} />
</li>

View file

@ -1,40 +1,41 @@
<script lang="ts">
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
import type { Feature } from "geojson"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import TagRenderingAnswer from "./TagRenderingAnswer.svelte"
import { PencilAltIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import TagRenderingQuestion from "./TagRenderingQuestion.svelte"
import { onDestroy } from "svelte"
import Tr from "../../Base/Tr.svelte"
import Translations from "../../i18n/Translations.js"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { Utils } from "../../../Utils"
import { twMerge } from "tailwind-merge"
export let config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>>
export let selectedElement: Feature | undefined
export let state: SpecialVisualizationState
export let layer: LayerConfig = undefined
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { Store, UIEventSource } from "../../../Logic/UIEventSource";
import type { Feature } from "geojson";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import TagRenderingAnswer from "./TagRenderingAnswer.svelte";
import { PencilAltIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import TagRenderingQuestion from "./TagRenderingQuestion.svelte";
import { onDestroy } from "svelte";
import Tr from "../../Base/Tr.svelte";
import Translations from "../../i18n/Translations.js";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import { Utils } from "../../../Utils";
import { twMerge } from "tailwind-merge";
export let editingEnabled: Store<boolean> | undefined = state?.featureSwitchUserbadge
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature | undefined;
export let state: SpecialVisualizationState;
export let layer: LayerConfig = undefined;
export let highlightedRendering: UIEventSource<string> = undefined
export let clss
export let editingEnabled: Store<boolean> | undefined = state?.featureSwitchUserbadge;
export let highlightedRendering: UIEventSource<string> = undefined;
export let clss;
/**
* Indicates if this tagRendering currently shows the attribute or asks the question to _change_ the property
*/
export let editMode = !config.IsKnown(tags.data) // || showQuestionIfUnknown;
export let editMode = !config.IsKnown(tags.data); // || showQuestionIfUnknown;
if (tags) {
onDestroy(
tags.addCallbackD((tags) => {
editMode = !config.IsKnown(tags)
editMode = !config.IsKnown(tags);
})
)
);
}
let htmlElem: HTMLDivElement
let htmlElem: HTMLDivElement;
$: {
if (editMode && htmlElem !== undefined && config.IsKnown(tags)) {
// EditMode switched to true yet the answer is already known, so the person wants to make a change
@ -42,32 +43,36 @@
// Some delay is applied to give Svelte the time to render the _question_
window.setTimeout(() => {
Utils.scrollIntoView(<any>htmlElem)
}, 50)
Utils.scrollIntoView(<any>htmlElem);
}, 50);
}
}
const _htmlElement = new UIEventSource<HTMLElement>(undefined)
$: _htmlElement.setData(htmlElem)
const _htmlElement = new UIEventSource<HTMLElement>(undefined);
$: _htmlElement.setData(htmlElem);
function setHighlighting() {
if (highlightedRendering === undefined) {
return
return;
}
if (htmlElem === undefined) {
return
return;
}
const highlighted = highlightedRendering.data
const highlighted = highlightedRendering.data;
if (config.id === highlighted) {
htmlElem.classList.add("glowing-shadow")
htmlElem.classList.add("glowing-shadow");
htmlElem.tabIndex = "-1";
console.log("Scrolling to", htmlElem);
htmlElem.scrollIntoView({ behavior: "smooth" });
Utils.focusOnFocusableChild(htmlElem);
} else {
htmlElem.classList.remove("glowing-shadow")
htmlElem.classList.remove("glowing-shadow");
}
}
if (highlightedRendering) {
onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()))
onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()))
onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()));
onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()));
}
</script>
@ -84,13 +89,13 @@
>
<Tr t={Translations.t.general.cancel} />
</button>
<XCircleIcon
slot="upper-right"
class="h-8 w-8 cursor-pointer"
on:click={() => {
<button slot="upper-right"
class="h-8 w-8 cursor-pointer border-none p-0"
on:click={() => {
editMode = false
}}
/>
}}>
<XCircleIcon />
</button>
</TagRenderingQuestion>
{:else}
<div class="low-interaction flex items-center justify-between overflow-hidden rounded px-2">

View file

@ -136,7 +136,6 @@
function onSave() {
if (selectedTags === undefined) {
console.log("SelectedTags is undefined, ignoring 'onSave'-event");
return;
}
if (layer === undefined || (layer?.source === null && layer.id !== "favourite")) {

View file

@ -1,138 +1,116 @@
<script lang="ts">
import { Store, UIEventSource } from "../Logic/UIEventSource"
import { Map as MlMap } from "maplibre-gl"
import MaplibreMap from "./Map/MaplibreMap.svelte"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import MapControlButton from "./Base/MapControlButton.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
import If from "./Base/If.svelte"
import { GeolocationControl } from "./BigComponents/GeolocationControl"
import type { Feature } from "geojson"
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import Filterview from "./BigComponents/Filterview.svelte"
import ThemeViewState from "../Models/ThemeViewState"
import type { MapProperties } from "../Models/MapProperties"
import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations"
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
import Constants from "../Models/Constants"
import TabbedGroup from "./Base/TabbedGroup.svelte"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LoginToggle from "./Base/LoginToggle.svelte"
import LoginButton from "./Base/LoginButton.svelte"
import CopyrightPanel from "./BigComponents/CopyrightPanel"
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
import ModalRight from "./Base/ModalRight.svelte"
import { Utils } from "../Utils"
import Hotkeys from "./Base/Hotkeys"
import { VariableUiElement } from "./Base/VariableUIElement"
import SvelteUIElement from "./Base/SvelteUIElement"
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
import LevelSelector from "./BigComponents/LevelSelector.svelte"
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
import type { RasterLayerPolygon } from "../Models/RasterLayers"
import { AvailableRasterLayers } from "../Models/RasterLayers"
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
import IfHidden from "./Base/IfHidden.svelte"
import { onDestroy } from "svelte"
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
import StateIndicator from "./BigComponents/StateIndicator.svelte"
import ShareScreen from "./BigComponents/ShareScreen.svelte"
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
import Cross from "../assets/svg/Cross.svelte"
import Summary from "./BigComponents/Summary.svelte"
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
import Mastodon from "../assets/svg/Mastodon.svelte"
import Bug from "../assets/svg/Bug.svelte"
import Liberapay from "../assets/svg/Liberapay.svelte"
import OpenJosm from "./Base/OpenJosm.svelte"
import Min from "../assets/svg/Min.svelte"
import Plus from "../assets/svg/Plus.svelte"
import Filter from "../assets/svg/Filter.svelte"
import Add from "../assets/svg/Add.svelte"
import Statistics from "../assets/svg/Statistics.svelte"
import Community from "../assets/svg/Community.svelte"
import Download from "../assets/svg/Download.svelte"
import Share from "../assets/svg/Share.svelte"
import Favourites from "./Favourites/Favourites.svelte"
import ImageOperations from "./Image/ImageOperations.svelte"
import { Store, UIEventSource } from "../Logic/UIEventSource";
import { Map as MlMap } from "maplibre-gl";
import MaplibreMap from "./Map/MaplibreMap.svelte";
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
import MapControlButton from "./Base/MapControlButton.svelte";
import ToSvelte from "./Base/ToSvelte.svelte";
import If from "./Base/If.svelte";
import { GeolocationControl } from "./BigComponents/GeolocationControl";
import type { Feature } from "geojson";
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import Filterview from "./BigComponents/Filterview.svelte";
import ThemeViewState from "../Models/ThemeViewState";
import type { MapProperties } from "../Models/MapProperties";
import Geosearch from "./BigComponents/Geosearch.svelte";
import Translations from "./i18n/Translations";
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import Tr from "./Base/Tr.svelte";
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
import FloatOver from "./Base/FloatOver.svelte";
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
import Constants from "../Models/Constants";
import TabbedGroup from "./Base/TabbedGroup.svelte";
import UserRelatedState from "../Logic/State/UserRelatedState";
import LoginToggle from "./Base/LoginToggle.svelte";
import LoginButton from "./Base/LoginButton.svelte";
import CopyrightPanel from "./BigComponents/CopyrightPanel";
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
import ModalRight from "./Base/ModalRight.svelte";
import { Utils } from "../Utils";
import Hotkeys from "./Base/Hotkeys";
import SvelteUIElement from "./Base/SvelteUIElement";
import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
import LevelSelector from "./BigComponents/LevelSelector.svelte";
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
import type { RasterLayerPolygon } from "../Models/RasterLayers";
import { AvailableRasterLayers } from "../Models/RasterLayers";
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
import IfHidden from "./Base/IfHidden.svelte";
import { onDestroy } from "svelte";
import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
import StateIndicator from "./BigComponents/StateIndicator.svelte";
import ShareScreen from "./BigComponents/ShareScreen.svelte";
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte";
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte";
import Cross from "../assets/svg/Cross.svelte";
import Summary from "./BigComponents/Summary.svelte";
import LanguagePicker from "./InputElement/LanguagePicker.svelte";
import Mastodon from "../assets/svg/Mastodon.svelte";
import Bug from "../assets/svg/Bug.svelte";
import Liberapay from "../assets/svg/Liberapay.svelte";
import OpenJosm from "./Base/OpenJosm.svelte";
import Min from "../assets/svg/Min.svelte";
import Plus from "../assets/svg/Plus.svelte";
import Filter from "../assets/svg/Filter.svelte";
import Add from "../assets/svg/Add.svelte";
import Statistics from "../assets/svg/Statistics.svelte";
import Community from "../assets/svg/Community.svelte";
import Download from "../assets/svg/Download.svelte";
import Share from "../assets/svg/Share.svelte";
import Favourites from "./Favourites/Favourites.svelte";
import ImageOperations from "./Image/ImageOperations.svelte";
export let state: ThemeViewState
let layout = state.layout
export let state: ThemeViewState;
let layout = state.layout;
let maplibremap: UIEventSource<MlMap> = state.map
let selectedElement: UIEventSource<Feature> = state.selectedElement
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
let maplibremap: UIEventSource<MlMap> = state.map;
let selectedElement: UIEventSource<Feature> = new UIEventSource<Feature>(undefined);
state.selectedElement.addCallback(selected => {
if(!selected){
selectedElement.setData(selected)
return
}
if(selected !== selectedElement.data){
// We first set the selected element to 'undefined' to force the popup to close...
selectedElement.setData(undefined)
}
// ... we give svelte some time to update with requestAnimationFrame ...
window.requestAnimationFrame(() => {
// ... and we force a fresh popup window
selectedElement.setData(selected)
})
})
let selectedLayer: UIEventSource<LayerConfig> = state.selectedElement.mapD(element => state.layout.getMatchingLayer(element.properties));
let currentZoom = state.mapProperties.zoom
let showCrosshair = state.userRelatedState.showCrosshair
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation
let centerFeatures = state.closestFeatures.features
const selectedElementView = selectedElement.map(
(selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
const layer = selectedLayer.data
if (selectedElement === undefined || layer === undefined) {
return undefined
}
let currentZoom = state.mapProperties.zoom;
let showCrosshair = state.userRelatedState.showCrosshair;
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation;
let centerFeatures = state.closestFeatures.features;
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
return undefined
}
const tags = state.featureProperties.getStore(selectedElement.properties.id)
return new SvelteUIElement(SelectedElementView, {
state,
layer,
selectedElement,
tags,
}).SetClass("h-full w-full")
},
[selectedLayer],
)
const selectedElementTitle = selectedElement.map(
(selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
const layer = selectedLayer.data
if (selectedElement === undefined || layer === undefined) {
return undefined
}
const tags = state.featureProperties.getStore(selectedElement.properties.id)
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags })
},
[selectedLayer],
)
let mapproperties: MapProperties = state.mapProperties
let featureSwitches: FeatureSwitchState = state.featureSwitches
let availableLayers = state.availableLayers
let userdetails = state.osmConnection.userDetails
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
let rasterLayerName =
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name
onDestroy(
rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name
}),
)
let previewedImage = state.previewedImage
let mapproperties: MapProperties = state.mapProperties;
let featureSwitches: FeatureSwitchState = state.featureSwitches;
let availableLayers = state.availableLayers;
let userdetails = state.osmConnection.userDetails;
let currentViewLayer = layout.layers.find((l) => l.id === "current_view");
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer;
let rasterLayerName =
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name;
onDestroy(
rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name;
})
);
let previewedImage = state.previewedImage;
</script>
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
@ -146,7 +124,7 @@
<Geosearch
bounds={state.mapProperties.bounds}
perLayer={state.perLayer}
{selectedElement}
selectedElement={state.selectedElement}
{selectedLayer}
/>
</div>
@ -264,9 +242,7 @@
<If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton>
<ToSvelte
construct={new GeolocationControl(state.geolocation, mapproperties).SetClass(
"block w-8 h-8"
)}
construct={new GeolocationControl(state.geolocation, mapproperties).SetClass("block w-8 h-8")}
/>
</MapControlButton>
</If>
@ -285,66 +261,52 @@
</LoginToggle>
<If condition={state.previewedImage.map(i => i!==undefined)}>
<FloatOver on:close={() => state.previewedImage.setData(undefined)} extraClasses="">
<FloatOver extraClasses="p-1" on:close={() => state.previewedImage.setData(undefined)}>
<div
slot="close-button"
class="absolute right-4 top-4 h-8 w-8 cursor-pointer rounded-full hover:bg-white bg-white/50 transition-colors duration-200"
on:click={() => previewedImage.setData(undefined)}
slot="close-button"
>
<XCircleIcon />
</div>
<ImageOperations image={$previewedImage} />
<ImageOperations clss="focusable" image={$previewedImage} />
</FloatOver>
</If>
<If
condition={selectedElementView.map(
(v) =>
v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,
[selectedLayer]
)}
>
{#if $selectedElement !== undefined && $selectedLayer !== undefined && !($selectedLayer.popupInFloatover)}
<!-- right modal with the selected element view -->
<ModalRight
on:close={() => {
selectedElement.setData(undefined)
}}
>
<div slot="close-button"/>
<div class="normal-background absolute flex h-full w-full flex-col">
<ToSvelte construct={new VariableUiElement(selectedElementTitle)}>
<!-- Title -->
</ToSvelte>
<ToSvelte construct={new VariableUiElement(selectedElementView).SetClass("overflow-auto")}>
<!-- Main view -->
</ToSvelte>
<SelectedElementTitle {state} layer={$selectedLayer} selectedElement={$selectedElement} />
<SelectedElementView {state} layer={$selectedLayer} selectedElement={$selectedElement} />
</div>
</ModalRight>
</If>
{/if}
<If
condition={selectedElementView.map(
(v) =>
v !== undefined && selectedLayer.data !== undefined && selectedLayer.data.popupInFloatover,
[selectedLayer]
)}
>
{#if $selectedElement !== undefined && $selectedLayer !== undefined && $selectedLayer.popupInFloatover}
<!-- Floatover with the selected element, if applicable -->
<FloatOver
on:close={() => {
selectedElement.setData(undefined)
}}
>
<ToSvelte
construct={new VariableUiElement(selectedElementView).SetClass("h-full w-full flex")}
/>
<div class="h-full w-full flex focusable">
<SelectedElementView {state} layer={$selectedLayer} selectedElement={$selectedElement} />
</div>
</FloatOver>
</If>
{/if}
<If condition={state.guistate.themeIsOpened}>
<!-- Theme menu -->
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
<span slot="close-button"><!-- Disable the close button --></span>
<TabbedGroup
condition1={state.featureSwitches.featureSwitchFilter}
tab={state.guistate.themeViewTabIndex}
>
@ -421,7 +383,7 @@
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
}}
>
<div class="h-full p-2">
<div class="h-full p-2 focusable">
<RasterLayerOverview
{availableLayers}
map={state.map}