Merge develop

This commit is contained in:
Pieter Vander Vennet 2023-12-03 04:44:59 +01:00
commit d959b6b40b
290 changed files with 37178 additions and 2200 deletions

View file

@ -1,74 +1,73 @@
<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";
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 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);
),
})
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()}
<div class="m-4 flex flex-col">
<LanguagePicker clss="self-end" 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 class="mt-4 flex">
<div class="m-3 flex-none">
<Logo alt="MapComplete Logo" class="h-12 w-12 sm:h-24 sm:w-24" />
</div>
<div class="flex flex-col">
<h1 class="tracking-tight font-extrabold md:text-6xl m-0">
<h1 class="m-0 font-extrabold tracking-tight md:text-6xl">
<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} />
<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}>
<LoginToggle {state}>
<div slot="not-logged-in">
<button class="w-full" on:click={() => osmConnection.AttemptLogin()}>
<Login class="w-6 h-6 mr-2 "/>
<Login class="mr-2 h-6 w-6 " />
<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
class="button h-fit w-full"
href={window.location.protocol + "//" + window.location.host + "/studio.html"}
>
<Pencil class="mr-2 h-6 w-6" />
<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">
<Tr cls="link-underline" t={Translations.t.general.aboutMapComplete.intro} />
<div class="subtle mb-16 self-end">
v{Constants.vNumber}
</div>
</div>

View file

@ -5,7 +5,7 @@
*/
import { Store } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"
import Hand from "../../assets/svg/Hand.svelte";
import Hand from "../../assets/svg/Hand.svelte"
let mainElem: HTMLElement
export let hideSignal: Store<any>

View file

@ -1,31 +1,31 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource.js";
import { UIEventSource } from "../../Logic/UIEventSource.js"
export let value: UIEventSource<any>
let i: any = value.data
let htmlElement : HTMLSelectElement
function selectAppropriateValue(){
if(!htmlElement){
return;
let htmlElement: HTMLSelectElement
function selectAppropriateValue() {
if (!htmlElement) {
return
}
const v = value.data
for (let option of htmlElement.getElementsByTagName("option")) {
if(option.value === v){
if (option.value === v) {
option.selected = true
return
}
}
}
value.addCallbackD(() => selectAppropriateValue())
$: {
if(htmlElement){
if (htmlElement) {
selectAppropriateValue()
}
}
export let cls : string = undefined
</script>
<select bind:this={htmlElement} on:change={(e) => {value.setData(e.srcElement.value)}}>
<select class={cls} bind:this={htmlElement} on:change={(e) => {value.setData(e.srcElement.value)}}>
<slot />
</select>

View file

@ -6,7 +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";
import Invalid from "../../assets/svg/Invalid.svelte"
export let state: {
osmConnection: OsmConnection

View file

@ -1,13 +1,17 @@
<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";
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={() => {osmConnection.LogOut()}}>
<Logout class="w-6 h-6" />
<button
on:click={() => {
state.osmConnection.LogOut()
}}
>
<Logout class="h-6 w-6" />
<Tr t={Translations.t.general.logout} />
</button>

View file

@ -1,37 +1,37 @@
<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";
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);
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));
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;
const bbox = state.mapProperties.bounds.data
if (bbox === undefined) {
return;
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}`;
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"));
.catch(() => josmState.setData("ERROR"))
}
</script>
{#if $showButton}
{#if $josmState === undefined}
<!-- empty -->

View file

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

View file

@ -1,7 +1,7 @@
<script lang="ts">
import Locale from "../i18n/Locale"
import LinkToWeblate from "./LinkToWeblate"
import Translate from "../../assets/svg/Translate.svelte";
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

View file

@ -1,84 +1,99 @@
<script lang="ts">
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Utils } from "../../Utils"
import Loading from "../../assets/svg/Loading.svelte"
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { Utils } from "../../Utils";
import Loading from "../../assets/svg/Loading.svelte";
export let tags: Store<Record<string, string>>
export let giggityUrl: string
export let state: SpecialVisualizationState
export let tags: Store<Record<string, string>>;
export let giggityUrl: string;
export let state: SpecialVisualizationState;
let name = $tags["name"];
let events: UIEventSource<{
date: Date,
start: string,
duration: string,
room: string,
slug: string,
url: string,
title: string,
track: string,
type: string,
language: string,
abstract: string,
description: string,
persons: string,
} []> = new UIEventSource(undefined);
let name = $tags["name"]
let events: UIEventSource<
{
date: Date
start: string
duration: string
room: string
slug: string
url: string
title: string
track: string
type: string
language: string
abstract: string
description: string
persons: string
}[]
> = new UIEventSource(undefined)
async function loadXml() {
if (!name) {
console.log("Not fetching giggity events as name is", name, tags);
return;
console.log("Not fetching giggity events as name is", name, tags)
return
}
const xmlStr = await Utils.downloadAdvanced(giggityUrl);
console.log("Raw xml", xmlStr);
const parser = new DOMParser();
let doc = parser.parseFromString(xmlStr.content, "application/xml");
let days = Array.from(doc.documentElement.getElementsByTagName("day"));
let today = new Date().toISOString().split("T")[0];
const eventsToday = days.find(day => day.getAttribute("date") === today);
console.log("Events today", eventsToday);
const childs = ["date", "start", "duration", "room", "slug", "url", "title", "track", "type", "language", "abstract", "description", "persons"];
const xmlStr = await Utils.downloadAdvanced(giggityUrl)
console.log("Raw xml", xmlStr)
const parser = new DOMParser()
let doc = parser.parseFromString(xmlStr.content, "application/xml")
let days = Array.from(doc.documentElement.getElementsByTagName("day"))
let today = new Date().toISOString().split("T")[0]
const eventsToday = days.find((day) => day.getAttribute("date") === today)
console.log("Events today", eventsToday)
const childs = [
"date",
"start",
"duration",
"room",
"slug",
"url",
"title",
"track",
"type",
"language",
"abstract",
"description",
"persons",
]
const now = new Date().toISOString().split("T")[1].substring(0, 5)
let eventsList = [];
let eventsList = []
for (const eventXml of Array.from(eventsToday.getElementsByTagName("event"))) {
const event: Record<string, string> = {};
const event: Record<string, string> = {}
for (const child of childs) {
const v = Array.from(eventXml.getElementsByTagName(child)).map(xml => xml.textContent).join("; ");
event[child] = v;
const v = Array.from(eventXml.getElementsByTagName(child))
.map((xml) => xml.textContent)
.join("; ")
event[child] = v
}
if(!name.startsWith(event.room)){
if (!name.startsWith(event.room)) {
continue
}
if(now > event.start){
if (now > event.start) {
continue
}
eventsList.push(event);
eventsList.push(event)
}
events.setData(eventsList);
events.setData(eventsList)
}
loadXml();
loadXml()
</script>
{#if $events === undefined}
<Loading class="h-4">Loading giggity events from {giggityUrl}</Loading>
{:else if $events.length === 0}
{:else if $events.length === 0}
<i>No upcoming events in this room</i>
{:else}
<div>
<h2>Upcoming events</h2>
{#each $events as event}
<div class="flex flex-col m-2 border border-gray-200 border-dotted">
<div class="m-2 flex flex-col border border-dotted border-gray-200">
{#if event.url}
<h3><a href={event.url} target="_blank">{event.title}</a></h3>
{:else }
<h3>{event.title}</h3>
{/if}
{:else}
<h3>{event.title}</h3>
{/if}
<div><b>{event.start}</b></div>
<i>By {event.persons}</i>
<div>

View file

@ -1,14 +1,13 @@
<script lang="ts">
import Translations from "../i18n/Translations"
import Svg from "../../Svg"
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";
import Translations from "../i18n/Translations"
import { Store } from "../../Logic/UIEventSource"
import Tr from "../Base/Tr.svelte"
import Mapillary_black from "../../assets/svg/Mapillary_black.svelte"
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
/*
A subtleButton which opens mapillary in a new tab at the current location
*/
/*
A subtleButton which opens mapillary in a new tab at the current location
*/
export let mapProperties: {
readonly zoom: Store<number>
@ -16,13 +15,11 @@
}
let location = mapProperties.location
let zoom = mapProperties.zoom
let mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${$location?.lat ?? 0}&lng=${
$location?.lon ?? 0
}&z=${Math.max(($zoom ?? 2) - 1, 1)}`
let mapillaryLink = Mapillary.createLink($location, $zoom)
</script>
<a class="button flex items-center" href={mapillaryLink} target="_blank">
<Mapillary_black class="w-12 h-12 m-2 mr-4 shrink-0"/>
<Mapillary_black class="m-2 mr-4 h-12 w-12 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

@ -118,7 +118,7 @@ export default class MoreScreen extends Combine {
if (search === undefined) {
return true
}
search = search.toLocaleLowerCase()
search = Utils.RemoveDiacritics(search.toLocaleLowerCase())
if (search.length > 3 && layout.id.toLowerCase().indexOf(search) >= 0) {
return true
}
@ -131,7 +131,7 @@ export default class MoreScreen extends Combine {
continue
}
const term = entity["*"] ?? entity[Locale.language.data]
if (term?.toLowerCase()?.indexOf(search) >= 0) {
if (Utils.RemoveDiacritics(term?.toLowerCase())?.indexOf(search) >= 0) {
return true
}
}

View file

@ -16,7 +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";
import Move_arrows from "../../assets/svg/Move_arrows.svelte"
/**
* An advanced location input, which has support to:
@ -126,6 +126,6 @@
maxDistanceInMeters="50"
>
<slot name="image" slot="image">
<Move_arrows class="h-full max-h-24" />
<Move_arrows class="h-full max-h-24" />
</slot>
</LocationInput>

View file

@ -38,7 +38,6 @@
<div class="flex flex-col">
<!-- Title element-->
<h3>
<TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer} />
</h3>
<div

View file

@ -20,15 +20,20 @@
_metatags = tags
})
)
let knownTagRenderings = layer.tagRenderings
.filter(config => (config.condition?.matchesProperties($tags) ?? true) && (config.metacondition?.matchesProperties({ ...$tags, ..._metatags } ?? true)
&& config.IsKnown($tags)))
let knownTagRenderings = layer.tagRenderings.filter(
(config) =>
(config.condition?.matchesProperties($tags) ?? true) &&
config.metacondition?.matchesProperties({ ...$tags, ..._metatags } ?? true) &&
config.IsKnown($tags)
)
$: {
knownTagRenderings = layer.tagRenderings
.filter(config => (config.condition?.matchesProperties($tags) ?? true) && (config.metacondition?.matchesProperties({ ...$tags, ..._metatags } ?? true)
&& config.IsKnown($tags)))
knownTagRenderings = layer.tagRenderings.filter(
(config) =>
(config.condition?.matchesProperties($tags) ?? true) &&
config.metacondition?.matchesProperties({ ...$tags, ..._metatags } ?? true) &&
config.IsKnown($tags)
)
}
</script>
@ -40,15 +45,15 @@
{:else}
<div class="flex h-full flex-col gap-y-2 overflow-y-auto p-1 px-2">
{#each knownTagRenderings as config (config.id)}
<TagRenderingEditable
{tags}
{config}
{state}
{selectedElement}
{layer}
{highlightedRendering}
clss={knownTagRenderings.length === 1 ? "h-full" : "tr-length-"+knownTagRenderings.length}
/>
<TagRenderingEditable
{tags}
{config}
{state}
{selectedElement}
{layer}
{highlightedRendering}
clss={knownTagRenderings.length === 1 ? "h-full" : "tr-length-" + knownTagRenderings.length}
/>
{/each}
</div>
{/if}

View file

@ -1,16 +1,15 @@
<script lang="ts">
import type { Feature } from "geojson";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import SelectedElementTitle from "./SelectedElementTitle.svelte";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
import type { Feature } from "geojson"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import SelectedElementTitle from "./SelectedElementTitle.svelte"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
export let state: SpecialVisualizationState;
export let state: SpecialVisualizationState
export let feature: Feature
let id = feature.properties.id
let tags = state.featureProperties.getStore(id);
let tags = state.featureProperties.getStore(id)
let layer: LayerConfig = state.layout.getMatchingLayer(tags.data)
</script>
<TagRenderingAnswer config={layer.title} selectedElement={feature} {state} {tags} {layer} />
<TagRenderingAnswer config={layer.title} selectedElement={feature} {state} {tags} {layer} />

View file

@ -87,10 +87,10 @@
{#if theme.id !== personal.id || $unlockedPersonal}
<SubtleLink href={$href} options={{ extraClasses: "w-full" }}>
<img slot="image" src={theme.icon} class="mr-2 m-1 sm:mr-4 sm:m-2 block h-11 w-11" alt="" />
<img slot="image" src={theme.icon} class="m-1 mr-2 block h-11 w-11 sm:m-2 sm:mr-4" alt="" />
<span class="flex flex-col overflow-hidden text-ellipsis">
<Tr t={title} />
{#if selected}
<span class="alert">
<Tr t={Translations.t.general.morescreen.enterToOpen} />

View file

@ -1,21 +1,21 @@
<script lang="ts">
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";
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
@ -71,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}>
<Crosshair class="w-8 h-8"/>
<Crosshair class="h-8 w-8" />
<Tr t={Translations.t.general.openTheMapAtGeolocation} />
</button>
<!-- No geolocation granted - we don't show the button -->
@ -81,17 +81,23 @@
on:click={jumpToCurrentLocation}
>
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
<Crosshair class="w-8 h-8" style="animation: 3s linear 0s infinite normal none running spin;" />
<Crosshair
class="h-8 w-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">
<Location_refused class="w-8 h-8"/>
<Location_refused class="h-8 w-8" />
<Tr t={Translations.t.general.geopermissionDenied} />
</button>
{:else}
<button class="disabled flex w-full items-center gap-x-2">
<Crosshair class="w-8 h-8" style="animation: 3s linear 0s infinite normal none running spin;" />
<Crosshair
class="h-8 w-8"
style="animation: 3s linear 0s infinite normal none running spin;"
/>
<Tr t={Translations.t.general.waitingForLocation} />
</button>
{/if}
@ -149,7 +155,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()}>
<Add class="h-6 w-6"/>
<Add class="h-6 w-6" />
<Tr t={Translations.t.general.backToIndex} />
</a>
</div>

View file

@ -71,7 +71,6 @@
gpsLayer.isDisplayed.setData(gpsIsDisplayed)
state.userRelatedState.preferencesAsTags.data["__showTimeSensitiveIcons"] = "yes"
state.userRelatedState.preferencesAsTags.ping()
}
}
</script>

View file

@ -11,7 +11,7 @@
import Locale from "../i18n/Locale"
import { UIEventSource } from "../../Logic/UIEventSource"
import DownloadHelper from "./DownloadHelper"
import Qr from "../../Utils/Qr";
import Qr from "../../Utils/Qr"
export let templateName: string
export let state: ThemeViewState
@ -31,11 +31,11 @@
freeComponentId: "belowmap",
createImage: (key: string, width: string, height: string) => {
console.log("Creating an image for key", key)
if(key === "qr"){
if (key === "qr") {
const toShare = window.location.href.split("#")[0]
return new Qr(toShare).toImageElement(parseFloat(width), parseFloat(height))
}
return downloadHelper.createImage(key, width, height);
return downloadHelper.createImage(key, width, height)
},
textSubstitutions: <Record<string, string>>{
"layout.title": state.layout.title,
@ -62,7 +62,10 @@
extension="pdf"
helperText={t.downloadAsPdfHelper}
metaIsIncluded={false}
mainText={t.pdf.current_view_generic.Subs({orientation: template.orientation, paper_size: template.format.toUpperCase()})}
mainText={t.pdf.current_view_generic.Subs({
orientation: template.orientation,
paper_size: template.format.toUpperCase(),
})}
mimetype="application/pdf"
{state}
/>

View file

@ -5,21 +5,37 @@ import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
import BaseUIElement from "../BaseUIElement"
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
import { GeoOperations } from "../../Logic/GeoOperations"
export class AttributedImage extends Combine {
constructor(imageInfo: { url: string; provider?: ImageProvider; date?: Date }) {
constructor(imageInfo: {
id: string,
url: string;
provider?: ImageProvider;
date?: Date
}, feature?: Feature) {
let img: BaseUIElement
img = new Img(imageInfo.url, false, {
fallbackImage:
imageInfo.provider === Mapillary.singleton ? "./assets/svg/blocked.svg" : undefined,
})
let location: {
lon: number,
lat: number
} = undefined
if (feature) {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
location = { lon, lat }
}
let attr: BaseUIElement = undefined
if (imageInfo.provider !== undefined) {
attr = new Attribution(
UIEventSource.FromPromise(imageInfo.provider?.DownloadAttribution(imageInfo.url)),
imageInfo.provider?.SourceIcon(),
imageInfo.date
imageInfo.provider?.SourceIcon(imageInfo.id, location),
imageInfo.date,
)
}

View file

@ -28,6 +28,7 @@ export default class Attribution extends VariableUiElement {
title = new Link(title, license.informationLocation.href, true)
}
}
return new Combine([
icon
?.SetClass("block left")

View file

@ -9,19 +9,21 @@ import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Changes } from "../../Logic/Osm/Changes"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Feature } from "geojson"
export class ImageCarousel extends Toggle {
constructor(
images: Store<{ key: string; url: string; provider: ImageProvider }[]>,
images: Store<{ id:string, key: string; url: string; provider: ImageProvider }[]>,
tags: Store<any>,
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig }
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig },
feature: Feature
) {
const uiElements = images.map(
(imageURLS: { key: string; url: string; provider: ImageProvider }[]) => {
(imageURLS: { key: string; url: string; provider: ImageProvider, id: string }[]) => {
const uiElements: BaseUIElement[] = []
for (const url of imageURLS) {
try {
let image = new AttributedImage(url)
let image = new AttributedImage(url, feature)
if (url.key !== undefined) {
image = new Combine([

View file

@ -3,15 +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 Camera_plus from "../../assets/svg/Camera_plus.svelte";
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
@ -57,7 +57,7 @@
{#if image !== undefined}
<img src={image} />
{:else}
<Camera_plus class="block w-12 h-12 p-1 text-4xl"/>
<Camera_plus class="block h-12 w-12 p-1 text-4xl" />
{/if}
{#if labelText}
{labelText}

View file

@ -6,7 +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";
import Direction_stroke from "../../../assets/svg/Direction_stroke.svelte"
/**
* A visualisation to pick a direction on a map background.
@ -68,6 +68,6 @@
</div>
<div bind:this={directionElem} class="absolute top-0 left-0 h-full w-full">
<Direction_stroke/>
<Direction_stroke />
</div>
</div>

View file

@ -12,7 +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";
import Move_arrows from "../../../assets/svg/Move_arrows.svelte"
/**
* A visualisation to pick a location on a map background
@ -91,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">
<Move_arrows class="h-full max-h-24"/>
<Move_arrows class="h-full max-h-24" />
</slot>
</div>

View file

@ -1,45 +1,44 @@
<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";
// 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";
import { UIEventSource } from "../../Logic/UIEventSource"
import Locale from "../i18n/Locale"
import { LanguageIcon } from "@babeard/svelte-heroicons/solid"
import Dropdown from "../Base/Dropdown.svelte"
import { twMerge } from "tailwind-merge"
/**
/**
* Languages one can choose from
* Defaults to _all_ languages known by MapComplete
*/
export let availableLanguages: string[] = Object.keys(native);
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;
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";
lng = "en"
}
if (preferredLanguages?.indexOf(lng) < 0) {
preferredLanguages?.push(lng);
preferredLanguages?.push(lng)
}
preferredFiltered = preferredLanguages?.filter(l => availableLanguages.indexOf(l) >= 0);
});
let current = Locale.language;
preferredFiltered = preferredLanguages?.filter((l) => availableLanguages.indexOf(l) >= 0)
})
export let clss : string = undefined
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}>
<form class={twMerge("flex items-center max-w-full pr-4", clss)}>
<LanguageIcon class="h-4 w-4 mr-1 shrink-0" />
<Dropdown cls="max-w-full" value={assignTo}>
{#if preferredFiltered}
{#each preferredFiltered as language}
<option value={language} class="font-bold">
@ -49,18 +48,17 @@
{/if}
</option>
{/each}
<option disabled></option>
<option disabled />
{/if}
{#each availableLanguages as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
({(language_translations[language]?.[$current] + " - " + language) ?? language})
({language_translations[language]?.[$current] + " - " + language ?? language})
{/if}
</option>
{/each}
</Dropdown>
</form>
{/if}

View file

@ -1,16 +1,18 @@
<script lang="ts">
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
import { ImmutableStore, Store } from "../../Logic/UIEventSource";
import DynamicIcon from "./DynamicIcon.svelte";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
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 marker: IconConfig[] = config?.marker;
export let tags: Store<Record<string, string>>
export let rotation: TagRenderingConfig = undefined;
export let tags: Store<Record<string, string>>;
let _rotation = rotation ? tags.map(tags => rotation.GetRenderValue(tags).Subs(tags).txt) : new ImmutableStore(0);
let _rotation = rotation
? tags.map((tags) => rotation.GetRenderValue(tags).Subs(tags).txt)
: new ImmutableStore(0)
</script>
{#if marker && marker}

View file

@ -12,7 +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";
import Plantnet_logo from "../../assets/svg/Plantnet_logo.svelte"
/**
* The main entry point for the plantnet wizard
@ -143,7 +143,7 @@
</BackButton>
{/if}
<div class="low-interaction flex self-end rounded-xl p-2">
<Plantnet_logo class="w-8 h-8 p-1 mr-1 bg-white rounded-full"/>
<Plantnet_logo class="mr-1 h-8 w-8 rounded-full bg-white p-1" />
<Tr t={t.poweredByPlantnet} />
</div>
</div>

View file

@ -3,110 +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 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";
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
}
}
@ -114,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>

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,11 @@
return parts
})
let _allTags = []
onDestroy(
allTags.addCallbackAndRunD((allTags) => {
_allTags = allTags
})
const tagsTable = new VariableUiElement(
allTags.mapD((_allTags) =>
new Table(["Key", "Value"], _allTags).SetClass("zebra-table break-all")
)
)
const tagsTable = new Table(["Key", "Value"], _allTags).SetClass("zebra-table break-all")
</script>
<section>

View file

@ -15,8 +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";
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
@ -99,7 +99,7 @@
<Tr t={Translations.t.notes.noteLayerHasFilters} />
</div>
<SubtleButton on:click={() => notelayer.disableAllFilters()}>
<Layers 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>

View file

@ -28,11 +28,13 @@
const t = Translations.t.image.nearby
const c = [lon, lat]
console.log(">>>", image)
let attributedImage = new AttributedImage({
url: image.thumbUrl ?? image.pictureUrl,
provider: AllImageProviders.byName(image.provider),
date: new Date(image.date),
})
id: Object.values(image.osmTags)[0]
}, feature)
let distance = Math.round(
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
)
@ -42,7 +44,7 @@
const key = Object.keys(image.osmTags)[0]
const url = image.osmTags[key]
if (isLinked) {
const action = new LinkImageAction(currentTags.id, key, url, currentTags, {
const action = new LinkImageAction(currentTags.id, key, url, tags, {
theme: state.layout.id,
changeType: "link-image",
})

View file

@ -35,6 +35,7 @@
)
let images: Store<P4CPicture[]> = imagesProvider.store.map((images) => images.slice(0, 20))
let allDone = imagesProvider.allDone
</script>
<div class="interactive border-interactive rounded-2xl p-2">
@ -44,8 +45,10 @@
</h4>
<slot name="corner" />
</div>
{#if $images.length === 0}
{#if !$allDone}
<Loading />
{:else if $images.length === 0}
<Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert"/>
{:else}
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
{#each $images as image (image.pictureUrl)}

View file

@ -11,7 +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";
import Camera_plus from "../../assets/svg/Camera_plus.svelte"
export let tags: Store<OsmTags>
export let state: SpecialVisualizationState
@ -43,7 +43,7 @@
expanded = true
}}
>
<Camera_plus class="block w-8 h-8 p-1 mr-2"/>
<Camera_plus class="mr-2 block h-8 w-8 p-1" />
<Tr t={t.seeNearby} />
</button>
{/if}

View file

@ -67,12 +67,12 @@
},
[skippedQuestions]
)
let firstQuestion = questionsToAsk.map(qta => qta[0])
let firstQuestion = questionsToAsk.map((qta) => qta[0])
let answered: number = 0
let skipped: number = 0
function skip(question: {id: string}, didAnswer: boolean = false) {
function skip(question: { id: string }, didAnswer: boolean = false) {
skippedQuestions.data.add(question.id)
skippedQuestions.ping()
if (didAnswer) {
@ -145,25 +145,25 @@
</div>
{:else}
<TagRenderingQuestion
config={$firstQuestion}
{layer}
{selectedElement}
{state}
{tags}
on:saved={() => {
skip($firstQuestion, true)
config={$firstQuestion}
{layer}
{selectedElement}
{state}
{tags}
on:saved={() => {
skip($firstQuestion, true)
}}
>
<button
class="secondary"
on:click={() => {
skip($firstQuestion)
}}
slot="cancel"
>
<button
class="secondary"
on:click={() => {
skip($firstQuestion)
}}
slot="cancel"
>
<Tr t={Translations.t.general.skip} />
</button>
</TagRenderingQuestion>
<Tr t={Translations.t.general.skip} />
</button>
</TagRenderingQuestion>
{/if}
</div>
{/if}

View file

@ -106,7 +106,7 @@
</div>
{/if}
{:else}
<div class="w-full h-full overflow-hidden p-2">
<div class="h-full w-full overflow-hidden p-2">
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
</div>
{/if}

View file

@ -1,45 +1,56 @@
<script lang="ts">
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import Tr from "../../Base/Tr.svelte"
import type { Feature } from "geojson"
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import FreeformInput from "./FreeformInput.svelte"
import Translations from "../../i18n/Translations.js"
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
import { createEventDispatcher, onDestroy } from "svelte"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
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"
import Constants from "../../../Models/Constants"
import { Unit } from "../../../Models/Unit"
import UserRelatedState from "../../../Logic/State/UserRelatedState"
import { twJoin } from "tailwind-merge"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import Tr from "../../Base/Tr.svelte";
import type { Feature } from "geojson";
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig";
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
import FreeformInput from "./FreeformInput.svelte";
import Translations from "../../i18n/Translations.js";
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
import { createEventDispatcher, onDestroy } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
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";
import Constants from "../../../Models/Constants";
import { Unit } from "../../../Models/Unit";
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>>
export let selectedElement: Feature
export let state: SpecialVisualizationState
export let layer: LayerConfig | undefined
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
export let layer: LayerConfig | undefined;
export let selectedTags: TagsFilter = undefined;
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
// Will be bound if a freeform is available
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
let selectedMapping: number = undefined
let checkedMappings: boolean[]
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]);
let selectedMapping: number = undefined;
let checkedMappings: boolean[];
let mappings: Mapping[] = config?.mappings;
let searchTerm: UIEventSource<string> = new UIEventSource("");
let dispatch = createEventDispatcher<{
saved: {
config: TagRenderingConfig
applied: TagsFilter
}
}>();
/**
* Prepares and fills the checkedMappings
@ -47,12 +58,12 @@
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
mappings = confg.mappings?.filter((m) => {
if (typeof m.hideInAnswer === "boolean") {
return !m.hideInAnswer
return !m.hideInAnswer;
}
return !m.hideInAnswer.matchesProperties(tgs)
})
return !m.hideInAnswer.matchesProperties(tgs);
});
// We received a new config -> reinit
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
if (
confg.mappings?.length > 0 &&
@ -60,59 +71,53 @@
(checkedMappings === undefined ||
checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
) {
const seenFreeforms = []
TagUtils.FlattenMultiAnswer()
const seenFreeforms = [];
TagUtils.FlattenMultiAnswer();
checkedMappings = [
...confg.mappings.map((mapping) => {
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs)
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs);
if (matches && confg.freeform) {
const newProps = TagUtils.changeAsProperties(mapping.if.asChange())
seenFreeforms.push(newProps[confg.freeform.key])
const newProps = TagUtils.changeAsProperties(mapping.if.asChange());
seenFreeforms.push(newProps[confg.freeform.key]);
}
return matches
}),
]
return matches;
})
];
if (tgs !== undefined && confg.freeform) {
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? []
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? [];
for (const seenFreeform of seenFreeforms) {
if (!seenFreeform) {
continue
continue;
}
const index = unseenFreeformValues.indexOf(seenFreeform)
const index = unseenFreeformValues.indexOf(seenFreeform);
if (index < 0) {
continue
continue;
}
unseenFreeformValues.splice(index, 1)
unseenFreeformValues.splice(index, 1);
}
// TODO this has _to much_ values
freeformInput.setData(unseenFreeformValues.join(";"))
checkedMappings.push(unseenFreeformValues.length > 0)
freeformInput.addCallbackAndRun(freeformValue => {
checkedMappings[checkedMappings.length - 1] = !!freeformValue
})
freeformInput.setData(unseenFreeformValues.join(";"));
checkedMappings.push(unseenFreeformValues.length > 0);
}
}
if (confg.freeform?.key) {
if (!confg.multiAnswer) {
// Somehow, setting multi-answer freeform values is broken if this is not set
freeformInput.setData(tgs[confg.freeform.key])
freeformInput.setData(tgs[confg.freeform.key]);
}
} else {
freeformInput.setData(undefined)
freeformInput.setData(undefined);
}
feedback.setData(undefined)
feedback.setData(undefined);
}
$: {
// Even though 'config' is not declared as a store, Svelte uses it as one to update the component
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
initialize($tags, config)
initialize($tags, config);
}
export let selectedTags: TagsFilter = undefined
let mappings: Mapping[] = config?.mappings
let searchTerm: UIEventSource<string> = new UIEventSource("")
$: {
try {
@ -121,10 +126,67 @@
selectedMapping,
checkedMappings,
tags.data
)
);
} catch (e) {
console.error("Could not calculate changeSpecification:", e)
selectedTags = undefined
console.error("Could not calculate changeSpecification:", e);
selectedTags = undefined;
}
}
function onSave() {
if (selectedTags === undefined) {
console.log("SelectedTags is undefined, ignoring 'onSave'-event");
return;
}
if (layer === undefined || (layer?.source === null && layer.id !== "favourite")) {
/**
* This is a special, priviliged layer.
* We simply apply the tags onto the records
*/
const kv = selectedTags.asChange(tags.data);
for (const { k, v } of kv) {
if (v === undefined || v === "") {
delete tags.data[k];
} else {
freeformInput.setData(undefined);
}
feedback.setData(undefined);
}
}
dispatch("saved", { config, applied: selectedTags });
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
theme: tags.data["_orig_theme"] ?? state.layout.id,
changeType: "answer"
});
freeformInput.setData(undefined);
selectedMapping = undefined;
selectedTags = undefined;
change
.CreateChangeDescriptions()
.then((changes) => state.changes.applyChanges(changes))
.catch(console.error);
}
function onInputKeypress(e: Event) {
if (e.key === "Enter") {
onSave();
}
}
$: {
try {
selectedTags = config?.constructChangeSpecification(
$freeformInput,
selectedMapping,
checkedMappings,
tags.data
);
} catch (e) {
console.error("Could not calculate changeSpecification:", e);
selectedTags = undefined;
}
}
@ -133,85 +195,35 @@
config: TagRenderingConfig
applied: TagsFilter
}
}>()
}>();
function onSave() {
if (selectedTags === undefined) {
console.log("SelectedTags is undefined, ignoring 'onSave'-event")
return
}
if (layer === undefined || (layer?.source === null && layer.id !== "favourite")) {
/**
* This is a special, priviliged layer.
* We simply apply the tags onto the records
*/
const kv = selectedTags.asChange(tags.data)
for (const { k, v } of kv) {
if (v === undefined || v === "") {
delete tags.data[k]
} else {
tags.data[k] = v
}
}
tags.ping()
return
}
dispatch("saved", { config, applied: selectedTags })
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
theme: tags.data["_orig_theme"] ?? state.layout.id,
changeType: "answer",
})
freeformInput.setData(undefined)
selectedMapping = undefined
selectedTags = undefined
change
.CreateChangeDescriptions()
.then((changes) => state.changes.applyChanges(changes))
.catch(console.error)
}
function onInputKeypress(e: Event){
if (e.key === "Enter") {
onSave();
}
}
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false);
let featureSwitchIsDebugging =
state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined)
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0
let question = config.question
$: question = config.question
state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false);
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined);
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0;
let question = config.question;
$: question = config.question;
if (state?.osmConnection) {
onDestroy(
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
numberOfCs = ud.csCount
numberOfCs = ud.csCount;
})
)
);
}
</script>
{#if question !== undefined}
<div
class="interactive border-interactive relative flex flex-col overflow-y-auto p-1 px-2"
style="max-height: 85vh"
class="interactive border-interactive relative flex flex-col overflow-y-auto px-2"
style="max-height: 75vh"
>
<div class="sticky top-0" style="z-index: 11">
<div class="interactive sticky top-0 flex justify-between">
<div class="sticky top-0 interactive pt-1 flex justify-between" style="z-index: 11">
<span class="font-bold">
<SpecialTranslation
t={question}
{tags}
{state}
{layer}
feature={selectedElement}
/>
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
</span>
<slot name="upper-right" />
</div>
<slot name="upper-right" />
</div>
{#if config.questionhint}
@ -228,7 +240,7 @@
{#if config.mappings?.length >= 8}
<div class="sticky flex w-full">
<Search class="h-6 w-6"/>
<Search class="h-6 w-6" />
<input type="text" bind:value={$searchTerm} class="w-full" />
</div>
{/if}
@ -263,8 +275,8 @@
bind:group={selectedMapping}
name={"mappings-radio-" + config.id}
value={i}
on:keypress={onInputKeypress}
on:keypress={e => onInputKeypress(e)}
/>
</TagRenderingMappingInput>
{/each}
@ -275,7 +287,7 @@
bind:group={selectedMapping}
name={"mappings-radio-" + config.id}
value={config.mappings?.length}
on:keypress={onInputKeypress}
on:keypress={e => onInputKeypress(e)}
/>
<FreeformInput
{config}
@ -307,7 +319,7 @@
type="checkbox"
name={"mappings-checkbox-" + config.id + "-" + i}
bind:checked={checkedMappings[i]}
on:keypress={onInputKeypress}
on:keypress={e => onInputKeypress(e)}
/>
</TagRenderingMappingInput>
{/each}
@ -317,7 +329,7 @@
type="checkbox"
name={"mappings-checkbox-" + config.id + "-" + config.mappings?.length}
bind:checked={checkedMappings[config.mappings.length]}
on:keypress={onInputKeypress}
on:keypress={e => onInputKeypress(e)}
/>
<FreeformInput
{config}

View file

@ -12,7 +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";
import Mangrove_logo from "../../assets/svg/Mangrove_logo.svelte"
/**
* An element showing all reviews
@ -41,7 +41,7 @@
<Tr t={Translations.t.reviews.no_reviews_yet} />
{/if}
<div class="flex justify-end">
<Mangrove_logo class="w-12 h-12 shrink-0 p-1"/>
<Mangrove_logo class="h-12 w-12 shrink-0 p-1" />
<Tr cls="text-sm subtle" t={Translations.t.reviews.attribution} />
</div>
</div>

View file

@ -2,9 +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";
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
@ -26,10 +26,10 @@
on:mousemove={(e) => dispatch("hover", { score: getScore(e) })}
>
{#if score >= cutoff}
<Star class={starSize}/>
<Star class={starSize} />
{:else if score + 10 >= cutoff}
<Star_half class={starSize}/>
<Star_half class={starSize} />
{:else}
<Star_outline class={starSize}/>
<Star_outline class={starSize} />
{/if}
</div>

View file

@ -90,7 +90,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

@ -659,7 +659,7 @@ export default class SpecialVisualizations {
},
],
needsUrls: AllImageProviders.apiUrls,
constr: (state, tags, args) => {
constr: (state, tags, args, feature) => {
let imagePrefixes: string[] = undefined
if (args.length > 0) {
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
@ -667,7 +667,8 @@ export default class SpecialVisualizations {
return new ImageCarousel(
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
tags,
state
state,
feature
)
},
},
@ -1461,7 +1462,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

@ -10,13 +10,14 @@
export let info: { id: string; owner: number };
export let category: "layers" | "themes";
export let osmConnection: OsmConnection;
const dispatch = createEventDispatcher<{ layerSelected: string }>();
let displayName = UIEventSource.FromPromise(
osmConnection.getInformationAboutUser(info.owner)
).mapD((response) => response.display_name);
let selfId = osmConnection.userDetails.mapD((ud) => ud.uid);
function fetchIconDescription(layerId): any {
if (category === "themes") {
return AllKnownLayouts.allKnownLayouts.get(layerId).icon;
@ -24,7 +25,6 @@
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon;
}
const dispatch = createEventDispatcher<{ layerSelected: string }>();
</script>
<NextButton clss="small" on:click={() => dispatch("layerSelected", info)}>
@ -33,12 +33,14 @@
</div>
<b class="px-1">{info.id}</b>
{#if info.owner && info.owner !== $selfId}
{#if displayName}
{#if $displayName}
(made by {$displayName}
{#if window.location.host.startsWith("127.0.0.1")}
- {info.owner}
{/if}
)
{:else }
({info.owner})
{/if}
{/if}
</NextButton>

View file

@ -9,7 +9,7 @@
import { Utils } from "../../Utils"
import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"
import ErrorIndicatorForRegion from "./ErrorIndicatorForRegion.svelte"
import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"
import { ChevronRightIcon, TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
import SchemaBasedInput from "./SchemaBasedInput.svelte"
import FloatOver from "../Base/FloatOver.svelte"
import TagRenderingInput from "./TagRenderingInput.svelte"
@ -21,6 +21,7 @@
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw
export let state: EditLayerState
export let backToStudio: () => void
let messages = state.messages
let hasErrors = messages.mapD(
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length
@ -72,6 +73,10 @@
})
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem
function deleteLayer() {
state.delete()
backToStudio()
}
</script>
<div class="flex h-screen flex-col">
@ -113,6 +118,12 @@
</div>
<div class="flex flex-col" slot="content0">
<Region {state} configs={perRegion["Basic"]} />
<div class="mt-12">
<button on:click={() => deleteLayer()} class="small" >
<TrashIcon class="h-6 w-6"/> Delete this layer
</button>
</div>
</div>
<div slot="title1" class="flex">

View file

@ -107,6 +107,9 @@ export abstract class EditJsonState<T> {
return entry
}
public async delete(){
await this.server.delete(this.getId().data, this.category)
}
public getStoreFor<T>(path: ReadonlyArray<string | number>): UIEventSource<T | undefined> {
const key = path.join(".")
@ -294,16 +297,39 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
this.addMissingTagRenderingIds()
this.configuration.addCallbackAndRunD((layer) => {
if (layer.tagRenderings) {
function cleanArray(data: object, key: string): boolean{
if(!data){
return false
}
if (data[key]) {
// A bit of cleanup
const lBefore = layer.tagRenderings.length
const cleaned = Utils.NoNull(layer.tagRenderings)
const lBefore = data[key].length
const cleaned = Utils.NoNull(data[key])
if (cleaned.length != lBefore) {
layer.tagRenderings = cleaned
this.configuration.ping()
data[key] = cleaned
return true
}
}
return false
}
this.configuration.addCallbackAndRunD((layer) => {
let changed = cleanArray(layer, "tagRenderings") || cleanArray(layer, "pointRenderings")
for (const tr of layer.tagRenderings ?? []) {
if(typeof tr === "string"){
continue
}
const qtr = (<QuestionableTagRenderingConfigJson> tr)
if(qtr.freeform && Object.keys(qtr.freeform ).length === 0){
delete qtr.freeform
changed = true
}
}
if(changed){
this.configuration.ping()
}
})
}

View file

@ -64,7 +64,16 @@
}
}
newPath.push(...toAdd)
console.log("Fused path ", path.join("."), "+", i,"+", subpartPath.join("."),"into",newPath.join("."))
console.log(
"Fused path ",
path.join("."),
"+",
i,
"+",
subpartPath.join("."),
"into",
newPath.join(".")
)
return newPath
}

View file

@ -58,7 +58,14 @@ export default class StudioServer {
return undefined
}
}
async delete(id: string, category: "layers" | "themes") {
if (id === undefined || id === "") {
return
}
await fetch(this.urlFor(id, category), {
method: "DELETE"
})
}
async update(id: string, config: string, category: "layers" | "themes") {
if (id === undefined || id === "") {
return

View file

@ -3,104 +3,104 @@
* Little helper class to deal with choosing a builtin tagRendering or defining one yourself.
* Breaks the ideology that everything should be schema based
*/
import EditLayerState from "./EditLayerState";
import type { ConfigMeta } from "./configMeta";
import EditLayerState from "./EditLayerState"
import type { ConfigMeta } from "./configMeta"
import type {
MappingConfigJson,
QuestionableTagRenderingConfigJson
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import * as questions from "../../assets/generated/layers/questions.json";
import MappingInput from "./MappingInput.svelte";
import { TrashIcon } from "@rgossiaux/svelte-heroicons/outline";
import questionableTagRenderingSchemaRaw from "../../assets/schemas/questionabletagrenderingconfigmeta.json";
import SchemaBasedField from "./SchemaBasedField.svelte";
import Region from "./Region.svelte";
import NextButton from "../Base/NextButton.svelte";
import { QuestionMarkCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource";
import { onMount } from "svelte";
QuestionableTagRenderingConfigJson,
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import * as questions from "../../assets/generated/layers/questions.json"
import MappingInput from "./MappingInput.svelte"
import { TrashIcon } from "@rgossiaux/svelte-heroicons/outline"
import questionableTagRenderingSchemaRaw from "../../assets/schemas/questionabletagrenderingconfigmeta.json"
import SchemaBasedField from "./SchemaBasedField.svelte"
import Region from "./Region.svelte"
import NextButton from "../Base/NextButton.svelte"
import { QuestionMarkCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
import { onMount } from "svelte"
export let state: EditLayerState;
export let schema: ConfigMeta;
export let path: (string | number)[];
let expertMode = state.expertMode;
const store = state.getStoreFor(path);
let value = store.data;
export let state: EditLayerState
export let schema: ConfigMeta
export let path: (string | number)[]
let expertMode = state.expertMode
const store = state.getStoreFor(path)
let value = store.data
let hasSeenIntro = UIEventSource.asBoolean(
LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false")
);
)
onMount(() => {
if (!hasSeenIntro.data) {
state.showIntro.setData("tagrenderings");
hasSeenIntro.setData(true);
state.showIntro.setData("tagrenderings")
hasSeenIntro.setData(true)
}
});
})
/**
* Allows the theme builder to create 'writable' themes.
* Should only be enabled for 'tagrenderings' in the theme, if the source is OSM
*/
let allowQuestions: Store<boolean> = state.configuration.mapD(
(config) => path.at(0) === "tagRenderings" && config.source?.geoJson === undefined
);
)
let mappingsBuiltin: MappingConfigJson[] = [];
let perLabel: Record<string, MappingConfigJson> = {};
let mappingsBuiltin: MappingConfigJson[] = []
let perLabel: Record<string, MappingConfigJson> = {}
for (const tr of questions.tagRenderings) {
let description = tr["description"] ?? tr["question"] ?? "No description available";
description = description["en"] ?? description;
let description = tr["description"] ?? tr["question"] ?? "No description available"
description = description["en"] ?? description
if (tr["labels"]) {
const labels: string[] = tr["labels"];
const labels: string[] = tr["labels"]
for (const label of labels) {
let labelMapping: MappingConfigJson = perLabel[label];
let labelMapping: MappingConfigJson = perLabel[label]
if (!labelMapping) {
labelMapping = {
if: "value=" + label,
then: {
en: "Builtin collection <b>" + label + "</b>:"
}
};
perLabel[label] = labelMapping;
mappingsBuiltin.push(labelMapping);
en: "Builtin collection <b>" + label + "</b>:",
},
}
perLabel[label] = labelMapping
mappingsBuiltin.push(labelMapping)
}
labelMapping.then.en = labelMapping.then.en + "<div>" + description + "</div>";
labelMapping.then.en = labelMapping.then.en + "<div>" + description + "</div>"
}
}
mappingsBuiltin.push({
if: "value=" + tr["id"],
then: {
en: "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>"
}
});
en: "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>",
},
})
}
const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson>{
question: "Which builtin element should be shown?",
mappings: mappingsBuiltin
});
mappings: mappingsBuiltin,
})
const tags = new UIEventSource({ value });
const tags = new UIEventSource({ value })
tags.addCallbackAndRunD((tgs) => {
store.setData(tgs["value"]);
});
store.setData(tgs["value"])
})
let mappings: UIEventSource<MappingConfigJson[]> = state.getStoreFor([...path, "mappings"]);
let mappings: UIEventSource<MappingConfigJson[]> = state.getStoreFor([...path, "mappings"])
const topLevelItems: Record<string, ConfigMeta> = {};
const topLevelItems: Record<string, ConfigMeta> = {}
for (const item of questionableTagRenderingSchemaRaw) {
if (item.path.length === 1) {
topLevelItems[item.path[0]] = <ConfigMeta>item;
topLevelItems[item.path[0]] = <ConfigMeta>item
}
}
function initMappings() {
if (mappings.data === undefined) {
mappings.setData([]);
mappings.setData([])
}
}
@ -113,28 +113,25 @@
"condition",
"metacondition",
"mappings",
"icon"
]);
const ignored = new Set(["labels", "description", "classes"]);
"icon",
])
const ignored = new Set(["labels", "description", "classes"])
const freeformSchemaAll = <ConfigMeta[]>(
questionableTagRenderingSchemaRaw.filter(
(schema) =>
schema.path.length == 2 &&
schema.path[0] === "freeform" &&
($allowQuestions)
(schema) => schema.path.length == 2 && schema.path[0] === "freeform" && $allowQuestions
)
);
)
let freeformSchema = $expertMode
? freeformSchemaAll
: freeformSchemaAll.filter((schema) => schema.hints?.group !== "expert");
: freeformSchemaAll.filter((schema) => schema.hints?.group !== "expert")
const missing: string[] = questionableTagRenderingSchemaRaw
.filter(
(schema) =>
schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0])
)
.map((schema) => schema.path.join("."));
console.log({ state });
.map((schema) => schema.path.join("."))
console.log({ state })
</script>
{#if typeof $store === "string"}

View file

@ -15,7 +15,6 @@
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"
@ -26,10 +25,10 @@
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";
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 =
window.location.hostname === "127.0.0.2"
@ -155,7 +154,7 @@
Contact <a href="https://app.element.io/#/room/#MapComplete:matrix.org">
the MapComplete community via the chat.
</a>
Someone might be able to help you
Someone might be able to help you
</li>
<li>
File <a href="https://github.com/pietervdvn/MapComplete/issues">an issue</a>
@ -197,7 +196,7 @@
<QuestionMarkCircleIcon class="h-6 w-6" />
Show the introduction again
</button>
<a class="flex button" href={Utils.HomepageLink()}>
<a class="button flex" href={Utils.HomepageLink()}>
<Add class="h-6 w-6" />
<Tr t={Translations.t.general.backToIndex} />
</a>
@ -261,7 +260,7 @@
<Loading />
</div>
{:else if state === "editing_layer"}
<EditLayer state={editLayerState}>
<EditLayer state={editLayerState} backToStudio={() => {state = undefined}}>
<BackButton
clss="small p-1"
imageClass="w-8 h-8"
@ -285,22 +284,23 @@
</BackButton>
</EditTheme>
{/if}
</LoginToggle>
</If>
{#if { intro, tagrenderings: intro_tagrenderings }[$showIntro]?.sections}
<FloatOver
on:close={() => {
{#if { intro, tagrenderings: intro_tagrenderings }[$showIntro]?.sections}
<FloatOver
on:close={() => {
showIntro.setData("no")
}}
>
<div class="flex h-full p-4 pr-12">
<Walkthrough
pages={{ intro, tagrenderings: intro_tagrenderings }[$showIntro]?.sections}
on:done={() => {
>
<div class="flex h-full p-4 pr-12">
<Walkthrough
pages={{ intro, tagrenderings: intro_tagrenderings }[$showIntro]?.sections}
on:done={() => {
showIntro.setData("no")
}}
/>
</div>
</FloatOver>
{/if}
/>
</div>
</FloatOver>
{/if}
</LoginToggle>
</If>

View file

@ -1,8 +1,8 @@
<script lang="ts">
import Svg from "../Svg";
import Loading from "./Base/Loading.svelte";
import ToSvelte from "./Base/ToSvelte.svelte";
import Community from "../assets/svg/Community.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>
@ -41,30 +41,29 @@
<div class="flex">
<button class="primary">
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Main action
</button>
<button class="primary disabled">
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Main action (disabled)
</button>
<button class="small">
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Small button
</button>
<button class="small primary">Small button</button>
<button class="small primary disabled">Small, disabled button</button>
</div>
<div class="flex">
<button>
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Secondary action
</button>
<button class="disabled">
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Secondary action (disabled)
</button>
</div>
@ -83,7 +82,7 @@
</label>
<label for="javascript">
<input id="javascript" name="fav_language" type="radio" value="JavaScript" />
<Community class="w-8 h-8"/>
<Community class="h-8 w-8" />
JavaScript
</label>
</div>
@ -107,26 +106,26 @@
<div class="flex">
<button class="primary">
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Main action
</button>
<button class="primary disabled">
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Main action (disabled)
</button>
<button class="small">
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Small button
</button>
</div>
<div class="flex">
<button>
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Secondary action
</button>
<button class="disabled">
<Community class="w-6 h-6" />
<Community class="h-6 w-6" />
Secondary action (disabled)
</button>
</div>

View file

@ -1,16 +1,5 @@
<script lang="ts">
// Testing grounds
import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte";
import { UIEventSource } from "../Logic/UIEventSource";
import { OsmConnection } from "../Logic/Osm/OsmConnection";
const osmConnection = new OsmConnection({attemptLogin: true})
function resetFavs(){
osmConnection.preferencesHandler.removeAllWithPrefix("mapcomplete-favourite-")
console.log("CLEARED!")
}
</script>
<button on:click={() => resetFavs()} >Clear</button>
<div class="w-full">No tests</div>

View file

@ -65,13 +65,78 @@
import Download from "../assets/svg/Download.svelte";
import Share from "../assets/svg/Share.svelte";
import Favourites from "./Favourites/Favourites.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;
let layout = state.layout;
export let state: ThemeViewState
let layout = state.layout
let maplibremap: UIEventSource<MlMap> = state.map;
let selectedElement: UIEventSource<Feature> = state.selectedElement;
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer;
let maplibremap: UIEventSource<MlMap> = state.map
let selectedElement: UIEventSource<Feature> = state.selectedElement
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
let currentZoom = state.mapProperties.zoom;
let showCrosshair = state.userRelatedState.showCrosshair;
@ -87,50 +152,50 @@
return undefined;
}
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
return undefined;
}
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
return undefined
}
const tags = state.featureProperties.getStore(selectedElement.properties.id);
return new SvelteUIElement(SelectedElementView, {
state,
layer,
selectedElement,
tags
}).SetClass("h-full w-full");
},
[selectedLayer]
);
const tags = state.featureProperties.getStore(selectedElement.properties.id)
return new SvelteUIElement(SelectedElementView, {
state,
layer,
selectedElement,
tags,
}).SetClass("h-full w-full")
},
[selectedLayer],
)
const selectedElementTitle = selectedElement.map(
(selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
const layer = selectedLayer.data;
if (selectedElement === undefined || layer === undefined) {
return undefined;
}
const selectedElementTitle = selectedElement.map(
(selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
const layer = selectedLayer.data
if (selectedElement === undefined || layer === undefined) {
return undefined
}
const tags = state.featureProperties.getStore(selectedElement.properties.id);
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags });
},
[selectedLayer]
);
const tags = state.featureProperties.getStore(selectedElement.properties.id)
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags })
},
[selectedLayer],
)
let mapproperties: MapProperties = state.mapProperties;
let featureSwitches: FeatureSwitchState = state.featureSwitches;
let availableLayers = state.availableLayers;
let userdetails = state.osmConnection.userDetails;
let currentViewLayer = layout.layers.find((l) => l.id === "current_view");
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer;
let rasterLayerName =
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name;
onDestroy(
rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name;
})
);
let mapproperties: MapProperties = state.mapProperties
let featureSwitches: FeatureSwitchState = state.featureSwitches
let availableLayers = state.availableLayers
let userdetails = state.osmConnection.userDetails
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
let rasterLayerName =
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name
onDestroy(
rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name
}),
)
</script>
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
@ -214,7 +279,7 @@
<!-- bottom left elements -->
<If condition={state.featureSwitches.featureSwitchFilter}>
<MapControlButton on:click={() => state.guistate.openFilterView()}>
<Filter class="h-6 w-6"/>
<Filter class="h-6 w-6" />
</MapControlButton>
</If>
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
@ -253,10 +318,10 @@
</div>
</If>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}>
<Plus class="w-8 h-8" />
<Plus class="h-8 w-8" />
</MapControlButton>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)}>
<Min class="w-8 h-8"/>
<Min class="h-8 w-8" />
</MapControlButton>
<If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton>
@ -272,7 +337,7 @@
</div>
<LoginToggle ignoreLoading={true} {state}>
{#if ($showCrosshair === "yes" && $currentZoom >= 17) || $showCrosshair === "always" || $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"
>
@ -349,7 +414,7 @@
</div>
<div class="flex" slot="title1">
<Filter class="w-4 h-4"/>
<Filter class="h-4 w-4" />
<Tr t={Translations.t.general.menu.filter} />
</div>
@ -373,7 +438,7 @@
<div class="flex" slot="title2">
<If condition={state.featureSwitches.featureSwitchEnableExport}>
<Download class="w-4 h-4"/>
<Download class="h-4 w-4" />
<Tr t={Translations.t.general.download.title} />
</If>
</div>
@ -388,7 +453,7 @@
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
<div class="flex" slot="title4">
<Share class="w-4 h-4"/>
<Share class="h-4 w-4" />
<Tr t={Translations.t.general.sharescreen.title} />
</div>
<div class="m-2" slot="content4">
@ -440,17 +505,17 @@
<Tr t={Translations.t.general.aboutMapComplete.intro} />
<a class="flex" href={Utils.HomepageLink()}>
<Add class="h-6 w-6"/>
<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">
<Bug class="h-6 w-6"/>
<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">
<Mastodon class="w-6 h-6" />
<Mastodon class="h-6 w-6" />
<Tr t={Translations.t.general.attribution.followOnMastodon} />
</a>
@ -495,15 +560,15 @@
<div class="flex" slot="title2">
<HeartIcon class="h-6 w-6" />
Your favourites
<Tr t={Translations.t.favouritePoi.tab}/>
</div>
<div class="flex flex-col m-2" slot="content2">
<h3>Your favourite locations</h3>
<h3> <Tr t={Translations.t.favouritePoi.title}/></h3>
<Favourites {state}/>
</div>
<div class="flex" slot="title3">
<Community class="w-6 h-6"/>
<Community class="h-6 w-6" />
<Tr t={Translations.t.communityIndex.title} />
</div>
<div class="m-2" slot="content3">
@ -521,7 +586,7 @@
<div class="m-2 flex flex-col" slot="content5">
<If condition={featureSwitches.featureSwitchEnableLogin}>
<OpenIdEditor mapProperties={state.mapProperties} />
<OpenJosm {state}/>
<OpenJosm {state} />
<MapillaryLink mapProperties={state.mapProperties} />
</If>

View file

@ -1,25 +1,24 @@
<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;
export let pageNumber: number = undefined;
export let totalPages: number = undefined;
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 link-underline">
<div class="link-underline flex h-full w-full flex-col justify-between">
<div class="overflow-y-auto">
<slot />
</div>
<div class="flex flex-col">
{#if pageNumber !== undefined && totalPages !== undefined}
<div class="flex justify-end">
<div class="subtle">{pageNumber+1}/{totalPages}</div>
<div class="subtle">{pageNumber + 1}/{totalPages}</div>
</div>
{/if}
<div class="flex w-full">
@ -35,8 +34,6 @@
Next
{/if}
</NextButton>
</div>
</div>
</div>

View file

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