Merge develop

This commit is contained in:
Pieter Vander Vennet 2024-01-16 04:27:59 +01:00
commit c672fe7668
138 changed files with 14304 additions and 1299 deletions

View file

@ -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"

View file

@ -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}

View 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>

View file

@ -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
}
}