forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
c672fe7668
138 changed files with 14304 additions and 1299 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export let accept: string
|
||||
|
|
@ -9,10 +9,52 @@
|
|||
export let cls: string = ""
|
||||
let drawAttention = false
|
||||
let inputElement: HTMLInputElement
|
||||
let id = Math.random() * 1000000000 + ""
|
||||
let formElement: HTMLFormElement
|
||||
let id = "fileinput_" + Math.round(Math.random() * 1000000000000)
|
||||
|
||||
function handleDragEvent(e: DragEvent) {
|
||||
if (e.target["id"] == id) {
|
||||
return
|
||||
}
|
||||
if(formElement.contains(e.target) || document.getElementsByClassName("selected-element-view")[0]?.contains(e.target)){
|
||||
e.preventDefault()
|
||||
|
||||
if(e.type === "drop"){
|
||||
console.log("Got a 'drop'", e)
|
||||
drawAttention = false
|
||||
dispatcher("submit", e.dataTransfer.files)
|
||||
return
|
||||
}
|
||||
|
||||
drawAttention = true
|
||||
e.dataTransfer.dropEffect = "copy"
|
||||
|
||||
return
|
||||
/*
|
||||
drawAttention = false
|
||||
dispatcher("submit", e.dataTransfer.files)
|
||||
console.log("Committing")*/
|
||||
}
|
||||
drawAttention = false
|
||||
e.preventDefault()
|
||||
e.dataTransfer.effectAllowed = "none"
|
||||
e.dataTransfer.dropEffect = "none"
|
||||
}
|
||||
|
||||
window.addEventListener("dragenter", handleDragEvent)
|
||||
window.addEventListener("dragover", handleDragEvent)
|
||||
window.addEventListener("drop", handleDragEvent)
|
||||
|
||||
onDestroy(() => {
|
||||
window.removeEventListener("dragenter", handleDragEvent)
|
||||
window.removeEventListener("dragover", handleDragEvent)
|
||||
window.removeEventListener("drop", handleDragEvent)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<form
|
||||
bind:this={formElement}
|
||||
on:change|preventDefault={() => {
|
||||
drawAttention = false
|
||||
dispatcher("submit", inputElement.files)
|
||||
|
|
@ -24,27 +66,24 @@
|
|||
on:dragenter|preventDefault|stopPropagation={(e) => {
|
||||
console.log("Dragging enter")
|
||||
drawAttention = true
|
||||
e.dataTransfer.drop = "copy"
|
||||
e.dataTransfer.dropEffect = "copy"
|
||||
}}
|
||||
on:dragstart={() => {
|
||||
console.log("DragStart")
|
||||
drawAttention = false
|
||||
}}
|
||||
on:drop|preventDefault|stopPropagation={(e) => {
|
||||
console.log("Got a 'drop'")
|
||||
drawAttention = false
|
||||
dispatcher("submit", e.dataTransfer.files)
|
||||
}}
|
||||
>
|
||||
<label
|
||||
class={twMerge(cls, drawAttention ? "glowing-shadow" : "")}
|
||||
style="margin-left:0"
|
||||
tabindex="0"
|
||||
for={"fileinput" + id}
|
||||
for={id}
|
||||
on:click={() => {
|
||||
console.log("Clicked", inputElement)
|
||||
inputElement.click()
|
||||
}}
|
||||
style="margin-left:0"
|
||||
tabindex="0"
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
|
|
@ -52,7 +91,7 @@
|
|||
{accept}
|
||||
bind:this={inputElement}
|
||||
class="hidden"
|
||||
id={"fileinput" + id}
|
||||
{id}
|
||||
{multiple}
|
||||
name="file-input"
|
||||
type="file"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
style="z-index: 21"
|
||||
use:trapFocus
|
||||
>
|
||||
<div class="content normal-background" on:click|stopPropagation={() => {}}>
|
||||
<div class="h-full content normal-background" on:click|stopPropagation={() => {}}>
|
||||
<div class="h-full rounded-xl">
|
||||
<slot />
|
||||
</div>
|
||||
|
|
@ -47,7 +47,6 @@
|
|||
|
||||
<style>
|
||||
.content {
|
||||
height: 100%;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: hidden;
|
||||
box-shadow: 0 0 1rem #00000088;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { VariableUiElement } from "./VariableUIElement"
|
|||
import Locale from "../i18n/Locale"
|
||||
import Link from "./Link"
|
||||
import Svg from "../../Svg"
|
||||
import SvelteUIElement from "./SvelteUIElement"
|
||||
import Translate from "../../assets/svg/Translate.svelte"
|
||||
|
||||
/**
|
||||
* The little 'translate'-icon next to every icon + some static helper functions
|
||||
|
|
@ -20,7 +22,7 @@ export default class LinkToWeblate extends VariableUiElement {
|
|||
if (context === undefined || context.indexOf(":") < 0) {
|
||||
return undefined
|
||||
}
|
||||
const icon = Svg.translate_svg().SetClass(
|
||||
const icon = new SvelteUIElement(Translate).SetClass(
|
||||
"rounded-full inline-block w-3 h-3 ml-1 weblate-link self-center"
|
||||
)
|
||||
if (availableTranslations[ln] === undefined) {
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import BaseUIElement from "../BaseUIElement"
|
||||
|
||||
export class Paragraph extends BaseUIElement {
|
||||
public readonly content: string | BaseUIElement
|
||||
|
||||
constructor(html: string | BaseUIElement) {
|
||||
super()
|
||||
this.content = html ?? ""
|
||||
}
|
||||
|
||||
AsMarkdown(): string {
|
||||
let c: string
|
||||
if (typeof this.content !== "string") {
|
||||
c = this.content.AsMarkdown()
|
||||
} else {
|
||||
c = this.content
|
||||
}
|
||||
return "\n\n" + c + "\n\n"
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const e = document.createElement("p")
|
||||
if (typeof this.content !== "string") {
|
||||
e.appendChild(this.content.ConstructElement())
|
||||
} else {
|
||||
e.innerHTML = this.content
|
||||
}
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
|
@ -66,32 +66,4 @@ export class SubtleButton extends UIElement {
|
|||
this.SetClass(classes)
|
||||
return button
|
||||
}
|
||||
|
||||
public OnClickWithLoading(
|
||||
loadingText: BaseUIElement | string,
|
||||
action: () => Promise<void>
|
||||
): BaseUIElement {
|
||||
const state = new UIEventSource<"idle" | "running">("idle")
|
||||
const button = this
|
||||
|
||||
button.onClick(async () => {
|
||||
state.setData("running")
|
||||
try {
|
||||
await action()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
state.setData("idle")
|
||||
}
|
||||
})
|
||||
const loading = new Lazy(() => new Loading(loadingText))
|
||||
return new VariableUiElement(
|
||||
state.map((st) => {
|
||||
if (st === "idle") {
|
||||
return button
|
||||
}
|
||||
return loading
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* A mapcontrol button which allows the user to select a different background.
|
||||
* Even though the componenet is very small, it gets it's own class as it is often reused
|
||||
* Even though the component is very small, it gets it's own class as it is often reused
|
||||
*/
|
||||
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
<Tr t={Translations.t.general.returnToTheMap} />
|
||||
</button>
|
||||
{:else}
|
||||
<div class="flex h-full w-full flex-col gap-y-2 overflow-y-auto p-1 px-4" tabindex="-1">
|
||||
<div class="flex h-full flex-col gap-y-2 overflow-y-auto p-1 px-4 w-full selected-element-view" tabindex="-1">
|
||||
{#each $knownTagRenderings as config (config.id)}
|
||||
<TagRenderingEditable
|
||||
{tags}
|
||||
|
|
|
|||
181
src/UI/BigComponents/UploadTraceToOsmUI.svelte
Normal file
181
src/UI/BigComponents/UploadTraceToOsmUI.svelte
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<script lang="ts">
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import Invalid from "../../assets/svg/Invalid.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Confirm from "../../assets/svg/Confirm.svelte"
|
||||
import Upload from "../../assets/svg/Upload.svelte"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import Close from "../../assets/svg/Close.svelte"
|
||||
import { placeholder } from "../../Utils/placeholder"
|
||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||
import { selectDefault } from "../../Utils/selectDefault"
|
||||
|
||||
export let trace: (title: string) => string
|
||||
export let state: {
|
||||
layout: LayoutConfig
|
||||
osmConnection: OsmConnection
|
||||
readonly featureSwitchUserbadge: Store<boolean>
|
||||
}
|
||||
export let options: {
|
||||
whenUploaded?: () => void | Promise<void>
|
||||
} = undefined
|
||||
|
||||
|
||||
let t = Translations.t.general.uploadGpx
|
||||
let currentStep = new UIEventSource<"init" | "please_confirm" | "uploading" | "done" | "error">("init")
|
||||
|
||||
let traceVisibilities: {
|
||||
key: "private" | "public"
|
||||
name: Translation
|
||||
docs: Translation
|
||||
}[] = [
|
||||
{
|
||||
key: "private",
|
||||
...t.modes.private,
|
||||
},
|
||||
{
|
||||
key: "public",
|
||||
...t.modes.public,
|
||||
},
|
||||
]
|
||||
|
||||
let gpxServerIsOnline: Store<boolean> = state.osmConnection.gpxServiceIsOnline.map((serviceState) => serviceState === "online")
|
||||
|
||||
|
||||
/**
|
||||
* More or less the same as the coalescing-operator '??', except that it checks for empty strings too
|
||||
*/
|
||||
function createDefault(s: string, defaultValue: string): string {
|
||||
if (defaultValue.length < 1) {
|
||||
throw "Default value should have some characters"
|
||||
}
|
||||
if (s === undefined || s === null || s === "") {
|
||||
return defaultValue
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
let title: string = undefined
|
||||
let description: string = undefined
|
||||
|
||||
let visibility = <UIEventSource<"public" | "private">>state?.osmConnection?.GetPreference("gps.trace.visibility") ?? new UIEventSource<"public" | "private">("private")
|
||||
async function uploadTrace() {
|
||||
try {
|
||||
|
||||
currentStep.setData("uploading")
|
||||
const titleStr = createDefault(
|
||||
title,
|
||||
"Track with mapcomplete",
|
||||
)
|
||||
const descriptionStr = createDefault(
|
||||
description,
|
||||
"Track created with MapComplete with theme " + state?.layout?.id,
|
||||
)
|
||||
await state?.osmConnection?.uploadGpxTrack(trace(titleStr), {
|
||||
visibility: visibility.data ?? "private",
|
||||
description: descriptionStr,
|
||||
filename: titleStr + ".gpx",
|
||||
labels: ["MapComplete", state?.layout?.id],
|
||||
})
|
||||
|
||||
if (options?.whenUploaded !== undefined) {
|
||||
await options.whenUploaded()
|
||||
}
|
||||
currentStep.setData("done")
|
||||
} catch (e) {
|
||||
currentStep.setData("error")
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoginToggle {state}>
|
||||
|
||||
{#if !$gpxServerIsOnline}
|
||||
<div class="flex border alert items-center">
|
||||
<Invalid class="w-8 h-8 m-2" />
|
||||
<Tr t={t.gpxServiceOffline} cls="p-2" />
|
||||
</div>
|
||||
{:else if $currentStep === "error"}
|
||||
<div class="alert flex w-full gap-x-2">
|
||||
<Invalid class="w-8 h-8"/>
|
||||
<Tr t={Translations.t.general.error} />
|
||||
</div>
|
||||
{:else if $currentStep === "init"}
|
||||
<button class="flex w-full m-0" on:click={() => {currentStep.setData("please_confirm")}}>
|
||||
<Upload class="w-12 h-12" />
|
||||
<Tr t={t.title} />
|
||||
</button>
|
||||
{:else if $currentStep === "please_confirm"}
|
||||
<form on:submit|preventDefault={() => uploadTrace()}
|
||||
class="flex flex-col border-interactive interactive px-2 gap-y-1">
|
||||
<h2>
|
||||
<Tr t={t.title} />
|
||||
</h2>
|
||||
<Tr t={t.intro0} />
|
||||
<Tr t={t.intro1} />
|
||||
|
||||
|
||||
<h3>
|
||||
<Tr t={t.meta.title} />
|
||||
</h3>
|
||||
<Tr t={t.meta.intro} />
|
||||
<input type="text" use:ariaLabel={t.meta.titlePlaceholder} use:placeholder={t.meta.titlePlaceholder}
|
||||
bind:value={title} />
|
||||
<Tr t={t.meta.descriptionIntro} />
|
||||
|
||||
<textarea use:ariaLabel={t.meta.descriptionPlaceHolder} use:placeholder={t.meta.descriptionPlaceHolder}
|
||||
bind:value={description} />
|
||||
|
||||
<Tr t={t.choosePermission} />
|
||||
|
||||
{#each traceVisibilities as option}
|
||||
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="visibility"
|
||||
value={option.key}
|
||||
bind:group={$visibility}
|
||||
use:selectDefault={visibility}
|
||||
/>
|
||||
|
||||
<Tr t={option.name} cls="font-bold" />
|
||||
-
|
||||
<Tr t={option.docs} />
|
||||
</label>
|
||||
{/each}
|
||||
|
||||
|
||||
<div class="flex flex-wrap-reverse justify-between items-stretch">
|
||||
<button class="flex gap-x-2 w-1/2 flex-grow" on:click={() => currentStep.setData("init")}>
|
||||
<Close class="w-8 h-8" />
|
||||
<Tr t={Translations.t.general.cancel} />
|
||||
</button>
|
||||
|
||||
<button class="flex gap-x-2 primary flex-grow" on:click={() => uploadTrace()}>
|
||||
<Upload class="w-8 h-8" />
|
||||
<Tr t={t.confirm} />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{:else if $currentStep === "uploading"}
|
||||
<Loading>
|
||||
<Tr t={t.uploading} />
|
||||
</Loading>
|
||||
|
||||
|
||||
{:else if $currentStep === "done"}
|
||||
<div class="flex p-2 rounded-xl border-2 subtle-border items-center">
|
||||
<Confirm class="w-12 h-12 mr-2" />
|
||||
<Tr t={t.uploadFinished} />
|
||||
</div>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
import Toggle from "../Input/Toggle"
|
||||
import { RadioButton } from "../Input/RadioButton"
|
||||
import { FixedInputElement } from "../Input/FixedInputElement"
|
||||
import Combine from "../Base/Combine"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { TextField } from "../Input/TextField"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Title from "../Base/Title"
|
||||
import { SubtleButton } from "../Base/SubtleButton"
|
||||
import Svg from "../../Svg"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { LoginToggle } from "../Popup/LoginButton"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import Upload from "../../assets/svg/Upload.svelte"
|
||||
|
||||
export default class UploadTraceToOsmUI extends LoginToggle {
|
||||
constructor(
|
||||
trace: (title: string) => string,
|
||||
state: {
|
||||
layout: LayoutConfig
|
||||
osmConnection: OsmConnection
|
||||
readonly featureSwitchUserbadge: Store<boolean>
|
||||
},
|
||||
options?: {
|
||||
whenUploaded?: () => void | Promise<void>
|
||||
}
|
||||
) {
|
||||
const t = Translations.t.general.uploadGpx
|
||||
const uploadFinished = new UIEventSource(false)
|
||||
const traceVisibilities: {
|
||||
key: "private" | "public"
|
||||
name: Translation
|
||||
docs: Translation
|
||||
}[] = [
|
||||
{
|
||||
key: "private",
|
||||
...t.modes.private,
|
||||
},
|
||||
{
|
||||
key: "public",
|
||||
...t.modes.public,
|
||||
},
|
||||
]
|
||||
|
||||
const dropdown = new RadioButton<"private" | "public">(
|
||||
traceVisibilities.map(
|
||||
(tv) =>
|
||||
new FixedInputElement<"private" | "public">(
|
||||
new Combine([
|
||||
Translations.W(tv.name).SetClass("font-bold"),
|
||||
tv.docs,
|
||||
]).SetClass("flex flex-col"),
|
||||
tv.key
|
||||
)
|
||||
),
|
||||
{
|
||||
value: <any>state?.osmConnection?.GetPreference("gps.trace.visibility"),
|
||||
}
|
||||
)
|
||||
const description = new TextField({
|
||||
placeholder: t.meta.descriptionPlaceHolder,
|
||||
})
|
||||
const title = new TextField({
|
||||
placeholder: t.meta.titlePlaceholder,
|
||||
})
|
||||
const clicked = new UIEventSource<boolean>(false)
|
||||
|
||||
const confirmPanel = new Combine([
|
||||
new Title(t.title),
|
||||
t.intro0,
|
||||
t.intro1,
|
||||
|
||||
t.choosePermission,
|
||||
dropdown,
|
||||
new Title(t.meta.title, 4),
|
||||
t.meta.intro,
|
||||
title,
|
||||
t.meta.descriptionIntro,
|
||||
description,
|
||||
new Combine([
|
||||
new SubtleButton(Svg.close_svg(), Translations.t.general.cancel)
|
||||
.onClick(() => {
|
||||
clicked.setData(false)
|
||||
})
|
||||
.SetClass(""),
|
||||
new SubtleButton(new SvelteUIElement(Upload, {}), t.confirm).OnClickWithLoading(
|
||||
t.uploading,
|
||||
async () => {
|
||||
const titleStr = UploadTraceToOsmUI.createDefault(
|
||||
title.GetValue().data,
|
||||
"Track with mapcomplete"
|
||||
)
|
||||
const descriptionStr = UploadTraceToOsmUI.createDefault(
|
||||
description.GetValue().data,
|
||||
"Track created with MapComplete with theme " + state?.layout?.id
|
||||
)
|
||||
await state?.osmConnection?.uploadGpxTrack(trace(title.GetValue().data), {
|
||||
visibility: dropdown.GetValue().data,
|
||||
description: descriptionStr,
|
||||
filename: titleStr + ".gpx",
|
||||
labels: ["MapComplete", state?.layout?.id],
|
||||
})
|
||||
|
||||
if (options?.whenUploaded !== undefined) {
|
||||
await options.whenUploaded()
|
||||
}
|
||||
uploadFinished.setData(true)
|
||||
}
|
||||
),
|
||||
]).SetClass("flex flex-wrap flex-wrap-reverse justify-between items-stretch"),
|
||||
]).SetClass("flex flex-col p-4 rounded border-2 m-2 border-subtle")
|
||||
|
||||
super(
|
||||
new Toggle(
|
||||
new Toggle(
|
||||
new Combine([
|
||||
Svg.confirm_svg().SetClass("w-12 h-12 mr-2"),
|
||||
t.uploadFinished,
|
||||
]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"),
|
||||
new Toggle(
|
||||
confirmPanel,
|
||||
new SubtleButton(new SvelteUIElement(Upload), t.title)
|
||||
.onClick(() => clicked.setData(true))
|
||||
.SetClass("w-full"),
|
||||
clicked
|
||||
),
|
||||
uploadFinished
|
||||
),
|
||||
new Combine([
|
||||
Svg.invalid_svg().SetClass("w-8 h-8 m-2"),
|
||||
t.gpxServiceOffline.SetClass("p-2"),
|
||||
]).SetClass("flex border alert items-center"),
|
||||
state.osmConnection.gpxServiceIsOnline.map(
|
||||
(serviceState) => serviceState === "online"
|
||||
)
|
||||
),
|
||||
undefined,
|
||||
state
|
||||
)
|
||||
}
|
||||
|
||||
private static createDefault(s: string, defaultValue: string) {
|
||||
if (defaultValue.length < 1) {
|
||||
throw "Default value should have some characters"
|
||||
}
|
||||
if (s === undefined || s === null || s === "") {
|
||||
return defaultValue
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,16 @@
|
|||
import panzoom from "panzoom"
|
||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Zoomcontrol from "../Zoomcontrol"
|
||||
import { onDestroy, onMount } from "svelte"
|
||||
|
||||
export let image: ProvidedImage
|
||||
let panzoomInstance = undefined
|
||||
let panzoomEl: HTMLElement
|
||||
export let isLoaded: UIEventSource<boolean> = undefined
|
||||
|
||||
|
||||
onDestroy(Zoomcontrol.createLock())
|
||||
|
||||
$: {
|
||||
if (panzoomEl) {
|
||||
panzoomInstance = panzoom(panzoomEl, {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
import Translations from "../i18n/Translations"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
|
||||
export let tags: Store<OsmTags>
|
||||
export let tags: UIEventSource<OsmTags>
|
||||
export let state: SpecialVisualizationState
|
||||
export let lon: number
|
||||
export let lat: number
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { OsmTags } from "../../Models/OsmFeature"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import type { Feature } from "geojson"
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||
|
||||
export let tags: Store<OsmTags>
|
||||
export let tags: UIEventSource<OsmTags>
|
||||
export let state: SpecialVisualizationState
|
||||
export let lon: number
|
||||
export let lat: number
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
{/if}
|
||||
{#if $failed > 0}
|
||||
<div class="alert flex flex-col">
|
||||
{#if failed === 1}
|
||||
{#if $failed === 1}
|
||||
<Tr cls="self-center" t={t.upload.one.failed} />
|
||||
{:else}
|
||||
<Tr cls="self-center" t={t.upload.multiple.someFailed.Subs({ count: $failed })} />
|
||||
|
|
|
|||
|
|
@ -51,6 +51,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function onKeyPress(e: KeyboardEvent){
|
||||
if(e.key === "Enter"){
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
dispatch("submit")
|
||||
}
|
||||
}
|
||||
initValueAndDenom()
|
||||
|
||||
$: {
|
||||
|
|
@ -126,7 +135,7 @@
|
|||
|
||||
let htmlElem: HTMLInputElement | HTMLTextAreaElement
|
||||
|
||||
let dispatch = createEventDispatcher<{ selected }>()
|
||||
let dispatch = createEventDispatcher<{ selected, submit }>()
|
||||
$: {
|
||||
if (htmlElem !== undefined) {
|
||||
htmlElem.onfocus = () => dispatch("selected")
|
||||
|
|
@ -144,6 +153,7 @@
|
|||
inputmode={validator?.inputmode ?? "text"}
|
||||
placeholder={_placeholder}
|
||||
bind:this={htmlElem}
|
||||
on:keypress={onKeyPress}
|
||||
/>
|
||||
{:else}
|
||||
<div class={twMerge("inline-flex", cls)}>
|
||||
|
|
@ -153,6 +163,7 @@
|
|||
class="w-full"
|
||||
inputmode={validator?.inputmode ?? "text"}
|
||||
placeholder={_placeholder}
|
||||
on:keypress={onKeyPress}
|
||||
/>
|
||||
{#if !$isValid}
|
||||
<ExclamationIcon class="-ml-6 h-6 w-6" />
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
let iconItem = icon.icon?.GetRenderValue($tags)?.Subs($tags)?.txt
|
||||
$: iconItem = icon.icon?.GetRenderValue($tags)?.Subs($tags)?.txt
|
||||
let color = icon.color?.GetRenderValue(tags)?.txt ?? "#000000"
|
||||
let color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000"
|
||||
$: color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000"
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,47 +1,48 @@
|
|||
<script lang="ts">
|
||||
import Pin from "../../assets/svg/Pin.svelte"
|
||||
import Square from "../../assets/svg/Square.svelte"
|
||||
import Circle from "../../assets/svg/Circle.svelte"
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte"
|
||||
import Clock from "../../assets/svg/Clock.svelte"
|
||||
import Close from "../../assets/svg/Close.svelte"
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte"
|
||||
import Help from "../../assets/svg/Help.svelte"
|
||||
import Home from "../../assets/svg/Home.svelte"
|
||||
import Invalid from "../../assets/svg/Invalid.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte"
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte"
|
||||
import Note from "../../assets/svg/Note.svelte"
|
||||
import Resolved from "../../assets/svg/Resolved.svelte"
|
||||
import Ring from "../../assets/svg/Ring.svelte"
|
||||
import Scissors from "../../assets/svg/Scissors.svelte"
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte"
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
|
||||
import Triangle from "../../assets/svg/Triangle.svelte"
|
||||
import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte"
|
||||
import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte"
|
||||
import Gps_arrow from "../../assets/svg/Gps_arrow.svelte"
|
||||
import { HeartIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"
|
||||
import Confirm from "../../assets/svg/Confirm.svelte"
|
||||
import Not_found from "../../assets/svg/Not_found.svelte"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import Direction_gradient from "../../assets/svg/Direction_gradient.svelte"
|
||||
import Mastodon from "../../assets/svg/Mastodon.svelte"
|
||||
import Party from "../../assets/svg/Party.svelte"
|
||||
import AddSmall from "../../assets/svg/AddSmall.svelte"
|
||||
import { LinkIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import Pin from "../../assets/svg/Pin.svelte"
|
||||
import Square from "../../assets/svg/Square.svelte"
|
||||
import Circle from "../../assets/svg/Circle.svelte"
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte"
|
||||
import Clock from "../../assets/svg/Clock.svelte"
|
||||
import Close from "../../assets/svg/Close.svelte"
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte"
|
||||
import Help from "../../assets/svg/Help.svelte"
|
||||
import Home from "../../assets/svg/Home.svelte"
|
||||
import Invalid from "../../assets/svg/Invalid.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte"
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte"
|
||||
import Note from "../../assets/svg/Note.svelte"
|
||||
import Resolved from "../../assets/svg/Resolved.svelte"
|
||||
import Ring from "../../assets/svg/Ring.svelte"
|
||||
import Scissors from "../../assets/svg/Scissors.svelte"
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte"
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
|
||||
import Triangle from "../../assets/svg/Triangle.svelte"
|
||||
import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte"
|
||||
import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte"
|
||||
import Gps_arrow from "../../assets/svg/Gps_arrow.svelte"
|
||||
import { HeartIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"
|
||||
import Confirm from "../../assets/svg/Confirm.svelte"
|
||||
import Not_found from "../../assets/svg/Not_found.svelte"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import Direction_gradient from "../../assets/svg/Direction_gradient.svelte"
|
||||
import Mastodon from "../../assets/svg/Mastodon.svelte"
|
||||
import Party from "../../assets/svg/Party.svelte"
|
||||
import AddSmall from "../../assets/svg/AddSmall.svelte"
|
||||
import { LinkIcon } from "@babeard/svelte-heroicons/mini"
|
||||
|
||||
/**
|
||||
* Renders a single icon.
|
||||
*
|
||||
* Icons -placed on top of each other- form a 'Marker' together
|
||||
*/
|
||||
/**
|
||||
* Renders a single icon.
|
||||
*
|
||||
* Icons -placed on top of each other- form a 'Marker' together
|
||||
*/
|
||||
|
||||
export let icon: string | undefined
|
||||
export let color: string | undefined = undefined
|
||||
export let clss: string | undefined = undefined
|
||||
|
||||
export let icon: string | undefined
|
||||
export let color: string | undefined = undefined
|
||||
export let clss: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
{#if icon}
|
||||
|
|
@ -100,9 +101,9 @@
|
|||
{:else if icon === "invalid"}
|
||||
<Invalid {color} class={clss} />
|
||||
{:else if icon === "heart"}
|
||||
<HeartIcon class={clss} />
|
||||
<HeartIcon style="--svg-color: {color}" class={twMerge(clss,"apply-fill")} />
|
||||
{:else if icon === "heart_outline"}
|
||||
<HeartOutlineIcon class={clss} />
|
||||
<HeartOutlineIcon style="--svg-color: {color}" class={twMerge(clss, "apply-fill")} />
|
||||
{:else if icon === "confirm"}
|
||||
<Confirm class={clss} {color} />
|
||||
{:else if icon === "direction"}
|
||||
|
|
@ -115,9 +116,10 @@
|
|||
<Party {color} class={clss} />
|
||||
{:else if icon === "addSmall"}
|
||||
<AddSmall {color} class={clss} />
|
||||
{:else if icon === "link"}
|
||||
<LinkIcon class={clss}/>
|
||||
{:else if icon === "link"}
|
||||
<LinkIcon style="--svg-color: {color}" class={twMerge(clss, "apply-fill")}/>
|
||||
{:else}
|
||||
<img class={clss ?? "h-full w-full"} src={icon} aria-hidden="true" alt="" />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
}
|
||||
}
|
||||
|
||||
private async setBackground(): Promise<void> {
|
||||
private async setBackground(retryAttempts: number = 3): Promise<void> {
|
||||
const map = this._maplibreMap.data
|
||||
if (!map) {
|
||||
return
|
||||
|
|
@ -585,12 +585,23 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
} else {
|
||||
// Make sure that the default maptiler style is loaded as it gives an overlay with roads
|
||||
const maptiler = AvailableRasterLayers.maptilerDefaultLayer.properties
|
||||
try {
|
||||
|
||||
await this.awaitStyleIsLoaded()
|
||||
if (!map.getSource(maptiler.id)) {
|
||||
this.removeCurrentLayer(map)
|
||||
map.addSource(maptiler.id, MapLibreAdaptor.prepareWmsSource(maptiler))
|
||||
map.setStyle(maptiler.url)
|
||||
await this.awaitStyleIsLoaded()
|
||||
}
|
||||
}catch (e) {
|
||||
if(retryAttempts > 0){
|
||||
window.requestAnimationFrame(() => {
|
||||
console.log("Retrying to set the background ("+retryAttempts+" attempts remaining)... Failed because",e)
|
||||
this.setBackground(retryAttempts-1)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!map.getLayer(addLayerBeforeId)) {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
</script>
|
||||
|
||||
{#if hasLayers}
|
||||
<form class="flex h-full w-full flex-col">
|
||||
<form class="flex h-full w-full flex-col" on:submit|preventDefault={() => {}}>
|
||||
<button
|
||||
tabindex="-1"
|
||||
on:click={() => apply()}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import FilteredLayer from "../../../Models/FilteredLayer"
|
||||
import Confirm from "../../../assets/svg/Confirm.svelte"
|
||||
|
||||
export let importFlow: ImportFlow<ImportFlowArguments>
|
||||
let state = importFlow.state
|
||||
|
|
@ -159,7 +160,7 @@
|
|||
{#if importFlow.args.icon}
|
||||
<img src={importFlow.args.icon} />
|
||||
{:else}
|
||||
<ToSvelte construct={Svg.confirm_svg().SetClass("w-8 h-8 pr-4")} />
|
||||
<Confirm class="w-8 h-8 pr-4"/>
|
||||
{/if}
|
||||
</span>
|
||||
<slot name="confirm-text">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import Svg from "../../Svg"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
|
|
@ -10,6 +9,9 @@ import { And } from "../../Logic/Tags/And"
|
|||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { Feature, Point } from "geojson"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import Confirm from "../../assets/svg/Confirm.svelte"
|
||||
import Relocation from "../../assets/svg/Relocation.svelte"
|
||||
|
||||
export interface MoveReason {
|
||||
text: Translation | string
|
||||
|
|
@ -32,10 +34,7 @@ export class MoveWizardState {
|
|||
|
||||
constructor(id: string, options: MoveConfig, state: SpecialVisualizationState) {
|
||||
this._state = state
|
||||
const t = Translations.t.move
|
||||
|
||||
this.reasons = MoveWizardState.initReasons(options)
|
||||
|
||||
if (this.reasons.length > 0) {
|
||||
this.checkIsAllowed(id)
|
||||
}
|
||||
|
|
@ -49,7 +48,7 @@ export class MoveWizardState {
|
|||
reasons.push({
|
||||
text: t.reasons.reasonRelocation,
|
||||
invitingText: t.inviteToMove.reasonRelocation,
|
||||
icon: Svg.relocation_svg(),
|
||||
icon: new SvelteUIElement(Relocation),
|
||||
changesetCommentValue: "relocated",
|
||||
lockBounds: false,
|
||||
background: undefined,
|
||||
|
|
@ -63,7 +62,7 @@ export class MoveWizardState {
|
|||
reasons.push({
|
||||
text: t.reasons.reasonInaccurate,
|
||||
invitingText: t.inviteToMove.reasonInaccurate,
|
||||
icon: Svg.crosshair_svg(),
|
||||
icon: new SvelteUIElement(Confirm),
|
||||
changesetCommentValue: "improve_accuracy",
|
||||
lockBounds: true,
|
||||
includeSearch: false,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { LoginToggle } from ".././LoginButton"
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import Constants from "../../../Models/Constants"
|
||||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
||||
import Checkmark from "../../../assets/svg/Checkmark.svelte"
|
||||
|
||||
export class CloseNoteButton implements SpecialVisualization {
|
||||
public readonly funcName = "close_note"
|
||||
|
|
@ -61,7 +63,7 @@ export class CloseNoteButton implements SpecialVisualization {
|
|||
zoomButton: string
|
||||
} = <any>Utils.ParseVisArgs(this.args, args)
|
||||
|
||||
let icon = Svg.checkmark_svg()
|
||||
let icon: BaseUIElement = new SvelteUIElement(Checkmark)
|
||||
if (params.icon !== "checkmark.svg" && (args[2] ?? "") !== "") {
|
||||
icon = new Img(args[1])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import type { Feature } from "geojson"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import TagRenderingAnswer from "./TagRenderingAnswer.svelte"
|
||||
import { PencilAltIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { PencilAltIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import TagRenderingQuestion from "./TagRenderingQuestion.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
|
|
@ -27,12 +27,12 @@
|
|||
/**
|
||||
* 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)
|
||||
if (tags) {
|
||||
onDestroy(
|
||||
tags.addCallbackD((tags) => {
|
||||
editMode = !config.IsKnown(tags)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
function onSave() {
|
||||
function onSave(e) {
|
||||
if (selectedTags === undefined) {
|
||||
return
|
||||
}
|
||||
|
|
@ -198,7 +198,9 @@
|
|||
|
||||
function onInputKeypress(e: KeyboardEvent) {
|
||||
if (e.key === "Enter") {
|
||||
onSave()
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onSave(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI"
|
||||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import Constants from "../../Models/Constants"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI.svelte"
|
||||
|
||||
/**
|
||||
* Wrapper around 'UploadTraceToOsmUI'
|
||||
|
|
@ -20,10 +21,9 @@ export class UploadToOsmViz implements SpecialVisualization {
|
|||
__: string[]
|
||||
) {
|
||||
const locations = state.historicalUserLocations.features.data
|
||||
return new UploadTraceToOsmUI((title) => GeoOperations.toGpx(locations, title), state, {
|
||||
whenUploaded: async () => {
|
||||
state.historicalUserLocations.features.setData([])
|
||||
},
|
||||
return new SvelteUIElement(UploadTraceToOsmUI, {
|
||||
state,
|
||||
trace: (title: string) => GeoOperations.toGpx(locations, title)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@
|
|||
<TagRenderingEditable
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ export abstract class EditJsonState<T> {
|
|||
|
||||
public setValueAt(path: ReadonlyArray<string | number>, v: any) {
|
||||
let entry = this.configuration.data
|
||||
console.trace("Setting value at", path,"to",v)
|
||||
const isUndefined =
|
||||
v === undefined ||
|
||||
v === null ||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,6 @@
|
|||
selectedElement={state.exampleFeature}
|
||||
{config}
|
||||
editingEnabled={new ImmutableStore(true)}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@
|
|||
return { ...<object>v }
|
||||
}
|
||||
if (schema.type === "boolean") {
|
||||
if(v === null || v === undefined){
|
||||
return v
|
||||
}
|
||||
return v === "true" || v === "yes" || v === "1"
|
||||
}
|
||||
if (mightBeBoolean(schema.type)) {
|
||||
|
|
@ -187,7 +190,6 @@
|
|||
editMode={startInEditMode}
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -215,7 +215,6 @@
|
|||
<TagRenderingEditable
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={!schema.hints?.ifunset}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -139,7 +139,6 @@
|
|||
<TagRenderingEditable
|
||||
config={configBuiltin}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,7 @@
|
|||
<script lang="ts">
|
||||
// Testing grounds
|
||||
import Motion from "../Sensors/Motion"
|
||||
import { Store, Stores } from "../Logic/UIEventSource"
|
||||
|
||||
let maxAcc = Motion.singleton.maxAcc
|
||||
let shaken = Motion.singleton.lastShakeEvent
|
||||
let recentlyShaken = Stores.Chronic(250).mapD(
|
||||
(now) => now.getTime() - 3000 < shaken.data?.getTime()
|
||||
)
|
||||
import Icon from "./Map/Icon.svelte"
|
||||
</script>
|
||||
|
||||
Acc: {$maxAcc}
|
||||
{#if $recentlyShaken}
|
||||
<div class="text-5xl text-red-500">SHAKEN</div>
|
||||
{/if}
|
||||
<Icon clss="h-16 w-16" icon="heart" color="#ff0000"/>
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@
|
|||
if (element.properties.id.startsWith("current_view")) {
|
||||
return currentViewLayer
|
||||
}
|
||||
if(element.properties.id === "location_track"){
|
||||
return layout.layers.find(l => l.id === "gps_track")
|
||||
}
|
||||
return state.layout.getMatchingLayer(element.properties)
|
||||
},
|
||||
)
|
||||
|
|
@ -392,7 +395,7 @@
|
|||
<!-- Floatover with the selected element, if applicable -->
|
||||
<FloatOver
|
||||
on:close={() => {
|
||||
selectedElement.setData(undefined)
|
||||
state.selectedElement.setData(undefined)
|
||||
}}
|
||||
>
|
||||
<div class="flex h-full w-full">
|
||||
|
|
|
|||
|
|
@ -9,9 +9,15 @@ import Combine from "../Base/Combine"
|
|||
import Img from "../Base/Img"
|
||||
import { WikimediaImageProvider } from "../../Logic/ImageProviders/WikimediaImageProvider"
|
||||
import Link from "../Base/Link"
|
||||
import Svg from "../../Svg"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { Utils } from "../../Utils"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import * as Wikidata_icon from "../../assets/svg/Wikidata.svelte"
|
||||
import Gender_male from "../../assets/svg/Gender_male.svelte"
|
||||
import Gender_female from "../../assets/svg/Gender_female.svelte"
|
||||
import Gender_inter from "../../assets/svg/Gender_inter.svelte"
|
||||
import Gender_trans from "../../assets/svg/Gender_trans.svelte"
|
||||
import Gender_queer from "../../assets/svg/Gender_queer.svelte"
|
||||
|
||||
export default class WikidataPreviewBox extends VariableUiElement {
|
||||
private static isHuman = [{ p: 31 /*is a*/, q: 5 /* human */ }]
|
||||
|
|
@ -28,22 +34,36 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
requires: WikidataPreviewBox.isHuman,
|
||||
property: "P21",
|
||||
display: new Map([
|
||||
["Q6581097", () => Svg.gender_male_svg().SetStyle("width: 1rem; height: auto")],
|
||||
["Q6581072", () => Svg.gender_female_svg().SetStyle("width: 1rem; height: auto")],
|
||||
["Q1097630", () => Svg.gender_inter_svg().SetStyle("width: 1rem; height: auto")],
|
||||
[
|
||||
"Q6581097",
|
||||
() => new SvelteUIElement(Gender_male).SetStyle("width: 1rem; height: auto"),
|
||||
],
|
||||
[
|
||||
"Q6581072",
|
||||
() => new SvelteUIElement(Gender_female).SetStyle("width: 1rem; height: auto"),
|
||||
],
|
||||
[
|
||||
"Q1097630",
|
||||
() => new SvelteUIElement(Gender_inter).SetStyle("width: 1rem; height: auto"),
|
||||
],
|
||||
[
|
||||
"Q1052281",
|
||||
() =>
|
||||
Svg.gender_trans_svg().SetStyle(
|
||||
new SvelteUIElement(Gender_trans).SetStyle(
|
||||
"width: 1rem; height: auto"
|
||||
) /*'transwomen'*/,
|
||||
],
|
||||
[
|
||||
"Q2449503",
|
||||
() =>
|
||||
Svg.gender_trans_svg().SetStyle("width: 1rem; height: auto") /*'transmen'*/,
|
||||
new SvelteUIElement(Gender_trans).SetStyle(
|
||||
"width: 1rem; height: auto"
|
||||
) /*'transmen'*/,
|
||||
],
|
||||
[
|
||||
"Q48270",
|
||||
() => new SvelteUIElement(Gender_queer).SetStyle("width: 1rem; height: auto"),
|
||||
],
|
||||
["Q48270", () => Svg.gender_queer_svg().SetStyle("width: 1rem; height: auto")],
|
||||
]),
|
||||
textMode: new Map([
|
||||
["Q6581097", "♂️"],
|
||||
|
|
@ -116,7 +136,9 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
wikidata.id,
|
||||
options?.noImages
|
||||
? wikidata.id
|
||||
: Svg.wikidata_svg().SetStyle("width: 2.5rem").SetClass("block"),
|
||||
: new SvelteUIElement(Wikidata_icon)
|
||||
.SetStyle("width: 2.5rem")
|
||||
.SetClass("block"),
|
||||
]).SetClass("flex"),
|
||||
Wikidata.IdToArticle(wikidata.id),
|
||||
true
|
||||
|
|
|
|||
74
src/UI/Zoomcontrol.ts
Normal file
74
src/UI/Zoomcontrol.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { Stores, UIEventSource } from "../Logic/UIEventSource"
|
||||
import { Utils } from "../Utils"
|
||||
|
||||
/**
|
||||
* Utilities to (re)set user zoom (this is when the user enlarges HTML-elements by pinching out a non-map element).
|
||||
* If the user zooms in and goes back to the map, it should reset to 1.0
|
||||
*/
|
||||
export default class Zoomcontrol {
|
||||
private static readonly singleton = new Zoomcontrol()
|
||||
private static readonly initialValue =
|
||||
"width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0, user-scalable=1"
|
||||
private static readonly noZoom =
|
||||
"width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=1"
|
||||
|
||||
private readonly viewportElement: HTMLMetaElement
|
||||
|
||||
private readonly _allowZooming: UIEventSource<boolean>
|
||||
private readonly _lockTokens: Set<any> = new Set<any>()
|
||||
|
||||
private constructor() {
|
||||
if (Utils.runningFromConsole) {
|
||||
return
|
||||
}
|
||||
const metaElems = document.getElementsByTagName("head")[0].getElementsByTagName("meta")
|
||||
this.viewportElement = Array.from(metaElems).find(
|
||||
(meta) => meta.getAttribute("name") === "viewport"
|
||||
)
|
||||
this._allowZooming = new UIEventSource<boolean>(true)
|
||||
this._allowZooming.addCallback((allowed) => {
|
||||
this.apply(allowed ? Zoomcontrol.initialValue : Zoomcontrol.noZoom)
|
||||
})
|
||||
Stores.Chronic(1000).addCallback((_) =>
|
||||
console.log(this.viewportElement.getAttribute("content"))
|
||||
)
|
||||
}
|
||||
|
||||
private _resetZoom() {
|
||||
this.apply(Zoomcontrol.noZoom)
|
||||
requestAnimationFrame(() => {
|
||||
// Does not work on firefox, see https://bugzilla.mozilla.org/show_bug.cgi?id=1873934
|
||||
this.allowZoomIfUnlocked()
|
||||
})
|
||||
}
|
||||
|
||||
private apply(fullSpec: string) {
|
||||
this.viewportElement?.setAttribute("content", fullSpec)
|
||||
}
|
||||
|
||||
public static createLock(): () => void {
|
||||
return Zoomcontrol.singleton._createLock()
|
||||
}
|
||||
|
||||
private allowZoomIfUnlocked() {
|
||||
if (this._lockTokens.size > 0) {
|
||||
return
|
||||
}
|
||||
this.apply(Zoomcontrol.initialValue)
|
||||
}
|
||||
|
||||
private _createLock(): () => void {
|
||||
const token = {}
|
||||
const lockTokens = this._lockTokens
|
||||
lockTokens.add(token)
|
||||
this._resetZoom()
|
||||
return () => {
|
||||
lockTokens.delete(token)
|
||||
this.allowZoomIfUnlocked()
|
||||
}
|
||||
}
|
||||
|
||||
public static resetzoom() {
|
||||
this.singleton._resetZoom()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue