forked from MapComplete/MapComplete
Refactoring: overhaul of the visual style with CSS
This commit is contained in:
parent
a1f5032232
commit
7f1e8d3f9c
37 changed files with 1280 additions and 741 deletions
|
@ -5,7 +5,7 @@
|
|||
|
||||
<div class="pl-2 p-1 flex">
|
||||
<div class="animate-spin self-center w-6 h-6 min-w-6">
|
||||
<ToSvelte construct={Svg.loading_ui}></ToSvelte>
|
||||
<ToSvelte construct={Svg.loading_svg()}></ToSvelte>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<slot></slot>
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
</script>
|
||||
|
||||
|
||||
<div on:click={e => dispatch("click", e)} class="subtle-background rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1 cursor-pointer">
|
||||
<button on:click={e => dispatch("click", e)} class="secondary rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1">
|
||||
<slot/>
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
@ -1,73 +1,35 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Img from "./Img";
|
||||
import Translations from "../i18n/Translations";
|
||||
import { ImmutableStore } from "../../Logic/UIEventSource.js";
|
||||
|
||||
export let imageUrl: string | BaseUIElement = undefined
|
||||
export let message: string | BaseUIElement = undefined
|
||||
export let options: {
|
||||
url?: string | Store<string>
|
||||
newTab?: boolean
|
||||
imgSize?: string
|
||||
extraClasses?: string
|
||||
} = {}
|
||||
|
||||
// Website to open when clicked
|
||||
let href: Store<string> = undefined
|
||||
if (options?.url) {
|
||||
href = typeof options?.url == "string" ? new ImmutableStore(options.url) : options.url
|
||||
}
|
||||
|
||||
let imgElem: HTMLElement;
|
||||
let msgElem: HTMLElement;
|
||||
let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11");
|
||||
const dispatch = createEventDispatcher<{click}>()
|
||||
|
||||
onMount(() => {
|
||||
// Image
|
||||
if (imgElem && imageUrl) {
|
||||
let img: BaseUIElement
|
||||
|
||||
if ((imageUrl ?? "") === "") {
|
||||
img = undefined
|
||||
} else if (typeof imageUrl !== "string") {
|
||||
img = imageUrl?.SetClass(imgClasses)
|
||||
}
|
||||
if (img) imgElem.replaceWith(img.ConstructElement())
|
||||
}
|
||||
|
||||
// Message
|
||||
if (msgElem && message) {
|
||||
let msg = Translations.W(message)?.SetClass("block text-ellipsis no-images flex-shrink")
|
||||
msgElem.replaceWith(msg.ConstructElement())
|
||||
}
|
||||
})
|
||||
console.log("Slots:", $$slots)
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
<button
|
||||
class={(options.extraClasses??"") + 'flex hover:shadow-xl transition-[color,background-color,box-shadow] hover:bg-unsubtle cursor-pointer'}
|
||||
href={$href}
|
||||
target={options?.newTab ? "_blank" : ""}
|
||||
this={href === undefined ? "span" : "a"}
|
||||
on:click={(e) => dispatch("click", e)}
|
||||
>
|
||||
<slot name="image">
|
||||
{#if imageUrl !== undefined}
|
||||
{#if typeof imageUrl === "string"}
|
||||
<Img src={imageUrl} class={imgClasses}></Img>
|
||||
{:else }
|
||||
<template bind:this={imgElem} />
|
||||
{/if}
|
||||
{/if}
|
||||
</slot>
|
||||
|
||||
<slot name="message">
|
||||
<template bind:this={msgElem} />
|
||||
</slot>
|
||||
</svelte:element>
|
||||
<slot name="message"/>
|
||||
</button>
|
||||
|
||||
<style lang="scss">
|
||||
span,
|
||||
|
|
|
@ -6,6 +6,10 @@ import Lazy from "./Lazy"
|
|||
import Loading from "./Loading"
|
||||
import SubtleButtonSvelte from "./SubtleButton.svelte"
|
||||
import SvelteUIElement from "./SvelteUIElement"
|
||||
import SubtleLink from "./SubtleLink.svelte";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Combine from "./Combine";
|
||||
import Img from "./Img";
|
||||
|
||||
export class SubtleButton extends UIElement {
|
||||
private readonly imageUrl: string | BaseUIElement
|
||||
|
@ -34,11 +38,29 @@ export class SubtleButton extends UIElement {
|
|||
}
|
||||
|
||||
protected InnerRender(): string | BaseUIElement {
|
||||
return new SvelteUIElement(SubtleButtonSvelte, {
|
||||
imageUrl: this?.imageUrl ?? undefined,
|
||||
message: this?.message ?? "",
|
||||
options: this?.options ?? {},
|
||||
})
|
||||
if(this.options.url !== undefined){
|
||||
return new SvelteUIElement(SubtleLink, {href: this.options.url, newTab: this.options.newTab})
|
||||
}
|
||||
|
||||
const classes = "block flex p-3 my-2 bg-subtle rounded-lg hover:shadow-xl hover:bg-unsubtle transition-colors transition-shadow link-no-underline";
|
||||
const message = Translations.W(this.message)?.SetClass("block overflow-ellipsis no-images flex-shrink");
|
||||
let img;
|
||||
const imgClasses = "block justify-center flex-none mr-4 " + (this.options?.imgSize ?? "h-11 w-11")
|
||||
if ((this.imageUrl ?? "") === "") {
|
||||
img = undefined;
|
||||
} else if (typeof (this.imageUrl) === "string") {
|
||||
img = new Img(this.imageUrl)?.SetClass(imgClasses)
|
||||
} else {
|
||||
img = this.imageUrl?.SetClass(imgClasses);
|
||||
}
|
||||
const button = new Combine([
|
||||
img,
|
||||
message
|
||||
]).SetClass("flex items-center group w-full")
|
||||
|
||||
|
||||
this.SetClass(classes)
|
||||
return button
|
||||
}
|
||||
|
||||
public OnClickWithLoading(
|
||||
|
|
63
UI/Base/SubtleLink.svelte
Normal file
63
UI/Base/SubtleLink.svelte
Normal file
|
@ -0,0 +1,63 @@
|
|||
<script lang="ts">
|
||||
import {createEventDispatcher, onMount} from "svelte";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Img from "./Img";
|
||||
|
||||
export let imageUrl: string | BaseUIElement = undefined
|
||||
export let href: string
|
||||
export let newTab = false
|
||||
export let options: {
|
||||
imgSize?: string
|
||||
// extraClasses?: string
|
||||
} = {}
|
||||
|
||||
|
||||
let imgElem: HTMLElement;
|
||||
let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11");
|
||||
|
||||
onMount(() => {
|
||||
// Image
|
||||
if (imgElem && imageUrl) {
|
||||
let img: BaseUIElement
|
||||
|
||||
if ((imageUrl ?? "") === "") {
|
||||
img = undefined
|
||||
} else if (typeof imageUrl !== "string") {
|
||||
img = imageUrl?.SetClass(imgClasses)
|
||||
}
|
||||
if (img) imgElem.replaceWith(img.ConstructElement())
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<a
|
||||
class={(options.extraClasses??"") + 'flex hover:shadow-xl transition-[color,background-color,box-shadow] hover:bg-unsubtle cursor-pointer'}
|
||||
{href}
|
||||
target={newTab ? "_blank" : ""}}
|
||||
>
|
||||
<slot name="image">
|
||||
{#if imageUrl !== undefined}
|
||||
{#if typeof imageUrl === "string"}
|
||||
<Img src={imageUrl} class={imgClasses}></Img>
|
||||
{:else }
|
||||
<template bind:this={imgElem} />
|
||||
{/if}
|
||||
{/if}
|
||||
</slot>
|
||||
|
||||
<slot/>
|
||||
</a>
|
||||
|
||||
<style lang="scss">
|
||||
span,
|
||||
a {
|
||||
@apply flex p-3 my-2 py-4 rounded-lg shrink-0;
|
||||
@apply items-center w-full no-underline;
|
||||
@apply bg-subtle text-black;
|
||||
|
||||
:global(span) {
|
||||
@apply block text-ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -19,10 +19,10 @@
|
|||
<div class="tabbedgroup w-full h-full">
|
||||
<TabGroup class="h-full w-full flex flex-col" defaultIndex={1}
|
||||
on:change={(e) =>{if(e.detail >= 0){tab.setData( e.detail); }} }>
|
||||
<div class="tablist flex bg-gray-300 items-center justify-between sticky top-0">
|
||||
<div class="interactive flex items-center justify-between sticky top-0">
|
||||
<TabList class="flex flex-wrap">
|
||||
{#if $$slots.title1}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<div bind:this={tabElements[0]} class="flex">
|
||||
<slot name="title0">
|
||||
Tab 0
|
||||
|
@ -31,28 +31,28 @@
|
|||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title1}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<div bind:this={tabElements[1]} class="flex">
|
||||
<slot name="title1"/>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title2}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<div bind:this={tabElements[2]} class="flex">
|
||||
<slot name="title2"/>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title3}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<div bind:this={tabElements[3]} class="flex">
|
||||
<slot name="title3"/>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title4}
|
||||
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
|
||||
<Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}>
|
||||
<div bind:this={tabElements[4]} class="flex">
|
||||
<slot name="title4"/>
|
||||
</div>
|
||||
|
@ -110,15 +110,6 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
:global(.tab-selected) {
|
||||
/**
|
||||
For some reason, the exported tailwind style takes priority in production (but not in development)
|
||||
As the tabs are buttons, tailwind restyles them
|
||||
*/
|
||||
background-color: var(--catch-detail-color) !important;
|
||||
color: var(--catch-detail-color-contrast) !important;
|
||||
}
|
||||
|
||||
:global(.tab-selected svg) {
|
||||
fill: var(--catch-detail-color-contrast);
|
||||
}
|
||||
|
|
|
@ -3,36 +3,30 @@
|
|||
import {UIEventSource} from "../../Logic/UIEventSource"
|
||||
import Constants from "../../Models/Constants"
|
||||
import Svg from "../../Svg"
|
||||
import SubtleButton from "../Base/SubtleButton.svelte"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import SubtleLink from "../Base/SubtleLink.svelte";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
|
||||
export let userDetails: UIEventSource<UserDetails>
|
||||
const t = Translations.t.general.morescreen
|
||||
|
||||
console.log($userDetails.csCount < 50)
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if $userDetails.csCount < Constants.userJourney.themeGeneratorReadOnlyUnlock}
|
||||
<SubtleButton
|
||||
options={{
|
||||
url: "https://github.com/pietervdvn/MapComplete/issues",
|
||||
newTab: true,
|
||||
}}
|
||||
<SubtleLink
|
||||
url="https://github.com/pietervdvn/MapComplete/issues"
|
||||
newTab={true}
|
||||
>
|
||||
<span slot="message">{t.requestATheme.toString()}</span>
|
||||
</SubtleButton>
|
||||
<Tr t={t.requestATheme}/>
|
||||
</SubtleLink>
|
||||
{:else}
|
||||
<SubtleButton
|
||||
options={{
|
||||
url: "https://pietervdvn.github.io/mc/legacy/070/customGenerator.html",
|
||||
}}
|
||||
>
|
||||
<span slot="image">
|
||||
<ToSvelte construct={Svg.pencil_svg().SetClass("h-11 w-11 mx-4 bg-red")}/>
|
||||
</span>
|
||||
<span slot="message">{t.createYourOwnTheme.toString()}</span>
|
||||
</SubtleButton>
|
||||
<SubtleLink href="https://pietervdvn.github.io/mc/legacy/070/customGenerator.html">
|
||||
<span slot="image">
|
||||
<ToSvelte construct={Svg.pencil_svg().SetClass("h-11 w-11 mx-4 bg-red")}/>
|
||||
</span>
|
||||
<Tr t={t.createYourOwnTheme}/>
|
||||
</SubtleLink>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -109,51 +109,6 @@ export default class MoreScreen extends Combine {
|
|||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button linking to the given theme
|
||||
* @private
|
||||
*/
|
||||
public static createLinkButton(
|
||||
state: {
|
||||
locationControl?: UIEventSource<Loc>
|
||||
layoutToUse?: LayoutConfig
|
||||
},
|
||||
layout: {
|
||||
id: string
|
||||
icon: string
|
||||
title: any
|
||||
shortDescription: any
|
||||
definition?: any
|
||||
mustHaveLanguage?: boolean
|
||||
},
|
||||
isCustom: boolean = false
|
||||
): BaseUIElement {
|
||||
const url = MoreScreen.createUrlFor(layout, isCustom, state)
|
||||
let content = new Combine([
|
||||
new Translation(
|
||||
layout.title,
|
||||
!isCustom && !layout.mustHaveLanguage ? "themes:" + layout.id + ".title" : undefined
|
||||
),
|
||||
new Translation(layout.shortDescription)?.SetClass("subtle") ?? "",
|
||||
]).SetClass("overflow-hidden flex flex-col")
|
||||
|
||||
if (state.layoutToUse === undefined) {
|
||||
// Currently on the index screen: we style the buttons equally large
|
||||
content = new Combine([content]).SetClass("flex flex-col justify-center h-24")
|
||||
}
|
||||
|
||||
return new SubtleButton(layout.icon, content, { url, newTab: false })
|
||||
}
|
||||
|
||||
public static CreateProffessionalSerivesButton() {
|
||||
const t = Translations.t.professional.indexPage
|
||||
return new Combine([
|
||||
new Title(t.hook, 4),
|
||||
t.hookMore,
|
||||
new SubtleButton(undefined, t.button, { url: "./professional.html" }),
|
||||
]).SetClass("flex flex-col border border-gray-300 p-2 rounded-lg")
|
||||
}
|
||||
|
||||
public static MatchesLayout(
|
||||
layout: {
|
||||
id: string
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import SubtleButton from "../Base/SubtleButton.svelte"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
|
||||
export let search: UIEventSource<string>
|
||||
|
||||
|
@ -30,14 +31,12 @@
|
|||
search.setData("")
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<SubtleButton>
|
||||
<span slot="image">
|
||||
<ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")} />
|
||||
</span>
|
||||
<span slot="message">{t.noSearch.toString()}</span>
|
||||
<Tr t={t.noSearch} slot="message"/>
|
||||
</SubtleButton>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script lang="ts">
|
||||
import SubtleButton from "../Base/SubtleButton.svelte"
|
||||
import Title from "../Base/Title"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import SubtleLink from "../Base/SubtleLink.svelte";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
|
||||
const t = Translations.t.professional.indexPage
|
||||
</script>
|
||||
|
@ -12,9 +13,9 @@
|
|||
<span>
|
||||
{t.hookMore.toString()}
|
||||
</span>
|
||||
<SubtleButton options={{ url: "./professional.html" }}>
|
||||
<span slot="message">{t.button.toString()}</span>
|
||||
</SubtleButton>
|
||||
<SubtleLink href="./professional.html">
|
||||
<Tr slot="message" t={t.button} />
|
||||
</SubtleLink>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -1,114 +1,94 @@
|
|||
<script lang="ts">
|
||||
import SubtleButton from "../Base/SubtleButton.svelte"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import * as personal from "../../assets/themes/personal/personal.json"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import Constants from "../../Models/Constants"
|
||||
import type Loc from "../../Models/Loc"
|
||||
import type { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import {Translation} from "../i18n/Translation"
|
||||
import * as personal from "../../assets/themes/personal/personal.json"
|
||||
import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource"
|
||||
import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection"
|
||||
import Constants from "../../Models/Constants"
|
||||
import type Loc from "../../Models/Loc"
|
||||
import type {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import SubtleLink from "../Base/SubtleLink.svelte";
|
||||
|
||||
export let theme: LayoutInformation
|
||||
export let isCustom: boolean = false
|
||||
export let userDetails: UIEventSource<UserDetails>
|
||||
export let state: { osmConnection: OsmConnection; locationControl?: UIEventSource<Loc> }
|
||||
export let theme: LayoutInformation
|
||||
export let isCustom: boolean = false
|
||||
export let userDetails: UIEventSource<UserDetails>
|
||||
export let state: { osmConnection: OsmConnection; locationControl?: UIEventSource<Loc> }
|
||||
|
||||
$: title = new Translation(
|
||||
theme.title,
|
||||
!isCustom && !theme.mustHaveLanguage ? "themes:" + theme.id + ".title" : undefined
|
||||
)
|
||||
$: description = new Translation(theme.shortDescription)
|
||||
|
||||
// TODO: Improve this function
|
||||
function createUrl(
|
||||
layout: { id: string; definition?: string },
|
||||
isCustom: boolean,
|
||||
state?: { locationControl?: UIEventSource<{ lat; lon; zoom }>; layoutToUse?: { id } }
|
||||
): Store<string> {
|
||||
if (layout === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (layout.id === undefined) {
|
||||
console.error("ID is undefined for layout", layout)
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (layout.id === state?.layoutToUse?.id) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const currentLocation = state?.locationControl
|
||||
|
||||
let path = window.location.pathname
|
||||
// Path starts with a '/' and contains everything, e.g. '/dir/dir/page.html'
|
||||
path = path.substr(0, path.lastIndexOf("/"))
|
||||
// Path will now contain '/dir/dir', or empty string in case of nothing
|
||||
if (path === "") {
|
||||
path = "."
|
||||
}
|
||||
|
||||
let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?`
|
||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1" || location.port === "1234") {
|
||||
// Redirect to 'theme.html?layout=* instead of 'layout.html'. This is probably a debug run, where the routing does not work
|
||||
linkPrefix = `${path}/theme.html?layout=${layout.id}&`
|
||||
}
|
||||
|
||||
if (isCustom) {
|
||||
linkPrefix = `${path}/theme.html?userlayout=${layout.id}&`
|
||||
}
|
||||
|
||||
let hash = ""
|
||||
if (layout.definition !== undefined) {
|
||||
hash = "#" + btoa(JSON.stringify(layout.definition))
|
||||
}
|
||||
|
||||
return (
|
||||
currentLocation?.map((currentLocation) => {
|
||||
const params = [
|
||||
["z", currentLocation?.zoom],
|
||||
["lat", currentLocation?.lat],
|
||||
["lon", currentLocation?.lon],
|
||||
]
|
||||
.filter((part) => part[1] !== undefined)
|
||||
.map((part) => part[0] + "=" + part[1])
|
||||
.join("&")
|
||||
return `${linkPrefix}${params}${hash}`
|
||||
}) ?? new ImmutableStore<string>(`${linkPrefix}`)
|
||||
$: title = new Translation(
|
||||
theme.title,
|
||||
!isCustom && !theme.mustHaveLanguage ? "themes:" + theme.id + ".title" : undefined
|
||||
)
|
||||
}
|
||||
$: description = new Translation(theme.shortDescription)
|
||||
|
||||
// TODO: Improve this function
|
||||
function createUrl(
|
||||
layout: { id: string; definition?: string },
|
||||
isCustom: boolean,
|
||||
state?: { locationControl?: UIEventSource<{ lat; lon; zoom }>; layoutToUse?: { id } }
|
||||
): Store<string> {
|
||||
if (layout === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (layout.id === undefined) {
|
||||
console.error("ID is undefined for layout", layout)
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (layout.id === state?.layoutToUse?.id) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const currentLocation = state?.locationControl
|
||||
|
||||
let path = window.location.pathname
|
||||
// Path starts with a '/' and contains everything, e.g. '/dir/dir/page.html'
|
||||
path = path.substr(0, path.lastIndexOf("/"))
|
||||
// Path will now contain '/dir/dir', or empty string in case of nothing
|
||||
if (path === "") {
|
||||
path = "."
|
||||
}
|
||||
|
||||
let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?`
|
||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1" || location.port === "1234") {
|
||||
// Redirect to 'theme.html?layout=* instead of 'layout.html'. This is probably a debug run, where the routing does not work
|
||||
linkPrefix = `${path}/theme.html?layout=${layout.id}&`
|
||||
}
|
||||
|
||||
if (isCustom) {
|
||||
linkPrefix = `${path}/theme.html?userlayout=${layout.id}&`
|
||||
}
|
||||
|
||||
let hash = ""
|
||||
if (layout.definition !== undefined) {
|
||||
hash = "#" + btoa(JSON.stringify(layout.definition))
|
||||
}
|
||||
|
||||
return (
|
||||
currentLocation?.map((currentLocation) => {
|
||||
const params = [
|
||||
["z", currentLocation?.zoom],
|
||||
["lat", currentLocation?.lat],
|
||||
["lon", currentLocation?.lon],
|
||||
]
|
||||
.filter((part) => part[1] !== undefined)
|
||||
.map((part) => part[0] + "=" + part[1])
|
||||
.join("&")
|
||||
return `${linkPrefix}${params}${hash}`
|
||||
}) ?? new ImmutableStore<string>(`${linkPrefix}`)
|
||||
)
|
||||
}
|
||||
|
||||
let href = createUrl(theme, isCustom, state)
|
||||
</script>
|
||||
|
||||
{#if theme.id !== personal.id || $userDetails.csCount > Constants.userJourney.personalLayoutUnlock}
|
||||
<div>
|
||||
<SubtleButton options={{ url: createUrl(theme, isCustom, state) }}>
|
||||
<img slot="image" src={theme.icon} class="block h-11 w-11 bg-red mx-4" alt="" />
|
||||
<span slot="message" class="message">
|
||||
<span>
|
||||
<Tr t={title}></Tr>
|
||||
<span class="subtle">
|
||||
<Tr t={description}></Tr>
|
||||
<SubtleLink href={ $href }>
|
||||
<img slot="image" src={theme.icon} class="block h-11 w-11 bg-red mx-4" alt=""/>
|
||||
<span class="flex flex-col text-ellipsis overflow-hidden">
|
||||
<Tr t={title}/>
|
||||
<span class="subtle max-h-12">
|
||||
<Tr t={description}/>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</SubtleButton>
|
||||
</div>
|
||||
</SubtleLink>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
div {
|
||||
@apply h-32 min-h-[8rem] max-h-32 text-ellipsis overflow-hidden;
|
||||
|
||||
span.message {
|
||||
@apply flex flex-col justify-center h-24;
|
||||
|
||||
& > span {
|
||||
@apply flex flex-col overflow-hidden;
|
||||
|
||||
span:nth-child(2) {
|
||||
@apply text-[#999];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
const t = Translations.t.general
|
||||
const currentIds: Store<string[]> = state.installedUserThemes
|
||||
const stableIds = Stores.ListStabilized<string>(currentIds)
|
||||
let customThemes
|
||||
$: customThemes = Utils.NoNull($stableIds.map((id) => state.GetUnofficialTheme(id)))
|
||||
$: console.log("Custom themes are", customThemes)
|
||||
</script>
|
||||
|
||||
<ThemesList
|
||||
|
|
|
@ -28,7 +28,6 @@ export class CheckBox extends InputElementMap<number[], boolean> {
|
|||
*/
|
||||
export default class CheckBoxes extends InputElement<number[]> {
|
||||
private static _nextId = 0
|
||||
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
private readonly value: UIEventSource<number[]>
|
||||
private readonly _elements: BaseUIElement[]
|
||||
|
||||
|
@ -65,12 +64,12 @@ export default class CheckBoxes extends InputElement<number[]> {
|
|||
|
||||
const label = document.createElement("label")
|
||||
label.htmlFor = input.id
|
||||
label.appendChild(input)
|
||||
label.appendChild(inputI.ConstructElement())
|
||||
label.classList.add("block", "w-full", "p-2", "cursor-pointer", "bg-red")
|
||||
|
||||
const wrapper = document.createElement("div")
|
||||
wrapper.classList.add("wrapper", "flex", "w-full", "border", "border-gray-400", "mb-1")
|
||||
wrapper.appendChild(input)
|
||||
wrapper.appendChild(label)
|
||||
formTag.appendChild(wrapper)
|
||||
|
||||
|
@ -78,11 +77,9 @@ export default class CheckBoxes extends InputElement<number[]> {
|
|||
input.checked = selectedValues.indexOf(i) >= 0
|
||||
|
||||
if (input.checked) {
|
||||
wrapper.classList.remove("border-gray-400")
|
||||
wrapper.classList.add("border-black")
|
||||
wrapper.classList.add("checked")
|
||||
} else {
|
||||
wrapper.classList.add("border-gray-400")
|
||||
wrapper.classList.remove("border-black")
|
||||
wrapper.classList.remove("checked")
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
if (index.data === forceIndex) {
|
||||
forceIndex = undefined;
|
||||
}
|
||||
top = Math.max(top, 0)
|
||||
}
|
||||
|
||||
Stores.Chronic(50).addCallback(_ => stabilize());
|
||||
|
@ -103,7 +104,7 @@
|
|||
<div class="h-full absolute w-min right-0">
|
||||
{#each $floors as floor, i}
|
||||
<button style={`height: ${HEIGHT}px; width: ${HEIGHT}px`}
|
||||
class={"border-2 border-gray-300 flex content-box justify-center items-center "+(i === (forceIndex ?? $index) ? "selected": "normal-background" )
|
||||
class={"m-0 border-2 border-gray-300 flex content-box justify-center items-center "+(i === (forceIndex ?? $index) ? "selected": "" )
|
||||
}
|
||||
on:click={() => {forceIndex = i}}
|
||||
> {floor}</button>
|
||||
|
@ -119,11 +120,6 @@
|
|||
<svelte:window on:mousemove={onMove} on:mouseup={unclick} />
|
||||
|
||||
<style>
|
||||
.selected {
|
||||
background: var(--subtle-detail-color);
|
||||
font-weight: bold;
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
user-select: none;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
// The type changed -> reset some values
|
||||
validator = Validators.get(type)
|
||||
_value.setData(value.data ?? "")
|
||||
console.log("REseting validated input, _value is ", _value.data, validator?.getFeedback(_value.data, getCountry))
|
||||
feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry));
|
||||
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
}
|
||||
|
|
|
@ -44,8 +44,13 @@ export abstract class Validator {
|
|||
/**
|
||||
* Gets a piece of feedback. By default, validation.<type> will be used, resulting in a generic 'not a valid <type>'.
|
||||
* However, inheritors might overwrite this to give more specific feedback
|
||||
*
|
||||
* Returns 'undefined' if the element is valid
|
||||
*/
|
||||
public getFeedback(s: string, requestCountry?: () => string): Translation {
|
||||
public getFeedback(s: string, requestCountry?: () => string): Translation | undefined {
|
||||
if(this.isValid(s)){
|
||||
return undefined
|
||||
}
|
||||
const tr = Translations.t.validation[this.name]
|
||||
if (tr !== undefined) {
|
||||
return tr["feedback"]
|
||||
|
|
|
@ -10,6 +10,9 @@ export default class PhoneValidator extends Validator {
|
|||
|
||||
|
||||
getFeedback(s: string, requestCountry?: () => string): Translation {
|
||||
if(this.isValid(s, requestCountry)){
|
||||
return undefined
|
||||
}
|
||||
const tr = Translations.t.validation.phone
|
||||
const generic = tr.feedback
|
||||
if(requestCountry){
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
<TagHint embedIn={tags => t.presetInfo.Subs({tags})} osmConnection={state.osmConnection}
|
||||
<TagHint embedIn={tags => t.presetInfo.Subs({tags})} {state}
|
||||
tags={new And(selectedPreset.preset.tags)}></TagHint>
|
||||
|
||||
|
||||
|
|
|
@ -1,35 +1,41 @@
|
|||
<script lang="ts">
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
|
||||
import { TagsFilter } from "../../Logic/Tags/TagsFilter";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import Constants from "../../Models/Constants.js";
|
||||
import { Translation } from "../i18n/Translation";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
|
||||
import FromHtml from "../Base/FromHtml.svelte";
|
||||
import Constants from "../../Models/Constants.js";
|
||||
import {Translation} from "../i18n/Translation";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
import type {SpecialVisualizationState} from "../SpecialVisualization";
|
||||
|
||||
/**
|
||||
* A 'TagHint' will show the given tags in a human readable form.
|
||||
* Depending on the options, it'll link through to the wiki or might be completely hidden
|
||||
*/
|
||||
export let tags: TagsFilter;
|
||||
export let osmConnection: OsmConnection;
|
||||
/**
|
||||
* If given, this function will be called to embed the given tags hint into this translation
|
||||
*/
|
||||
export let embedIn: (() => Translation) | undefined = undefined;
|
||||
const userDetails = osmConnection.userDetails;
|
||||
let linkToWiki = false;
|
||||
onDestroy(osmConnection.userDetails.addCallbackAndRunD(userdetails => {
|
||||
linkToWiki = userdetails.csCount > Constants.userJourney.tagsVisibleAndWikiLinked;
|
||||
}));
|
||||
let tagsExplanation = "";
|
||||
$: tagsExplanation = tags?.asHumanString(linkToWiki, false, {});
|
||||
/**
|
||||
* A 'TagHint' will show the given tags in a human readable form.
|
||||
* Depending on the options, it'll link through to the wiki or might be completely hidden
|
||||
*/
|
||||
export let tags: TagsFilter;
|
||||
export let state: SpecialVisualizationState;
|
||||
/**
|
||||
* If given, this function will be called to embed the given tags hint into this translation
|
||||
*/
|
||||
export let embedIn: (() => Translation) | undefined = undefined;
|
||||
const userDetails = state.osmConnection.userDetails;
|
||||
let linkToWiki = false;
|
||||
onDestroy(state.osmConnection.userDetails.addCallbackAndRunD(userdetails => {
|
||||
linkToWiki = userdetails.csCount > Constants.userJourney.tagsVisibleAndWikiLinked;
|
||||
}));
|
||||
let tagsExplanation = "";
|
||||
$: tagsExplanation = tags?.asHumanString(linkToWiki, false, {});
|
||||
</script>
|
||||
|
||||
{#if $userDetails.loggedIn}
|
||||
{#if embedIn === undefined}
|
||||
<FromHtml src={tagsExplanation} />
|
||||
{:else}
|
||||
<Tr t={embedIn(tagsExplanation)} />
|
||||
{/if}
|
||||
<div>
|
||||
{#if tags === undefined}
|
||||
<slot name="no-tags">
|
||||
No tags
|
||||
</slot>
|
||||
{:else if embedIn === undefined}
|
||||
<FromHtml src={tagsExplanation}/>
|
||||
{:else}
|
||||
<Tr t={embedIn(tagsExplanation)}/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,51 +1,48 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource";
|
||||
import { Translation } from "../../i18n/Translation";
|
||||
import ValidatedInput from "../../InputElement/ValidatedInput.svelte";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import Inline from "./Inline.svelte";
|
||||
import { createEventDispatcher, onDestroy } from "svelte";
|
||||
import InputHelper from "../../InputElement/InputHelper.svelte";
|
||||
import type { Feature } from "geojson";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import {Translation} from "../../i18n/Translation";
|
||||
import ValidatedInput from "../../InputElement/ValidatedInput.svelte";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import Inline from "./Inline.svelte";
|
||||
import {createEventDispatcher, onDestroy} from "svelte";
|
||||
import InputHelper from "../../InputElement/InputHelper.svelte";
|
||||
import type {Feature} from "geojson";
|
||||
|
||||
export let value: UIEventSource<string>;
|
||||
export let config: TagRenderingConfig;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
export let value: UIEventSource<string>;
|
||||
export let config: TagRenderingConfig;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
|
||||
export let feature: Feature = undefined;
|
||||
|
||||
let placeholder = config.freeform?.placeholder
|
||||
$: {
|
||||
placeholder = config.freeform?.placeholder
|
||||
}
|
||||
export let feature: Feature = undefined;
|
||||
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
|
||||
let placeholder = config.freeform?.placeholder
|
||||
$: {
|
||||
placeholder = config.freeform?.placeholder
|
||||
}
|
||||
|
||||
let dispatch = createEventDispatcher<{ "selected" }>();
|
||||
onDestroy(value.addCallbackD(() => {dispatch("selected")}))
|
||||
function getCountry() {
|
||||
return tags.data["_country"]
|
||||
}
|
||||
export let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
|
||||
|
||||
let dispatch = createEventDispatcher<{ "selected" }>();
|
||||
onDestroy(value.addCallbackD(() => {
|
||||
dispatch("selected")
|
||||
}))
|
||||
|
||||
function getCountry() {
|
||||
return tags.data["_country"]
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="inline-flex flex-col">
|
||||
|
||||
{#if config.freeform.inline}
|
||||
<Inline key={config.freeform.key} {tags} template={config.render}>
|
||||
<ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
|
||||
type={config.freeform.type} {placeholder} {value}></ValidatedInput>
|
||||
</Inline>
|
||||
{:else}
|
||||
<ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
|
||||
type={config.freeform.type} {placeholder} {value}></ValidatedInput>
|
||||
{#if config.freeform.inline}
|
||||
<Inline key={config.freeform.key} {tags} template={config.render}>
|
||||
<ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
|
||||
type={config.freeform.type} {placeholder} {value}></ValidatedInput>
|
||||
</Inline>
|
||||
{:else}
|
||||
<ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
|
||||
type={config.freeform.type} {placeholder} {value}></ValidatedInput>
|
||||
|
||||
{/if}
|
||||
<InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value}/>
|
||||
{/if}
|
||||
<InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value}/>
|
||||
</div>
|
||||
|
||||
{#if $feedback !== undefined}
|
||||
<div class="alert">
|
||||
<Tr t={$feedback} />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -143,7 +143,7 @@
|
|||
<TagRenderingQuestion
|
||||
config={_firstQuestion} {layer} {selectedElement} {state} {tags}
|
||||
on:saved={() => {skip(_firstQuestion, true)}}>
|
||||
<button on:click={() => {skip(_firstQuestion)} }
|
||||
<button class="secondary" on:click={() => {skip(_firstQuestion)} }
|
||||
slot="cancel">
|
||||
<Tr t={Translations.t.general.skip}></Tr>
|
||||
</button>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import type {Feature} from "geojson";
|
||||
import type {SpecialVisualizationState} from "../../SpecialVisualization";
|
||||
import TagRenderingAnswer from "./TagRenderingAnswer.svelte";
|
||||
import {PencilAltIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import {PencilAltIcon, XCircleIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import TagRenderingQuestion from "./TagRenderingQuestion.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
|
@ -33,10 +33,10 @@
|
|||
if (editMode && htmlElem !== undefined) {
|
||||
// EditMode switched to true, so the person wants to make a change
|
||||
// Make sure that the question is in the scrollview!
|
||||
|
||||
|
||||
// Some delay is applied to give Svelte the time to render the _question_
|
||||
window.setTimeout(() => {
|
||||
|
||||
|
||||
Utils.scrollIntoView(htmlElem)
|
||||
}, 50)
|
||||
}
|
||||
|
@ -68,23 +68,28 @@
|
|||
|
||||
</script>
|
||||
|
||||
<div bind:this={htmlElem}>
|
||||
<div bind:this={htmlElem} class="">
|
||||
{#if config.question && $editingEnabled}
|
||||
{#if editMode}
|
||||
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}>
|
||||
<button slot="cancel" on:click={() => {editMode = false}}>
|
||||
<Tr t={Translations.t.general.cancel}/>
|
||||
</button>
|
||||
</TagRenderingQuestion>
|
||||
<div class="m-1 mx-2">
|
||||
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}>
|
||||
<button slot="cancel" class="secondary" on:click={() => {editMode = false}}>
|
||||
<Tr t={Translations.t.general.cancel}/>
|
||||
</button>
|
||||
<XCircleIcon slot="upper-right" class="w-8 h-8" on:click={() => {editMode = false}}/>
|
||||
</TagRenderingQuestion>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex justify-between">
|
||||
<div class="flex justify-between low-interaction items-center m-1 mx-2 p-1 px-2 rounded">
|
||||
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/>
|
||||
<button on:click={() => {editMode = true}} class="shrink-0 w-6 h-6 rounded-full subtle-background p-1">
|
||||
<button on:click={() => {editMode = true}} class="shrink-0 w-8 h-8 rounded-full p-1 secondary self-start">
|
||||
<PencilAltIcon/>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{:else }
|
||||
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/>
|
||||
<div class="m-1 p-1 px-2 mx-2">
|
||||
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -75,7 +75,7 @@ let mappingIsHidden: Store<boolean> = tags.map(tags => {
|
|||
|
||||
{#if $matchesTerm && !$mappingIsHidden }
|
||||
|
||||
<label class="flex">
|
||||
<label class={"flex "+ (mappingIsSelected ? "checked": "")}>
|
||||
<slot/>
|
||||
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}
|
||||
{layer}></TagRenderingMapping>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import {Store, UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import type {SpecialVisualizationState} from "../../SpecialVisualization";
|
||||
import Tr from "../../Base/Tr.svelte";
|
||||
import If from "../../Base/If.svelte";
|
||||
import type {Feature} from "geojson";
|
||||
import type {Mapping} from "../../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
|
||||
|
@ -10,15 +9,15 @@
|
|||
import FreeformInput from "./FreeformInput.svelte";
|
||||
import Translations from "../../i18n/Translations.js";
|
||||
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
|
||||
import {createEventDispatcher, onDestroy} from "svelte";
|
||||
import {createEventDispatcher} from "svelte";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import {ExclamationIcon} from "@rgossiaux/svelte-heroicons/solid";
|
||||
import SpecialTranslation from "./SpecialTranslation.svelte";
|
||||
import TagHint from "../TagHint.svelte";
|
||||
import LoginToggle from "../../Base/LoginToggle.svelte";
|
||||
import SubtleButton from "../../Base/SubtleButton.svelte";
|
||||
import Loading from "../../Base/Loading.svelte";
|
||||
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte";
|
||||
import {Translation} from "../../i18n/Translation";
|
||||
|
||||
export let config: TagRenderingConfig;
|
||||
export let tags: UIEventSource<Record<string, string>>;
|
||||
|
@ -26,13 +25,16 @@
|
|||
export let state: SpecialVisualizationState;
|
||||
export let layer: LayerConfig;
|
||||
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
|
||||
feedback.addCallbackAndRunD(f => console.trace("Feedback is now", f.txt))
|
||||
|
||||
// Will be bound if a freeform is available
|
||||
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]);
|
||||
let selectedMapping: number = undefined;
|
||||
let checkedMappings: boolean[];
|
||||
$: {
|
||||
mappings = config.mappings?.filter(m => {
|
||||
if(typeof m.hideInAnswer === "boolean"){
|
||||
if (typeof m.hideInAnswer === "boolean") {
|
||||
return !m.hideInAnswer
|
||||
}
|
||||
return m.hideInAnswer.matchesProperties(tags.data)
|
||||
|
@ -43,9 +45,10 @@
|
|||
}
|
||||
if (config.freeform?.key) {
|
||||
freeformInput.setData(tags.data[config.freeform.key]);
|
||||
}else{
|
||||
} else {
|
||||
freeformInput.setData(undefined)
|
||||
}
|
||||
feedback.setData(undefined)
|
||||
}
|
||||
let selectedTags: TagsFilter = undefined;
|
||||
|
||||
|
@ -108,24 +111,24 @@
|
|||
).catch(console.error);
|
||||
}
|
||||
|
||||
|
||||
let featureSwitchIsTesting = state.featureSwitchIsTesting
|
||||
let featureSwitchIsDebugging = state.featureSwitches.featureSwitchIsDebugging
|
||||
let showTags = state.userRelatedState.showTags
|
||||
</script>
|
||||
|
||||
{#if config.question !== undefined}
|
||||
<div class="border border-black subtle-background flex flex-col">
|
||||
<If condition={state.featureSwitchIsTesting}>
|
||||
<div class="flex justify-between">
|
||||
<span>
|
||||
<SpecialTranslation t={config.question} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
|
||||
</span>
|
||||
<span class="alert">{config.id}</span>
|
||||
</div>
|
||||
<SpecialTranslation slot="else" t={config.question} {tags} {state} {layer}
|
||||
feature={selectedElement}></SpecialTranslation>
|
||||
</If>
|
||||
<div class="interactive border-interactive p-1 px-2 flex flex-col">
|
||||
<div class="flex justify-between">
|
||||
<span class="font-bold">
|
||||
<SpecialTranslation t={config.question} {tags} {state} {layer}
|
||||
feature={selectedElement}></SpecialTranslation>
|
||||
</span>
|
||||
<slot name="upper-right"/>
|
||||
|
||||
</div>
|
||||
|
||||
{#if config.questionhint}
|
||||
<div class="subtle">
|
||||
<div>
|
||||
<SpecialTranslation t={config.questionhint} {tags} {state} {layer}
|
||||
feature={selectedElement}></SpecialTranslation>
|
||||
</div>
|
||||
|
@ -140,7 +143,7 @@
|
|||
|
||||
{#if config.freeform?.key && !(mappings?.length > 0)}
|
||||
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
|
||||
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}/>
|
||||
<FreeformInput {config} {tags} {feedback} feature={selectedElement} value={freeformInput}/>
|
||||
{:else if mappings !== undefined && !config.multiAnswer}
|
||||
<!-- Simple radiobuttons as mapping -->
|
||||
<div class="flex flex-col">
|
||||
|
@ -176,7 +179,7 @@
|
|||
<label class="flex">
|
||||
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+config.mappings?.length}
|
||||
bind:checked={checkedMappings[config.mappings.length]}>
|
||||
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}
|
||||
<FreeformInput {config} {tags} {feedback} feature={selectedElement} value={freeformInput}
|
||||
on:selected={() => checkedMappings[config.mappings.length] = true}/>
|
||||
</label>
|
||||
{/if}
|
||||
|
@ -189,24 +192,34 @@
|
|||
<img slot="image" src="./assets/svg/login.svg" class="w-8 h-8"/>
|
||||
<Tr t={Translations.t.general.loginToStart} slot="message"></Tr>
|
||||
</SubtleButton>
|
||||
|
||||
|
||||
<TagHint osmConnection={state.osmConnection} tags={selectedTags}></TagHint>
|
||||
<div>
|
||||
<div class="flex justify-end">
|
||||
<!-- TagRenderingQuestion-buttons -->
|
||||
<slot name="cancel"></slot>
|
||||
|
||||
{#if selectedTags !== undefined}
|
||||
<button on:click={onSave}>
|
||||
<Tr t={Translations.t.general.save}></Tr>
|
||||
</button>
|
||||
{:else }
|
||||
<div class="inline-flex w-6 h-6">
|
||||
<!-- Invalid value; show an inactive button or something like that-->
|
||||
<ExclamationIcon/>
|
||||
{#if $feedback !== undefined}
|
||||
<div class="alert">
|
||||
<Tr t={$feedback}/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<slot name="cancel"></slot>
|
||||
|
||||
<button on:click={onSave} class={selectedTags === undefined ? "disabled" : "button-shadow"}>
|
||||
<Tr t={Translations.t.general.save}></Tr>
|
||||
</button>
|
||||
</div>
|
||||
{#if $showTags === "yes" || $showTags === "always" || $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
||||
<span class="flex justify-between flex-wrap">
|
||||
<TagHint {state} tags={selectedTags}></TagHint>
|
||||
<span class="flex flex-wrap">
|
||||
{#if $featureSwitchIsTesting}
|
||||
Testmode
|
||||
{/if}
|
||||
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
||||
<span class="subtle">{config.id}</span>
|
||||
{/if}
|
||||
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -63,6 +63,7 @@ export interface SpecialVisualizationState {
|
|||
|
||||
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
|
||||
readonly userRelatedState: {
|
||||
readonly showTags: UIEventSource<"no" | undefined | "always" | "yes">;
|
||||
readonly mangroveIdentity: MangroveIdentity
|
||||
readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
||||
readonly preferencesAsTags: Store<Record<string, string>>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<script lang="ts">
|
||||
|
||||
|
||||
import Svg from "../Svg";
|
||||
import Loading from "./Base/Loading.svelte";
|
||||
import ToSvelte from "./Base/ToSvelte.svelte";
|
||||
</script>
|
||||
|
||||
<div>
|
||||
|
@ -11,11 +14,14 @@
|
|||
|
||||
<div class="normal-background">
|
||||
<h2>Normal background</h2>
|
||||
There are a few styles, such as the <span class="literal-code">normal-background</span>-style which is used if there is
|
||||
There are a few styles, such as the <span class="literal-code">normal-background</span>-style which is used if
|
||||
there is
|
||||
nothing special going on. Some general information, with at most <a href="https://example.com" target="_blank">a
|
||||
link to someplace</a>.
|
||||
<span class="alert">Alert: something went wrong</span>
|
||||
<span class="thanks">Thank you! Operation successful</span>
|
||||
<ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")}/>
|
||||
<Loading>Loading...</Loading>
|
||||
</div>
|
||||
|
||||
<div class="low-interaction flex flex-col">
|
||||
|
@ -24,35 +30,91 @@
|
|||
There are <span class="literal-code">low-interaction</span> areas, where some buttons might appear.
|
||||
</p>
|
||||
|
||||
<button class="btn">Main action</button>
|
||||
<button class="btn-secondary">Secondary action</button>
|
||||
<button class="btn-disabled">Disabled</button>
|
||||
<div class="flex">
|
||||
<button>
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Main action
|
||||
</button>
|
||||
<button class="disabled">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Main action (disabled)
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button class="secondary">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Secondary action
|
||||
</button>
|
||||
<button class="secondary disabled">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Secondary action (disabled)
|
||||
</button>
|
||||
</div>
|
||||
<input type="text">
|
||||
|
||||
<div>
|
||||
<input id="html" name="fav_language" type="radio" value="HTML">
|
||||
<label for="html">HTML</label><br>
|
||||
<input id="css" name="fav_language" type="radio" value="CSS">
|
||||
<label for="css">CSS</label><br>
|
||||
<input id="javascript" name="fav_language" type="radio" value="JavaScript">
|
||||
<label for="javascript">JavaScript</label>
|
||||
<label for="html" class="checked">
|
||||
<input id="html" name="fav_language" type="radio" value="HTML">
|
||||
HTML (mimicks a <span class="literal-code">checked</span>-element)</label>
|
||||
<label for="css">
|
||||
<input id="css" name="fav_language" type="radio" value="CSS">
|
||||
CSS</label>
|
||||
<label for="javascript">
|
||||
<input id="javascript" name="fav_language" type="radio" value="JavaScript">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-8 h-8")}/>
|
||||
JavaScript</label>
|
||||
</div>
|
||||
|
||||
<span class="alert">Alert: something went wrong</span>
|
||||
<span class="thanks">Thank you! Operation successful</span>
|
||||
<ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")}/>
|
||||
<Loading>Loading...</Loading>
|
||||
</div>
|
||||
|
||||
<div class="interactive flex flex-col">
|
||||
<h2>Interactive area</h2>
|
||||
<p>
|
||||
There are <span class="literal-code">interactive</span> areas, where some buttons might appear.
|
||||
There are <span class="literal-code">interactive</span> areas, where many buttons and input elements
|
||||
will appear.
|
||||
</p>
|
||||
|
||||
<button class="btn">Main action</button>
|
||||
<button class="btn-secondary">Secondary action</button>
|
||||
<button class="btn-disabled">Disabled</button>
|
||||
<div class="flex">
|
||||
<button>
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Main action
|
||||
</button>
|
||||
<button class="disabled">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Main action (disabled)
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button class="secondary">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Secondary action
|
||||
</button>
|
||||
<button class="secondary disabled">
|
||||
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
|
||||
Secondary action (disabled)
|
||||
</button>
|
||||
</div>
|
||||
<span class="alert">Alert: something went wrong</span>
|
||||
<span class="thanks">Thank you! Operation successful</span>
|
||||
<ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")}/>
|
||||
<Loading>Loading...</Loading>
|
||||
<div>
|
||||
<label for="html0">
|
||||
<input id="html0" name="fav_language" type="radio" value="HTML">
|
||||
HTML</label>
|
||||
<label for="css0">
|
||||
<input id="css0" name="fav_language" type="radio" value="CSS">
|
||||
CSS</label>
|
||||
<label for="javascript0">
|
||||
|
||||
<input id="javascript0" name="fav_language" type="radio" value="JavaScript">
|
||||
JavaScript
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue