Merge master

This commit is contained in:
Pieter Vander Vennet 2023-12-19 23:02:02 +01:00
commit f51b51c491
340 changed files with 15515 additions and 11114 deletions

View file

@ -31,7 +31,7 @@ export default class GenericImageProvider extends ImageProvider {
key: key,
url: value,
provider: this,
id: value
id: value,
}),
]
}

View file

@ -9,6 +9,7 @@ export interface ProvidedImage {
key: string
provider: ImageProvider
id: string
date?: Date
}
export default abstract class ImageProvider {

View file

@ -66,7 +66,7 @@ export class Imgur extends ImageProvider implements ImageUploader {
url: value,
key: key,
provider: this,
id: value
id: value,
}),
]
}
@ -109,7 +109,7 @@ export class Imgur extends ImageProvider implements ImageUploader {
licenseInfo.licenseShortName = data.license
licenseInfo.artist = data.author
licenseInfo.date = new Date(Number(imgurData.datetime) * 1000)
licenseInfo.views = imgurData.views
licenseInfo.views = imgurData.views
return licenseInfo
}

View file

@ -163,6 +163,11 @@ export class WikimediaImageProvider extends ImageProvider {
if (!image.startsWith("File:")) {
image = "File:" + image
}
return { url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this , id: image}
return {
url: WikimediaImageProvider.PrepareUrl(image),
key: undefined,
provider: this,
id: image,
}
}
}

View file

@ -1,14 +1,42 @@
import { Utils } from "../../Utils"
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
export class ThemeMetaTagging {
public static readonly themeName = "usersettings"
public static readonly themeName = "usersettings"
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
feat.properties['__current_backgroun'] = 'initial_value'
}
}
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
feat.properties._description
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
?.at(1)
)
Utils.AddLazyProperty(
feat.properties,
"_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/g, ">") ?? ""
)
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.href.match(/mastodon|en.osm.town/) !== null
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(
feat.properties,
"_mastodon_candidate",
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
)
feat.properties["__current_backgroun"] = "initial_value"
}
}

View file

@ -40,7 +40,9 @@ export interface P4CPicture {
export default class NearbyImagesSearch {
public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const
public static readonly apiUrls = ["https://api.flickr.com"]
private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number } | undefined>[]
private readonly individualStores: Store<
{ images: P4CPicture[]; beforeFilter: number } | undefined
>[]
private readonly _store: UIEventSource<P4CPicture[]> = new UIEventSource<P4CPicture[]>([])
public readonly store: Store<P4CPicture[]> = this._store
public readonly allDone: Store<boolean>
@ -54,11 +56,11 @@ export default class NearbyImagesSearch {
const allDone = new UIEventSource(false)
this.allDone = allDone
const self = this
function updateAllDone(){
const stillRunning = self.individualStores.some(store => store.data === undefined)
function updateAllDone() {
const stillRunning = self.individualStores.some((store) => store.data === undefined)
allDone.setData(!stillRunning)
}
self.individualStores.forEach(s => s.addCallback(_ => updateAllDone()))
self.individualStores.forEach((s) => s.addCallback((_) => updateAllDone()))
this._options = options
if (features !== undefined) {
@ -111,7 +113,7 @@ export default class NearbyImagesSearch {
const searchRadius = options.searchRadius ?? 100
return p4cStore.mapD(
(imagesState) => {
if(imagesState["error"]){
if (imagesState["error"]) {
return null
}
let images = imagesState["success"]

View file

@ -147,11 +147,14 @@ export class ExtractImages extends Conversion<
.warn("Found an emtpy image")
} else if (typeof img.leaf !== "string") {
const c = context.enter(img.path)
const w = this._isOfficial ? c.err : c.warn
w(
const msg =
"found an image path that is not a string: " +
JSON.stringify(img.leaf)
)
JSON.stringify(img.leaf)
if (this._isOfficial) {
c.err(msg)
} else {
c.warn(msg)
}
} else {
allFoundImages.push({
path: img.leaf,

View file

@ -1454,10 +1454,10 @@ export class ValidateLayer extends Conversion<
for (let i = 0; i < layerConfig.titleIcons.length; i++) {
const titleIcon = layerConfig.titleIcons[i]
if (<any> titleIcon.render === "icons.defaults") {
if (<any>titleIcon.render === "icons.defaults") {
context.enters("titleIcons", i).err("Detected a literal 'icons.defaults'")
}
if (<any> titleIcon.render === "icons.rating") {
if (<any>titleIcon.render === "icons.rating") {
context.enters("titleIcons", i).err("Detected a literal 'icons.rating'")
}
}

View file

@ -61,7 +61,6 @@ import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSou
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl"
import { Orientation } from "../Sensors/Orientation"
/**
*
@ -552,6 +551,17 @@ export default class ThemeViewState implements SpecialVisualizationState {
nomod: "b",
},
Translations.t.hotkeyDocumentation.openLayersPanel,
() => {
if (this.featureSwitches.featureSwitchBackgroundSelection.data) {
this.guistate.backgroundLayerSelectionIsOpened.setData(true)
}
}
)
Hotkeys.RegisterHotkey(
{
nomod: "s",
},
Translations.t.hotkeyDocumentation.openFilterPanel,
() => {
if (this.featureSwitches.featureSwitchFilter.data) {
this.guistate.openFilterView()

View file

@ -63,8 +63,12 @@
</script>
<div class="m-4 flex flex-col">
<LanguagePicker assignTo={state.language} availableLanguages={t.title.SupportedLanguages()} clss="self-end"
preferredLanguages={userLanguages} />
<LanguagePicker
clss="self-end"
assignTo={state.language}
availableLanguages={t.title.SupportedLanguages()}
preferredLanguages={userLanguages}
/>
<div class="mt-4 flex">
<div class="m-3 flex-none">

View file

@ -22,9 +22,15 @@
selectAppropriateValue()
}
}
export let cls : string = undefined
export let cls: string = undefined
</script>
<select class={cls} 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

@ -36,9 +36,15 @@
dispatcher("submit", e.dataTransfer.files)
}}
>
<label class={twMerge(cls, drawAttention ? "glowing-shadow" : "")}
tabindex="0" for={"fileinput" + id}
on:click={() => {console.log("Clicked", inputElement); inputElement.click()}}>
<label
class={twMerge(cls, drawAttention ? "glowing-shadow" : "")}
tabindex="0"
for={"fileinput" + id}
on:click={() => {
console.log("Clicked", inputElement)
inputElement.click()
}}
>
<slot />
</label>
<input

View file

@ -1,38 +1,43 @@
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { twMerge } from "tailwind-merge";
import { Utils } from "../../Utils";
import { trapFocus } from 'trap-focus-svelte'
import { createEventDispatcher } from "svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { twMerge } from "tailwind-merge"
import { trapFocus } from "trap-focus-svelte"
/**
* The slotted element will be shown on top, with a lower-opacity border
*/
const dispatch = createEventDispatcher<{ close }>();
export let extraClasses = "p-4 md:p-6";
const dispatch = createEventDispatcher<{ close }>()
export let extraClasses = "p-4 md:p-6"
</script>
<!-- Draw the background over the total screen -->
<div class="w-screen h-screen absolute top-0 left-0" style="background-color: #00000088; z-index: 20" on:click={() => {
<!-- Draw the background over the total screen -->
<div
class="absolute top-0 left-0 h-screen w-screen"
on:click={() => {
dispatch("close")
}}>
</div>
}}
style="background-color: #00000088; z-index: 20"
/>
<!-- draw a _second_ absolute div, placed using 'bottom' which will be above the navigation bar on mobile browsers -->
<div
class={twMerge("absolute bottom-0 right-0 h-full w-screen", extraClasses)}
use:trapFocus
style="z-index: 21"
use:trapFocus
>
<div class="content normal-background" on:click|stopPropagation={() => {}}>
<div
class="content normal-background"
on:click|stopPropagation={() => {}}
>
<div class="h-full rounded-xl">
<slot />
</div>
<slot name="close-button">
<!-- The close button is placed _after_ the default slot in order to always paint it on top -->
<button
class="absolute right-10 top-10 h-8 w-8 cursor-pointer p-0 border-none bg-white"
class="absolute right-10 top-10 h-8 w-8 cursor-pointer border-none bg-white p-0"
on:click={() => dispatch("close")}
>
<XCircleIcon />
@ -41,8 +46,6 @@
</div>
</div>
<style>
.content {
height: 100%;

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { Store } from "../../Logic/UIEventSource";
import { Store } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"
/**

View file

@ -4,7 +4,7 @@
import Translations from "../i18n/Translations"
import Tr from "./Tr.svelte"
export let osmConnection: OsmConnection;
export let osmConnection: OsmConnection
</script>
<button

View file

@ -2,25 +2,20 @@
import { createEventDispatcher } from "svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { trapFocus } from "trap-focus-svelte"
import { Utils } from "../../Utils"
/**
* The slotted element will be shown on the right side
*/
const dispatch = createEventDispatcher<{ close }>()
let mainContent: HTMLElement
</script>
<div
aria-modal="true"
autofocus
bind:this={mainContent}
class="absolute top-0 right-0 h-screen w-full overflow-y-auto drop-shadow-2xl md:w-6/12 lg:w-5/12 xl:w-4/12 normal-background flex flex-col"
role="dialog"
tabindex="-1"
aria-modal="true"
style="max-width: 100vw; max-height: 100vh"
tabindex="-1"
use:trapFocus
>
<slot name="close-button">
@ -31,7 +26,7 @@
<XCircleIcon />
</button>
</slot>
<div role="document" >
<slot />
<div role="document">
<slot />
</div>
</div>

View file

@ -1,11 +1,11 @@
<script lang="ts">
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"
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
*/

View file

@ -1,23 +1,25 @@
<script lang="ts">
import type { Feature } from "geojson";
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import type { Feature } from "geojson"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { ariaLabel } from "../../Utils/ariaLabel"
export let state: SpecialVisualizationState;
export let layer: LayerConfig;
export let selectedElement: Feature;
let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(selectedElement.properties.id);
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let selectedElement: Feature
let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(
selectedElement.properties.id,
)
$: {
tags = state.featureProperties.getStore(selectedElement.properties.id);
tags = state.featureProperties.getStore(selectedElement.properties.id)
}
let metatags: Store<Record<string, string>> = state.userRelatedState.preferencesAsTags;
let metatags: Store<Record<string, string>> = state.userRelatedState.preferencesAsTags
</script>
{#if $tags._deleted === "yes"}
@ -51,7 +53,7 @@
</div>
</div>
<button on:click={() => state.selectedElement.setData(undefined)}
<button on:click={() => state.selectedElement.setData(undefined)}
use:ariaLabel={Translations.t.general.backToMap}
class="border-none p-0">
<XCircleIcon aria-hidden={true} class="h-8 w-8" />

View file

@ -1,20 +1,22 @@
<script lang="ts">
import type { Feature } from "geojson"
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
import { onDestroy } from "svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let selectedElement: Feature
export let highlightedRendering: UIEventSource<string> = undefined
export let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(selectedElement.properties.id)
export let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(
selectedElement.properties.id
)
let _metatags: Record<string, string>
onDestroy(
@ -23,12 +25,14 @@
})
)
let knownTagRenderings: Store<TagRenderingConfig[]> = tags.mapD(tgs => layer.tagRenderings.filter(
(config) =>
(config.condition?.matchesProperties(tgs) ?? true) &&
(config.metacondition?.matchesProperties({ ...tgs, ..._metatags }) ?? true) &&
config.IsKnown(tgs)
))
let knownTagRenderings: Store<TagRenderingConfig[]> = tags.mapD((tgs) =>
layer.tagRenderings.filter(
(config) =>
(config.condition?.matchesProperties(tgs) ?? true) &&
(config.metacondition?.matchesProperties({ ...tgs, ..._metatags }) ?? true) &&
config.IsKnown(tgs)
)
)
</script>
{#if $tags._deleted === "yes"}
@ -46,7 +50,9 @@
{selectedElement}
{layer}
{highlightedRendering}
clss={$knownTagRenderings.length === 1 ? "h-full" : "tr-length-" + $knownTagRenderings.length}
clss={$knownTagRenderings.length === 1
? "h-full"
: "tr-length-" + $knownTagRenderings.length}
/>
{/each}
</div>

View file

@ -1,84 +1,90 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization"
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
import type { Feature } from "geojson"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import { GeoOperations } from "../../Logic/GeoOperations"
import Center from "../../assets/svg/Center.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization";
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
import type { Feature } from "geojson";
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource";
import { GeoOperations } from "../../Logic/GeoOperations";
import Center from "../../assets/svg/Center.svelte";
export let feature: Feature
let properties: Record<string, string> = feature.properties
export let state: SpecialVisualizationState
let tags =
state.featureProperties.getStore(properties.id) ??
new UIEventSource<Record<string, string>>(properties)
export let feature: Feature;
let properties: Record<string, string> = feature.properties;
export let state: SpecialVisualizationState;
let tags = state.featureProperties.getStore(properties.id) ?? new UIEventSource<Record<string, string>>(properties);
const favLayer = state.layerState.filteredLayers.get("favourite");
const favConfig = favLayer?.layerDef;
const titleConfig = favConfig?.title;
const favLayer = state.layerState.filteredLayers.get("favourite")
const favConfig = favLayer?.layerDef
const titleConfig = favConfig?.title
function center() {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature);
state.mapProperties.location.setData(
{ lon, lat }
);
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
state.mapProperties.location.setData({ lon, lat })
const z = state.mapProperties.zoom.data
state.mapProperties.zoom.setData( Math.min(17, Math.max(12, z )) )
state.guistate.menuIsOpened.setData(false);
state.mapProperties.zoom.setData(Math.min(17, Math.max(12, z)))
state.guistate.menuIsOpened.setData(false)
}
function select() {
state.selectedElement.setData(feature);
center();
state.selectedElement.setData(feature)
center()
}
const coord = GeoOperations.centerpointCoordinates(feature);
const coord = GeoOperations.centerpointCoordinates(feature)
const distance = state.mapProperties.location.stabilized(500).mapD(({ lon, lat }) => {
let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat]));
let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat]))
if (meters < 1000) {
return meters + "m";
return meters + "m"
}
meters = Math.round(meters / 100);
const kmStr = "" + meters;
return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km";
});
const titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"];
meters = Math.round(meters / 100)
const kmStr = "" + meters
return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km"
})
const titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"]
</script>
{#if favLayer !== undefined}
<div class="px-1 my-1 border-2 border-dashed border-gray-300 rounded flex justify-between flex-wrap grid-cols-2 items-center no-weblate">
<button class="cursor-pointer ml-1 m-0 link justify-self-start" on:click={() => select()}>
<TagRenderingAnswer {state} config={titleConfig} extraClasses="underline" layer={favConfig} selectedElement={feature}
{tags} />
</button>
<div class="self-end flex items-center flex-wrap justify-self-end title-icons links-as-button gap-x-0.5 p-1 pt-0.5 sm:pt-1">
{#each favConfig.titleIcons as titleIconConfig}
{#if (titleIconBlacklist.indexOf(titleIconConfig.id) < 0) && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties({ ...properties, ...state.userRelatedState.preferencesAsTags.data }) ?? true) && titleIconConfig.IsKnown(properties)}
<div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}>
<TagRenderingAnswer
config={titleIconConfig}
{tags}
selectedElement={feature}
{state}
layer={favLayer.layerDef}
extraClasses="h-full justify-center"
/>
</div>
{/if}
{/each}
<button class="p-1" on:click={() => center()}>
<Center class="w-6 h-6" />
<div
class="no-weblate my-1 flex grid-cols-2 flex-wrap items-center justify-between rounded border-2 border-dashed border-gray-300 px-1"
>
<button class="link m-0 ml-1 cursor-pointer justify-self-start" on:click={() => select()}>
<TagRenderingAnswer
{state}
config={titleConfig}
extraClasses="underline"
layer={favConfig}
selectedElement={feature}
{tags}
/>
</button>
<div class="w-14">
{$distance}
<div
class="title-icons links-as-button flex flex-wrap items-center gap-x-0.5 self-end justify-self-end p-1 pt-0.5 sm:pt-1"
>
{#each favConfig.titleIcons as titleIconConfig}
{#if titleIconBlacklist.indexOf(titleIconConfig.id) < 0 && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ...properties, ...state.userRelatedState.preferencesAsTags.data } ) ?? true) && titleIconConfig.IsKnown(properties)}
<div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}>
<TagRenderingAnswer
config={titleIconConfig}
{tags}
selectedElement={feature}
{state}
layer={favLayer.layerDef}
extraClasses="h-full justify-center"
/>
</div>
{/if}
{/each}
<button class="p-1" on:click={() => center()}>
<Center class="h-6 w-6" />
</button>
<div class="w-14">
{$distance}
</div>
</div>
</div>
</div>
{/if}

View file

@ -1,68 +1,68 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization";
import FavouriteSummary from "./FavouriteSummary.svelte";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid";
import { Utils } from "../../Utils";
import { GeoOperations } from "../../Logic/GeoOperations";
import type { Feature, LineString, Point } from "geojson";
import LoginToggle from "../Base/LoginToggle.svelte";
import LoginButton from "../Base/LoginButton.svelte";
import type { SpecialVisualizationState } from "../SpecialVisualization"
import FavouriteSummary from "./FavouriteSummary.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
import { Utils } from "../../Utils"
import { GeoOperations } from "../../Logic/GeoOperations"
import type { Feature, LineString, Point } from "geojson"
import LoginToggle from "../Base/LoginToggle.svelte"
import LoginButton from "../Base/LoginButton.svelte"
/**
* A panel showing all your favourites
*/
export let state: SpecialVisualizationState;
let favourites = state.favourites.allFavourites;
export let state: SpecialVisualizationState
let favourites = state.favourites.allFavourites
function downloadGeojson() {
const contents = { features: favourites.data, type: "FeatureCollection" };
const contents = { features: favourites.data, type: "FeatureCollection" }
Utils.offerContentsAsDownloadableFile(
JSON.stringify(contents),
"mapcomplete-favourites-" + (new Date().toISOString()) + ".geojson",
"mapcomplete-favourites-" + new Date().toISOString() + ".geojson",
{
mimetype: "application/vnd.geo+json"
mimetype: "application/vnd.geo+json",
}
);
)
}
function downloadGPX() {
const gpx = GeoOperations.toGpxPoints(<Feature<Point>>favourites.data, "MapComplete favourites");
Utils.offerContentsAsDownloadableFile(gpx,
"mapcomplete-favourites-" + (new Date().toISOString()) + ".gpx",
const gpx = GeoOperations.toGpxPoints(<Feature<Point>>favourites.data, "MapComplete favourites")
Utils.offerContentsAsDownloadableFile(
gpx,
"mapcomplete-favourites-" + new Date().toISOString() + ".gpx",
{
mimetype: "{gpx=application/gpx+xml}"
});
mimetype: "{gpx=application/gpx+xml}",
}
)
}
</script>
<LoginToggle {state}>
<div slot="not-logged-in">
<LoginButton osmConnection={state.osmConnection}>
<Tr t={Translations.t.favouritePoi.loginToSeeList}/>
<Tr t={Translations.t.favouritePoi.loginToSeeList} />
</LoginButton>
</div>
<div class="flex flex-col" on:keypress={(e) => console.log("Got keypress", e)}>
<Tr t={Translations.t.favouritePoi.intro.Subs({length: $favourites?.length ?? 0})} />
<Tr t={Translations.t.favouritePoi.privacy} />
{#each $favourites as feature (feature.properties.id)}
<FavouriteSummary {feature} {state} />
{/each}
<div class="flex flex-col" on:keypress={(e) => console.log("Got keypress", e)}>
<Tr t={Translations.t.favouritePoi.intro.Subs({ length: $favourites?.length ?? 0 })} />
<Tr t={Translations.t.favouritePoi.privacy} />
<div class="mt-8">
<button class="flex p-2" on:click={() => downloadGeojson()}>
<DownloadIcon class="h-6 w-6" />
<Tr t={Translations.t.favouritePoi.downloadGeojson} />
</button>
<button class="flex p-2" on:click={() => downloadGPX()}>
<DownloadIcon class="h-6 w-6" />
<Tr t={Translations.t.favouritePoi.downloadGpx} />
</button>
{#each $favourites as feature (feature.properties.id)}
<FavouriteSummary {feature} {state} />
{/each}
<div class="mt-8">
<button class="flex p-2" on:click={() => downloadGeojson()}>
<DownloadIcon class="h-6 w-6" />
<Tr t={Translations.t.favouritePoi.downloadGeojson} />
</button>
<button class="flex p-2" on:click={() => downloadGPX()}>
<DownloadIcon class="h-6 w-6" />
<Tr t={Translations.t.favouritePoi.downloadGpx} />
</button>
</div>
</div>
</div>
</LoginToggle>

View file

@ -1,34 +1,36 @@
<script lang="ts">
/**
* Shows an image with attribution
*/
import ImageAttribution from "./ImageAttribution.svelte"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
import { UIEventSource } from "../../Logic/UIEventSource"
/**
* Shows an image with attribution
*/
import ImageAttribution from "./ImageAttribution.svelte"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
import { UIEventSource } from "../../Logic/UIEventSource"
export let image: ProvidedImage
let fallbackImage: string = undefined
if (image.provider === Mapillary.singleton) {
fallbackImage = "./assets/svg/blocked.svg"
}
export let image: ProvidedImage
let fallbackImage: string = undefined
if (image.provider === Mapillary.singleton) {
fallbackImage = "./assets/svg/blocked.svg"
}
let imgEl: HTMLImageElement
export let imgClass: string = undefined
export let previewedImage: UIEventSource<string> = undefined
let imgEl: HTMLImageElement
export let imgClass: string = undefined
export let previewedImage: UIEventSource<ProvidedImage> = undefined
</script>
<div class="relative">
<img bind:this={imgEl} src={image.url} class={imgClass ?? ""}
<img bind:this={imgEl}
class={imgClass ?? ""}
class:cursor-pointer={previewedImage !== undefined}
on:click={() => {previewedImage?.setData(image)}}
on:error={(event) => {
if(fallbackImage){
imgEl.src = fallbackImage
}
}}>
}}
src={image.url}>
<div class="absolute bottom-0 left-0">
<ImageAttribution {image}/>
<ImageAttribution {image} />
</div>
</div>

View file

@ -1,28 +1,26 @@
<script lang="ts">
import { LicenseInfo } from "../../Logic/ImageProviders/LicenseInfo"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import ToSvelte from "../Base/ToSvelte.svelte"
import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid"
/**
* A small element showing the attribution of a single image
*/
export let image: ProvidedImage
let license: Store<LicenseInfo> = UIEventSource.FromPromise(image.provider?.DownloadAttribution(image.url))
let icon = image.provider?.SourceIcon(image.id)?.SetClass("block h-8 w-8 pr-2")
import { LicenseInfo } from "../../Logic/ImageProviders/LicenseInfo"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import ToSvelte from "../Base/ToSvelte.svelte"
import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid"
/**
* A small element showing the attribution of a single image
*/
export let image: ProvidedImage
let license: Store<LicenseInfo> = UIEventSource.FromPromise(
image.provider?.DownloadAttribution(image.url)
)
let icon = image.provider?.SourceIcon(image.id)?.SetClass("block h-8 w-8 pr-2")
</script>
{#if $license !== undefined}
<div class="flex bg-black text-white text-sm p-0.5 pl-5 pr-3 rounded-lg no-images">
<div class="no-images flex rounded-lg bg-black p-0.5 pl-5 pr-3 text-sm text-white">
{#if icon !== undefined}
<ToSvelte construct={icon} />
{/if}
<div class="flex flex-col">
{#if $license.title}
{#if $license.informationLocation}
@ -39,7 +37,6 @@
{/if}
<div class="flex justify-between">
{#if $license.license !== undefined || $license.licenseShortName !== undefined}
<div>
{$license?.license ?? $license?.licenseShortName}
@ -55,12 +52,10 @@
{#if $license.views}
<div class="flex justify-around self-center">
<EyeIcon class="w-4 h-4 pr-1"/>
<EyeIcon class="h-4 w-4 pr-1" />
{$license.views}
</div>
{/if}
</div>
</div>
{/if}

View file

@ -43,7 +43,7 @@ export class ImageCarousel extends Toggle {
]).SetClass("relative")
}
image
.SetClass("w-full block")
.SetClass("w-full block cursor-zoom-in")
.SetStyle("min-width: 50px; background: grey;")
uiElements.push(image)
} catch (e) {

View file

@ -1,42 +1,45 @@
<script lang="ts">/**
* The 'imageOperations' previews an image and offers some extra tools (e.g. download)
*/
<script lang="ts">
/**
* The 'imageOperations' previews an image and offers some extra tools (e.g. download)
*/
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import ImageAttribution from "./ImageAttribution.svelte"
import ImagePreview from "./ImagePreview.svelte"
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
import { Utils } from "../../Utils"
import { twMerge } from "tailwind-merge";
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import ImageAttribution from "./ImageAttribution.svelte"
import ImagePreview from "./ImagePreview.svelte"
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
import { Utils } from "../../Utils"
import { twMerge } from "tailwind-merge"
export let image: ProvidedImage
export let clss: string = undefined
async function download() {
const response = await fetch(image.url_hd ?? image.url )
export let image: ProvidedImage
export let clss: string = undefined
async function download() {
const response = await fetch(image.url_hd ?? image.url)
const blob = await response.blob()
Utils.offerContentsAsDownloadableFile(blob, new URL(image.url).pathname.split("/").at(-1), {
mimetype: "image/jpg",
mimetype: "image/jpg",
})
}
}
</script>
<div class={twMerge("w-full h-full relative", clss)}>
<div class="absolute top-0 left-0 w-full h-full overflow-hidden panzoom-container focusable">
<ImagePreview image={image} />
<div class={twMerge("relative h-full w-full", clss)}>
<div class="panzoom-container focusable absolute top-0 left-0 h-full w-full overflow-hidden">
<ImagePreview {image} />
</div>
<div class="absolute bottom-0 left-0 w-full pointer-events-none flex flex-wrap justify-between items-end">
<div class="pointer-events-auto w-fit opacity-50 hover:opacity-100 transition-colors duration-200 m-1">
<ImageAttribution image={image} />
<div
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"
>
<div
class="pointer-events-auto m-1 w-fit opacity-50 transition-colors duration-200 hover:opacity-100"
>
<ImageAttribution {image} />
</div>
<button
class="no-image-background flex items-center pointer-events-auto bg-black opacity-50 hover:opacity-100 text-white transition-colors duration-200"
on:click={() => download()}>
<DownloadIcon class="w-6 h-6 px-2 opacity-100" />
class="no-image-background pointer-events-auto flex items-center bg-black text-white opacity-50 transition-colors duration-200 hover:opacity-100"
on:click={() => download()}
>
<DownloadIcon class="h-6 w-6 px-2 opacity-100" />
Download
</button>
</div>
</div>

View file

@ -1,28 +1,27 @@
<script lang="ts">
/**
* The image preview allows to drag and zoom in to the image
*/
import panzoom from "panzoom"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
/**
* The image preview allows to drag and zoom in to the image
*/
import panzoom from "panzoom"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
export let image: ProvidedImage
let panzoomInstance = undefined
let panzoomEl: HTMLElement
export let image : ProvidedImage
let panzoomInstance = undefined
let panzoomEl: HTMLElement
$: {
if (panzoomEl) {
panzoomInstance = panzoom(panzoomEl, { bounds: true,
boundsPadding: 0.49,
minZoom: 1,
maxZoom: 25,
initialZoom: 1.2
})
} else {
panzoomInstance?.dispose()
}
$: {
if (panzoomEl) {
panzoomInstance = panzoom(panzoomEl, {
bounds: true,
boundsPadding: 0.49,
minZoom: 1,
maxZoom: 25,
initialZoom: 1.2,
})
} else {
panzoomInstance?.dispose()
}
}
</script>
<img bind:this={panzoomEl} src={image.url_hd ?? image.url} class="w-fit h-fit panzoom-image"/>
<img bind:this={panzoomEl} src={image.url_hd ?? image.url} class="panzoom-image h-fit w-fit" />

View file

@ -7,7 +7,6 @@
import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { Tag } from "../../Logic/Tags/Tag"
import { GeoOperations } from "../../Logic/GeoOperations"
import type { Feature } from "geojson"
import Translations from "../i18n/Translations"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
@ -30,13 +29,11 @@
const c = [lon, lat]
const providedImage: ProvidedImage = {
url: image.thumbUrl ?? image.pictureUrl,
key: undefined,
provider: AllImageProviders.byName(image.provider),
date: new Date(image.date),
id: Object.values(image.osmTags)[0],
}
let distance = Math.round(
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c),
)
$: {
const currentTags = tags.data
@ -64,8 +61,8 @@
</script>
<div class="flex w-fit shrink-0 flex-col">
<div on:click={() => state.previewedImage.setData(providedImage)}>
<AttributedImage image={providedImage} imgClass="max-h-64 w-auto" />
<div class="cursor-zoom-in" on:click={() => state.previewedImage.setData(providedImage)}>
<AttributedImage image={providedImage} imgClass="max-h-64 w-auto" previewedImage="{state.previewedImage}"/>
</div>
{#if linkable}
<label>

View file

@ -1,43 +1,44 @@
<script lang="ts">
/**
* Show nearby images which can be clicked
*/
import type { OsmTags } from "../../Models/OsmFeature"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
import NearbyImagesSearch from "../../Logic/Web/NearbyImagesSearch"
import LinkableImage from "./LinkableImage.svelte"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Loading from "../Base/Loading.svelte"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import LoginToggle from "../Base/LoginToggle.svelte"
/**
* Show nearby images which can be clicked
*/
import type { OsmTags } from "../../Models/OsmFeature"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
import NearbyImagesSearch from "../../Logic/Web/NearbyImagesSearch"
import LinkableImage from "./LinkableImage.svelte"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Loading from "../Base/Loading.svelte"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import LoginToggle from "../Base/LoginToggle.svelte"
export let tags: Store<OsmTags>
export let state: SpecialVisualizationState
export let lon: number
export let lat: number
export let feature: Feature
export let tags: Store<OsmTags>
export let state: SpecialVisualizationState
export let lon: number
export let lat: number
export let feature: Feature
export let linkable: boolean = true
export let layer: LayerConfig
export let linkable: boolean = true
export let layer: LayerConfig
let imagesProvider = new NearbyImagesSearch(
{
lon,
lat,
allowSpherical: new UIEventSource<boolean>(false),
blacklist: AllImageProviders.LoadImagesFor(tags),
},
state.indexedFeatures,
)
let imagesProvider = new NearbyImagesSearch(
{
lon,
lat,
allowSpherical: new UIEventSource<boolean>(false),
blacklist: AllImageProviders.LoadImagesFor(tags),
},
state.indexedFeatures
)
let images: Store<P4CPicture[]> = imagesProvider.store.map((images) => images.slice(0, 20))
let allDone = imagesProvider.allDone
let images: Store<P4CPicture[]> = imagesProvider.store.map((images) => images.slice(0, 20))
let allDone = imagesProvider.allDone
</script>
<LoginToggle {state}>
<div class="interactive border-interactive rounded-2xl p-2">
<div class="flex justify-between">
@ -53,9 +54,9 @@
{:else}
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
{#each $images as image (image.pictureUrl)}
<span class="w-fit shrink-0" style="scroll-snap-align: start">
<LinkableImage {tags} {image} {state} {lon} {lat} {feature} {layer} {linkable} />
</span>
<span class="w-fit shrink-0" style="scroll-snap-align: start">
<LinkableImage {tags} {image} {state} {lon} {lat} {feature} {layer} {linkable} />
</span>
{/each}
</div>
{/if}

View file

@ -1,37 +1,37 @@
<script lang="ts">
import { Store } from "../../Logic/UIEventSource";
import type { OsmTags } from "../../Models/OsmFeature";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import type { Feature } from "geojson";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import NearbyImages from "./NearbyImages.svelte";
import { XCircleIcon } from "@babeard/svelte-heroicons/solid";
import Camera_plus from "../../assets/svg/Camera_plus.svelte";
import LoginToggle from "../Base/LoginToggle.svelte";
import { Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import NearbyImages from "./NearbyImages.svelte"
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
import Camera_plus from "../../assets/svg/Camera_plus.svelte"
import LoginToggle from "../Base/LoginToggle.svelte"
import { ariaLabel } from "../../Utils/ariaLabel"
export let tags: Store<OsmTags>;
export let state: SpecialVisualizationState;
export let lon: number;
export let lat: number;
export let feature: Feature;
export let tags: Store<OsmTags>
export let state: SpecialVisualizationState
export let lon: number
export let lat: number
export let feature: Feature
export let linkable: boolean = true;
export let layer: LayerConfig;
const t = Translations.t.image.nearby;
export let linkable: boolean = true
export let layer: LayerConfig
const t = Translations.t.image.nearby
let expanded = false;
let expanded = false
</script>
<LoginToggle {state}>
<LoginToggle {state}>
{#if expanded}
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer}>
<button slot="corner"
class="h-6 w-6 cursor-pointer no-image-background p-0 border-none"
use:ariaLabel={t.close}
on:click={() => {
expanded = false
}}>

View file

@ -1,51 +1,51 @@
<script lang="ts">
/**
* Shows an 'upload'-button which will start the upload for this feature
*/
/**
* Shows an 'upload'-button which will start the upload for this feature
*/
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { ImmutableStore, UIEventSource } 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 LoginButton from "../Base/LoginButton.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { ImmutableStore, UIEventSource } 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 LoginButton from "../Base/LoginButton.svelte"
export let state: SpecialVisualizationState
export let state: SpecialVisualizationState
export let tags: UIEventSource<OsmTags>
export let targetKey: string = undefined
/**
* Image to show in the button
* NOT the image to upload!
*/
export let image: string = undefined
if (image === "") {
image = undefined
}
export let labelText: string = undefined
const t = Translations.t.image
let licenseStore = state?.userRelatedState?.imageLicense ?? new ImmutableStore("CC0")
function handleFiles(files: FileList) {
for (let i = 0; i < files.length; i++) {
const file = files.item(i)
console.log("Got file", file.name)
try {
state?.imageUploadManager.uploadImageAndApply(file, tags, targetKey)
} catch (e) {
alert(e)
}
}
export let tags: UIEventSource<OsmTags>
export let targetKey: string = undefined
/**
* Image to show in the button
* NOT the image to upload!
*/
export let image: string = undefined
if (image === "") {
image = undefined
}
export let labelText: string = undefined
const t = Translations.t.image
let licenseStore = state?.userRelatedState?.imageLicense ?? new ImmutableStore("CC0")
function handleFiles(files: FileList) {
for (let i = 0; i < files.length; i++) {
const file = files.item(i)
console.log("Got file", file.name)
try {
state?.imageUploadManager.uploadImageAndApply(file, tags, targetKey)
} catch (e) {
alert(e)
}
}
}
</script>
<LoginToggle {state}>
<LoginButton slot="not-logged-in" clss="small w-full" osmConnection={state.osmConnection}>
<LoginButton clss="small w-full" osmConnection={state.osmConnection} slot="not-logged-in">
<Tr t={Translations.t.image.pleaseLogin} />
</LoginButton>
<div class="flex flex-col">
@ -56,7 +56,7 @@
multiple={true}
on:submit={(e) => handleFiles(e.detail)}
>
<div class="flex items-center" >
<div class="flex items-center">
{#if image !== undefined}
<img src={image} aria-hidden="true" />
{:else}
@ -71,14 +71,14 @@
</FileSelector>
<div class="text-sm">
<button
class="link small "
class="link small"
on:click={() => {
state.guistate.openUsersettings("picture-license")
}}
>
<Tr t={t.currentLicense.Subs({ license: $licenseStore })} />
</button>
<Tr t={t.respectPrivacy} />
<Tr t={t.respectPrivacy} />
</div>
</div>
</LoginToggle>

View file

@ -15,7 +15,7 @@
export let state: SpecialVisualizationState
export let tags: Store<OsmTags> = undefined
export let featureId = tags?.data?.id
if(featureId === undefined){
if (featureId === undefined) {
throw "No tags or featureID given"
}
export let showThankYou: boolean = true

View file

@ -10,7 +10,8 @@
import Dropdown from "../Base/Dropdown.svelte"
import { twMerge } from "tailwind-merge"
import Translations from "../i18n/Translations"
import { ariaLabel } from "../../Utils/ariaLabel"
import { ariaLabel } from "../../Utils/ariaLabel"
/**
* Languages one can choose from
* Defaults to _all_ languages known by MapComplete
@ -37,7 +38,7 @@ import { ariaLabel } from "../../Utils/ariaLabel"
</script>
{#if availableLanguages?.length > 1}
<form class={twMerge("flex items-center max-w-full pr-4", clss)}>
<form class={twMerge("flex max-w-full items-center pr-4", clss)}>
<label class="flex neutral-label" use:ariaLabel={Translations.t.general.pickLanguage}>
<LanguageIcon class="h-4 w-4 mr-1 shrink-0" aria-hidden="true" />
<Dropdown cls="max-w-full" value={assignTo}>

View file

@ -1,42 +0,0 @@
import { VariableUiElement } from "./Base/VariableUIElement"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import Svg from "../Svg"
import Img from "./Base/Img"
import Combine from "./Base/Combine"
import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement"
export default class LoggedInUserIndicator extends VariableUiElement {
constructor(
osmConnection: OsmConnection,
options?: {
size?: "small" | "medium" | "large"
firstLine?: BaseUIElement
}
) {
options = options ?? {}
let size = "w-8 h-8 mr-2"
if (options.size == "medium") {
size = "w-16 h-16 mr-4"
} else if (options.size == "large") {
size = "w-32 h-32 mr-6"
}
super(
osmConnection.userDetails.mapD((ud) => {
let img = Svg.person_svg().SetClass(
"rounded-full border border-black overflow-hidden"
)
if (ud.img) {
img = new Img(ud.img)
}
let contents: BaseUIElement = new FixedUiElement(ud.name).SetClass("font-bold")
if (options?.firstLine) {
contents = new Combine([options.firstLine, contents]).SetClass("flex flex-col")
}
return new Combine([img.SetClass("rounded-full " + size), contents]).SetClass(
"flex items-center"
)
})
)
}
}

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
import { Store } from "../../Logic/UIEventSource";
import Icon from "./Icon.svelte";
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig"
import { Store } from "../../Logic/UIEventSource"
import Icon from "./Icon.svelte"
/**
* Renders a single icon.

View file

@ -7,9 +7,9 @@
/**
* Renders a 'marker', which consists of multiple 'icons'
*/
export let marker: IconConfig[] = config?.marker;
export let marker: IconConfig[] = config?.marker
export let tags: Store<Record<string, string>>
export let rotation: TagRenderingConfig = undefined;
export let rotation: TagRenderingConfig = undefined
let _rotation = rotation
? tags.map((tags) => rotation.GetRenderValue(tags).Subs(tags).txt)
: new ImmutableStore(0)

View file

@ -1,29 +1,32 @@
<script lang="ts">
import Pin from "../../assets/svg/Pin.svelte";
import Square from "../../assets/svg/Square.svelte";
import Circle from "../../assets/svg/Circle.svelte";
import Checkmark from "../../assets/svg/Checkmark.svelte";
import Clock from "../../assets/svg/Clock.svelte";
import Close from "../../assets/svg/Close.svelte";
import Crosshair from "../../assets/svg/Crosshair.svelte";
import Help from "../../assets/svg/Help.svelte";
import Home from "../../assets/svg/Home.svelte";
import Invalid from "../../assets/svg/Invalid.svelte";
import Location from "../../assets/svg/Location.svelte";
import Location_empty from "../../assets/svg/Location_empty.svelte";
import Location_locked from "../../assets/svg/Location_locked.svelte";
import Note from "../../assets/svg/Note.svelte";
import Resolved from "../../assets/svg/Resolved.svelte";
import Ring from "../../assets/svg/Ring.svelte";
import Scissors from "../../assets/svg/Scissors.svelte";
import Teardrop from "../../assets/svg/Teardrop.svelte";
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte";
import Triangle from "../../assets/svg/Triangle.svelte";
import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte";
import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte";
import Gps_arrow from "../../assets/svg/Gps_arrow.svelte";
import { HeartIcon } from "@babeard/svelte-heroicons/solid";
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline";
import Pin from "../../assets/svg/Pin.svelte"
import Square from "../../assets/svg/Square.svelte"
import Circle from "../../assets/svg/Circle.svelte"
import Checkmark from "../../assets/svg/Checkmark.svelte"
import Clock from "../../assets/svg/Clock.svelte"
import Close from "../../assets/svg/Close.svelte"
import Crosshair from "../../assets/svg/Crosshair.svelte"
import Help from "../../assets/svg/Help.svelte"
import Home from "../../assets/svg/Home.svelte"
import Invalid from "../../assets/svg/Invalid.svelte"
import Location from "../../assets/svg/Location.svelte"
import Location_empty from "../../assets/svg/Location_empty.svelte"
import Location_locked from "../../assets/svg/Location_locked.svelte"
import Note from "../../assets/svg/Note.svelte"
import Resolved from "../../assets/svg/Resolved.svelte"
import Ring from "../../assets/svg/Ring.svelte"
import Scissors from "../../assets/svg/Scissors.svelte"
import Teardrop from "../../assets/svg/Teardrop.svelte"
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
import Triangle from "../../assets/svg/Triangle.svelte"
import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte"
import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte"
import Gps_arrow from "../../assets/svg/Gps_arrow.svelte"
import { HeartIcon } from "@babeard/svelte-heroicons/solid"
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"
import Confirm from "../../assets/svg/Confirm.svelte"
import Not_found from "../../assets/svg/Not_found.svelte"
import { twMerge } from "tailwind-merge"
/**
* Renders a single icon.
@ -31,72 +34,75 @@
* Icons -placed on top of each other- form a 'Marker' together
*/
export let icon: string | undefined;
export let icon: string | undefined
export let color: string | undefined = undefined
export let clss: string | undefined = undefined
</script>
{#if icon}
{#if icon === "pin"}
<Pin {color} class={clss}/>
{:else if icon === "square"}
<Square {color} class={clss}/>
{:else if icon === "circle"}
<Circle {color} class={clss}/>
{:else if icon === "checkmark"}
<Checkmark {color} class={clss}/>
{:else if icon === "clock"}
<Clock {color} class={clss}/>
{:else if icon === "close"}
<Close {color} class={clss}/>
{:else if icon === "crosshair"}
<Crosshair {color} class={clss}/>
{:else if icon === "help"}
<Help {color} class={clss}/>
{:else if icon === "home"}
<Home {color} class={clss}/>
{:else if icon === "invalid"}
<Invalid {color} class={clss}/>
{:else if icon === "location"}
<Location {color} class={clss}/>
{:else if icon === "location_empty"}
<Location_empty {color} class={clss}/>
{:else if icon === "location_locked"}
<Location_locked {color} class={clss}/>
{:else if icon === "note"}
<Note {color} class={clss}/>
{:else if icon === "resolved"}
<Resolved {color} class={clss}/>
{:else if icon === "ring"}
<Ring {color} class={clss}/>
{:else if icon === "scissors"}
<Scissors {color} class={clss}/>
{:else if icon === "teardrop"}
<Teardrop {color} class={clss}/>
{:else if icon === "teardrop_with_hole_green"}
<Teardrop_with_hole_green {color} class={clss}/>
{:else if icon === "triangle"}
<Triangle {color} class={clss}/>
{:else if icon === "brick_wall_square"}
<Brick_wall_square {color} class={clss}/>
{:else if icon === "brick_wall_round"}
<Brick_wall_round {color} class={clss}/>
{:else if icon === "gps_arrow"}
<Gps_arrow {color} class={clss}/>
{:else if icon === "checkmark"}
<Checkmark {color} class={clss}/>
{:else if icon === "help"}
<Help {color} class={clss}/>
{:else if icon === "close"}
<Close {color} class={clss}/>
{:else if icon === "invalid"}
<Invalid {color} class={clss}/>
{:else if icon === "heart"}
<HeartIcon class={clss}/>
{:else if icon === "heart_outline"}
<HeartOutlineIcon class={clss}/>
{:else}
<img class={clss ?? "h-full w-full"} src={icon} aria-hidden="true"
alt="" />
{/if}
{#if icon === "pin"}
<Pin {color} class={clss} />
{:else if icon === "square"}
<Square {color} class={clss} />
{:else if icon === "circle"}
<Circle {color} class={clss} />
{:else if icon === "checkmark"}
<Checkmark {color} class={clss} />
{:else if icon === "clock"}
<Clock {color} class={clss} />
{:else if icon === "close"}
<Close {color} class={clss} />
{:else if icon === "crosshair"}
<Crosshair {color} class={clss} />
{:else if icon === "help"}
<Help {color} class={clss} />
{:else if icon === "home"}
<Home {color} class={clss} />
{:else if icon === "invalid"}
<Invalid {color} class={clss} />
{:else if icon === "location"}
<Location {color} class={clss} />
{:else if icon === "location_empty"}
<Location_empty {color} class={clss} />
{:else if icon === "location_locked"}
<Location_locked {color} class={clss} />
{:else if icon === "note"}
<Note {color} class={clss} />
{:else if icon === "resolved"}
<Resolved {color} class={clss} />
{:else if icon === "ring"}
<Ring {color} class={clss} />
{:else if icon === "scissors"}
<Scissors {color} class={clss} />
{:else if icon === "teardrop"}
<Teardrop {color} class={clss} />
{:else if icon === "teardrop_with_hole_green"}
<Teardrop_with_hole_green {color} class={clss} />
{:else if icon === "triangle"}
<Triangle {color} class={clss} />
{:else if icon === "brick_wall_square"}
<Brick_wall_square {color} class={clss} />
{:else if icon === "brick_wall_round"}
<Brick_wall_round {color} class={clss} />
{:else if icon === "gps_arrow"}
<Gps_arrow {color} class={clss} />
{:else if icon === "checkmark"}
<Checkmark {color} class={clss} />
{:else if icon === "help"}
<Help {color} class={clss} />
{:else if icon === "close"}
<Close {color} class={clss} />
{:else if icon === "invalid"}
<Invalid {color} class={clss} />
{:else if icon === "heart"}
<HeartIcon class={clss} />
{:else if icon === "heart_outline"}
<HeartOutlineIcon class={clss} />
{:else if icon === "confirm"}
<Confirm class={clss} {color} />
{:else if icon === "not_found"}
<Not_found class={twMerge(clss, "no-image-background")} {color} />
{:else}
<img class={clss ?? "h-full w-full"} src={icon} aria-hidden="true" alt="" />
{/if}
{/if}

View file

@ -1,10 +1,10 @@
<script lang="ts">
import Icon from "./Icon.svelte";
import Icon from "./Icon.svelte"
/**
* Renders a 'marker', which consists of multiple 'icons'
*/
export let icons: { icon: string; color: string }[];
export let icons: { icon: string; color: string }[]
</script>
{#if icons !== undefined && icons.length > 0}

View file

@ -0,0 +1,63 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Loading from "../../assets/svg/Loading.svelte"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import Icon from "../Map/Icon.svelte"
import Maproulette from "../../Logic/Maproulette"
/**
* A UI-element to change the status of a maproulette-task
*/
export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>
export let message: string
export let image: string
export let message_closed: string
export let statusToSet: string
export let maproulette_id_key: string
let applying = false
let failed = false
/** Current status of the task*/
let status: Store<number> = tags
.map((tgs) => {
if (tgs["status"]) {
return tgs["status"]
}
return Maproulette.codeToIndex(tgs["mr_taskStatus"])
})
.map(Number)
async function apply() {
const maproulette_id = tags.data[maproulette_id_key] ?? tags.data.mr_taskId ?? tags.data.id
try {
await Maproulette.singleton.closeTask(Number(maproulette_id), Number(statusToSet), {
tags: `MapComplete MapComplete:${state.layout.id}`,
})
tags.data["mr_taskStatus"] = Maproulette.STATUS_MEANING[Number(statusToSet)]
tags.data.status = statusToSet
tags.ping()
} catch (e) {
console.error(e)
failed = true
}
}
</script>
{#if failed}
<div class="alert">ERROR - could not close the MapRoulette task</div>
{:else if applying}
<Loading>
<Tr t={Translations.t.general.loading} />
</Loading>
{:else if $status === Maproulette.STATUS_OPEN}
<button class="no-image-background w-full p-4" on:click={() => apply()}>
<Icon clss="w-8 h-8 mr-2" icon={image} />
{message}
</button>
{:else}
{message_closed}
{/if}

View file

@ -1,32 +1,42 @@
<script lang="ts">/**
* Simple visualisation which shows when the POI opens/closes next.
*/
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { Store, Stores } from "../../Logic/UIEventSource";
import { OH } from "./OpeningHours";
import opening_hours from "opening_hours";
import Clock from "../../assets/svg/Clock.svelte";
import { Utils } from "../../Utils";
import Circle from "../../assets/svg/Circle.svelte";
import Ring from "../../assets/svg/Ring.svelte";
import { twMerge } from "tailwind-merge";
<script lang="ts">
/**
* Simple visualisation which shows when the POI opens/closes next.
*/
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store, Stores } from "../../Logic/UIEventSource"
import { OH } from "./OpeningHours"
import opening_hours from "opening_hours"
import Clock from "../../assets/svg/Clock.svelte"
import { Utils } from "../../Utils"
import Circle from "../../assets/svg/Circle.svelte"
import Ring from "../../assets/svg/Ring.svelte"
import { twMerge } from "tailwind-merge"
export let state: SpecialVisualizationState;
export let tags: Store<Record<string, string>>;
export let keyToUse: string = "opening_hours";
export let prefix: string = undefined;
export let postfix: string = undefined;
let oh: Store<opening_hours | "error" | undefined> = OH.CreateOhObjectStore(tags, keyToUse, prefix, postfix);
export let state: SpecialVisualizationState
export let tags: Store<Record<string, string>>
export let keyToUse: string = "opening_hours"
export let prefix: string = undefined
export let postfix: string = undefined
let oh: Store<opening_hours | "error" | undefined> = OH.CreateOhObjectStore(
tags,
keyToUse,
prefix,
postfix
)
let currentState = oh.mapD(oh => typeof oh === "string" ? undefined : oh.getState());
let tomorrow = new Date();
tomorrow.setTime(tomorrow.getTime() + 24 * 60 * 60 * 1000);
let nextChange = oh
.mapD(oh => typeof oh === "string" ? undefined : oh.getNextChange(new Date(), tomorrow), [Stores.Chronic(5 * 60 * 1000)])
.mapD(date => Utils.TwoDigits(date.getHours()) + ":" + Utils.TwoDigits(date.getMinutes()));
let size = nextChange.map(change => change === undefined ? "absolute h-7 w-7" : "absolute h-5 w-5 top-0 left-1/4");
let currentState = oh.mapD((oh) => (typeof oh === "string" ? undefined : oh.getState()))
let tomorrow = new Date()
tomorrow.setTime(tomorrow.getTime() + 24 * 60 * 60 * 1000)
let nextChange = oh
.mapD(
(oh) => (typeof oh === "string" ? undefined : oh.getNextChange(new Date(), tomorrow)),
[Stores.Chronic(5 * 60 * 1000)]
)
.mapD((date) => Utils.TwoDigits(date.getHours()) + ":" + Utils.TwoDigits(date.getMinutes()))
let size = nextChange.map((change) =>
change === undefined ? "absolute h-7 w-7" : "absolute h-5 w-5 top-0 left-1/4"
)
</script>
{#if $currentState !== undefined}
@ -40,11 +50,12 @@ let size = nextChange.map(change => change === undefined ? "absolute h-7 w-7" :
{/if}
{#if $nextChange !== undefined}
<span class="absolute bottom-0 font-bold text-sm" style="z-index: 1; background-color: #ffffff88; margin-top: 3px">
<span
class="absolute bottom-0 text-sm font-bold"
style="z-index: 1; background-color: #ffffff88; margin-top: 3px"
>
{$nextChange}
</span>
{/if}
</div>
{/if}

View file

@ -1,162 +1,160 @@
<script lang="ts">
import LoginToggle from "../../Base/LoginToggle.svelte"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import Translations from "../../i18n/Translations"
import Tr from "../../Base/Tr.svelte"
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
import type { OsmId, OsmTags } from "../../../Models/OsmFeature"
import DeleteConfig from "../../../Models/ThemeConfig/DeleteConfig"
import TagRenderingQuestion from "../TagRendering/TagRenderingQuestion.svelte"
import type { Feature } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import OsmChangeAction from "../../../Logic/Osm/Actions/OsmChangeAction"
import DeleteAction from "../../../Logic/Osm/Actions/DeleteAction"
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
import Loading from "../../Base/Loading.svelte"
import { DeleteFlowState } from "./DeleteFlowState"
import { twJoin } from "tailwind-merge"
import LoginToggle from "../../Base/LoginToggle.svelte"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import Translations from "../../i18n/Translations"
import Tr from "../../Base/Tr.svelte"
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
import type { OsmId, OsmTags } from "../../../Models/OsmFeature"
import DeleteConfig from "../../../Models/ThemeConfig/DeleteConfig"
import TagRenderingQuestion from "../TagRendering/TagRenderingQuestion.svelte"
import type { Feature } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import OsmChangeAction from "../../../Logic/Osm/Actions/OsmChangeAction"
import DeleteAction from "../../../Logic/Osm/Actions/DeleteAction"
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
import Loading from "../../Base/Loading.svelte"
import { DeleteFlowState } from "./DeleteFlowState"
import { twJoin } from "tailwind-merge"
export let state: SpecialVisualizationState
export let deleteConfig: DeleteConfig
export let state: SpecialVisualizationState
export let deleteConfig: DeleteConfig
export let tags: UIEventSource<OsmTags>
export let tags: UIEventSource<OsmTags>
let featureId: OsmId = <OsmId>tags.data.id
let featureId: OsmId = <OsmId>tags.data.id
export let feature: Feature
export let layer: LayerConfig
export let feature: Feature
export let layer: LayerConfig
const deleteAbility = new DeleteFlowState(featureId, state, deleteConfig.neededChangesets)
const deleteAbility = new DeleteFlowState(featureId, state, deleteConfig.neededChangesets)
const canBeDeleted: UIEventSource<boolean | undefined> = deleteAbility.canBeDeleted
const canBeDeletedReason = deleteAbility.canBeDeletedReason
const canBeDeleted: UIEventSource<boolean | undefined> = deleteAbility.canBeDeleted
const canBeDeletedReason = deleteAbility.canBeDeletedReason
const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined
let currentState: "start" | "confirm" | "applying" | "deleted" = "start"
$: {
deleteAbility.CheckDeleteability(true)
const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined
let currentState: "start" | "confirm" | "applying" | "deleted" = "start"
$: {
deleteAbility.CheckDeleteability(true)
}
const t = Translations.t.delete
let selectedTags: TagsFilter
let changedProperties = undefined
$: changedProperties = TagUtils.changeAsProperties(selectedTags?.asChange(tags?.data ?? {}) ?? [])
let isHardDelete = undefined
$: isHardDelete = changedProperties[DeleteConfig.deleteReasonKey] !== undefined
async function onDelete() {
if (selectedTags === undefined) {
return
}
currentState = "applying"
let actionToTake: OsmChangeAction
const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {}))
const deleteReason = changedProperties[DeleteConfig.deleteReasonKey]
if (deleteReason) {
// This is a proper, hard deletion
actionToTake = new DeleteAction(
featureId,
deleteConfig.softDeletionTags,
{
theme: state?.layout?.id ?? "unknown",
specialMotivation: deleteReason,
},
canBeDeleted.data
)
} else {
// no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping
actionToTake = new ChangeTagAction(featureId, selectedTags, tags.data, {
theme: state?.layout?.id ?? "unkown",
changeType: "special-delete",
})
}
const t = Translations.t.delete
let selectedTags: TagsFilter
let changedProperties = undefined
$: changedProperties = TagUtils.changeAsProperties(selectedTags?.asChange(tags?.data ?? {}) ?? [])
let isHardDelete = undefined
$: isHardDelete = changedProperties[DeleteConfig.deleteReasonKey] !== undefined
async function onDelete() {
if (selectedTags === undefined) {
return
}
currentState = "applying"
let actionToTake: OsmChangeAction
const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {}))
const deleteReason = changedProperties[DeleteConfig.deleteReasonKey]
if (deleteReason) {
// This is a proper, hard deletion
actionToTake = new DeleteAction(
featureId,
deleteConfig.softDeletionTags,
{
theme: state?.layout?.id ?? "unknown",
specialMotivation: deleteReason,
},
canBeDeleted.data,
)
} else {
// no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping
actionToTake = new ChangeTagAction(featureId, selectedTags, tags.data, {
theme: state?.layout?.id ?? "unkown",
changeType: "special-delete",
})
}
await state.changes?.applyAction(actionToTake)
tags.data["_deleted"] = "yes"
tags.ping()
currentState = "deleted"
}
await state.changes?.applyAction(actionToTake)
tags.data["_deleted"] = "yes"
tags.ping()
currentState = "deleted"
}
</script>
<LoginToggle ignoreLoading={true} {state}>
<LoginToggle ignoreLoading={true} {state}>
{#if $canBeDeleted === false && !hasSoftDeletion}
<div class="low-interaction flex flex-col">
<Tr t={$canBeDeletedReason} />
<Tr cls="subtle" t={t.useSomethingElse} />
</div>
{:else}
{#if currentState === "start"}
{:else if currentState === "start"}
<button
class="flex items-center"
on:click={() => {
currentState = "confirm"
}}
>
<TrashIcon class="h-6 w-6" />
<Tr t={t.delete} />
</button>
{:else if currentState === "confirm"}
<TagRenderingQuestion
bind:selectedTags
{tags}
config={deleteConfig.constructTagRendering()}
{state}
selectedElement={feature}
{layer}
>
<button
class="flex items-center"
on:click={() => {
currentState = "confirm"
}}
slot="save-button"
on:click={onDelete}
class={twJoin(
selectedTags === undefined && "disabled",
"primary flex items-center bg-red-600"
)}
>
<TrashIcon class="h-6 w-6" />
<TrashIcon
class={twJoin(
"ml-1 mr-2 h-6 w-6 rounded-full p-1",
selectedTags !== undefined && "bg-red-600"
)}
/>
<Tr t={t.delete} />
</button>
{:else if currentState === "confirm"}
<TagRenderingQuestion
bind:selectedTags
{tags}
config={deleteConfig.constructTagRendering()}
{state}
selectedElement={feature}
{layer}
>
<button
slot="save-button"
on:click={onDelete}
class={twJoin(
selectedTags === undefined && "disabled",
"primary flex items-center bg-red-600"
)}
>
<TrashIcon
class={twJoin(
"ml-1 mr-2 h-6 w-6 rounded-full p-1",
selectedTags !== undefined && "bg-red-600"
)}
/>
<Tr t={t.delete} />
</button>
<button slot="cancel" class="items-center" on:click={() => (currentState = "start")}>
<Tr t={t.cancel} />
</button>
<XCircleIcon
slot="upper-right"
class="h-8 w-8 cursor-pointer"
on:click={() => {
currentState = "start"
}}
/>
<button slot="cancel" class="items-center" on:click={() => (currentState = "start")}>
<Tr t={t.cancel} />
</button>
<XCircleIcon
slot="upper-right"
class="h-8 w-8 cursor-pointer"
on:click={() => {
currentState = "start"
}}
/>
<div slot="under-buttons">
{#if selectedTags !== undefined}
{#if canBeDeleted && isHardDelete}
<!-- This is a hard delete - explain that this is a hard delete...-->
<Tr t={t.explanations.hardDelete} />
{:else}
<!-- This is a soft deletion: we explain _why_ the deletion is soft -->
<Tr t={t.explanations.softDelete.Subs({ reason: $canBeDeletedReason })} />
{/if}
<div slot="under-buttons">
{#if selectedTags !== undefined}
{#if canBeDeleted && isHardDelete}
<!-- This is a hard delete - explain that this is a hard delete...-->
<Tr t={t.explanations.hardDelete} />
{:else}
<!-- This is a soft deletion: we explain _why_ the deletion is soft -->
<Tr t={t.explanations.softDelete.Subs({ reason: $canBeDeletedReason })} />
{/if}
</div>
</TagRenderingQuestion>
{:else if currentState === "applying"}
<Loading />
{:else}
<!-- currentState === 'deleted' -->
<div class="low-interaction flex">
<TrashIcon class="h-6 w-6" />
<Tr t={t.isDeleted} />
{/if}
</div>
{/if}
</TagRenderingQuestion>
{:else if currentState === "applying"}
<Loading />
{:else}
<!-- currentState === 'deleted' -->
<div class="low-interaction flex">
<TrashIcon class="h-6 w-6" />
<Tr t={t.isDeleted} />
</div>
{/if}
</LoginToggle>

View file

@ -1,48 +1,47 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid";
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations";
import LoginToggle from "../Base/LoginToggle.svelte";
import type { Feature } from "geojson";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid"
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import LoginToggle from "../Base/LoginToggle.svelte"
import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
/**
* A full-blown 'mark as favourite'-button
*/
export let state: SpecialVisualizationState;
export let state: SpecialVisualizationState
export let feature: Feature
export let tags: Record<string, string>;
export let tags: Record<string, string>
export let layer: LayerConfig
let isFavourite = tags?.map(tags => tags._favourite === "yes");
const t = Translations.t.favouritePoi;
let isFavourite = tags?.map((tags) => tags._favourite === "yes")
const t = Translations.t.favouritePoi
function markFavourite(isFavourite: boolean) {
state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite)
}
</script>
<LoginToggle ignoreLoading={true} {state}>
{#if $isFavourite}
<div class="flex h-fit items-start">
<HeartSolidIcon class="w-16 shrink-0 mr-2" on:click={() => markFavourite(false)} />
<div class="flex flex-col w-full">
<button class="flex flex-col items-start" on:click={() => markFavourite(false)}>
<Tr t={t.button.unmark} />
<Tr cls="normal-font subtle" t={t.button.unmarkNotDeleted}/>
</button>
{#if $isFavourite}
<div class="flex h-fit items-start">
<HeartSolidIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(false)} />
<div class="flex w-full flex-col">
<button class="flex flex-col items-start" on:click={() => markFavourite(false)}>
<Tr t={t.button.unmark} />
<Tr cls="normal-font subtle" t={t.button.unmarkNotDeleted} />
</button>
</div>
</div>
</div>
<Tr cls="font-bold thanks m-2 p-2 block" t={t.button.isFavourite} />
{:else}
<div class="flex items-start">
<HeartOutlineIcon class="w-16 shrink-0 mr-2" on:click={() => markFavourite(true)} />
{:else}
<div class="flex items-start">
<HeartOutlineIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(true)} />
<button class="flex w-full flex-col items-start" on:click={() => markFavourite(true)}>
<Tr t={t.button.markAsFavouriteTitle} />
<Tr cls="normal-font subtle" t={t.button.markDescription}/>
<Tr cls="normal-font subtle" t={t.button.markDescription} />
</button>
</div>
{/if}
</div>
{/if}
</LoginToggle>

View file

@ -16,7 +16,7 @@
export let feature: Feature
export let tags: UIEventSource<Record<string, string>>
export let layer: LayerConfig
let isFavourite = tags?.map(tags => tags._favourite === "yes")
let isFavourite = tags?.map((tags) => tags._favourite === "yes")
const t = Translations.t.favouritePoi
function markFavourite(isFavourite: boolean) {

View file

@ -1,9 +1,7 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource"
import type { MoveReason } from "./MoveWizardState"
import { MoveWizardState } from "./MoveWizardState"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte"
import Tr from "../Base/Tr.svelte"
@ -21,7 +19,6 @@
import If from "../Base/If.svelte"
import Constants from "../../Models/Constants"
export let state: SpecialVisualizationState
export let layer: LayerConfig
@ -48,16 +45,15 @@
}
}
let moveWizardState = new MoveWizardState(id, layer.allowMove, state)
let notAllowed = moveWizardState.moveDisallowedReason
let currentMapProperties: MapProperties = undefined
</script>
{#if moveWizardState.reasons.length > 0}
{#if moveWizardState.reasons.length > 0}
{#if $notAllowed}
<div class="flex m-2 p-2 rounded-lg bg-gray-200">
<Move_not_allowed class="h-8 w-8 m-2" />
<div class="m-2 flex rounded-lg bg-gray-200 p-2">
<Move_not_allowed class="m-2 h-8 w-8" />
<div class="flex flex-col">
<Tr t={t.cannotBeMoved} />
<Tr t={$notAllowed} />
@ -65,75 +61,91 @@
</div>
{:else if currentStep === "start"}
{#if moveWizardState.reasons.length === 1}
<button class="flex" on:click={() => {reason.setData(moveWizardState.reasons[0]); currentStep = "pick_location"}}>
<ToSvelte construct={moveWizardState.reasons[0].icon.SetStyle("height: 1.5rem; width: 1.5rem;")}></ToSvelte>
<button
class="flex"
on:click={() => {
reason.setData(moveWizardState.reasons[0])
currentStep = "pick_location"
}}
>
<ToSvelte
construct={moveWizardState.reasons[0].icon.SetStyle("height: 1.5rem; width: 1.5rem;")}
/>
<Tr t={Translations.T(moveWizardState.reasons[0].invitingText)} />
</button>
{:else}
<button class="flex" on:click={() => {currentStep = "reason"}}>
<Move class="w-6 h-6" />
<button
class="flex"
on:click={() => {
currentStep = "reason"
}}
>
<Move class="h-6 w-6" />
<Tr t={t.inviteToMove.generic} />
</button>
{/if}
{:else if currentStep === "reason"}
<div class="flex flex-col interactive border-interactive p-2">
<div class="interactive border-interactive flex flex-col p-2">
<Tr cls="text-lg font-bold" t={t.whyMove} />
{#each moveWizardState.reasons as reasonSpec}
<button on:click={() => {reason.setData(reasonSpec); currentStep = "pick_location"}}>
<button
on:click={() => {
reason.setData(reasonSpec)
currentStep = "pick_location"
}}
>
<ToSvelte construct={reasonSpec.icon.SetClass("w-16 h-16 pr-2")} />
<Tr t={Translations.T(reasonSpec.text)} />
</button>
{/each}
</div>
{:else if currentStep === "pick_location"}
<div class="flex flex-col border-interactive interactive p-2">
<div class="border-interactive interactive flex flex-col p-2">
<Tr cls="text-lg font-bold" t={t.moveTitle} />
<div class="relative w-full h-64">
<LocationInput mapProperties={currentMapProperties = initMapProperties()} value={newLocation}
initialCoordinate={{lon, lat}} />
<div class="relative h-64 w-full">
<LocationInput
mapProperties={(currentMapProperties = initMapProperties())}
value={newLocation}
initialCoordinate={{ lon, lat }}
/>
<div class="absolute bottom-0 left-0">
<OpenBackgroundSelectorButton {state} />
</div>
</div>
{#if $reason.includeSearch}
<Geosearch bounds={ currentMapProperties.bounds} clearAfterView={false} />
<Geosearch bounds={currentMapProperties.bounds} clearAfterView={false} />
{/if}
<div class="flex flex-wrap">
<If condition={currentMapProperties.zoom.mapD(zoom => zoom >= Constants.minZoomLevelToAddNewPoint)}>
<button class="flex flex primary w-full"
<button class="flex primary w-full"
on:click={() => {
moveWizardState.moveFeature(newLocation.data, reason.data, featureToMove);
currentStep = "moved"
}}>
<Move class="w-6 h-6 mr-2" />
<Move class="mr-2 h-6 w-6" />
<Tr t={t.confirmMove} />
</button>
<div slot="else" class="alert">
<Tr t={t.zoomInFurther} />
</div>
</If>
<button class="w-full" on:click={() => {currentStep= "start"}}>
<XCircleIcon class="w-6 h-6 mr-2" />
<button
class="w-full"
on:click={() => {
currentStep = "start"
}}
>
<XCircleIcon class="mr-2 h-6 w-6" />
<Tr t={t.cancel} />
</button>
</div>
</div>
{:else if currentStep === "moved"}
<div class="flex flex-col">
<Tr cls="thanks" t={t.pointIsMoved} />
<button on:click={() => {currentStep = "reason"}}>
@ -141,6 +153,5 @@
<Tr t={t.inviteToMoveAgain} />
</button>
</div>
{/if}
{/if}

View file

@ -3,15 +3,15 @@
* Shows all questions for which the answers are unknown.
* The questions can either be shown all at once or one at a time (in which case they can be skipped)
*/
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { UIEventSource } from "../../../Logic/UIEventSource";
import type { Feature } from "geojson";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import TagRenderingQuestion from "./TagRenderingQuestion.svelte";
import Tr from "../../Base/Tr.svelte";
import Translations from "../../i18n/Translations.js";
import { Utils } from "../../../Utils";
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
import { UIEventSource } from "../../../Logic/UIEventSource"
import type { Feature } from "geojson"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import TagRenderingQuestion from "./TagRenderingQuestion.svelte"
import Tr from "../../Base/Tr.svelte"
import Translations from "../../i18n/Translations.js"
import { Utils } from "../../../Utils"
export let layer: LayerConfig
export let tags: UIEventSource<Record<string, string>>

View file

@ -5,7 +5,7 @@
import TagRenderingMapping from "./TagRenderingMapping.svelte"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import type { Feature } from "geojson"
import { Store, UIEventSource } from "../../../Logic/UIEventSource";
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
import { onDestroy } from "svelte"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { twMerge } from "tailwind-merge"
@ -23,8 +23,9 @@
if (config === undefined) {
throw "Config is undefined in tagRenderingAnswer"
}
let trs : Store<{then: Translation, icon?: string, iconClass?: string}[]> = tags.mapD(tags => Utils.NoNull(config?.GetRenderValues(tags)))
let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD((tags) =>
Utils.NoNull(config?.GetRenderValues(tags))
)
</script>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties($tags))}

View file

@ -32,7 +32,7 @@
onDestroy(
tags.addCallbackD((tags) => {
editMode = !config.IsKnown(tags)
}),
})
)
}
@ -78,7 +78,7 @@
let answerId = "answer-"+Utils.randomString(5)
</script>
<div bind:this={htmlElem} class={twMerge(clss, "tr-"+config.id)}>
<div bind:this={htmlElem} class={twMerge(clss, "tr-" + config.id)}>
{#if config.question && (!editingEnabled || $editingEnabled)}
{#if editMode}
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer} on:saved={() => editMode = false}>
@ -91,12 +91,14 @@
>
<Tr t={Translations.t.general.cancel} />
</button>
<button slot="upper-right"
class="h-8 w-8 cursor-pointer border-none p-0"
use:ariaLabel={Translations.t.general.cancel}
on:click={() => {
<button
slot="upper-right"
class="h-8 w-8 cursor-pointer border-none p-0"
use:ariaLabel={Translations.t.general.cancel}
on:click={() => {
editMode = false
}}>
}}
>
<XCircleIcon />
</button>
</TagRenderingQuestion>

View file

@ -6,7 +6,7 @@
import { UIEventSource } from "../../../Logic/UIEventSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { twJoin } from "tailwind-merge"
import Icon from "../../Map/Icon.svelte";
import Icon from "../../Map/Icon.svelte"
export let selectedElement: Feature
export let tags: UIEventSource<Record<string, string>>

View file

@ -84,7 +84,7 @@
}
const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs)
if (matches && confg.freeform) {
const newProps = TagUtils.changeAsProperties(mapping.if.asChange({}))
const newProps = TagUtils.changeAsProperties(mapping.if.asChange(tgs))
seenFreeforms.push(newProps[confg.freeform.key])
}
return matches
@ -113,7 +113,6 @@
// Somehow, setting multi-answer freeform values is broken if this is not set
freeformInput.setData(tgs[confg.freeform.key])
}
} else {
freeformInput.setData(undefined)
}
@ -132,7 +131,7 @@
$freeformInput,
selectedMapping,
checkedMappings,
tags.data,
tags.data
)
} catch (e) {
console.error("Could not calculate changeSpecification:", e)
@ -140,7 +139,6 @@
}
}
function onSave() {
if (selectedTags === undefined) {
return
@ -190,7 +188,7 @@
$freeformInput,
selectedMapping,
checkedMappings,
tags.data,
tags.data
)
} catch (e) {
console.error("Could not calculate changeSpecification:", e)
@ -198,7 +196,6 @@
}
}
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)
let featureSwitchIsDebugging =
state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
@ -210,7 +207,7 @@
onDestroy(
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
numberOfCs = ud.csCount
}),
})
)
}
</script>
@ -220,10 +217,10 @@
class="interactive border-interactive relative flex flex-col overflow-y-auto px-2"
style="max-height: 75vh"
>
<div class="sticky top-0 interactive pt-1 flex justify-between" style="z-index: 11">
<span class="font-bold" aria-live="assertive">
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
</span>
<div class="interactive sticky top-0 flex justify-between pt-1" style="z-index: 11">
<span class="font-bold">
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
</span>
<slot name="upper-right" />
</div>
@ -278,8 +275,7 @@
bind:group={selectedMapping}
name={"mappings-radio-" + config.id}
value={i}
on:keypress={e => onInputKeypress(e)}
on:keypress={(e) => onInputKeypress(e)}
/>
</TagRenderingMappingInput>
{/each}
@ -290,7 +286,7 @@
bind:group={selectedMapping}
name={"mappings-radio-" + config.id}
value={config.mappings?.length}
on:keypress={e => onInputKeypress(e)}
on:keypress={(e) => onInputKeypress(e)}
/>
<FreeformInput
{config}
@ -323,7 +319,7 @@
type="checkbox"
name={"mappings-checkbox-" + config.id + "-" + i}
bind:checked={checkedMappings[i]}
on:keypress={e => onInputKeypress(e)}
on:keypress={(e) => onInputKeypress(e)}
/>
</TagRenderingMappingInput>
{/each}
@ -333,7 +329,7 @@
type="checkbox"
name={"mappings-checkbox-" + config.id + "-" + config.mappings?.length}
bind:checked={checkedMappings[config.mappings.length]}
on:keypress={e => onInputKeypress(e)}
on:keypress={(e) => onInputKeypress(e)}
/>
<FreeformInput
{config}

View file

@ -84,6 +84,7 @@ import MoveWizard from "./Popup/MoveWizard.svelte"
import { Unit } from "../Models/Unit"
import Link from "./Base/Link.svelte"
import OrientationDebugPanel from "./Debug/OrientationDebugPanel.svelte"
import MaprouletteSetStatus from "./MapRoulette/MaprouletteSetStatus.svelte"
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -1139,73 +1140,22 @@ export default class SpecialVisualizations {
},
],
constr: (state, tagsSource, args) => {
let [message, image, message_closed, status, maproulette_id_key] = args
let [message, image, message_closed, statusToSet, maproulette_id_key] = args
if (image === "") {
image = "confirm"
}
if (maproulette_id_key === "" || maproulette_id_key === undefined) {
maproulette_id_key = "mr_taskId"
}
const failed = new UIEventSource(false)
const closeButton = new SubtleButton(image, message).OnClickWithLoading(
Translations.t.general.loading,
async () => {
const maproulette_id =
tagsSource.data[maproulette_id_key] ??
tagsSource.data.mr_taskId ??
tagsSource.data.id
try {
await Maproulette.singleton.closeTask(
Number(maproulette_id),
Number(status),
{
tags: `MapComplete MapComplete:${state.layout.id}`,
}
)
tagsSource.data["mr_taskStatus"] =
Maproulette.STATUS_MEANING[Number(status)]
tagsSource.data.status = status
tagsSource.ping()
} catch (e) {
console.error(e)
failed.setData(true)
}
}
)
let message_closed_element = undefined
if (message_closed !== undefined && message_closed !== "") {
message_closed_element = new FixedUiElement(message_closed)
}
return new VariableUiElement(
tagsSource
.map((tgs) => {
if (tgs["status"]) {
return tgs["status"]
}
const code = tgs["mr_taskStatus"]
console.log("Code is", code, Maproulette.codeToIndex(code))
return Maproulette.codeToIndex(code)
})
.map(Number)
.map(
(status) => {
console.log("Close MR button: status is", status)
if (failed.data) {
return new FixedUiElement(
"ERROR - could not close the MapRoulette task"
).SetClass("block alert")
}
if (status === Maproulette.STATUS_OPEN) {
return closeButton
}
return message_closed_element ?? "Closed!"
},
[failed]
)
)
return new SvelteUIElement(MaprouletteSetStatus, {
state,
tags: tagsSource,
message,
image,
message_closed,
statusToSet,
maproulette_id_key,
})
},
},
{

View file

@ -1,30 +1,28 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource";
import { OsmConnection } from "../../Logic/Osm/OsmConnection";
import Marker from "../Map/Marker.svelte";
import NextButton from "../Base/NextButton.svelte";
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts";
import { AllSharedLayers } from "../../Customizations/AllSharedLayers";
import { createEventDispatcher } from "svelte";
import { UIEventSource } from "../../Logic/UIEventSource"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Marker from "../Map/Marker.svelte"
import NextButton from "../Base/NextButton.svelte"
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts"
import { AllSharedLayers } from "../../Customizations/AllSharedLayers"
import { createEventDispatcher } from "svelte"
export let info: { id: string; owner: number };
export let category: "layers" | "themes";
export let osmConnection: OsmConnection;
const dispatch = createEventDispatcher<{ layerSelected: string }>();
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);
).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;
return AllKnownLayouts.allKnownLayouts.get(layerId).icon
}
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon;
return AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon
}
</script>
<NextButton clss="small" on:click={() => dispatch("layerSelected", info)}>
@ -39,7 +37,7 @@
- {info.owner}
{/if}
)
{:else }
{:else}
({info.owner})
{/if}
{/if}

View file

@ -74,8 +74,8 @@
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem
function deleteLayer() {
state.delete()
backToStudio()
state.delete()
backToStudio()
}
</script>
@ -119,10 +119,9 @@
<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>
<button on:click={() => deleteLayer()} class="small">
<TrashIcon class="h-6 w-6" /> Delete this layer
</button>
</div>
</div>

View file

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

View file

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

View file

@ -260,7 +260,12 @@
<Loading />
</div>
{:else if state === "editing_layer"}
<EditLayer state={editLayerState} backToStudio={() => {state = undefined}}>
<EditLayer
state={editLayerState}
backToStudio={() => {
state = undefined
}}
>
<BackButton
clss="small p-1"
imageClass="w-8 h-8"
@ -288,19 +293,18 @@
{#if { intro, tagrenderings: intro_tagrenderings }[$showIntro]?.sections}
<FloatOver
on:close={() => {
showIntro.setData("no")
}}
showIntro.setData("no")
}}
>
<div class="flex h-full p-4 pr-12">
<Walkthrough
pages={{ intro, tagrenderings: intro_tagrenderings }[$showIntro]?.sections}
on:done={() => {
showIntro.setData("no")
}}
showIntro.setData("no")
}}
/>
</div>
</FloatOver>
{/if}
</LoginToggle>
</If>

View file

@ -9,7 +9,6 @@
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"
@ -29,7 +28,6 @@
import ModalRight from "./Base/ModalRight.svelte"
import { Utils } from "../Utils"
import Hotkeys from "./Base/Hotkeys"
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
import LevelSelector from "./BigComponents/LevelSelector.svelte"
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
@ -74,10 +72,14 @@
let maplibremap: UIEventSource<MlMap> = state.map
let selectedElement: UIEventSource<Feature> = new UIEventSource<Feature>(undefined)
let compass = Orientation.singleton.alpha
let compassLoaded = Orientation.singleton.gotMeasurement
Orientation.singleton.startMeasurements()
state.selectedElement.addCallback(selected => {
state.selectedElement.addCallback((selected) => {
if (!selected) {
selectedElement.setData(selected)
return
@ -91,15 +93,15 @@
// ... and we force a fresh popup window
selectedElement.setData(selected)
})
})
let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD(element => state.layout.getMatchingLayer(element.properties))
let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) =>
state.layout.getMatchingLayer(element.properties),
)
let currentZoom = state.mapProperties.zoom
let showCrosshair = state.userRelatedState.showCrosshair
let arrowKeysWereUsed = state.visualFeedback
let centerFeatures = state.closestFeatures.features
let mapproperties: MapProperties = state.mapProperties
let featureSwitches: FeatureSwitchState = state.featureSwitches
let availableLayers = state.availableLayers
@ -185,7 +187,7 @@
<!-- Flex and w-full are needed for the positioning -->
<!-- Centermessage -->
<StateIndicator {state} />
<ReverseGeocoding mapProperties={mapproperties}/>
<ReverseGeocoding mapProperties={mapproperties} />
</div>
</div>
@ -239,7 +241,6 @@
<VisualFeedbackPanel {state} />
</If>
<div class="flex flex-col items-end">
<!-- bottom right elements -->
<If condition={state.floors.map((f) => f.length > 1)}>
@ -273,7 +274,8 @@
</MapControlButton>
{#if $compassLoaded}
<div class="absolute top-0 left-0 w-0 h-0 m-0.5 sm:m-1">
<Compass_arrow class="compass_arrow" style={`rotate: calc(${-$compass}deg + 45deg); transform-origin: 50% 50%;`} />
<Compass_arrow class="compass_arrow"
style={`rotate: calc(${-$compass}deg + 45deg); transform-origin: 50% 50%;`} />
</div>
{/if}
</div>
@ -296,10 +298,10 @@
<svelte:fragment slot="error" /> <!-- Add in an empty container to remove errors -->
</LoginToggle>
<If condition={state.previewedImage.map(i => i!==undefined)}>
<If condition={state.previewedImage.map((i) => i !== undefined)}>
<FloatOver extraClasses="p-1" on:close={() => state.previewedImage.setData(undefined)}>
<div
class="absolute right-4 top-4 h-8 w-8 cursor-pointer rounded-full hover:bg-white bg-white/50 transition-colors duration-200"
class="absolute right-4 top-4 h-8 w-8 cursor-pointer rounded-full bg-white/50 transition-colors duration-200 hover:bg-white"
on:click={() => previewedImage.setData(undefined)}
slot="close-button"
>
@ -309,7 +311,7 @@
</FloatOver>
</If>
{#if $selectedElement !== undefined && $selectedLayer !== undefined && !($selectedLayer.popupInFloatover)}
{#if $selectedElement !== undefined && $selectedLayer !== undefined && !$selectedLayer.popupInFloatover}
<!-- right modal with the selected element view -->
<ModalRight
on:close={() => {
@ -342,7 +344,6 @@
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
<span slot="close-button"><!-- Disable the close button --></span>
<TabbedGroup
condition1={state.featureSwitches.featureSwitchFilter}
tab={state.guistate.themeViewTabIndex}
>
@ -391,7 +392,7 @@
<If condition={state.guistate.filtersPanelIsOpened}>
<FloatOver on:close={() => state.guistate.filtersPanelIsOpened.setData(false)}>
<FilterPanel {state}/>
<FilterPanel {state} />
</FloatOver>
</If>
@ -496,7 +497,7 @@
<Tr t={Translations.t.favouritePoi.tab} />
</div>
<div class="flex flex-col m-2" slot="content2">
<div class="m-2 flex flex-col" slot="content2">
<h3>
<Tr t={Translations.t.favouritePoi.title} />
</h3>

View file

@ -46,7 +46,7 @@
</span>
</DisclosureButton>
<DisclosurePanel>
<FromHtml clss="wikipedia-article" src={$wikipediaDetails.restOfArticle} />
<FromHtml clss="wikipedia-article" src={$wikipediaDetails.restOfArticle} />
</DisclosurePanel>
</Disclosure>
{/if}

View file

@ -1,11 +1,11 @@
{
"contributors": [
{
"commits": 6418,
"commits": 6533,
"contributor": "Pieter Vander Vennet"
},
{
"commits": 421,
"commits": 424,
"contributor": "Robin van der Linde"
},
{
@ -49,7 +49,7 @@
"contributor": "karelleketers"
},
{
"commits": 24,
"commits": 25,
"contributor": "dependabot[bot]"
},
{
@ -116,6 +116,10 @@
"commits": 10,
"contributor": "LiamSimons"
},
{
"commits": 9,
"contributor": "Codain"
},
{
"commits": 9,
"contributor": "Midgard"
@ -124,10 +128,6 @@
"commits": 8,
"contributor": "pelderson"
},
{
"commits": 8,
"contributor": "Codain"
},
{
"commits": 8,
"contributor": "Mateusz Konieczny"
@ -172,6 +172,10 @@
"commits": 3,
"contributor": "Léo Villeveygoux"
},
{
"commits": 2,
"contributor": "eMerzh"
},
{
"commits": 2,
"contributor": "bxl-forever"
@ -276,10 +280,6 @@
"commits": 1,
"contributor": "Wouter van der Wal"
},
{
"commits": 1,
"contributor": "eMerzh"
},
{
"commits": 1,
"contributor": "Reiner Herrmann"

View file

@ -3,6 +3,7 @@
"ca"
],
"AE": [
"en",
"ar"
],
"AF": [
@ -396,8 +397,8 @@
"ro"
],
"MG": [
"fr",
"mg"
"mg",
"fr"
],
"MH": [
"en",
@ -408,7 +409,8 @@
"mk"
],
"ML": [
"fr"
"bm",
"ff"
],
"MM": [
"my"

View file

@ -1449,6 +1449,9 @@
"ru": "бамана",
"sv": "bambara",
"_meta": {
"countries": [
"ML"
],
"dir": [
"left-to-right",
"right-to-left"
@ -3008,6 +3011,7 @@
"zh_Hant": "英語",
"_meta": {
"countries": [
"AE",
"AG",
"BB",
"BI",
@ -3373,6 +3377,9 @@
"zh_Hans": "富拉语",
"zh_Hant": "富拉語",
"_meta": {
"countries": [
"ML"
],
"dir": [
"left-to-right",
"right-to-left"
@ -3678,7 +3685,6 @@
"LU",
"MC",
"MG",
"ML",
"MU",
"NE",
"RW",

View file

@ -1,11 +1,11 @@
{
"contributors": [
{
"commits": 319,
"commits": 320,
"contributor": "kjon"
},
{
"commits": 296,
"commits": 298,
"contributor": "Pieter Vander Vennet"
},
{
@ -296,6 +296,10 @@
"commits": 4,
"contributor": "Jan Zabel"
},
{
"commits": 3,
"contributor": "ssantos"
},
{
"commits": 3,
"contributor": "Stéphane De Greef"
@ -540,10 +544,6 @@
"commits": 1,
"contributor": "Janina Ellinghaus"
},
{
"commits": 1,
"contributor": "ssantos"
},
{
"commits": 1,
"contributor": "Andre Fajar N"