Merge develop

This commit is contained in:
Pieter Vander Vennet 2023-11-27 16:05:41 +01:00
commit d0ebd0e233
187 changed files with 1201 additions and 22352 deletions

View file

@ -0,0 +1,74 @@
<script lang="ts">
import { OsmConnectionFeatureSwitches } from "../Logic/State/FeatureSwitchState";
import { OsmConnection } from "../Logic/Osm/OsmConnection";
import { QueryParameters } from "../Logic/Web/QueryParameters";
import UserRelatedState from "../Logic/State/UserRelatedState";
import LanguagePicker from "./InputElement/LanguagePicker.svelte";
import Translations from "./i18n/Translations";
import Logo from "../assets/svg/Logo.svelte";
import Tr from "./Base/Tr.svelte";
import ToSvelte from "./Base/ToSvelte.svelte";
import MoreScreen from "./BigComponents/MoreScreen";
import LoginToggle from "./Base/LoginToggle.svelte";
import Pencil from "../assets/svg/Pencil.svelte";
import Login from "../assets/svg/Login.svelte";
import Constants from "../Models/Constants";
const featureSwitches = new OsmConnectionFeatureSwitches();
const osmConnection = new OsmConnection({
fakeUser: featureSwitches.featureSwitchFakeUser.data,
oauth_token: QueryParameters.GetQueryParameter(
"oauth_token",
undefined,
"Used to complete the login"
)
});
const state = new UserRelatedState(osmConnection);
const t = Translations.t.index;
let userLanguages = osmConnection.userDetails.map(ud => ud.languages);
</script>
<div class="flex flex-col m-4">
<div class="self-end">
<LanguagePicker assignTo={state.language} availableLanguages={t.title.SupportedLanguages()}
preferredLanguages={userLanguages} />
</div>
<div class="flex mt-4">
<div class="flex-none m-3">
<Logo alt="MapComplete Logo" class="w-12 h-12 sm:h-24 sm:w-24" />
</div>
<div class="flex flex-col">
<h1 class="tracking-tight font-extrabold md:text-6xl m-0">
<Tr t={t.title} />
</h1>
<Tr cls="my-4 mr-4 text-base font-semibold sm:text-lg md:mt-5 md:text-xl lg:mx-0"
t={Translations.t.index.intro} />
</div>
</div>
<ToSvelte construct={new MoreScreen(state, true)} />
<LoginToggle state={state}>
<div slot="not-logged-in">
<button class="w-full" on:click={() => osmConnection.AttemptLogin()}>
<Login class="w-6 h-6 mr-2 "/>
<Tr t={Translations.t.index.logIn} />
</button>
</div>
<a class="w-full h-fit button" href={window.location.protocol + "//" + window.location.host + "/studio.html"}>
<Pencil class="w-6 h-6 mr-2" />
<Tr t={ Translations.t.general.morescreen.createYourOwnTheme} />
</a>
</LoginToggle>
<Tr cls="link-underline" t={Translations.t.general.aboutMapComplete.intro}/>
<div class="subtle self-end mb-16">
v{Constants.vNumber}
</div>
</div>

View file

@ -1,69 +0,0 @@
import UserRelatedState from "../Logic/State/UserRelatedState"
import { FixedUiElement } from "./Base/FixedUiElement"
import Combine from "./Base/Combine"
import MoreScreen from "./BigComponents/MoreScreen"
import Translations from "./i18n/Translations"
import Constants from "../Models/Constants"
import LanguagePicker from "./LanguagePicker"
import IndexText from "./BigComponents/IndexText"
import { LoginToggle } from "./Popup/LoginButton"
import { ImmutableStore } from "../Logic/UIEventSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { QueryParameters } from "../Logic/Web/QueryParameters"
import { OsmConnectionFeatureSwitches } from "../Logic/State/FeatureSwitchState"
import { SubtleButton } from "./Base/SubtleButton"
import Svg from "../Svg"
import Link from "./Base/Link"
export default class AllThemesGui {
setup() {
try {
const featureSwitches = new OsmConnectionFeatureSwitches()
const osmConnection = new OsmConnection({
fakeUser: featureSwitches.featureSwitchFakeUser.data,
oauth_token: QueryParameters.GetQueryParameter(
"oauth_token",
undefined,
"Used to complete the login"
),
})
const state = new UserRelatedState(osmConnection)
const intro = new Combine([
new LanguagePicker(
Translations.t.index.title.SupportedLanguages(),
state.language
).SetClass("flex absolute top-2 right-3"),
new IndexText(),
])
new Combine([
intro,
new MoreScreen(state, true),
new LoginToggle(
new Link(
new Combine([
Svg.pencil_svg().SetClass("w-6 h-6 mr-2"),
Translations.t.general.morescreen.createYourOwnTheme,
]).SetClass("flex p-2"),
window.location.protocol + "//" + window.location.host + "/studio.html"
).SetClass("w-full h-fit button"),
Translations.t.index.logIn,
{
osmConnection,
featureSwitchUserbadge: new ImmutableStore(true),
}
).SetClass("flex justify-center w-full"),
Translations.t.general.aboutMapComplete.intro.SetClass("link-underline"),
new FixedUiElement("v" + Constants.vNumber).SetClass("block"),
])
.SetClass("block m-5 lg:w-3/4 lg:ml-40")
.AttachTo("main")
} catch (e) {
console.error(">>>> CRITICAL", e)
new FixedUiElement(
"Seems like no layers are compiled - check the output of `npm run generate:layeroverview`. Is this visible online? Contact pietervdvn immediately!"
)
.SetClass("alert")
.AttachTo("main")
}
}
}

View file

@ -5,6 +5,7 @@
*/
import { Store } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"
import Hand from "../../assets/svg/Hand.svelte";
let mainElem: HTMLElement
export let hideSignal: Store<any>
@ -34,7 +35,7 @@
<div bind:this={mainElem} class="pointer-events-none absolute bottom-0 right-0 h-full w-full">
<div id="hand-container">
<img src="./assets/svg/hand.svg" />
<Hand />
</div>
</div>

View file

@ -1,14 +1,31 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource.js"
import { UIEventSource } from "../../Logic/UIEventSource.js";
/**
* For some stupid reason, it is very hard to bind inputs
*/
export let value: UIEventSource<number>
let i: number = value.data
$: value.setData(i)
export let value: UIEventSource<any>
let i: any = value.data
let htmlElement : HTMLSelectElement
function selectAppropriateValue(){
if(!htmlElement){
return;
}
const v = value.data
for (let option of htmlElement.getElementsByTagName("option")) {
if(option.value === v){
option.selected = true
return
}
}
}
value.addCallbackD(() => selectAppropriateValue())
$: {
if(htmlElement){
selectAppropriateValue()
}
}
</script>
<select bind:value={i}>
<select bind:this={htmlElement} on:change={(e) => {value.setData(e.srcElement.value)}}>
<slot />
</select>

View file

@ -6,6 +6,7 @@
import Tr from "./Tr.svelte"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import Invalid from "../../assets/svg/Invalid.svelte";
export let state: {
osmConnection: OsmConnection
@ -35,7 +36,7 @@
</slot>
{:else if $loadingStatus === "error"}
<div class="alert max-w-64 flex items-center">
<img src="./assets/svg/invalid.svg" class="m-2 h-8 w-8 shrink-0" />
<Invalid class="m-2 h-8 w-8 shrink-0" />
<Tr t={offlineModes[$apiState]} />
</div>
{:else if $loadingStatus === "logged-in"}

View file

@ -0,0 +1,15 @@
<script lang="ts">
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
import Logout from "../../assets/svg/Logout.svelte";
import Translations from "../i18n/Translations";
import Tr from "./Tr.svelte";
export let osmConnection: OsmConnection
</script>
<button on:click={() => {
state.osmConnection.LogOut()
}}>
<Logout class="w-6 h-6"/>
<Tr t={Translations.t.general.logout}/>
</button>

View file

@ -0,0 +1,48 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import Tr from "./Tr.svelte";
import Josm_logo from "../../assets/svg/Josm_logo.svelte";
import Constants from "../../Models/Constants";
import type { SpecialVisualizationState } from "../SpecialVisualization";
export let state : SpecialVisualizationState
const t = Translations.t.general.attribution;
const josmState = new UIEventSource<"OK" | string>(undefined);
// Reset after 15s
josmState.stabilized(15000).addCallbackD(() => josmState.setData(undefined));
const showButton = state.osmConnection.userDetails.map(
(ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible
);
function openJosm() {
const bbox = state.mapProperties. bounds.data;
if (bbox === undefined) {
return;
}
const top = bbox.getNorth();
const bottom = bbox.getSouth();
const right = bbox.getEast();
const left = bbox.getWest();
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`;
Utils.download(josmLink)
.then((answer) => josmState.setData(answer.replace(/\n/g, "").trim()))
.catch(() => josmState.setData("ERROR"));
}
</script>
{#if $showButton}
{#if $josmState === undefined}
<!-- empty -->
{:else if state === "OK"}
<Tr cls="thanks" t={t.josmOpened} />
{:else}
<Tr cls="alert" t={t.josmNotOpened} />
{/if}
<button class="flex items-center" on:click={openJosm}>
<Josm_logo class="h-12 w-12 p-2 pr-4" />
<Tr t={t.editJosm} />
</button>
{/if}

View file

@ -1,6 +1,7 @@
<script lang="ts">
import ToSvelte from "./ToSvelte.svelte"
import Svg from "../../Svg"
import Share from "../../assets/svg/Share.svelte";
export let generateShareData: () => {
text: string
@ -25,6 +26,6 @@
<button on:click={share} class="secondary m-0 h-8 w-8 p-0">
<slot name="content">
<ToSvelte construct={Svg.share_svg().SetClass("w-7 h-7 p-1")} />
<Share class="w-7 h-7 p-1"/>
</slot>
</button>

View file

@ -23,10 +23,10 @@ export default class SvelteUIElement<
private readonly _events: Events
private readonly _slots: Slots
constructor(svelteElement, props: Props, events?: Events, slots?: Slots) {
constructor(svelteElement, props?: Props, events?: Events, slots?: Slots) {
super()
this._svelteComponent = svelteElement
this._props = props
this._props = props ?? <Props>{}
this._events = events
this._slots = slots
}

View file

@ -1,6 +1,7 @@
<script lang="ts">
import Locale from "../i18n/Locale"
import LinkToWeblate from "./LinkToWeblate"
import Translate from "../../assets/svg/Translate.svelte";
/**
* Shows a small icon which will open up weblate; a contributor can translate the item for 'context' there
@ -19,7 +20,7 @@
target="_blank"
class="weblate-link mx-1"
>
<img src="./assets/svg/translate.svg" class="font-gray" />
<Translate class="font-gray" />
</a>
{:else if $linkToWeblate}
<a
@ -27,7 +28,7 @@
class="weblate-link hidden-on-mobile mx-1"
target="_blank"
>
<img src="./assets/svg/translate.svg" class="font-gray inline-block" />
<Translate class="font-gray inline-block" />
</a>
{/if}
{/if}

View file

@ -4,25 +4,7 @@ import { FixedUiElement } from "../Base/FixedUiElement"
export default class IndexText extends Combine {
constructor() {
super([
new FixedUiElement(
`<img class="w-12 h-12 sm:h-24 sm:w-24" src="./assets/svg/logo.svg" alt="MapComplete Logo">`
).SetClass("flex-none m-3"),
new Combine([
Translations.t.index.title.SetClass(
"text-2xl tracking-tight font-extrabold text-gray-900 sm:text-5xl md:text-6xl block text-gray-800 xl:inline"
),
Translations.t.index.intro.SetClass(
"mt-3 text-base font-semibold text-gray-500 sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0"
),
Translations.t.index.pickTheme.SetClass(
"mt-3 text-base sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0"
),
]).SetClass("flex flex-col sm:text-center lg:text-left m-1 mt-2 md:m-2 md:mt-4"),
])
super([])
this.SetClass("flex flex-row")
}

View file

@ -4,6 +4,7 @@
import { Store } from "../../Logic/UIEventSource"
import Tr from "../Base/Tr.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import Mapillary_black from "../../assets/svg/Mapillary_black.svelte";
/*
A subtleButton which opens mapillary in a new tab at the current location
@ -21,7 +22,7 @@
</script>
<a class="button flex items-center" href={mapillaryLink} target="_blank">
<ToSvelte construct={() => Svg.mapillary_black_svg().SetClass("w-12 h-12 m-2 mr-4 shrink-0")} />
<Mapillary_black class="w-12 h-12 m-2 mr-4 shrink-0"/>
<div class="flex flex-col">
<Tr t={Translations.t.general.attribution.openMapillary} />
<Tr cls="subtle" t={Translations.t.general.attribution.mapillaryHelp} />

View file

@ -16,6 +16,7 @@
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Utils } from "../../Utils"
import { createEventDispatcher } from "svelte"
import Move_arrows from "../../assets/svg/Move_arrows.svelte";
/**
* An advanced location input, which has support to:
@ -125,6 +126,6 @@
maxDistanceInMeters="50"
>
<slot name="image" slot="image">
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" />
<Move_arrows class="h-full max-h-24" />
</slot>
</LocationInput>

View file

@ -1,60 +0,0 @@
import Combine from "../Base/Combine"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { BBox } from "../../Logic/BBox"
import Translations from "../i18n/Translations"
import { VariableUiElement } from "../Base/VariableUIElement"
import Toggle from "../Input/Toggle"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import { Utils } from "../../Utils"
import Constants from "../../Models/Constants"
export class OpenJosm extends Combine {
public static readonly needsUrls = ["http://127.0.0.1:8111/load_and_zoom"]
constructor(osmConnection: OsmConnection, bounds: Store<BBox>, iconStyle?: string) {
const t = Translations.t.general.attribution
const josmState = new UIEventSource<string>(undefined)
// Reset after 15s
josmState.stabilized(15000).addCallbackD(() => josmState.setData(undefined))
const stateIndication = new VariableUiElement(
josmState.map((state) => {
if (state === undefined) {
return undefined
}
state = state.toUpperCase()
if (state === "OK") {
return t.josmOpened.SetClass("thanks")
}
return t.josmNotOpened.SetClass("alert")
})
)
const toggle = new Toggle(
new SubtleButton(Svg.josm_logo_svg().SetStyle(iconStyle), t.editJosm)
.onClick(() => {
const bbox = bounds.data
if (bbox === undefined) {
return
}
const top = bbox.getNorth()
const bottom = bbox.getSouth()
const right = bbox.getEast()
const left = bbox.getWest()
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
Utils.download(josmLink)
.then((answer) => josmState.setData(answer.replace(/\n/g, "").trim()))
.catch(() => josmState.setData("ERROR"))
})
.SetClass("w-full"),
undefined,
osmConnection.userDetails.map(
(ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible
)
)
super([stateIndication, toggle])
}
}

View file

@ -1,20 +1,21 @@
<script lang="ts">
import Translations from "../i18n/Translations"
import Svg from "../../Svg"
import Tr from "../Base/Tr.svelte"
import NextButton from "../Base/NextButton.svelte"
import Geosearch from "./Geosearch.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import { UIEventSource } from "../../Logic/UIEventSource"
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
import { twJoin } from "tailwind-merge"
import { Utils } from "../../Utils"
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState"
import { GeoLocationState } from "../../Logic/State/GeoLocationState"
import If from "../Base/If.svelte"
import { ExclamationTriangleIcon } from "@babeard/svelte-heroicons/mini"
import type { Readable } from "svelte/store"
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import NextButton from "../Base/NextButton.svelte";
import Geosearch from "./Geosearch.svelte";
import ThemeViewState from "../../Models/ThemeViewState";
import { UIEventSource } from "../../Logic/UIEventSource";
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid";
import { twJoin } from "tailwind-merge";
import { Utils } from "../../Utils";
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState";
import { GeoLocationState } from "../../Logic/State/GeoLocationState";
import If from "../Base/If.svelte";
import { ExclamationTriangleIcon } from "@babeard/svelte-heroicons/mini";
import type { Readable } from "svelte/store";
import Add from "../../assets/svg/Add.svelte";
import Location_refused from "../../assets/svg/Location_refused.svelte";
import Crosshair from "../../assets/svg/Crosshair.svelte";
/**
* The theme introduction panel
@ -70,7 +71,7 @@
<If condition={state.featureSwitches.featureSwitchGeolocation}>
{#if $currentGPSLocation !== undefined || $geopermission === "prompt"}
<button class="flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")} />
<Crosshair class="w-8 h-8"/>
<Tr t={Translations.t.general.openTheMapAtGeolocation} />
</button>
<!-- No geolocation granted - we don't show the button -->
@ -80,25 +81,17 @@
on:click={jumpToCurrentLocation}
>
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
<ToSvelte
construct={Svg.crosshair_svg()
.SetClass("w-8 h-8")
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
/>
<Crosshair class="w-8 h-8" style="animation: 3s linear 0s infinite normal none running spin;" />
<Tr t={Translations.t.general.waitingForGeopermission} />
</button>
{:else if $geopermission === "denied"}
<button class="disabled flex w-full items-center gap-x-2">
<ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} />
<Location_refused class="w-8 h-8"/>
<Tr t={Translations.t.general.geopermissionDenied} />
</button>
{:else}
<button class="disabled flex w-full items-center gap-x-2">
<ToSvelte
construct={Svg.crosshair_svg()
.SetClass("w-8 h-8")
.SetStyle("animation: 3s linear 0s infinite normal none running spin;")}
/>
<Crosshair class="w-8 h-8" style="animation: 3s linear 0s infinite normal none running spin;" />
<Tr t={Translations.t.general.waitingForLocation} />
</button>
{/if}
@ -156,7 +149,7 @@
<div class="links-as-button links-w-full m-2 flex flex-col gap-y-1">
<!-- bottom buttons, a bit hidden away: switch layout -->
<a class="flex" href={Utils.HomepageLink()}>
<img class="h-6 w-6" src="./assets/svg/add.svg" />
<Add class="h-6 w-6"/>
<Tr t={Translations.t.general.backToIndex} />
</a>
</div>

View file

@ -12,6 +12,8 @@ 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(
@ -83,7 +85,7 @@ export default class UploadTraceToOsmUI extends LoginToggle {
clicked.setData(false)
})
.SetClass(""),
new SubtleButton(Svg.upload_svg(), t.confirm).OnClickWithLoading(
new SubtleButton(new SvelteUIElement(Upload, {}), t.confirm).OnClickWithLoading(
t.uploading,
async () => {
const titleStr = UploadTraceToOsmUI.createDefault(
@ -119,7 +121,7 @@ export default class UploadTraceToOsmUI extends LoginToggle {
]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"),
new Toggle(
confirmPanel,
new SubtleButton(Svg.upload_svg(), t.title).onClick(() =>
new SubtleButton(new SvelteUIElement(Upload), t.title).onClick(() =>
clicked.setData(true)
),
clicked

View file

@ -3,16 +3,15 @@
* Shows an 'upload'-button which will start the upload for this feature
*/
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"
import LoginToggle from "../Base/LoginToggle.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import UploadingImageCounter from "./UploadingImageCounter.svelte"
import FileSelector from "../Base/FileSelector.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { ImmutableStore, Store } from "../../Logic/UIEventSource";
import type { OsmTags } from "../../Models/OsmFeature";
import LoginToggle from "../Base/LoginToggle.svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import UploadingImageCounter from "./UploadingImageCounter.svelte";
import FileSelector from "../Base/FileSelector.svelte";
import Camera_plus from "../../assets/svg/Camera_plus.svelte";
export let state: SpecialVisualizationState
@ -58,7 +57,7 @@
{#if image !== undefined}
<img src={image} />
{:else}
<ToSvelte construct={Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl ")} />
<Camera_plus class="block w-12 h-12 p-1 text-4xl"/>
{/if}
{#if labelText}
{labelText}

View file

@ -6,6 +6,7 @@
import MaplibreMap from "../../Map/MaplibreMap.svelte"
import ToSvelte from "../../Base/ToSvelte.svelte"
import Svg from "../../../Svg.js"
import Direction_stroke from "../../../assets/svg/Direction_stroke.svelte";
/**
* A visualisation to pick a direction on a map background.
@ -67,6 +68,6 @@
</div>
<div bind:this={directionElem} class="absolute top-0 left-0 h-full w-full">
<ToSvelte construct={Svg.direction_stroke_svg} />
<Direction_stroke/>
</div>
</div>

View file

@ -12,6 +12,7 @@
import * as turf from "@turf/turf"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { createEventDispatcher, onDestroy } from "svelte"
import Move_arrows from "../../../assets/svg/Move_arrows.svelte";
/**
* A visualisation to pick a location on a map background
@ -90,7 +91,7 @@
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center p-8 opacity-50"
>
<slot name="image">
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" />
<Move_arrows class="h-full max-h-24"/>
</slot>
</div>

View file

@ -0,0 +1,66 @@
<script lang="ts">
// Languages in the language itself
import native from "../../assets/language_native.json";
// Translated languages
import language_translations from "../../assets/language_translations.json";
import { UIEventSource } from "../../Logic/UIEventSource";
import Locale from "../i18n/Locale";
import { LanguageIcon } from "@babeard/svelte-heroicons/solid";
import Dropdown from "../Base/Dropdown.svelte";
/**
* Languages one can choose from
* Defaults to _all_ languages known by MapComplete
*/
export let availableLanguages: string[] = Object.keys(native);
/**
* EventStore to assign to, defaults to 'Locale.langauge'
*/
export let assignTo: UIEventSource<string> = Locale.language;
export let preferredLanguages: UIEventSource<string[]> = undefined;
let preferredFiltered: string[] = undefined;
preferredLanguages?.addCallbackAndRunD(preferredLanguages => {
let lng = navigator.language;
if (lng === "en-US") {
lng = "en";
}
if (preferredLanguages?.indexOf(lng) < 0) {
preferredLanguages?.push(lng);
}
preferredFiltered = preferredLanguages?.filter(l => availableLanguages.indexOf(l) >= 0);
});
let current = Locale.language;
</script>
{#if availableLanguages?.length > 1}
<form class="flex items-center">
<LanguageIcon class="h-4 w-4 mr-1" />
<Dropdown value={assignTo}>
{#if preferredFiltered}
{#each preferredFiltered as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
({language_translations[language]?.[$current] ?? language})
{/if}
</option>
{/each}
<option disabled></option>
{/if}
{#each availableLanguages as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
({(language_translations[language]?.[$current] + " - " + language) ?? language})
{/if}
</option>
{/each}
</Dropdown>
</form>
{/if}

View file

@ -1,67 +0,0 @@
import { DropDown } from "./Input/DropDown"
import Locale from "./i18n/Locale"
import BaseUIElement from "./BaseUIElement"
import native from "../assets/language_native.json"
import language_translations from "../assets/language_translations.json"
import { Translation } from "./i18n/Translation"
import Lazy from "./Base/Lazy"
import Toggle from "./Input/Toggle"
import LanguageUtils from "../Utils/LanguageUtils"
import { UIEventSource } from "../Logic/UIEventSource"
import { QueryParameters } from "../Logic/Web/QueryParameters"
export default class LanguagePicker extends Toggle {
constructor(languages: string[], assignTo: UIEventSource<string>) {
console.log("Constructing a language picker for languages", languages)
if (
languages === undefined ||
languages.length <= 1 ||
QueryParameters.wasInitialized("language")
) {
super(undefined, undefined, undefined)
} else {
const normalPicker = LanguagePicker.dropdownFor(languages, assignTo ?? Locale.language)
const fullPicker = new Lazy(() =>
LanguagePicker.dropdownFor(allLanguages, assignTo ?? Locale.language)
)
super(fullPicker, normalPicker, Locale.showLinkToWeblate)
const allLanguages: string[] = LanguageUtils.usedLanguagesSorted
}
}
private static dropdownFor(
languages: string[],
assignTo: UIEventSource<string>
): BaseUIElement {
return new DropDown(
undefined,
languages
.filter((lang) => lang !== "_context")
.map((lang) => {
return { value: lang, shown: LanguagePicker.hybrid(lang) }
}),
assignTo
)
}
private static hybrid(lang: string): Translation {
const nativeText = native[lang] ?? lang
const translation = {}
const trans = language_translations[lang]
if (trans === undefined) {
return new Translation({ "*": nativeText })
}
for (const key in trans) {
if (key.startsWith("_")) {
continue
}
const translationInKey = language_translations[lang][key]
if (nativeText.toLowerCase() === translationInKey.toLowerCase()) {
translation[key] = nativeText
} else {
translation[key] = nativeText + " (" + translationInKey + ")"
}
}
return new Translation(translation)
}
}

View file

@ -1,20 +1,22 @@
<script lang="ts">
import PointRenderingConfig, { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig"
import { Store } from "../../Logic/UIEventSource"
import DynamicIcon from "./DynamicIcon.svelte"
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
import { ImmutableStore, Store } from "../../Logic/UIEventSource";
import DynamicIcon from "./DynamicIcon.svelte";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
/**
* Renders a 'marker', which consists of multiple 'icons'
*/
export let config: PointRenderingConfig
let icons: IconConfig[] = config.marker
export let tags: Store<Record<string, string>>
let rotation = tags.map((tags) => config.rotation.GetRenderValue(tags).Subs(tags).txt)
export let marker: IconConfig[] = config?.marker;
export let tags: Store<Record<string, string>>;
export let rotation: TagRenderingConfig
let _rotation = rotation ? tags.map(tags => rotation.GetRenderValue(tags).Subs(tags).txt) : new ImmutableStore(0);
</script>
{#if config !== undefined}
<div class="relative h-full w-full" style={`transform: rotate(${$rotation})`}>
{#each icons as icon}
{#if marker && marker}
<div class="relative h-full w-full" style={`transform: rotate(${$_rotation})`}>
{#each marker as icon}
<DynamicIcon {icon} {tags} />
{/each}
</div>

View file

@ -19,6 +19,9 @@
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";
/**
* Renders a single icon.
@ -72,6 +75,20 @@
<Teardrop_with_hole_green {color} />
{:else if icon === "triangle"}
<Triangle {color} />
{:else if icon === "brick_wall_square"}
<Brick_wall_square {color} />
{:else if icon === "brick_wall_round"}
<Brick_wall_round {color} />
{:else if icon === "gps_arrow"}
<Gps_arrow {color} />
{:else if icon === "checkmark"}
<Checkmark {color} />
{:else if icon === "help"}
<Help {color} />
{:else if icon === "close"}
<Close {color} />
{:else if icon === "invalid"}
<Invalid {color} />
{:else}
<img class="h-full w-full" src={icon} />
{/if}

View file

@ -12,6 +12,7 @@
import { createEventDispatcher } from "svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
import Plantnet_logo from "../../assets/svg/Plantnet_logo.svelte";
/**
* The main entry point for the plantnet wizard
@ -142,9 +143,7 @@
</BackButton>
{/if}
<div class="low-interaction flex self-end rounded-xl p-2">
<ToSvelte
construct={Svg.plantnet_logo_svg().SetClass("w-8 h-8 p-1 mr-1 bg-white rounded-full")}
/>
<Plantnet_logo class="w-8 h-8 p-1 mr-1 bg-white rounded-full"/>
<Tr t={t.poweredByPlantnet} />
</div>
</div>

View file

@ -3,109 +3,110 @@
* This component ties together all the steps that are needed to create a new point.
* There are many subcomponents which help with that
*/
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import PresetList from "./PresetList.svelte"
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import Tr from "../../Base/Tr.svelte"
import SubtleButton from "../../Base/SubtleButton.svelte"
import FromHtml from "../../Base/FromHtml.svelte"
import Translations from "../../i18n/Translations.js"
import TagHint from "../TagHint.svelte"
import { And } from "../../../Logic/Tags/And.js"
import LoginToggle from "../../Base/LoginToggle.svelte"
import Constants from "../../../Models/Constants.js"
import FilteredLayer from "../../../Models/FilteredLayer"
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
import LoginButton from "../../Base/LoginButton.svelte"
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"
import { OsmWay } from "../../../Logic/Osm/OsmObject"
import { Tag } from "../../../Logic/Tags/Tag"
import type { WayId } from "../../../Models/OsmFeature"
import Loading from "../../Base/Loading.svelte"
import type { GlobalFilter } from "../../../Models/GlobalFilter"
import { onDestroy } from "svelte"
import NextButton from "../../Base/NextButton.svelte"
import BackButton from "../../Base/BackButton.svelte"
import ToSvelte from "../../Base/ToSvelte.svelte"
import Svg from "../../../Svg"
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
import { twJoin } from "tailwind-merge"
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import PresetList from "./PresetList.svelte";
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import Tr from "../../Base/Tr.svelte";
import SubtleButton from "../../Base/SubtleButton.svelte";
import Translations from "../../i18n/Translations.js";
import TagHint from "../TagHint.svelte";
import { And } from "../../../Logic/Tags/And.js";
import LoginToggle from "../../Base/LoginToggle.svelte";
import Constants from "../../../Models/Constants.js";
import FilteredLayer from "../../../Models/FilteredLayer";
import { Store, UIEventSource } from "../../../Logic/UIEventSource";
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid";
import LoginButton from "../../Base/LoginButton.svelte";
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte";
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction";
import { OsmWay } from "../../../Logic/Osm/OsmObject";
import { Tag } from "../../../Logic/Tags/Tag";
import type { WayId } from "../../../Models/OsmFeature";
import Loading from "../../Base/Loading.svelte";
import type { GlobalFilter } from "../../../Models/GlobalFilter";
import { onDestroy } from "svelte";
import NextButton from "../../Base/NextButton.svelte";
import BackButton from "../../Base/BackButton.svelte";
import ToSvelte from "../../Base/ToSvelte.svelte";
import Svg from "../../../Svg";
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte";
import { twJoin } from "tailwind-merge";
import Confirm from "../../../assets/svg/Confirm.svelte";
import Close from "../../../assets/svg/Close.svelte";
export let coordinate: { lon: number; lat: number }
export let state: SpecialVisualizationState
export let coordinate: { lon: number; lat: number };
export let state: SpecialVisualizationState;
let selectedPreset: {
preset: PresetConfig
layer: LayerConfig
icon: string
tags: Record<string, string>
} = undefined
let checkedOfGlobalFilters: number = 0
let confirmedCategory = false
} = undefined;
let checkedOfGlobalFilters: number = 0;
let confirmedCategory = false;
$: if (selectedPreset === undefined) {
confirmedCategory = false
creating = false
checkedOfGlobalFilters = 0
confirmedCategory = false;
creating = false;
checkedOfGlobalFilters = 0;
}
let flayer: FilteredLayer = undefined
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined
let layerHasFilters: Store<boolean> | undefined = undefined
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters
let _globalFilter: GlobalFilter[] = []
let flayer: FilteredLayer = undefined;
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined;
let layerHasFilters: Store<boolean> | undefined = undefined;
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters;
let _globalFilter: GlobalFilter[] = [];
onDestroy(
globalFilter.addCallbackAndRun((globalFilter) => {
console.log("Global filters are", globalFilter)
_globalFilter = globalFilter ?? []
console.log("Global filters are", globalFilter);
_globalFilter = globalFilter ?? [];
})
)
);
$: {
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id)
layerIsDisplayed = flayer?.isDisplayed
layerHasFilters = flayer?.hasFilter
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id);
layerIsDisplayed = flayer?.isDisplayed;
layerHasFilters = flayer?.hasFilter;
}
const t = Translations.t.general.add
const t = Translations.t.general.add;
const zoom = state.mapProperties.zoom
const zoom = state.mapProperties.zoom;
const isLoading = state.dataIsLoading
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined)
const isLoading = state.dataIsLoading;
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined);
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined);
// Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map
let preciseInputIsTapped = false
let preciseInputIsTapped = false;
let creating = false
let creating = false;
/**
* Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
* Will delete the lastclick-location
*/
function abort() {
state.selectedElement.setData(undefined)
state.selectedElement.setData(undefined);
// When aborted, we force the contributors to place the pin _again_
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
state.lastClickObject.features.setData([])
preciseInputIsTapped = false
state.lastClickObject.features.setData([]);
preciseInputIsTapped = false;
}
async function confirm() {
creating = true
const location: { lon: number; lat: number } = preciseCoordinate.data
const snapTo: WayId | undefined = <WayId>snappedToObject.data
creating = true;
const location: { lon: number; lat: number } = preciseCoordinate.data;
const snapTo: WayId | undefined = <WayId>snappedToObject.data;
const tags: Tag[] = selectedPreset.preset.tags.concat(
..._globalFilter.map((f) => f?.onNewPoint?.tags ?? [])
)
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags)
);
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags);
let snapToWay: undefined | OsmWay = undefined
let snapToWay: undefined | OsmWay = undefined;
if (snapTo !== undefined && snapTo !== null) {
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0)
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0);
if (downloaded !== "deleted") {
snapToWay = downloaded
snapToWay = downloaded;
}
}
@ -113,44 +114,44 @@
theme: state.layout?.id ?? "unkown",
changeType: "create",
snapOnto: snapToWay,
reusePointWithinMeters: 1,
})
await state.changes.applyAction(newElementAction)
state.newFeatures.features.ping()
reusePointWithinMeters: 1
});
await state.changes.applyAction(newElementAction);
state.newFeatures.features.ping();
// The 'changes' should have created a new point, which added this into the 'featureProperties'
const newId = newElementAction.newElementId
console.log("Applied pending changes, fetching store for", newId)
const tagsStore = state.featureProperties.getStore(newId)
const newId = newElementAction.newElementId;
console.log("Applied pending changes, fetching store for", newId);
const tagsStore = state.featureProperties.getStore(newId);
if (!tagsStore) {
console.error("Bug: no tagsStore found for", newId)
console.error("Bug: no tagsStore found for", newId);
}
{
// Set some metainfo
const properties = tagsStore.data
const properties = tagsStore.data;
if (snapTo) {
// metatags (starting with underscore) are not uploaded, so we can safely mark this
delete properties["_referencing_ways"]
properties["_referencing_ways"] = `["${snapTo}"]`
delete properties["_referencing_ways"];
properties["_referencing_ways"] = `["${snapTo}"]`;
}
properties["_backend"] = state.osmConnection.Backend()
properties["_last_edit:timestamp"] = new Date().toISOString()
const userdetails = state.osmConnection.userDetails.data
properties["_last_edit:contributor"] = userdetails.name
properties["_last_edit:uid"] = "" + userdetails.uid
tagsStore.ping()
properties["_backend"] = state.osmConnection.Backend();
properties["_last_edit:timestamp"] = new Date().toISOString();
const userdetails = state.osmConnection.userDetails.data;
properties["_last_edit:contributor"] = userdetails.name;
properties["_last_edit:uid"] = "" + userdetails.uid;
tagsStore.ping();
}
const feature = state.indexedFeatures.featuresById.data.get(newId)
console.log("Selecting feature", feature, "and opening their popup")
abort()
state.selectedLayer.setData(selectedPreset.layer)
state.selectedElement.setData(feature)
tagsStore.ping()
const feature = state.indexedFeatures.featuresById.data.get(newId);
console.log("Selecting feature", feature, "and opening their popup");
abort();
state.selectedLayer.setData(selectedPreset.layer);
state.selectedElement.setData(feature);
tagsStore.ping();
}
function confirmSync() {
confirm()
.then((_) => console.debug("New point successfully handled"))
.catch((e) => console.error("Handling the new point went wrong due to", e))
.catch((e) => console.error("Handling the new point went wrong due to", e));
}
</script>
@ -285,7 +286,7 @@
<NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full">
<div slot="image" class="relative">
<ToSvelte construct={selectedPreset.icon} />
<img class="absolute bottom-0 right-0 h-4 w-4" src="./assets/svg/confirm.svg" />
<Confirm class="absolute bottom-0 right-0 h-4 w-4" />
</div>
<div class="w-full">
<Tr t={selectedPreset.text} />
@ -299,11 +300,7 @@
checkedOfGlobalFilters = checkedOfGlobalFilters + 1
}}
>
<img
slot="image"
src={_globalFilter[checkedOfGlobalFilters].onNewPoint?.icon ?? "./assets/svg/confirm.svg"}
class="h-12 w-12"
/>
<Confirm slot="image" class="h-12 w-12" />
<Tr
slot="message"
t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({
@ -317,7 +314,7 @@
abort()
}}
>
<img slot="image" src="./assets/svg/close.svg" class="h-8 w-8" />
<Close slot="image" class="h-8 w-8" />
<Tr slot="message" t={Translations.t.general.cancel} />
</SubtleButton>
{:else if !creating}

View file

@ -9,6 +9,7 @@
import Lazy from "../Base/Lazy"
import BaseUIElement from "../BaseUIElement"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { VariableUiElement } from "../Base/VariableUIElement";
//Svelte props
export let tags: UIEventSource<any>
@ -54,13 +55,7 @@
return parts
})
let _allTags = []
onDestroy(
allTags.addCallbackAndRunD((allTags) => {
_allTags = allTags
})
)
const tagsTable = new Table(["Key", "Value"], _allTags).SetClass("zebra-table break-all")
const tagsTable = new VariableUiElement(allTags.mapD(_allTags => new Table(["Key", "Value"], _allTags).SetClass("zebra-table break-all")))
</script>
<section>

View file

@ -15,6 +15,8 @@
import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
import Layers from "../../assets/svg/Layers.svelte";
import AddSmall from "../../assets/svg/AddSmall.svelte";
export let coordinate: UIEventSource<{ lon: number; lat: number }>
export let state: SpecialVisualizationState
@ -97,7 +99,7 @@
<Tr t={Translations.t.notes.noteLayerHasFilters} />
</div>
<SubtleButton on:click={() => notelayer.disableAllFilters()}>
<img slot="image" src="./assets/svg/filter.svg" class="mr-4 h-8 w-8" />
<Layers class="mr-4 h-8 w-8"/>
<Tr slot="message" t={Translations.t.notes.disableAllNoteFilters} />
</SubtleButton>
</div>
@ -126,7 +128,7 @@
{#if $comment?.length >= 3}
<SubtleButton on:click={uploadNote}>
<img slot="image" src="./assets/svg/addSmall.svg" class="mr-4 h-8 w-8" />
<AddSmall slot="image" class="mr-4 h-8 w-8" />
<Tr slot="message" t={Translations.t.notes.createNote} />
</SubtleButton>
{:else}
@ -143,7 +145,7 @@
<Tr t={Translations.t.notes.noteLayerNotEnabled} />
</div>
<SubtleButton on:click={enableNoteLayer}>
<img slot="image" src="./assets/svg/layers.svg" class="mr-4 h-8 w-8" />
<Layers slot="image" class="mr-4 h-8 w-8" />
<Tr slot="message" t={Translations.t.notes.noteLayerDoEnable} />
</SubtleButton>
</div>

View file

@ -11,6 +11,7 @@
import ToSvelte from "../Base/ToSvelte.svelte"
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
import exp from "constants"
import Camera_plus from "../../assets/svg/Camera_plus.svelte";
export let tags: Store<OsmTags>
export let state: SpecialVisualizationState
@ -42,7 +43,7 @@
expanded = true
}}
>
<ToSvelte construct={Svg.camera_plus_svg().SetClass("block w-8 h-8 p-1 mr-2 ")} />
<Camera_plus class="block w-8 h-8 p-1 mr-2"/>
<Tr t={t.seeNearby} />
</button>
{/if}

View file

@ -23,6 +23,8 @@
import UserRelatedState from "../../../Logic/State/UserRelatedState"
import { twJoin } from "tailwind-merge"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import Search from "../../../assets/svg/Search.svelte";
import Login from "../../../assets/svg/Login.svelte";
export let config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>>
@ -211,7 +213,7 @@
{#if config.mappings?.length >= 8}
<div class="sticky flex w-full">
<img src="./assets/svg/search.svg" class="h-6 w-6" />
<Search class="h-6 w-6"/>
<input type="text" bind:value={$searchTerm} class="w-full" />
</div>
{/if}
@ -318,7 +320,7 @@
<LoginToggle {state}>
<Loading slot="loading" />
<SubtleButton slot="not-logged-in" on:click={() => state?.osmConnection?.AttemptLogin()}>
<img slot="image" src="./assets/svg/login.svg" class="h-8 w-8" />
<Login slot="image" class="h-8 w-8" />
<Tr t={Translations.t.general.loginToStart} slot="message" />
</SubtleButton>
{#if $feedback !== undefined}

View file

@ -12,6 +12,7 @@
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
import Mangrove_logo from "../../assets/svg/Mangrove_logo.svelte";
/**
* An element showing all reviews
@ -40,7 +41,7 @@
<Tr t={Translations.t.reviews.no_reviews_yet} />
{/if}
<div class="flex justify-end">
<ToSvelte construct={Svg.mangrove_logo_svg().SetClass("w-12 h-12 shrink-0 p-1 ")} />
<Mangrove_logo class="w-12 h-12 shrink-0 p-1"/>
<Tr cls="text-sm subtle" t={Translations.t.reviews.attribution} />
</div>
</div>

View file

@ -2,6 +2,9 @@
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg"
import { createEventDispatcher } from "svelte"
import Star from "../../assets/svg/Star.svelte";
import Star_half from "../../assets/svg/Star_half.svelte";
import Star_outline from "../../assets/svg/Star_outline.svelte";
export let score: number
export let cutoff: number
@ -23,10 +26,10 @@
on:mousemove={(e) => dispatch("hover", { score: getScore(e) })}
>
{#if score >= cutoff}
<ToSvelte construct={Svg.star_svg().SetClass(starSize)} />
<Star class={starSize}/>
{:else if score + 10 >= cutoff}
<ToSvelte construct={Svg.star_half_svg().SetClass(starSize)} />
<Star_half class={starSize}/>
{:else}
<ToSvelte construct={Svg.star_outline_svg().SetClass(starSize)} />
<Star_outline class={starSize}/>
{/if}
</div>

View file

@ -88,7 +88,7 @@ export interface SpecialVisualization {
readonly funcName: string
readonly docs: string | BaseUIElement
readonly example?: string
readonly needsUrls: string[]
readonly needsUrls: string[] | ((args: string[]) => string)
/**
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included

View file

@ -45,7 +45,6 @@ import { GeoOperations } from "../Logic/GeoOperations"
import CreateNewNote from "./Popup/CreateNewNote.svelte"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
import UserProfile from "./BigComponents/UserProfile.svelte"
import LanguagePicker from "./LanguagePicker"
import Link from "./Base/Link"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
@ -59,7 +58,6 @@ import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz
import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"
import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"
import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
import { OpenJosm } from "./BigComponents/OpenJosm"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import FediverseValidator from "./InputElement/Validators/FediverseValidator"
import SendEmail from "./Popup/SendEmail.svelte"
@ -78,6 +76,9 @@ import Questionbox from "./Popup/TagRendering/Questionbox.svelte"
import { TagUtils } from "../Logic/Tags/TagUtils"
import Giggity from "./BigComponents/Giggity.svelte"
import ThemeViewState from "../Models/ThemeViewState"
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
import LogoutButton from "./Base/LogoutButton.svelte"
import OpenJosm from "./Base/OpenJosm.svelte"
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -453,10 +454,13 @@ export default class SpecialVisualizations {
needsUrls: [],
docs: "A component to set the language of the user interface",
constr(state: SpecialVisualizationState): BaseUIElement {
return new LanguagePicker(
state.layout.language,
state.userRelatedState.language
)
return new SvelteUIElement(LanguagePicker, {
assignTo: state.userRelatedState.language,
availableLanguages: state.layout.language,
preferredLanguages: state.osmConnection.userDetails.map(
(ud) => ud.languages
),
})
},
},
{
@ -465,11 +469,7 @@ export default class SpecialVisualizations {
needsUrls: [Constants.osmAuthConfig.url],
docs: "Shows a button where the user can log out",
constr(state: SpecialVisualizationState): BaseUIElement {
return new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, {
imgSize: "w-6 h-6",
}).onClick(() => {
state.osmConnection.LogOut()
})
return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection })
},
},
new HistogramViz(),
@ -903,10 +903,10 @@ export default class SpecialVisualizations {
funcName: "open_in_josm",
docs: "Opens the current view in the JOSM-editor",
args: [],
needsUrls: OpenJosm.needsUrls,
needsUrls: ["http://127.0.0.1:8111/load_and_zoom"],
constr: (state) => {
return new OpenJosm(state.osmConnection, state.mapProperties.bounds)
return new SvelteUIElement(OpenJosm, { state })
},
},
{
@ -1099,12 +1099,6 @@ export default class SpecialVisualizations {
if (maproulette_id_key === "" || maproulette_id_key === undefined) {
maproulette_id_key = "mr_taskId"
}
if (Svg.All[image] !== undefined || Svg.All[image + ".svg"] !== undefined) {
if (image.endsWith(".svg")) {
image = image.substring(0, image.length - 4)
}
image = Svg[image + "_svg"]()
}
const failed = new UIEventSource(false)
const closeButton = new SubtleButton(image, message).OnClickWithLoading(
@ -1460,7 +1454,7 @@ export default class SpecialVisualizations {
},
],
docs: "Shows events that are happening based on a Giggity URL",
needsUrls: ["*"],
needsUrls: (args) => args[0],
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,

View file

@ -1,36 +1,36 @@
<script lang="ts">
import NextButton from "./Base/NextButton.svelte"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import EditLayerState, { EditThemeState } from "./Studio/EditLayerState"
import EditLayer from "./Studio/EditLayer.svelte"
import Loading from "../assets/svg/Loading.svelte"
import StudioServer from "./Studio/StudioServer"
import LoginToggle from "./Base/LoginToggle.svelte"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { QueryParameters } from "../Logic/Web/QueryParameters"
import NextButton from "./Base/NextButton.svelte"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import EditLayerState, { EditThemeState } from "./Studio/EditLayerState"
import EditLayer from "./Studio/EditLayer.svelte"
import Loading from "../assets/svg/Loading.svelte"
import StudioServer from "./Studio/StudioServer"
import LoginToggle from "./Base/LoginToggle.svelte"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { QueryParameters } from "../Logic/Web/QueryParameters"
import layerSchemaRaw from "../../src/assets/schemas/layerconfigmeta.json"
import layoutSchemaRaw from "../../src/assets/schemas/layoutconfigmeta.json"
import layerSchemaRaw from "../../src/assets/schemas/layerconfigmeta.json"
import layoutSchemaRaw from "../../src/assets/schemas/layoutconfigmeta.json"
import If from "./Base/If.svelte"
import BackButton from "./Base/BackButton.svelte"
import ChooseLayerToEdit from "./Studio/ChooseLayerToEdit.svelte"
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
import FloatOver from "./Base/FloatOver.svelte"
import Walkthrough from "./Walkthrough/Walkthrough.svelte"
import * as intro from "../assets/studio_introduction.json"
import * as intro_tagrenderings from "../assets/studio_tagrenderings_intro.json"
import If from "./Base/If.svelte"
import BackButton from "./Base/BackButton.svelte"
import ChooseLayerToEdit from "./Studio/ChooseLayerToEdit.svelte"
import FloatOver from "./Base/FloatOver.svelte"
import Walkthrough from "./Walkthrough/Walkthrough.svelte"
import * as intro from "../assets/studio_introduction.json"
import * as intro_tagrenderings from "../assets/studio_tagrenderings_intro.json"
import { QuestionMarkCircleIcon } from "@babeard/svelte-heroicons/mini"
import type { ConfigMeta } from "./Studio/configMeta"
import EditTheme from "./Studio/EditTheme.svelte"
import * as meta from "../../package.json"
import Checkbox from "./Base/Checkbox.svelte"
import { Utils } from "../Utils"
import Translations from "./i18n/Translations"
import Tr from "./Base/Tr.svelte"
import { QuestionMarkCircleIcon } from "@babeard/svelte-heroicons/mini"
import type { ConfigMeta } from "./Studio/configMeta"
import EditTheme from "./Studio/EditTheme.svelte"
import * as meta from "../../package.json"
import Checkbox from "./Base/Checkbox.svelte"
import { Utils } from "../Utils"
import Translations from "./i18n/Translations"
import Tr from "./Base/Tr.svelte"
import Add from "../assets/svg/Add.svelte"
export let studioUrl =
export let studioUrl =
window.location.hostname === "127.0.0.2"
? "http://127.0.0.1:1235"
: "https://studio.mapcomplete.org"
@ -196,8 +196,8 @@
<QuestionMarkCircleIcon class="h-6 w-6" />
Show the introduction again
</button>
<a class="button flex" href={Utils.HomepageLink()}>
<img class="h-6 w-6" src="./assets/svg/add.svg" />
<a class="flex button" href={Utils.HomepageLink()}>
<Add class="h-6 w-6" />
<Tr t={Translations.t.general.backToIndex} />
</a>
</div>

View file

@ -1,7 +1,8 @@
<script lang="ts">
import Svg from "../Svg"
import Loading from "./Base/Loading.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
import Svg from "../Svg"
import Loading from "./Base/Loading.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
import Community from "../assets/svg/Community.svelte"
</script>
<div>
@ -40,16 +41,16 @@
<div class="flex">
<button class="primary">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Main action
</button>
<button class="primary disabled">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Main action (disabled)
</button>
<button class="small">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Small button
</button>
@ -58,11 +59,11 @@
</div>
<div class="flex">
<button>
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Secondary action
</button>
<button class="disabled">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Secondary action (disabled)
</button>
</div>
@ -81,7 +82,7 @@
</label>
<label for="javascript">
<input id="javascript" name="fav_language" type="radio" value="JavaScript" />
<ToSvelte construct={Svg.community_svg().SetClass("w-8 h-8")} />
<Community class="w-8 h-8"/>
JavaScript
</label>
</div>
@ -105,26 +106,26 @@
<div class="flex">
<button class="primary">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Main action
</button>
<button class="primary disabled">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Main action (disabled)
</button>
<button class="small">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Small button
</button>
</div>
<div class="flex">
<button>
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Secondary action
</button>
<button class="disabled">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6" />
Secondary action (disabled)
</button>
</div>

View file

@ -1,16 +1,6 @@
<script lang="ts">
// Testing grounds
import { UIEventSource } from "../Logic/UIEventSource"
import TabbedGroup from "./Base/TabbedGroup.svelte"
let tab = new UIEventSource(1)
console.log("Tab control", tab)
</script>
<TabbedGroup {tab}>
<div slot="title0">Title 0</div>
<div slot="content0">Content 0 loaded</div>
<div slot="title1">Title 1</div>
<div slot="content1">Content 1</div>
</TabbedGroup>
<div class="w-full">
No tests
</div>

View file

@ -1,62 +1,71 @@
<script lang="ts">
import { Store, UIEventSource } from "../Logic/UIEventSource"
import { Map as MlMap } from "maplibre-gl"
import MaplibreMap from "./Map/MaplibreMap.svelte"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import MapControlButton from "./Base/MapControlButton.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
import If from "./Base/If.svelte"
import { GeolocationControl } from "./BigComponents/GeolocationControl"
import type { Feature } from "geojson"
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import Filterview from "./BigComponents/Filterview.svelte"
import ThemeViewState from "../Models/ThemeViewState"
import type { MapProperties } from "../Models/MapProperties"
import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations"
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
import Constants from "../Models/Constants"
import TabbedGroup from "./Base/TabbedGroup.svelte"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LoginToggle from "./Base/LoginToggle.svelte"
import LoginButton from "./Base/LoginButton.svelte"
import CopyrightPanel from "./BigComponents/CopyrightPanel"
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
import ModalRight from "./Base/ModalRight.svelte"
import { Utils } from "../Utils"
import Hotkeys from "./Base/Hotkeys"
import { VariableUiElement } from "./Base/VariableUIElement"
import SvelteUIElement from "./Base/SvelteUIElement"
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
import LevelSelector from "./BigComponents/LevelSelector.svelte"
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
import Svg from "../Svg"
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
import type { RasterLayerPolygon } from "../Models/RasterLayers"
import { AvailableRasterLayers } from "../Models/RasterLayers"
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
import IfHidden from "./Base/IfHidden.svelte"
import { onDestroy } from "svelte"
import { OpenJosm } from "./BigComponents/OpenJosm"
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
import StateIndicator from "./BigComponents/StateIndicator.svelte"
import LanguagePicker from "./LanguagePicker"
import Locale from "./i18n/Locale"
import ShareScreen from "./BigComponents/ShareScreen.svelte"
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
import Cross from "../assets/svg/Cross.svelte"
import Summary from "./BigComponents/Summary.svelte"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import { Map as MlMap } from "maplibre-gl"
import MaplibreMap from "./Map/MaplibreMap.svelte"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import MapControlButton from "./Base/MapControlButton.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
import If from "./Base/If.svelte"
import { GeolocationControl } from "./BigComponents/GeolocationControl"
import type { Feature } from "geojson"
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import Filterview from "./BigComponents/Filterview.svelte"
import ThemeViewState from "../Models/ThemeViewState"
import type { MapProperties } from "../Models/MapProperties"
import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations"
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
import Constants from "../Models/Constants"
import TabbedGroup from "./Base/TabbedGroup.svelte"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LoginToggle from "./Base/LoginToggle.svelte"
import LoginButton from "./Base/LoginButton.svelte"
import CopyrightPanel from "./BigComponents/CopyrightPanel"
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
import ModalRight from "./Base/ModalRight.svelte"
import { Utils } from "../Utils"
import Hotkeys from "./Base/Hotkeys"
import { VariableUiElement } from "./Base/VariableUIElement"
import SvelteUIElement from "./Base/SvelteUIElement"
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
import LevelSelector from "./BigComponents/LevelSelector.svelte"
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
import type { RasterLayerPolygon } from "../Models/RasterLayers"
import { AvailableRasterLayers } from "../Models/RasterLayers"
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
import IfHidden from "./Base/IfHidden.svelte"
import { onDestroy } from "svelte"
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
import StateIndicator from "./BigComponents/StateIndicator.svelte"
import ShareScreen from "./BigComponents/ShareScreen.svelte"
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
import Cross from "../assets/svg/Cross.svelte"
import Summary from "./BigComponents/Summary.svelte"
import Mastodon from "../assets/svg/Mastodon.svelte"
import Bug from "../assets/svg/Bug.svelte"
import Liberapay from "../assets/svg/Liberapay.svelte"
import Min from "../assets/svg/Min.svelte"
import Plus from "../assets/svg/Plus.svelte"
import Filter from "../assets/svg/Filter.svelte"
import Add from "../assets/svg/Add.svelte"
import Statistics from "../assets/svg/Statistics.svelte"
import Community from "../assets/svg/Community.svelte"
import Download from "../assets/svg/Download.svelte"
import Share from "../assets/svg/Share.svelte"
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
import OpenJosm from "./Base/OpenJosm.svelte"
export let state: ThemeViewState
export let state: ThemeViewState
let layout = state.layout
let maplibremap: UIEventSource<MlMap> = state.map
@ -205,7 +214,7 @@
<!-- bottom left elements -->
<If condition={state.featureSwitches.featureSwitchFilter}>
<MapControlButton on:click={() => state.guistate.openFilterView()}>
<ToSvelte construct={Svg.filter_svg().SetClass("h-6 w-6")} />
<Filter class="h-6 w-6"/>
</MapControlButton>
</If>
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
@ -245,10 +254,10 @@
</div>
</If>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}>
<ToSvelte construct={Svg.plus_svg().SetClass("w-8 h-8")} />
<Plus class="w-8 h-8" />
</MapControlButton>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)}>
<ToSvelte construct={Svg.min_svg().SetClass("w-8 h-8")} />
<Min class="w-8 h-8"/>
</MapControlButton>
<If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton>
@ -264,7 +273,7 @@
</div>
<LoginToggle ignoreLoading={true} {state}>
{#if $showCrosshair === "yes" && ($currentZoom >= 17 || $arrowKeysWereUsed !== undefined)}
{#if ($showCrosshair === "yes" && $currentZoom >= 17) || $showCrosshair === "always" || $arrowKeysWereUsed !== undefined }
<div
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center justify-center"
>
@ -341,7 +350,7 @@
</div>
<div class="flex" slot="title1">
<ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")} />
<Filter class="w-4 h-4"/>
<Tr t={Translations.t.general.menu.filter} />
</div>
@ -365,7 +374,7 @@
<div class="flex" slot="title2">
<If condition={state.featureSwitches.featureSwitchEnableExport}>
<ToSvelte construct={Svg.download_svg().SetClass("w-4 h-4")} />
<Download class="w-4 h-4"/>
<Tr t={Translations.t.general.download.title} />
</If>
</div>
@ -380,7 +389,7 @@
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
<div class="flex" slot="title4">
<ToSvelte construct={Svg.share_svg().SetClass("w-4 h-4")} />
<Share class="w-4 h-4"/>
<Tr t={Translations.t.general.sharescreen.title} />
</div>
<div class="m-2" slot="content4">
@ -432,27 +441,27 @@
<Tr t={Translations.t.general.aboutMapComplete.intro} />
<a class="flex" href={Utils.HomepageLink()}>
<img class="h-6 w-6" src="./assets/svg/add.svg" />
<Add class="h-6 w-6"/>
<Tr t={Translations.t.general.backToIndex} />
</a>
<a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank">
<img class="h-6 w-6" src="./assets/svg/bug.svg" />
<Bug class="h-6 w-6"/>
<Tr t={Translations.t.general.attribution.openIssueTracker} />
</a>
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
<img class="h-6 w-6" src="./assets/svg/mastodon.svg" />
<Mastodon class="w-6 h-6" />
<Tr t={Translations.t.general.attribution.followOnMastodon} />
</a>
<a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank">
<img class="h-6 w-6" src="./assets/svg/liberapay.svg" />
<Liberapay class="h-6 w-6" />
<Tr t={Translations.t.general.attribution.donate} />
</a>
<a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank">
<img class="h-6 w-6" src="./assets/svg/statistics.svg" />
<Statistics class="h-6 w-6" />
<Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: "MapComplete" })} />
</a>
{Constants.vNumber}
@ -467,7 +476,7 @@
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
<LoginToggle {state}>
<div class="flex flex-col" slot="not-logged-in">
<ToSvelte construct={() => new LanguagePicker(layout.language, Locale.language)} />
<LanguagePicker availableLanguages={layout.language} />
<Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
<LoginButton clss="primary" osmConnection={state.osmConnection} />
</div>
@ -486,7 +495,7 @@
</div>
<div class="flex" slot="title2">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
<Community class="w-6 h-6"/>
<Tr t={Translations.t.communityIndex.title} />
</div>
<div class="m-2" slot="content2">
@ -504,10 +513,7 @@
<div class="m-2 flex flex-col" slot="content4">
<If condition={featureSwitches.featureSwitchEnableLogin}>
<OpenIdEditor mapProperties={state.mapProperties} />
<ToSvelte
construct={() =>
new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")}
/>
<OpenJosm {state}/>
<MapillaryLink mapProperties={state.mapProperties} />
</If>

View file

@ -28,6 +28,8 @@
on:next={() => step(1)}
isFirst={currentPage === 0}
islast={currentPage + 1 === pages.length}
totalPages={pages.length}
pageNumber={currentPage}
>
<FromHtml src={nmd(pages[currentPage])} />
</WalkthroughStep>

View file

@ -1,30 +1,42 @@
<script lang="ts">
import BackButton from "../Base/BackButton.svelte"
import NextButton from "../Base/NextButton.svelte"
import { createEventDispatcher } from "svelte"
import BackButton from "../Base/BackButton.svelte";
import NextButton from "../Base/NextButton.svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher<{ back; next }>()
export let islast = false
export let isFirst = false
const dispatch = createEventDispatcher<{ back; next }>();
export let islast = false;
export let isFirst = false;
export let pageNumber: number = undefined;
export let totalPages: number = undefined;
</script>
<div class="flex h-full w-full flex-col justify-between">
<div class="flex h-full w-full flex-col justify-between link-underline">
<div class="overflow-y-auto">
<slot />
</div>
<div class="flex w-full">
{#if !isFirst}
<BackButton clss="w-full" on:click={() => dispatch("back")}>Back</BackButton>
{:else}
<div class="w-full" />
<div class="flex flex-col">
{#if pageNumber !== undefined && totalPages !== undefined}
<div class="flex justify-end">
<div class="subtle">{pageNumber+1}/{totalPages}</div>
</div>
{/if}
<NextButton clss="primary w-full" on:click={() => dispatch("next")}>
{#if islast}
Finish
<div class="flex w-full">
{#if !isFirst}
<BackButton clss="w-full" on:click={() => dispatch("back")}>Back</BackButton>
{:else}
Next
<div class="w-full" />
{/if}
</NextButton>
<NextButton clss="primary w-full" on:click={() => dispatch("next")}>
{#if islast}
Finish
{:else}
Next
{/if}
</NextButton>
</div>
</div>
</div>

View file

@ -9,6 +9,7 @@
import WikidataPreviewBox from "./WikidataPreviewBox"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import Wikipedia from "../../assets/svg/Wikipedia.svelte";
/**
* Shows a wikipedia-article + wikidata preview for the given item
@ -18,7 +19,7 @@
{#if $wikipediaDetails.articleUrl}
<a class="flex" href={$wikipediaDetails.articleUrl} rel="noreferrer" target="_blank">
<img class="h-6 w-6" src="./assets/svg/wikipedia.svg" />
<Wikipedia class="h-6 w-6"/>
<Tr t={Translations.t.general.wikipedia.fromWikipedia} />
</a>
{/if}

View file

@ -56,6 +56,9 @@ export default class Locale {
if (typeof navigator !== "undefined") {
browserLanguage = navigator.languages?.[0] ?? navigator.language ?? "en"
console.log("Browser language is", browserLanguage)
if (browserLanguage === "en-US") {
browserLanguage = "en"
}
}
source = LocalStorageSource.Get("language", browserLanguage)
}