A11y: add labels to previously unlabeled buttons, fix order

This commit is contained in:
Pieter Vander Vennet 2023-12-14 18:25:35 +01:00
parent 736ab13ceb
commit fdde0aaeb3
21 changed files with 287 additions and 86 deletions

View file

@ -524,7 +524,7 @@
}, },
{ {
"id": "Reservation", "id": "Reservation",
"condition": "amenity=restaurant", "condition": "takeaway=only",
"question": { "question": {
"en": "Is a reservation required for this place?", "en": "Is a reservation required for this place?",
"nl": "Is reserveren verplicht voor deze zaak?", "nl": "Is reserveren verplicht voor deze zaak?",
@ -581,7 +581,8 @@
"cs": "Rezervace na tomto místě není možná" "cs": "Rezervace na tomto místě není možná"
} }
} }
] ],
"#condition": "If one can only do takeaway or deliveries, it is nonsensical to ask if a 'reservation' is possible"
}, },
{ {
"question": { "question": {
@ -731,6 +732,13 @@
"pl": "Wszystkie dania są wegetariańskie", "pl": "Wszystkie dania są wegetariańskie",
"cs": "Všechna jídla jsou vegetariánská" "cs": "Všechna jídla jsou vegetariánská"
} }
},
{
"if": "diet:vegetarian=on_demand",
"then": {
"en": "Some dishes might be adapted to a vegetarian version, but this should be demanded",
"nl": "Sommige gerechten kunnen op vraag vegetarisch gemaakt worden"
}
} }
], ],
"condition": "cuisine!=friture", "condition": "cuisine!=friture",
@ -798,6 +806,13 @@
"pl": "Wszystkie dania są wegańskie", "pl": "Wszystkie dania są wegańskie",
"cs": "Všechna jídla jsou veganská" "cs": "Všechna jídla jsou veganská"
} }
},
{
"if": "diet:vegan=on_demand",
"then": {
"en": "Some dishes might be adapted to a vegan version if asked for",
"nl": "Op vraag kan een veganistische variant van een gerecht gemaakt worden"
}
} }
], ],
"condition": "cuisine!=friture", "condition": "cuisine!=friture",

View file

@ -28,7 +28,7 @@
{ {
"#": "ignore-image-in-then", "#": "ignore-image-in-then",
"if": "wikipedia=", "if": "wikipedia=",
"then": "<a class='h-8' href='https://www.wikidata.org/wiki/{wikidata}' target='_blank' rel='noopener'><img src='./assets/svg/wikidata.svg' alt='WD'/></a>" "then": "<a class='h-8' href='https://www.wikidata.org/wiki/{wikidata}' target='_blank' rel='noopener'><img src='./assets/svg/wikidata.svg' alt='Wikidata'/></a>"
} }
] ]
}, },
@ -119,12 +119,32 @@
"defaults", "defaults",
"in_favourite" "in_favourite"
], ],
"render": "<a href='tel:{phone}'><img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/></a>", "render": {
"special": {
"type": "link",
"href": "tel:{phone}",
"text": "<img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/>",
"arialabel": {
"en": "phone",
"nl": "Telefoneer"
}
}
},
"mappings": [ "mappings": [
{ {
"#": "ignore-image-in-then", "#": "ignore-image-in-then",
"if": "contact:phone~*", "if": "contact:phone~*",
"then": "<a href='tel:{contact:phone}'><img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/></a>" "then": {
"special": {
"type": "link",
"href": "tel:{contact:phone}",
"text": "<img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/>",
"arialabel": {
"en": "phone",
"nl": "Telefoneer"
}
}
}
} }
], ],
"condition": { "condition": {
@ -174,12 +194,12 @@
{ {
"#": "ignore-image-in-then", "#": "ignore-image-in-then",
"if": "smoking=no", "if": "smoking=no",
"then": "<img textmode='🚭️' alt='no-smoking' src='./assets/layers/questions/no_smoking.svg'/>" "then": "<img textmode='🚭️' alt='no smoking' src='./assets/layers/questions/no_smoking.svg'/>"
}, },
{ {
"#": "ignore-image-in-then", "#": "ignore-image-in-then",
"if": "smoking=yes", "if": "smoking=yes",
"then": "<img textmode='🚬️' alt='smoking-allowed' src='./assets/layers/questions/smoking.svg'/>" "then": "<img textmode='🚬️' alt='smoking allowed' src='./assets/layers/questions/smoking.svg'/>"
} }
] ]
}, },
@ -206,7 +226,17 @@
"labels": [ "labels": [
"defaults" "defaults"
], ],
"render": "<a href='https://openstreetmap.org/{id}' target='_blank' rel='noopener'><img alt='on osm' textmode='🗺️' src='./assets/svg/osm-logo-us.svg'/></a>", "render": {
"special": {
"type": "link",
"text": "<img alt='on osm' textmode='🗺️' src='./assets/svg/osm-logo-us.svg'/>",
"href": "https://openstreetmap.org/{id}",
"arialabel": {
"en": "Open on openstreetmap.org",
"nl": "Bekijk op openstreetmap.org"
}
}
},
"mappings": [ "mappings": [
{ {
"if": "id~.*/-.*", "if": "id~.*/-.*",
@ -215,7 +245,17 @@
{ {
"#": "ignore-image-in-then", "#": "ignore-image-in-then",
"if": "_backend~*", "if": "_backend~*",
"then": "<a href='{_backend}/{id}' target='_blank' rel='noopener'><img src='./assets/svg/osm-logo-us.svg'/></a>" "then": {
"special": {
"type": "link",
"text": "<img alt='on osm' textmode='🗺️' src='./assets/svg/osm-logo-us.svg'/>",
"href": "{_backend}/{id}",
"arialabel": {
"en": "Open on openstreetmap.org",
"nl": "Bekijk op openstreetmap.org"
}
}
}
} }
], ],
"condition": "id~(node|way|relation)/[0-9]*" "condition": "id~(node|way|relation)/[0-9]*"

View file

@ -4721,6 +4721,9 @@
}, },
"3": { "3": {
"then": "All dishes are vegan" "then": "All dishes are vegan"
},
"4": {
"then": "Some dishes might be adapted to a vegan version if asked for"
} }
}, },
"question": "Does this business serve vegan meals?" "question": "Does this business serve vegan meals?"
@ -4738,6 +4741,9 @@
}, },
"3": { "3": {
"then": "All dishes are vegetarian" "then": "All dishes are vegetarian"
},
"4": {
"then": "Some dishes might be adapted to a vegetarian version, but this should be demanded"
} }
}, },
"question": "Does this restaurant have a vegetarian option?" "question": "Does this restaurant have a vegetarian option?"
@ -5252,7 +5258,41 @@
} }
}, },
"icons": { "icons": {
"description": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI" "description": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI",
"tagRenderings": {
"osmlink": {
"mappings": {
"1": {
"then": {
"special": {
"arialabel": "Open on openstreetmap.org"
}
}
}
},
"render": {
"special": {
"arialabel": "Open on openstreetmap.org"
}
}
},
"phonelink": {
"mappings": {
"0": {
"then": {
"special": {
"arialabel": "phone"
}
}
}
},
"render": {
"special": {
"arialabel": "phone"
}
}
}
}
}, },
"indoors": { "indoors": {
"description": "Basic indoor mapping: shows room outlines", "description": "Basic indoor mapping: shows room outlines",

View file

@ -4190,6 +4190,9 @@
}, },
"3": { "3": {
"then": "Enkel veganistische opties zijn beschikbaar" "then": "Enkel veganistische opties zijn beschikbaar"
},
"4": {
"then": "Op vraag kan een veganistische variant van een gerecht gemaakt worden"
} }
}, },
"question": "Heeft deze eetgelegenheid een veganistische optie?" "question": "Heeft deze eetgelegenheid een veganistische optie?"
@ -4207,6 +4210,9 @@
}, },
"3": { "3": {
"then": "Enkel vegetarische opties zijn beschikbaar" "then": "Enkel vegetarische opties zijn beschikbaar"
},
"4": {
"then": "Sommige gerechten kunnen op vraag vegetarisch gemaakt worden"
} }
}, },
"question": "Heeft deze eetgelegenheid een vegetarische optie?" "question": "Heeft deze eetgelegenheid een vegetarische optie?"
@ -4654,6 +4660,42 @@
} }
} }
}, },
"icons": {
"tagRenderings": {
"osmlink": {
"mappings": {
"1": {
"then": {
"special": {
"arialabel": "Bekijk op openstreetmap.org"
}
}
}
},
"render": {
"special": {
"arialabel": "Bekijk op openstreetmap.org"
}
}
},
"phonelink": {
"mappings": {
"0": {
"then": {
"special": {
"arialabel": "Telefoneer"
}
}
}
},
"render": {
"special": {
"arialabel": "Telefoneer"
}
}
}
}
},
"indoors": { "indoors": {
"description": "Een basis voor indoor-navigatie: toont binnenruimtes", "description": "Een basis voor indoor-navigatie: toont binnenruimtes",
"name": "Binnenruimtes", "name": "Binnenruimtes",

View file

@ -886,16 +886,6 @@ video {
margin-right: 0.25rem; margin-right: 0.25rem;
} }
.mx-10 {
margin-left: 2.5rem;
margin-right: 2.5rem;
}
.my-3 {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
.mx-12 { .mx-12 {
margin-left: 3rem; margin-left: 3rem;
margin-right: 3rem; margin-right: 3rem;
@ -969,10 +959,6 @@ video {
margin-left: 1rem; margin-left: 1rem;
} }
.mb-10 {
margin-bottom: 2.5rem;
}
.mt-8 { .mt-8 {
margin-top: 2rem; margin-top: 2rem;
} }
@ -1529,10 +1515,6 @@ video {
justify-self: end; justify-self: end;
} }
.justify-self-center {
justify-self: center;
}
.overflow-auto { .overflow-auto {
overflow: auto; overflow: auto;
} }
@ -1935,6 +1917,11 @@ video {
line-height: 2.5rem; line-height: 2.5rem;
} }
.text-5xl {
font-size: 3rem;
line-height: 1;
}
.font-extrabold { .font-extrabold {
font-weight: 800; font-weight: 800;
} }

View file

@ -1,10 +1,10 @@
import ImageProvider, { ProvidedImage } from "./ImageProvider" import ImageProvider, { ProvidedImage } from "./ImageProvider"
import BaseUIElement from "../../UI/BaseUIElement" import BaseUIElement from "../../UI/BaseUIElement"
import Svg from "../../Svg"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { LicenseInfo } from "./LicenseInfo" import { LicenseInfo } from "./LicenseInfo"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import Link from "../../UI/Base/Link" import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import MapillaryIcon from "./MapillaryIcon.svelte"
export class Mapillary extends ImageProvider { export class Mapillary extends ImageProvider {
public static readonly singleton = new Mapillary() public static readonly singleton = new Mapillary()
@ -112,11 +112,11 @@ export class Mapillary extends ImageProvider {
lat: number lat: number
} }
): BaseUIElement { ): BaseUIElement {
const icon = Svg.mapillary_svg() let url: string = undefined
if (!id) { if (id) {
return icon url = Mapillary.createLink(location, 16, "" + id)
} }
return new Link(icon, Mapillary.createLink(location, 16, "" + id), true) return new SvelteUIElement(MapillaryIcon, { url })
} }
async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> { async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {

View file

@ -0,0 +1,21 @@
<script lang="ts">
import Mapillary from "../../assets/svg/Mapillary.svelte"
import { ariaLabel } from "../../Utils/ariaLabel"
import Translations from "../../UI/i18n/Translations"
/**
* Accessible, linked mapillary icon
*/
export let url: string = undefined
</script>
{#if url}
<a href={url}
use:ariaLabel={Translations.t.general.attribution.seeOnMapillary}
target="_blank"
rel="noopener nofollower" >
<Mapillary />
</a>
{:else}
<Mapillary />
{/if}

12
src/UI/Base/Link.svelte Normal file
View file

@ -0,0 +1,12 @@
<script lang="ts">
export let text : string
export let href : string
export let classnames : string = undefined
export let download : string = undefined
export let ariaLabel : string = undefined
export let newTab: boolean = false
</script>
<a {href} aria-label={ariaLabel} target={newTab ? "_blank" : undefined} {download} class={classnames}>
{@html text}</a>

View file

@ -1,7 +1,6 @@
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import { Store } from "../../Logic/UIEventSource" import { Store } from "../../Logic/UIEventSource"
import { Utils } from "../../Utils"
export default class Link extends BaseUIElement { export default class Link extends BaseUIElement {
private readonly _href: string | Store<string> private readonly _href: string | Store<string>

View file

@ -1,17 +1,21 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { twJoin } from "tailwind-merge" import { twJoin } from "tailwind-merge"
import { Translation } from "../i18n/Translation"
import { ariaLabel } from "../../Utils/ariaLabel"
/** /**
* A round button with an icon and possible a small text, which hovers above the map * A round button with an icon and possible a small text, which hovers above the map
*/ */
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let cls = "" export let cls = ""
export let arialabel: Translation = undefined
</script> </script>
<button <button
on:click={(e) => dispatch("click", e)} on:click={(e) => dispatch("click", e)}
on:keydown on:keydown
use:ariaLabel={arialabel}
class={twJoin("pointer-events-auto m-0.5 h-fit w-fit rounded-full p-0.5 sm:p-1 md:m-1", cls)} class={twJoin("pointer-events-auto m-0.5 h-fit w-fit rounded-full p-0.5 sm:p-1 md:m-1", cls)}
> >
<slot /> <slot />

View file

@ -2,6 +2,8 @@
import ToSvelte from "./ToSvelte.svelte" import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg" import Svg from "../../Svg"
import Share from "../../assets/svg/Share.svelte" import Share from "../../assets/svg/Share.svelte"
import { ariaLabel } from "../../Utils/ariaLabel"
import Translations from "../i18n/Translations"
export let generateShareData: () => { export let generateShareData: () => {
text: string text: string
@ -24,7 +26,7 @@
} }
</script> </script>
<button on:click={share} class="secondary m-0 h-8 w-8 p-0"> <button on:click={share} class="secondary m-0 h-8 w-8 p-0" use:ariaLabel={Translations.t.general.share}>
<slot name="content"> <slot name="content">
<Share class="h-7 w-7 p-1" /> <Share class="h-7 w-7 p-1" />
</slot> </slot>

View file

@ -13,7 +13,8 @@
export let hideTooltip = false export let hideTooltip = false
</script> </script>
<MapControlButton on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}> <MapControlButton arialabel={Translations.t.general.labels.background}
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}>
<Square3Stack3dIcon class="h-6 w-6" /> <Square3Stack3dIcon class="h-6 w-6" />
{#if !hideTooltip} {#if !hideTooltip}
<Tr cls="mx-2" t={Translations.t.general.backgroundSwitch} /> <Tr cls="mx-2" t={Translations.t.general.backgroundSwitch} />

View file

@ -7,6 +7,7 @@
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte"; import Tr from "../Base/Tr.svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"; import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { ariaLabel } from "../../Utils/ariaLabel"
export let state: SpecialVisualizationState; export let state: SpecialVisualizationState;
export let layer: LayerConfig; export let layer: LayerConfig;
@ -50,8 +51,10 @@
</div> </div>
</div> </div>
<button on:click={() => state.selectedElement.setData(undefined)} class="border-none p-0"> <button on:click={() => state.selectedElement.setData(undefined)}
<XCircleIcon class="h-8 w-8" /> use:ariaLabel={Translations.t.general.backToMap}
class="border-none p-0">
<XCircleIcon aria-hidden={true} class="h-8 w-8" />
</button> </button>
</div> </div>
{/if} {/if}

View file

@ -92,7 +92,7 @@
<Tr t={title} /> <Tr t={title} />
{#if selected} {#if selected}
<span class="alert"> <span class="alert" aria-hidden="true">
<Tr t={Translations.t.general.morescreen.enterToOpen} /> <Tr t={Translations.t.general.morescreen.enterToOpen} />
</span> </span>
{/if} {/if}

View file

@ -26,7 +26,7 @@
<div class="flex flex-col"> <div class="flex flex-col">
{#if $license.title} {#if $license.title}
{#if $license.informationLocation} {#if $license.informationLocation}
<a href={$license.informationLocation} target="_blank" rel="noopener nofollower">{$license.title}</a> <a href={$license.informationLocation.href} target="_blank" rel="noopener nofollower">{$license.title}</a>
{:else} {:else}
$license.title $license.title
{/if} {/if}

View file

@ -18,6 +18,7 @@
import DateInput from "./Helpers/DateInput.svelte" import DateInput from "./Helpers/DateInput.svelte"
import ColorInput from "./Helpers/ColorInput.svelte" import ColorInput from "./Helpers/ColorInput.svelte"
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte" import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
import SlopeInput from "./Helpers/SlopeInput.svelte"
export let type: ValidatorType export let type: ValidatorType
export let value: UIEventSource<string | object> export let value: UIEventSource<string | object>
@ -47,6 +48,8 @@
<SimpleTagInput {value} {args} on:submit /> <SimpleTagInput {value} {args} on:submit />
{:else if type === "opening_hours"} {:else if type === "opening_hours"}
<OpeningHoursInput {value} /> <OpeningHoursInput {value} />
{:else if type === "slope"}
<SlopeInput {value} />
{:else if type === "wikidata"} {:else if type === "wikidata"}
<ToSvelte construct={() => InputHelpers.constructWikidataHelper(value, properties)} /> <ToSvelte construct={() => InputHelpers.constructWikidataHelper(value, properties)} />
{/if} {/if}

View file

@ -1,36 +1,39 @@
<script lang="ts"> <script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization"; import type { SpecialVisualizationState } from "../SpecialVisualization"
import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid"; import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid"
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"; import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations"
import LoginToggle from "../Base/LoginToggle.svelte"; import LoginToggle from "../Base/LoginToggle.svelte"
import type { Feature } from "geojson"; import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { ariaLabel } from "../../Utils/ariaLabel"
import { UIEventSource } from "../../Logic/UIEventSource"
/** /**
* A small 'mark as favourite'-button to serve as title-icon * A small 'mark as favourite'-button to serve as title-icon
*/ */
export let state: SpecialVisualizationState; export let state: SpecialVisualizationState
export let feature: Feature; export let feature: Feature
export let tags: Record<string, string>; export let tags: UIEventSource<Record<string, string>>
export let layer: LayerConfig; export let layer: LayerConfig
let isFavourite = tags?.map(tags => tags._favourite === "yes"); let isFavourite = tags?.map(tags => tags._favourite === "yes")
const t = Translations.t.favouritePoi; const t = Translations.t.favouritePoi
function markFavourite(isFavourite: boolean) { function markFavourite(isFavourite: boolean) {
state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite); state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite)
} }
</script> </script>
<LoginToggle ignoreLoading={true} {state}> <LoginToggle ignoreLoading={true} {state}>
{#if $isFavourite} {#if $isFavourite}
<button class="p-0 m-0 h-8 w-8" on:click={() => markFavourite(false)}> <button class="p-0 m-0 h-8 w-8" on:click={() => markFavourite(false)}
<HeartSolidIcon/> use:ariaLabel={Translations.t.favouritePoi.button.isMarkedShort}>
<HeartSolidIcon aria-hidden={true} />
</button> </button>
{:else} {:else}
<button class="p-0 m-0 h-8 w-8 no-image-background" on:click={() => markFavourite(true)} > <button class="p-0 m-0 h-8 w-8 no-image-background" on:click={() => markFavourite(true)}
<HeartOutlineIcon/> use:ariaLabel={Translations.t.favouritePoi.button.isNotMarkedShort}>
<HeartOutlineIcon aria-hidden={true} />
</button> </button>
{/if} {/if}
</LoginToggle> </LoginToggle>

View file

@ -45,7 +45,6 @@ import { GeoOperations } from "../Logic/GeoOperations"
import CreateNewNote from "./Popup/CreateNewNote.svelte" import CreateNewNote from "./Popup/CreateNewNote.svelte"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
import UserProfile from "./BigComponents/UserProfile.svelte" import UserProfile from "./BigComponents/UserProfile.svelte"
import Link from "./Base/Link"
import LayerConfig from "../Models/ThemeConfig/LayerConfig" import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import { WayId } from "../Models/OsmFeature" import { WayId } from "../Models/OsmFeature"
@ -81,9 +80,9 @@ import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte" import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
import NearbyImages from "./Image/NearbyImages.svelte" import NearbyImages from "./Image/NearbyImages.svelte"
import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte" import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte"
import { svelte } from "@sveltejs/vite-plugin-svelte"
import MoveWizard from "./Popup/MoveWizard.svelte" import MoveWizard from "./Popup/MoveWizard.svelte"
import { Unit } from "../Models/Unit" import { Unit } from "../Models/Unit"
import Link from "./Base/Link.svelte"
class NearbyImageVis implements SpecialVisualization { class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -1288,6 +1287,10 @@ export default class SpecialVisualizations {
name: "download", name: "download",
doc: "If set, this link will act as a download-button. The contents of `href` will be offered for download; this parameter will act as the proposed filename", doc: "If set, this link will act as a download-button. The contents of `href` will be offered for download; this parameter will act as the proposed filename",
}, },
{
name: "arialabel",
doc: "If set, this text will be used as aria-label",
},
], ],
needsUrls: [], needsUrls: [],
constr( constr(
@ -1295,15 +1298,19 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
args: string[] args: string[]
): BaseUIElement { ): BaseUIElement {
const [text, href, classnames, download] = args const [text, href, classnames, download, ariaLabel] = args
const newTab = download === undefined && !href.startsWith("#")
return new VariableUiElement( return new VariableUiElement(
tagSource.map((tags) => tagSource.map(
new Link( (tags) =>
Utils.SubstituteKeys(text, tags), new SvelteUIElement(Link, {
Utils.SubstituteKeys(href, tags), text: Utils.SubstituteKeys(text, tags),
download === undefined && !href.startsWith("#"), href: Utils.SubstituteKeys(href, tags),
Utils.SubstituteKeys(download, tags) classnames,
).SetClass(classnames) download: Utils.SubstituteKeys(download, tags),
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
newTab,
})
) )
) )
}, },
@ -1422,12 +1429,11 @@ export default class SpecialVisualizations {
const [_, username, host] = fediAccount.match( const [_, username, host] = fediAccount.match(
FediverseValidator.usernameAtServer FediverseValidator.usernameAtServer
) )
return new SvelteUIElement(Link, {
return new Link( text: fediAccount,
fediAccount, url: "https://" + host + "/@" + username,
"https://" + host + "/@" + username, newTab: true,
true })
)
}) })
) )
}, },

View file

@ -1,5 +1,11 @@
<script lang="ts"> <script lang="ts">
// Testing grounds // Testing grounds
import { UIEventSource } from "../Logic/UIEventSource"
import SlopeInput from "./InputElement/Helpers/SlopeInput.svelte"
let value: UIEventSource<string> = new UIEventSource(undefined)
</script> </script>
<div class="w-full">No tests</div> <div class="w-full flex flex-col">
<div>Value: {$value}</div>
</div>
<SlopeInput {value}></SlopeInput>

View file

@ -64,10 +64,10 @@
import Share from "../assets/svg/Share.svelte" import Share from "../assets/svg/Share.svelte"
import Favourites from "./Favourites/Favourites.svelte" import Favourites from "./Favourites/Favourites.svelte"
import ImageOperations from "./Image/ImageOperations.svelte" import ImageOperations from "./Image/ImageOperations.svelte"
import { ariaLabel } from "../Utils/ariaLabel"
export let state: ThemeViewState export let state: ThemeViewState
let layout = state.layout let layout = state.layout
let maplibremap: UIEventSource<MlMap> = state.map let maplibremap: UIEventSource<MlMap> = state.map
let selectedElement: UIEventSource<Feature> = new UIEventSource<Feature>(undefined) let selectedElement: UIEventSource<Feature> = new UIEventSource<Feature>(undefined)
@ -142,7 +142,8 @@
</div> </div>
</If> </If>
<div class="float-left m-1 flex flex-col sm:mt-2"> <div class="float-left m-1 flex flex-col sm:mt-2">
<MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)} on:keydown={forwardEventToMap}> <MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)}
on:keydown={forwardEventToMap}>
<div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2"> <div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2">
<img class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8" src={layout.icon} /> <img class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8" src={layout.icon} />
<b class="mr-1"> <b class="mr-1">
@ -150,15 +151,19 @@
</b> </b>
</div> </div>
</MapControlButton> </MapControlButton>
<MapControlButton on:click={() => state.guistate.menuIsOpened.setData(true)} on:keydown={forwardEventToMap}> <MapControlButton
on:click={() => state.guistate.menuIsOpened.setData(true)}
on:keydown={forwardEventToMap}
arialabel={Translations.t.general.labels.menu}
>
<MenuIcon class="h-8 w-8 cursor-pointer" /> <MenuIcon class="h-8 w-8 cursor-pointer" />
</MapControlButton> </MapControlButton>
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()} {#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
<MapControlButton <MapControlButton
on:click={() => { on:click={() => {
selectedElement.setData(state.currentView.features?.data?.[0]) selectedElement.setData(state.currentView.features?.data?.[0])
}} on:keydown={forwardEventToMap} }}
on:keydown={forwardEventToMap}
> >
<ToSvelte <ToSvelte
construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")} construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")}
@ -206,7 +211,9 @@
<div class="flex"> <div class="flex">
<!-- bottom left elements --> <!-- bottom left elements -->
<If condition={state.featureSwitches.featureSwitchFilter}> <If condition={state.featureSwitches.featureSwitchFilter}>
<MapControlButton on:click={() => state.guistate.openFilterView()} on:keydown={forwardEventToMap}> <MapControlButton on:click={() => state.guistate.openFilterView()} on:keydown={forwardEventToMap}
arialabel={Translations.t.general.labels.filter}
>
<Filter class="h-6 w-6" /> <Filter class="h-6 w-6" />
</MapControlButton> </MapControlButton>
</If> </If>
@ -246,14 +253,21 @@
/> />
</div> </div>
</If> </If>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)} on:keydown={forwardEventToMap}> <MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}
on:keydown={forwardEventToMap}
arialabel={Translations.t.general.labels.zoomIn}
>
<Plus class="h-8 w-8" /> <Plus class="h-8 w-8" />
</MapControlButton> </MapControlButton>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)} on:keydown={forwardEventToMap}> <MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)} on:keydown={forwardEventToMap}
arialabel={Translations.t.general.labels.zoomOut}
>
<Min class="h-8 w-8" /> <Min class="h-8 w-8" />
</MapControlButton> </MapControlButton>
<If condition={featureSwitches.featureSwitchGeolocation}> <If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton on:keydown={forwardEventToMap} on:click={() => geolocationControl.handleClick()}> <MapControlButton on:keydown={forwardEventToMap} on:click={() => geolocationControl.handleClick()}
arialabel={Translations.t.general.labels.jumpToLocation}
>
<ToSvelte <ToSvelte
construct={geolocationControl.SetClass("block w-8 h-8")} construct={geolocationControl.SetClass("block w-8 h-8")}
/> />

View file

@ -1,6 +1,9 @@
import { Translation } from "../UI/i18n/Translation" import { Translation } from "../UI/i18n/Translation"
export function ariaLabel(htmlElement: Element, t: Translation) { export function ariaLabel(htmlElement: Element, t: Translation) {
if (!t) {
return
}
let destroy: () => void = undefined let destroy: () => void = undefined
t.current.map( t.current.map(