forked from MapComplete/MapComplete
Accessibility: add focus trapping, debug tab cycling, UI tweaks for mobile browser
This commit is contained in:
parent
307549b593
commit
8ae4d810d6
19 changed files with 123 additions and 77 deletions
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -6,7 +6,7 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mapcomplete",
|
"name": "mapcomplete",
|
||||||
"version": "0.36.1",
|
"version": "0.36.2",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||||
|
@ -56,6 +56,7 @@
|
||||||
"svg-path-parser": "^1.1.0",
|
"svg-path-parser": "^1.1.0",
|
||||||
"tailwind-merge": "^1.13.1",
|
"tailwind-merge": "^1.13.1",
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.1.8",
|
||||||
|
"trap-focus-svelte": "^1.0.1",
|
||||||
"vite-node": "^0.28.3",
|
"vite-node": "^0.28.3",
|
||||||
"vitest": "^0.28.3",
|
"vitest": "^0.28.3",
|
||||||
"wikibase-sdk": "^7.14.0",
|
"wikibase-sdk": "^7.14.0",
|
||||||
|
@ -12140,6 +12141,11 @@
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/trap-focus-svelte": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/trap-focus-svelte/-/trap-focus-svelte-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-qacSd68+c12mudUu9Mo70Ea16263ich2APFh1d0K7k9rLtwNcxlxNqA6l7Wv7xdzhJbC9TASxroiDSkiN2349w=="
|
||||||
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz",
|
||||||
|
@ -22659,6 +22665,11 @@
|
||||||
"punycode": "^2.3.0"
|
"punycode": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"trap-focus-svelte": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/trap-focus-svelte/-/trap-focus-svelte-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-qacSd68+c12mudUu9Mo70Ea16263ich2APFh1d0K7k9rLtwNcxlxNqA6l7Wv7xdzhJbC9TASxroiDSkiN2349w=="
|
||||||
|
},
|
||||||
"ts-api-utils": {
|
"ts-api-utils": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz",
|
||||||
|
|
|
@ -143,6 +143,7 @@
|
||||||
"svg-path-parser": "^1.1.0",
|
"svg-path-parser": "^1.1.0",
|
||||||
"tailwind-merge": "^1.13.1",
|
"tailwind-merge": "^1.13.1",
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.1.8",
|
||||||
|
"trap-focus-svelte": "^1.0.1",
|
||||||
"vite-node": "^0.28.3",
|
"vite-node": "^0.28.3",
|
||||||
"vitest": "^0.28.3",
|
"vitest": "^0.28.3",
|
||||||
"wikibase-sdk": "^7.14.0",
|
"wikibase-sdk": "^7.14.0",
|
||||||
|
|
|
@ -1796,14 +1796,14 @@ video {
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-0\.5 {
|
|
||||||
padding: 0.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-0 {
|
.p-0 {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-0\.5 {
|
||||||
|
padding: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
.p-12 {
|
.p-12 {
|
||||||
padding: 3rem;
|
padding: 3rem;
|
||||||
}
|
}
|
||||||
|
@ -2244,7 +2244,6 @@ body {
|
||||||
|
|
||||||
.focusable {
|
.focusable {
|
||||||
/* Not a 'real' class, but rather an indication to FloatOver and ModalRight to, when they open, grab the focus */
|
/* Not a 'real' class, but rather an indication to FloatOver and ModalRight to, when they open, grab the focus */
|
||||||
border: 1px solid red
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svg,
|
svg,
|
||||||
|
|
|
@ -190,19 +190,45 @@ export default class GenerateImageAnalysis extends Script {
|
||||||
if (!existsSync(viewDir)) {
|
if (!existsSync(viewDir)) {
|
||||||
mkdirSync(viewDir)
|
mkdirSync(viewDir)
|
||||||
}
|
}
|
||||||
|
const targetpath = datapath + "/views.csv"
|
||||||
|
|
||||||
|
const total = allImages.size
|
||||||
|
let dloaded = 0
|
||||||
|
let skipped = 0
|
||||||
|
let err = 0
|
||||||
for (const image of Array.from(allImages)) {
|
for (const image of Array.from(allImages)) {
|
||||||
const cachedView = viewDir + "/" + image.replace(/\\/g, "_")
|
const cachedView = viewDir + "/" + image.replace(/\//g, "_")
|
||||||
let attribution: LicenseInfo
|
let attribution: LicenseInfo
|
||||||
if (existsSync(cachedView)) {
|
if (existsSync(cachedView)) {
|
||||||
attribution = JSON.parse(readFileSync(cachedView, "utf8"))
|
attribution = JSON.parse(readFileSync(cachedView, "utf8"))
|
||||||
|
skipped++
|
||||||
} else {
|
} else {
|
||||||
attribution = await Imgur.singleton.DownloadAttribution(image)
|
try {
|
||||||
writeFileSync(cachedView, JSON.stringify(attribution))
|
attribution = await Imgur.singleton.DownloadAttribution(image)
|
||||||
|
await ScriptUtils.sleep(500)
|
||||||
|
writeFileSync(cachedView, JSON.stringify(attribution))
|
||||||
|
dloaded++
|
||||||
|
} catch (e) {
|
||||||
|
err++
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
results.push([image, attribution.views])
|
results.push([image, attribution.views])
|
||||||
|
if (dloaded % 50 === 0) {
|
||||||
|
console.log({
|
||||||
|
dloaded,
|
||||||
|
skipped,
|
||||||
|
total,
|
||||||
|
err,
|
||||||
|
progress: Math.round(dloaded + skipped + err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((dloaded + skipped + err) % 100 === 0) {
|
||||||
|
console.log("Writing views to", targetpath)
|
||||||
|
fs.writeFileSync(targetpath, results.map((r) => r.join(",")).join("\n"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const targetpath = datapath + "/views.csv"
|
|
||||||
console.log("Writing views to", targetpath)
|
console.log("Writing views to", targetpath)
|
||||||
fs.writeFileSync(targetpath, results.map((r) => r.join(",")).join("\n"))
|
fs.writeFileSync(targetpath, results.map((r) => r.join(",")).join("\n"))
|
||||||
}
|
}
|
||||||
|
@ -416,8 +442,8 @@ export default class GenerateImageAnalysis extends Script {
|
||||||
const imageBackupPath = args[0]
|
const imageBackupPath = args[0]
|
||||||
await this.downloadData(datapath, cached)
|
await this.downloadData(datapath, cached)
|
||||||
|
|
||||||
await this.downloadMetadata(datapath)
|
|
||||||
await this.downloadViews(datapath)
|
await this.downloadViews(datapath)
|
||||||
|
await this.downloadMetadata(datapath)
|
||||||
await this.downloadAllImages(datapath, imageBackupPath)
|
await this.downloadAllImages(datapath, imageBackupPath)
|
||||||
this.analyze(datapath)
|
this.analyze(datapath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default class UserRelatedState {
|
||||||
public readonly installedUserThemes: Store<string[]>
|
public readonly installedUserThemes: Store<string[]>
|
||||||
public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
||||||
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
|
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
|
||||||
public readonly showCrosshair: UIEventSource<"yes" | undefined>
|
public readonly showCrosshair: UIEventSource<"yes" | "always" | "no" | undefined>
|
||||||
public readonly fixateNorth: UIEventSource<undefined | "yes">
|
public readonly fixateNorth: UIEventSource<undefined | "yes">
|
||||||
public readonly homeLocation: FeatureSource
|
public readonly homeLocation: FeatureSource
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Translation } from "../UI/i18n/Translation"
|
import { Translation } from "../UI/i18n/Translation"
|
||||||
import { DenominationConfigJson } from "./ThemeConfig/Json/UnitConfigJson"
|
import { DenominationConfigJson } from "./ThemeConfig/Json/UnitConfigJson"
|
||||||
import Translations from "../UI/i18n/Translations"
|
import Translations from "../UI/i18n/Translations"
|
||||||
import { Store } from "../Logic/UIEventSource"
|
|
||||||
import BaseUIElement from "../UI/BaseUIElement"
|
|
||||||
import Toggle from "../UI/Input/Toggle"
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 'denomination' is one way to write a certain quantity.
|
||||||
|
* For example, 'meter', 'kilometer', 'mile' and 'foot' are all possible ways to quantify 'length'
|
||||||
|
*/
|
||||||
export class Denomination {
|
export class Denomination {
|
||||||
public readonly canonical: string
|
public readonly canonical: string
|
||||||
public readonly _canonicalSingular: string
|
public readonly _canonicalSingular: string
|
||||||
|
@ -53,8 +54,8 @@ export class Denomination {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a representation of the given value
|
* Create a representation of the given value
|
||||||
* @param value: the value from OSM
|
* @param value the value from OSM
|
||||||
* @param actAsDefault: if set and the value can be parsed as number, will be parsed and trimmed
|
* @param actAsDefault if set and the value can be parsed as number, will be parsed and trimmed
|
||||||
*
|
*
|
||||||
* const unit = new Denomination({
|
* const unit = new Denomination({
|
||||||
* canonicalDenomination: "m",
|
* canonicalDenomination: "m",
|
||||||
|
@ -82,6 +83,8 @@ export class Denomination {
|
||||||
* unit.canonicalValue("42", true) // =>"42"
|
* unit.canonicalValue("42", true) // =>"42"
|
||||||
* unit.canonicalValue("42 m", true) // =>"42"
|
* unit.canonicalValue("42 m", true) // =>"42"
|
||||||
* unit.canonicalValue("42 meter", true) // =>"42"
|
* unit.canonicalValue("42 meter", true) // =>"42"
|
||||||
|
*
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public canonicalValue(value: string, actAsDefault: boolean): string {
|
public canonicalValue(value: string, actAsDefault: boolean): string {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
|
|
|
@ -1,28 +1,35 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge";
|
||||||
|
import { Utils } from "../../Utils";
|
||||||
|
import { trapFocus } from 'trap-focus-svelte'
|
||||||
/**
|
/**
|
||||||
* The slotted element will be shown on top, with a lower-opacity border
|
* The slotted element will be shown on top, with a lower-opacity border
|
||||||
*/
|
*/
|
||||||
const dispatch = createEventDispatcher<{ close }>()
|
const dispatch = createEventDispatcher<{ close }>();
|
||||||
|
|
||||||
export let extraClasses = "p-4 md:p-6"
|
export let extraClasses = "p-4 md:p-6";
|
||||||
|
|
||||||
let mainContent: HTMLElement
|
let mainContent: HTMLElement;
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
console.log("Mounting floatover")
|
requestAnimationFrame(() => {
|
||||||
mainContent?.focus()
|
Utils.focusOnFocusableChild(mainContent);
|
||||||
})
|
});
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<!-- Draw the background over the total screen -->
|
||||||
class={twMerge("absolute top-0 right-0 h-screen w-screen", extraClasses)}
|
<div class="w-screen h-screen absolute top-0 left-0" style="background-color: #00000088; z-index: 20" on:click={() => {
|
||||||
style="background-color: #00000088; z-index: 20"
|
|
||||||
on:click={() => {
|
|
||||||
dispatch("close")
|
dispatch("close")
|
||||||
}}
|
}}>
|
||||||
|
</div>
|
||||||
|
<!-- 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)}
|
||||||
|
use:trapFocus
|
||||||
|
style="z-index: 21"
|
||||||
>
|
>
|
||||||
<div bind:this={mainContent} class="content normal-background" on:click|stopPropagation={() => {}}>
|
<div bind:this={mainContent} class="content normal-background" on:click|stopPropagation={() => {}}>
|
||||||
<div class="h-full rounded-xl">
|
<div class="h-full rounded-xl">
|
||||||
|
@ -30,21 +37,23 @@
|
||||||
</div>
|
</div>
|
||||||
<slot name="close-button">
|
<slot name="close-button">
|
||||||
<!-- The close button is placed _after_ the default slot in order to always paint it on top -->
|
<!-- The close button is placed _after_ the default slot in order to always paint it on top -->
|
||||||
<div
|
<button
|
||||||
class="absolute right-10 top-10 h-8 w-8 cursor-pointer"
|
class="absolute right-10 top-10 h-8 w-8 cursor-pointer p-0 border-none bg-white"
|
||||||
on:click={() => dispatch("close")}
|
on:click={() => dispatch("close")}
|
||||||
>
|
>
|
||||||
<XCircleIcon />
|
<XCircleIcon />
|
||||||
</div>
|
</button>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.content {
|
.content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
box-shadow: 0 0 1rem #00000088;
|
box-shadow: 0 0 1rem #00000088;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,14 +10,14 @@
|
||||||
|
|
||||||
export let state: {
|
export let state: {
|
||||||
osmConnection: OsmConnection
|
osmConnection: OsmConnection
|
||||||
featureSwitches?: { featureSwitchUserbadge?: UIEventSource<boolean> }
|
featureSwitches?: { featureSwitchEnableLogin?: UIEventSource<boolean> }
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* If set, 'loading' will act as if we are already logged in.
|
* If set, 'loading' will act as if we are already logged in.
|
||||||
*/
|
*/
|
||||||
export let ignoreLoading: boolean = false
|
export let ignoreLoading: boolean = false
|
||||||
let loadingStatus = state?.osmConnection?.loadingStatus ?? new ImmutableStore("logged-in")
|
let loadingStatus = state?.osmConnection?.loadingStatus ?? new ImmutableStore("logged-in")
|
||||||
let badge = state?.featureSwitches?.featureSwitchUserbadge ?? new ImmutableStore(true)
|
let badge = state?.featureSwitches?.featureSwitchEnableLogin ?? new ImmutableStore(true)
|
||||||
const t = Translations.t.general
|
const t = Translations.t.general
|
||||||
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
|
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
|
||||||
offline: t.loginFailedOfflineMode,
|
offline: t.loginFailedOfflineMode,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||||
import { Utils } from "../../Utils";
|
import { Utils } from "../../Utils";
|
||||||
|
import { trapFocus } from 'trap-focus-svelte'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The slotted element will be shown on the right side
|
* The slotted element will be shown on the right side
|
||||||
|
@ -13,13 +14,13 @@
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
window.setTimeout(
|
window.setTimeout(
|
||||||
() => Utils.focusOnFocusableChild(mainContent), 250
|
() => Utils.focusOnFocusableChild(mainContent), 250
|
||||||
|
);
|
||||||
)
|
});
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={mainContent}
|
bind:this={mainContent}
|
||||||
|
use:trapFocus
|
||||||
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"
|
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"
|
style="max-width: 100vw; max-height: 100vh"
|
||||||
>
|
>
|
||||||
|
|
|
@ -14,10 +14,8 @@
|
||||||
export let selectedElement: Feature
|
export let selectedElement: Feature
|
||||||
export let highlightedRendering: UIEventSource<string> = undefined
|
export let highlightedRendering: UIEventSource<string> = undefined
|
||||||
|
|
||||||
let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(selectedElement.properties.id)
|
export let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(selectedElement.properties.id)
|
||||||
$: {
|
|
||||||
tags = state.featureProperties.getStore(selectedElement.properties.id)
|
|
||||||
}
|
|
||||||
let _metatags: Record<string, string>
|
let _metatags: Record<string, string>
|
||||||
onDestroy(
|
onDestroy(
|
||||||
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
|
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
|
||||||
|
@ -28,7 +26,7 @@
|
||||||
let knownTagRenderings: Store<TagRenderingConfig[]> = tags.mapD(tgs => layer.tagRenderings.filter(
|
let knownTagRenderings: Store<TagRenderingConfig[]> = tags.mapD(tgs => layer.tagRenderings.filter(
|
||||||
(config) =>
|
(config) =>
|
||||||
(config.condition?.matchesProperties(tgs) ?? true) &&
|
(config.condition?.matchesProperties(tgs) ?? true) &&
|
||||||
config.metacondition?.matchesProperties({ ...tgs, ..._metatags } ?? true) &&
|
(config.metacondition?.matchesProperties({ ...tgs, ..._metatags }) ?? true) &&
|
||||||
config.IsKnown(tgs)
|
config.IsKnown(tgs)
|
||||||
))
|
))
|
||||||
</script>
|
</script>
|
||||||
|
@ -39,7 +37,7 @@
|
||||||
<Tr t={Translations.t.general.returnToTheMap} />
|
<Tr t={Translations.t.general.returnToTheMap} />
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex h-full flex-col gap-y-2 overflow-y-auto p-1 px-2 focusable" tabindex="-1">
|
<div class="flex h-full w-full flex-col gap-y-2 overflow-y-auto p-1 px-2 focusable" tabindex="-1">
|
||||||
{#each $knownTagRenderings as config (config.id)}
|
{#each $knownTagRenderings as config (config.id)}
|
||||||
<TagRenderingEditable
|
<TagRenderingEditable
|
||||||
{tags}
|
{tags}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { twMerge } from "tailwind-merge";
|
||||||
export let image: ProvidedImage
|
export let image: ProvidedImage
|
||||||
export let clss: string = undefined
|
export let clss: string = undefined
|
||||||
async function download() {
|
async function download() {
|
||||||
const response = await fetch(image.url)
|
const response = await fetch(image.url_hd ?? image.url )
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
Utils.offerContentsAsDownloadableFile(blob, new URL(image.url).pathname.split("/").at(-1), {
|
Utils.offerContentsAsDownloadableFile(blob, new URL(image.url).pathname.split("/").at(-1), {
|
||||||
mimetype: "image/jpg",
|
mimetype: "image/jpg",
|
||||||
|
@ -22,11 +22,11 @@ async function download() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={twMerge("w-full h-full relative", clss)}>
|
<div class={twMerge("w-full h-full relative", clss)}>
|
||||||
<div class="absolute top-0 left-0 w-full h-full overflow-hidden">
|
<div class="absolute top-0 left-0 w-full h-full overflow-hidden panzoom-container focusable">
|
||||||
<ImagePreview image={image} />
|
<ImagePreview image={image} />
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-0 left-0 w-full pointer-events-none flex flex-wrap justify-between items-end">
|
<div class="absolute bottom-0 left-0 w-full pointer-events-none flex flex-wrap justify-between items-end">
|
||||||
<div class="pointer-events-auto w-fit opacity-50 hover:opacity-100 transition-colors duration-200">
|
<div class="pointer-events-auto w-fit opacity-50 hover:opacity-100 transition-colors duration-200 m-1">
|
||||||
<ImageAttribution image={image} />
|
<ImageAttribution image={image} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -25,4 +25,4 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img bind:this={panzoomEl} src={image.url_hd ?? image.url} class="w-full h-auto"/>
|
<img bind:this={panzoomEl} src={image.url_hd ?? image.url} class="w-fit h-fit panzoom-image"/>
|
||||||
|
|
|
@ -13,8 +13,11 @@
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
|
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let tags: Store<OsmTags>
|
export let tags: Store<OsmTags> = undefined
|
||||||
export let featureId = tags.data.id
|
export let featureId = tags?.data?.id
|
||||||
|
if(featureId === undefined){
|
||||||
|
throw "No tags or featureID given"
|
||||||
|
}
|
||||||
export let showThankYou: boolean = true
|
export let showThankYou: boolean = true
|
||||||
const { uploadStarted, uploadFinished, retried, failed } =
|
const { uploadStarted, uploadFinished, retried, failed } =
|
||||||
state.imageUploadManager.getCountsFor(featureId)
|
state.imageUploadManager.getCountsFor(featureId)
|
||||||
|
|
|
@ -77,7 +77,7 @@ export interface SpecialVisualizationState {
|
||||||
readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
|
readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
|
||||||
readonly mangroveIdentity: MangroveIdentity
|
readonly mangroveIdentity: MangroveIdentity
|
||||||
readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
||||||
readonly preferencesAsTags: Store<Record<string, string>>
|
readonly preferencesAsTags: UIEventSource<Record<string, string>>
|
||||||
readonly language: UIEventSource<string>
|
readonly language: UIEventSource<string>
|
||||||
}
|
}
|
||||||
readonly lastClickObject: WritableFeatureSource
|
readonly lastClickObject: WritableFeatureSource
|
||||||
|
|
|
@ -40,7 +40,7 @@ import FeatureReviews from "../Logic/Web/MangroveReviews"
|
||||||
import Maproulette from "../Logic/Maproulette"
|
import Maproulette from "../Logic/Maproulette"
|
||||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||||
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
||||||
import { Feature, Point } from "geojson"
|
import { Feature } from "geojson"
|
||||||
import { GeoOperations } from "../Logic/GeoOperations"
|
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"
|
||||||
|
@ -48,8 +48,7 @@ import UserProfile from "./BigComponents/UserProfile.svelte"
|
||||||
import Link from "./Base/Link"
|
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 { OsmTags, WayId } from "../Models/OsmFeature"
|
import { WayId } from "../Models/OsmFeature"
|
||||||
import MoveWizard from "./Popup/MoveWizard"
|
|
||||||
import SplitRoadWizard from "./Popup/SplitRoadWizard"
|
import SplitRoadWizard from "./Popup/SplitRoadWizard"
|
||||||
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
|
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
|
||||||
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
|
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
|
||||||
|
@ -82,6 +81,8 @@ 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"
|
||||||
|
|
||||||
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
|
||||||
|
@ -515,12 +516,11 @@ export default class SpecialVisualizations {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MoveWizard(
|
return new SvelteUIElement(MoveWizard, {
|
||||||
<Feature<Point>>feature,
|
|
||||||
<UIEventSource<OsmTags>>tagSource,
|
|
||||||
state,
|
state,
|
||||||
layer.allowMove
|
featureToMove: feature,
|
||||||
)
|
layer,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -89,7 +89,7 @@
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedElement.mapD(element => state.layout.getMatchingLayer(element.properties));
|
let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD(element => state.layout.getMatchingLayer(element.properties));
|
||||||
|
|
||||||
let currentZoom = state.mapProperties.zoom;
|
let currentZoom = state.mapProperties.zoom;
|
||||||
let showCrosshair = state.userRelatedState.showCrosshair;
|
let showCrosshair = state.userRelatedState.showCrosshair;
|
||||||
|
@ -125,7 +125,6 @@
|
||||||
bounds={state.mapProperties.bounds}
|
bounds={state.mapProperties.bounds}
|
||||||
perLayer={state.perLayer}
|
perLayer={state.perLayer}
|
||||||
selectedElement={state.selectedElement}
|
selectedElement={state.selectedElement}
|
||||||
{selectedLayer}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</If>
|
</If>
|
||||||
|
@ -144,7 +143,6 @@
|
||||||
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
|
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
|
||||||
<MapControlButton
|
<MapControlButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
selectedLayer.setData(currentViewLayer)
|
|
||||||
selectedElement.setData(state.currentView.features?.data?.[0])
|
selectedElement.setData(state.currentView.features?.data?.[0])
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -269,7 +267,7 @@
|
||||||
>
|
>
|
||||||
<XCircleIcon />
|
<XCircleIcon />
|
||||||
</div>
|
</div>
|
||||||
<ImageOperations clss="focusable" image={$previewedImage} />
|
<ImageOperations image={$previewedImage} />
|
||||||
</FloatOver>
|
</FloatOver>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
|
|
|
@ -1658,7 +1658,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
const child = <HTMLElement>childs.item(0)
|
const child = <HTMLElement>childs.item(0)
|
||||||
if (child === null) {
|
if (child === null) {
|
||||||
console.log("Focussing on child element: no child element found for", el)
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -1668,7 +1667,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
) {
|
) {
|
||||||
child.setAttribute("tabindex", "-1")
|
child.setAttribute("tabindex", "-1")
|
||||||
}
|
}
|
||||||
console.log("Focussing on", child)
|
|
||||||
child?.focus()
|
child?.focus()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,12 +69,11 @@ body {
|
||||||
color: var(--foreground-color);
|
color: var(--foreground-color);
|
||||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.focusable {
|
.focusable {
|
||||||
/* Not a 'real' class, but rather an indication to FloatOver and ModalRight to, when they open, grab the focus */
|
/* Not a 'real' class, but rather an indication to FloatOver and ModalRight to, when they open, grab the focus */
|
||||||
border: 1px solid red
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
svg,
|
svg,
|
||||||
img {
|
img {
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<div class="h-full" id="maindiv">
|
<div class="h-screen" id="maindiv">
|
||||||
<div id="default-main h-full">
|
<div id="default-main h-full">
|
||||||
<div class="w-full h-screen flex flex-col items-center justify-between p-8">
|
<div class="w-full h-screen flex flex-col items-center justify-between p-8">
|
||||||
<div class="w-full h-full flex flex-col items-center">
|
<div class="w-full h-full flex flex-col items-center">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue