diff --git a/src/UI/DownloadFlow/DownloadPdf.svelte b/src/UI/DownloadFlow/DownloadPdf.svelte
index 9a807c1ee6..379950b8b0 100644
--- a/src/UI/DownloadFlow/DownloadPdf.svelte
+++ b/src/UI/DownloadFlow/DownloadPdf.svelte
@@ -29,7 +29,7 @@
const templateUrls = SvgToPdf.templates[templateName].pages
const templates: string[] = await Promise.all(templateUrls.map((url) => Utils.download(url)))
console.log("Templates are", templates)
- const bg = state.mapProperties.rasterLayer.data ?? AvailableRasterLayers.maplibre
+ const bg = state.mapProperties.rasterLayer.data ?? AvailableRasterLayers.maptilerDefaultLayer
const creator = new SvgToPdf(title, templates, {
state,
freeComponentId: "belowmap",
diff --git a/src/UI/Image/ImageUploadFlow.ts b/src/UI/Image/ImageUploadFlow.ts
deleted file mode 100644
index 5c3f6b5c68..0000000000
--- a/src/UI/Image/ImageUploadFlow.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { Store, UIEventSource } from "../../Logic/UIEventSource"
-import Combine from "../Base/Combine"
-import Translations from "../i18n/Translations"
-import Svg from "../../Svg"
-import { Tag } from "../../Logic/Tags/Tag"
-import BaseUIElement from "../BaseUIElement"
-import Toggle from "../Input/Toggle"
-import FileSelectorButton from "../Input/FileSelectorButton"
-import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader"
-import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
-import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
-import { FixedUiElement } from "../Base/FixedUiElement"
-import { VariableUiElement } from "../Base/VariableUIElement"
-import Loading from "../Base/Loading"
-import { LoginToggle } from "../Popup/LoginButton"
-import Constants from "../../Models/Constants"
-import { SpecialVisualizationState } from "../SpecialVisualization"
-
-export class ImageUploadFlow extends Toggle {
- private static readonly uploadCountsPerId = new Map
>()
-
- constructor(
- tagsSource: Store,
- state: SpecialVisualizationState,
- imagePrefix: string = "image",
- text: string = undefined
- ) {
- const perId = ImageUploadFlow.uploadCountsPerId
- const id = tagsSource.data.id
- if (!perId.has(id)) {
- perId.set(id, new UIEventSource(0))
- }
- const uploadedCount = perId.get(id)
- const uploader = new ImgurUploader(async (url) => {
- // A file was uploaded - we add it to the tags of the object
-
- const tags = tagsSource.data
- let key = imagePrefix
- if (tags[imagePrefix] !== undefined) {
- let freeIndex = 0
- while (tags[imagePrefix + ":" + freeIndex] !== undefined) {
- freeIndex++
- }
- key = imagePrefix + ":" + freeIndex
- }
-
- await state.changes.applyAction(
- new ChangeTagAction(tags.id, new Tag(key, url), tagsSource.data, {
- changeType: "add-image",
- theme: state.layout.id,
- })
- )
- console.log("Adding image:" + key, url)
- uploadedCount.data++
- uploadedCount.ping()
- })
-
- const t = Translations.t.image
-
- let labelContent: BaseUIElement
- if (text === undefined) {
- labelContent = Translations.t.image.addPicture
- .Clone()
- .SetClass("block align-middle mt-1 ml-3 text-4xl ")
- } else {
- labelContent = new FixedUiElement(text).SetClass(
- "block align-middle mt-1 ml-3 text-2xl "
- )
- }
- const label = new Combine([
- Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "),
- labelContent,
- ]).SetClass("w-full flex justify-center items-center")
-
- const licenseStore = state?.osmConnection?.GetPreference("pictures-license", "CC0")
-
- const fileSelector = new FileSelectorButton(label, {
- acceptType: "image/*",
- allowMultiple: true,
- labelClasses: "rounded-full border-2 border-black font-bold",
- })
- /* fileSelector.SetClass(
- "p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center"
- )
- .SetStyle(" border-color: var(--foreground-color);")*/
- fileSelector.GetValue().addCallback((filelist) => {
- if (filelist === undefined || filelist.length === 0) {
- return
- }
-
- for (var i = 0; i < filelist.length; i++) {
- const sizeInBytes = filelist[i].size
- console.log(filelist[i].name + " has a size of " + sizeInBytes + " Bytes")
- if (sizeInBytes > uploader.maxFileSizeInMegabytes * 1000000) {
- alert(
- Translations.t.image.toBig.Subs({
- actual_size: Math.floor(sizeInBytes / 1000000) + "MB",
- max_size: uploader.maxFileSizeInMegabytes + "MB",
- }).txt
- )
- return
- }
- }
-
- const license = licenseStore?.data ?? "CC0"
-
- const tags = tagsSource.data
-
- const layout = state?.layout
- let matchingLayer: LayerConfig = undefined
- for (const layer of layout?.layers ?? []) {
- if (layer.source.osmTags.matchesProperties(tags)) {
- matchingLayer = layer
- break
- }
- }
-
- const title =
- matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.ConstructElement()
- ?.textContent ??
- tags.name ??
- "https//osm.org/" + tags.id
- const description = [
- "author:" + state.osmConnection.userDetails.data.name,
- "license:" + license,
- "osmid:" + tags.id,
- ].join("\n")
-
- uploader.uploadMany(title, description, filelist)
- })
-
- const uploadFlow: BaseUIElement = new Combine([
- new VariableUiElement(
- uploader.queue
- .map((q) => q.length)
- .map((l) => {
- if (l == 0) {
- return undefined
- }
- if (l == 1) {
- return new Loading(t.uploadingPicture).SetClass("alert")
- } else {
- return new Loading(
- t.uploadingMultiple.Subs({ count: "" + l })
- ).SetClass("alert")
- }
- })
- ),
- new VariableUiElement(
- uploader.failed
- .map((q) => q.length)
- .map((l) => {
- if (l == 0) {
- return undefined
- }
- console.log(l)
- return t.uploadFailed.SetClass("block alert")
- })
- ),
- new VariableUiElement(
- uploadedCount.map((l) => {
- if (l == 0) {
- return undefined
- }
- if (l == 1) {
- return t.uploadDone.Clone().SetClass("thanks block")
- }
- return t.uploadMultipleDone.Subs({ count: l }).SetClass("thanks block")
- })
- ),
-
- fileSelector,
- new Combine([
- Translations.t.image.respectPrivacy,
- new VariableUiElement(
- licenseStore.map((license) =>
- Translations.t.image.currentLicense.Subs({ license })
- )
- )
- .onClick(() => {
- console.log("Opening the license settings... ")
- state.guistate.openUsersettings("picture-license")
- })
- .SetClass("underline"),
- ]).SetStyle("font-size:small;"),
- ]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center leading-none")
-
- super(
- new LoginToggle(
- /*We can show the actual upload button!*/
- uploadFlow,
- /* User not logged in*/ t.pleaseLogin.Clone(),
- state
- ),
- undefined /* Nothing as the user badge is disabled*/,
- state?.featureSwitchUserbadge
- )
- }
-}
diff --git a/src/UI/Image/UploadImage.svelte b/src/UI/Image/UploadImage.svelte
new file mode 100644
index 0000000000..23408e778f
--- /dev/null
+++ b/src/UI/Image/UploadImage.svelte
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
handleFiles(e.detail)}>
+
+
+ {#if image !== undefined}
+

+ {:else}
+
+ {/if}
+ {#if labelText}
+ {labelText}
+ {:else}
+
|
+ {/if}
+
+
+
+
+
+
diff --git a/src/UI/Image/UploadingImageCounter.svelte b/src/UI/Image/UploadingImageCounter.svelte
new file mode 100644
index 0000000000..0c1b6f7776
--- /dev/null
+++ b/src/UI/Image/UploadingImageCounter.svelte
@@ -0,0 +1,67 @@
+
+
+{#if $uploadStarted == 1}
+ {#if $uploadFinished == 1 }
+
+ {:else if $failed == 1}
+
+
+
+
+
+ {:else if $retried == 1}
+
+
+
+ {:else }
+
+
+
+ {/if}
+{:else if $uploadStarted > 1}
+ {#if ($uploadFinished + $failed) == $uploadStarted && $uploadFinished > 0}
+
+ {:else if $uploadFinished == 0}
+
+
+
+ {:else if $uploadFinished > 0}
+
+
+
+ {/if}
+ {#if $failed > 0}
+
+ {#if failed === 1}
+
+ {:else}
+
+
+ {/if}
+
+
+
+ {/if}
+{/if}
diff --git a/src/UI/Input/FileSelectorButton.ts b/src/UI/Input/FileSelectorButton.ts
deleted file mode 100644
index c3f56d297a..0000000000
--- a/src/UI/Input/FileSelectorButton.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import BaseUIElement from "../BaseUIElement"
-import { InputElement } from "./InputElement"
-import { UIEventSource } from "../../Logic/UIEventSource"
-
-/**
- * @deprecated
- */
-export default class FileSelectorButton extends InputElement {
- private static _nextid = 0
- private readonly _value = new UIEventSource(undefined)
- private readonly _label: BaseUIElement
- private readonly _acceptType: string
- private readonly allowMultiple: boolean
- private readonly _labelClasses: string
-
- constructor(
- label: BaseUIElement,
- options?: {
- acceptType: "image/*" | string
- allowMultiple: true | boolean
- labelClasses?: string
- }
- ) {
- super()
- this._label = label
- this._acceptType = options?.acceptType ?? "image/*"
- this._labelClasses = options?.labelClasses ?? ""
- this.SetClass("block cursor-pointer")
- label.SetClass("cursor-pointer")
- this.allowMultiple = options?.allowMultiple ?? true
- }
-
- GetValue(): UIEventSource {
- return this._value
- }
-
- IsValid(t: FileList): boolean {
- return true
- }
-
- protected InnerConstructElement(): HTMLElement {
- const self = this
- const el = document.createElement("form")
- const label = document.createElement("label")
- label.appendChild(this._label.ConstructElement())
- label.classList.add(...this._labelClasses.split(" ").filter((t) => t !== ""))
- el.appendChild(label)
-
- const actualInputElement = document.createElement("input")
- actualInputElement.style.cssText = "display:none"
- actualInputElement.type = "file"
- actualInputElement.accept = this._acceptType
- actualInputElement.name = "picField"
- actualInputElement.multiple = this.allowMultiple
- actualInputElement.id = "fileselector" + FileSelectorButton._nextid
- FileSelectorButton._nextid++
-
- label.htmlFor = actualInputElement.id
-
- actualInputElement.onchange = () => {
- if (actualInputElement.files !== null) {
- self._value.setData(actualInputElement.files)
- }
- }
-
- el.addEventListener("submit", (e) => {
- if (actualInputElement.files !== null) {
- self._value.setData(actualInputElement.files)
- }
- actualInputElement.classList.remove("glowing-shadow")
-
- e.preventDefault()
- })
-
- el.appendChild(actualInputElement)
-
- function setDrawAttention(isOn: boolean) {
- if (isOn) {
- label.classList.add("glowing-shadow")
- } else {
- label.classList.remove("glowing-shadow")
- }
- }
-
- el.addEventListener("dragover", (event) => {
- event.stopPropagation()
- event.preventDefault()
- setDrawAttention(true)
- // Style the drag-and-drop as a "copy file" operation.
- event.dataTransfer.dropEffect = "copy"
- })
-
- window.document.addEventListener("dragenter", () => {
- setDrawAttention(true)
- })
-
- window.document.addEventListener("dragend", () => {
- setDrawAttention(false)
- })
-
- el.addEventListener("drop", (event) => {
- event.stopPropagation()
- event.preventDefault()
- label.classList.remove("glowing-shadow")
- const fileList = event.dataTransfer.files
- this._value.setData(fileList)
- })
-
- return el
- }
-}
diff --git a/src/UI/Input/Slider.ts b/src/UI/Input/Slider.ts
deleted file mode 100644
index 9fce626a7b..0000000000
--- a/src/UI/Input/Slider.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { InputElement } from "./InputElement"
-import { UIEventSource } from "../../Logic/UIEventSource"
-
-/**
- * @deprecated
- */
-export default class Slider extends InputElement {
- private readonly _value: UIEventSource
- private readonly min: number
- private readonly max: number
- private readonly step: number
- private readonly vertical: boolean
-
- /**
- * Constructs a slider input element for natural numbers
- * @param min: the minimum value that is allowed, inclusive
- * @param max: the max value that is allowed, inclusive
- * @param options: value: injectable value; step: the step size of the slider
- */
- constructor(
- min: number,
- max: number,
- options?: {
- value?: UIEventSource
- step?: 1 | number
- vertical?: false | boolean
- }
- ) {
- super()
- this.max = max
- this.min = min
- this._value = options?.value ?? new UIEventSource(min)
- this.step = options?.step ?? 1
- this.vertical = options?.vertical ?? false
- }
-
- GetValue(): UIEventSource {
- return this._value
- }
-
- protected InnerConstructElement(): HTMLElement {
- const el = document.createElement("input")
- el.type = "range"
- el.min = "" + this.min
- el.max = "" + this.max
- el.step = "" + this.step
- const valuestore = this._value
- el.oninput = () => {
- valuestore.setData(Number(el.value))
- }
- if (this.vertical) {
- el.classList.add("vertical")
- el.setAttribute("orient", "vertical") // firefox only workaround...
- }
- valuestore.addCallbackAndRunD((v) => (el.value = "" + valuestore.data))
- return el
- }
-
- IsValid(t: number): boolean {
- return Math.round(t) == t && t >= this.min && t <= this.max
- }
-}
diff --git a/src/UI/InputElement/Validators/FediverseValidator.ts b/src/UI/InputElement/Validators/FediverseValidator.ts
index 11a410bc97..aa56528e3f 100644
--- a/src/UI/InputElement/Validators/FediverseValidator.ts
+++ b/src/UI/InputElement/Validators/FediverseValidator.ts
@@ -40,7 +40,7 @@ export default class FediverseValidator extends Validator {
if (match) {
const host = match[2]
try {
- const url = new URL("https://" + host)
+ new URL("https://" + host)
return undefined
} catch (e) {
return Translations.t.validation.fediverse.invalidHost.Subs({ host })
diff --git a/src/UI/Map/MapLibreAdaptor.ts b/src/UI/Map/MapLibreAdaptor.ts
index 75f2d54d4a..3843108ceb 100644
--- a/src/UI/Map/MapLibreAdaptor.ts
+++ b/src/UI/Map/MapLibreAdaptor.ts
@@ -92,7 +92,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
maplibreMap.addCallbackAndRunD((map) => {
map.on("load", () => {
- self.setBackground()
+ map.resize()
self.MoveMapToCurrentLoc(self.location.data)
self.SetZoom(self.zoom.data)
self.setMaxBounds(self.maxbounds.data)
@@ -102,8 +102,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
self.setMinzoom(self.minzoom.data)
self.setMaxzoom(self.maxzoom.data)
self.setBounds(self.bounds.data)
+ self.setBackground()
this.updateStores(true)
})
+ map.resize()
self.MoveMapToCurrentLoc(self.location.data)
self.SetZoom(self.zoom.data)
self.setMaxBounds(self.maxbounds.data)
@@ -113,6 +115,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
self.setMinzoom(self.minzoom.data)
self.setMaxzoom(self.maxzoom.data)
self.setBounds(self.bounds.data)
+ self.setBackground()
this.updateStores(true)
map.on("moveend", () => this.updateStores())
map.on("click", (e) => {
@@ -126,7 +129,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
})
})
- this.rasterLayer.addCallback((_) =>
+ this.rasterLayer.addCallbackAndRun((_) =>
self.setBackground().catch((_) => {
console.error("Could not set background")
})
@@ -376,12 +379,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
}
const background: RasterLayerProperties = this.rasterLayer?.data?.properties
if (!background) {
- console.error(
- "Attempting to 'setBackground', but the background is",
- background,
- "for",
- map.getCanvas()
- )
return
}
if (this._currentRasterLayer === background.id) {
@@ -408,7 +405,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
this.removeCurrentLayer(map)
} else {
// Make sure that the default maptiler style is loaded as it gives an overlay with roads
- const maptiler = AvailableRasterLayers.maplibre.properties
+ const maptiler = AvailableRasterLayers.maptilerDefaultLayer.properties
if (!map.getSource(maptiler.id)) {
this.removeCurrentLayer(map)
map.addSource(maptiler.id, MapLibreAdaptor.prepareWmsSource(maptiler))
@@ -423,7 +420,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
if (!map.getSource(background.id)) {
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
}
- map.resize()
if (!map.getLayer(background.id)) {
map.addLayer(
{
@@ -436,7 +432,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
)
}
await this.awaitStyleIsLoaded()
- this.removeCurrentLayer(map)
+ if(this._currentRasterLayer !== background?.id){
+ this.removeCurrentLayer(map)
+ }
this._currentRasterLayer = background?.id
}
@@ -457,13 +455,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
if (!map) {
return
}
- console.log("Rotation allowed:", allow)
if (allow === false) {
map.rotateTo(0, { duration: 0 })
map.setPitch(0)
map.dragRotate.disable()
+ map.touchZoomRotate.disableRotation();
} else {
map.dragRotate.enable()
+ map.touchZoomRotate.enableRotation();
}
}
diff --git a/src/UI/Map/MaplibreMap.svelte b/src/UI/Map/MaplibreMap.svelte
index bb03350d28..f882b6eadb 100644
--- a/src/UI/Map/MaplibreMap.svelte
+++ b/src/UI/Map/MaplibreMap.svelte
@@ -24,7 +24,7 @@
writable({ lng: 0, lat: 0 })
export let zoom: Readable = writable(1)
- const styleUrl = AvailableRasterLayers.maplibre.properties.url
+ const styleUrl = AvailableRasterLayers.maptilerDefaultLayer.properties.url
let _map: Map
onMount(() => {
diff --git a/src/UI/PlantNet/PlantNet.svelte b/src/UI/PlantNet/PlantNet.svelte
new file mode 100644
index 0000000000..b828de04b9
--- /dev/null
+++ b/src/UI/PlantNet/PlantNet.svelte
@@ -0,0 +1,123 @@
+
+
+
+
+ {#if collapsedMode}
+
+ {:else if $error !== undefined}
+
|
+ {:else if $imageUrls.length === 0}
+
+
+
{collapsedMode = true}}>
+
|
+
|
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ {:else if selectedOption === undefined}
+
speciesSelected(species.detail)}>
+ {collapsedMode = true}}>
+
+
+ {:else if !done}
+
+
+
+
+
+
+ {selectedOption = undefined}}>
+
+
+ { done = true; onConfirm(selectedOption); }} >
+
+
+
+
+ {:else}
+
+
|
+
{done = false; selectedOption = undefined}}>
+
+
+ {/if}
+
+
+
+
+
+
diff --git a/src/UI/PlantNet/PlantNetSpeciesList.svelte b/src/UI/PlantNet/PlantNetSpeciesList.svelte
new file mode 100644
index 0000000000..5cb7341561
--- /dev/null
+++ b/src/UI/PlantNet/PlantNetSpeciesList.svelte
@@ -0,0 +1,37 @@
+
+
+{#if $options === undefined}
+
+
+
+{:else}
+
+
+
+
+
+
+
+
+
|
+
|
+ {#each $options as species}
+
+ {/each}
+
+{/if}
diff --git a/src/UI/PlantNet/SpeciesButton.svelte b/src/UI/PlantNet/SpeciesButton.svelte
new file mode 100644
index 0000000000..9e3a44b91b
--- /dev/null
+++ b/src/UI/PlantNet/SpeciesButton.svelte
@@ -0,0 +1,54 @@
+
+
+ dispatch("selected", $wikidataId)}>
+ {#if $wikidata === undefined}
+
+
+
+ {:else}
+ new WikidataPreviewBox(wikidataId,
+ { imageStyle: "max-width: 8rem; width: unset; height: 8rem",
+ extraItems: [t.matchPercentage
+ .Subs({ match: Math.round(species.score * 100) })
+ .SetClass("thanks w-fit self-center")]
+ }).SetClass("w-full")}>
+ {/if}
+
diff --git a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte
index b95fbb5a57..d89e94402e 100644
--- a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte
+++ b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte
@@ -3,109 +3,109 @@
* This component ties together all the steps that are needed to create a new point.
* There are many subcomponents which help with that
*/
- import type { SpecialVisualizationState } from "../../SpecialVisualization"
- import PresetList from "./PresetList.svelte"
- import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"
- import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
- import Tr from "../../Base/Tr.svelte"
- import SubtleButton from "../../Base/SubtleButton.svelte"
- import FromHtml from "../../Base/FromHtml.svelte"
- import Translations from "../../i18n/Translations.js"
- import TagHint from "../TagHint.svelte"
- import { And } from "../../../Logic/Tags/And.js"
- import LoginToggle from "../../Base/LoginToggle.svelte"
- import Constants from "../../../Models/Constants.js"
- import FilteredLayer from "../../../Models/FilteredLayer"
- import { Store, UIEventSource } from "../../../Logic/UIEventSource"
- import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
- import LoginButton from "../../Base/LoginButton.svelte"
- import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"
- import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"
- import { OsmWay } from "../../../Logic/Osm/OsmObject"
- import { Tag } from "../../../Logic/Tags/Tag"
- import type { WayId } from "../../../Models/OsmFeature"
- import Loading from "../../Base/Loading.svelte"
- import type { GlobalFilter } from "../../../Models/GlobalFilter"
- import { onDestroy } from "svelte"
- import NextButton from "../../Base/NextButton.svelte"
- import BackButton from "../../Base/BackButton.svelte"
- import ToSvelte from "../../Base/ToSvelte.svelte"
- import Svg from "../../../Svg"
- import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
- import { twJoin } from "tailwind-merge"
+ import type { SpecialVisualizationState } from "../../SpecialVisualization";
+ import PresetList from "./PresetList.svelte";
+ import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig";
+ import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
+ import Tr from "../../Base/Tr.svelte";
+ import SubtleButton from "../../Base/SubtleButton.svelte";
+ import FromHtml from "../../Base/FromHtml.svelte";
+ import Translations from "../../i18n/Translations.js";
+ import TagHint from "../TagHint.svelte";
+ import { And } from "../../../Logic/Tags/And.js";
+ import LoginToggle from "../../Base/LoginToggle.svelte";
+ import Constants from "../../../Models/Constants.js";
+ import FilteredLayer from "../../../Models/FilteredLayer";
+ import { Store, UIEventSource } from "../../../Logic/UIEventSource";
+ import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid";
+ import LoginButton from "../../Base/LoginButton.svelte";
+ import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte";
+ import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction";
+ import { OsmWay } from "../../../Logic/Osm/OsmObject";
+ import { Tag } from "../../../Logic/Tags/Tag";
+ import type { WayId } from "../../../Models/OsmFeature";
+ import Loading from "../../Base/Loading.svelte";
+ import type { GlobalFilter } from "../../../Models/GlobalFilter";
+ import { onDestroy } from "svelte";
+ import NextButton from "../../Base/NextButton.svelte";
+ import BackButton from "../../Base/BackButton.svelte";
+ import ToSvelte from "../../Base/ToSvelte.svelte";
+ import Svg from "../../../Svg";
+ import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte";
+ import { twJoin } from "tailwind-merge";
- export let coordinate: { lon: number; lat: number }
- export let state: SpecialVisualizationState
+ export let coordinate: { lon: number; lat: number };
+ export let state: SpecialVisualizationState;
let selectedPreset: {
preset: PresetConfig
layer: LayerConfig
icon: string
tags: Record
- } = undefined
- let checkedOfGlobalFilters: number = 0
- let confirmedCategory = false
+ } = undefined;
+ let checkedOfGlobalFilters: number = 0;
+ let confirmedCategory = false;
$: if (selectedPreset === undefined) {
- confirmedCategory = false
- creating = false
- checkedOfGlobalFilters = 0
+ confirmedCategory = false;
+ creating = false;
+ checkedOfGlobalFilters = 0;
}
- let flayer: FilteredLayer = undefined
- let layerIsDisplayed: UIEventSource | undefined = undefined
- let layerHasFilters: Store | undefined = undefined
- let globalFilter: UIEventSource = state.layerState.globalFilters
- let _globalFilter: GlobalFilter[] = []
+ let flayer: FilteredLayer = undefined;
+ let layerIsDisplayed: UIEventSource | undefined = undefined;
+ let layerHasFilters: Store | undefined = undefined;
+ let globalFilter: UIEventSource = state.layerState.globalFilters;
+ let _globalFilter: GlobalFilter[] = [];
onDestroy(
globalFilter.addCallbackAndRun((globalFilter) => {
- console.log("Global filters are", globalFilter)
- _globalFilter = globalFilter ?? []
+ console.log("Global filters are", globalFilter);
+ _globalFilter = globalFilter ?? [];
})
- )
+ );
$: {
- flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id)
- layerIsDisplayed = flayer?.isDisplayed
- layerHasFilters = flayer?.hasFilter
+ flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id);
+ layerIsDisplayed = flayer?.isDisplayed;
+ layerHasFilters = flayer?.hasFilter;
}
- const t = Translations.t.general.add
+ const t = Translations.t.general.add;
- const zoom = state.mapProperties.zoom
+ const zoom = state.mapProperties.zoom;
- const isLoading = state.dataIsLoading
- let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
- let snappedToObject: UIEventSource = new UIEventSource(undefined)
+ const isLoading = state.dataIsLoading;
+ let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined);
+ let snappedToObject: UIEventSource = new UIEventSource(undefined);
// Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map
- let preciseInputIsTapped = false
+ let preciseInputIsTapped = false;
- let creating = false
+ let creating = false;
/**
* Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
* Will delete the lastclick-location
*/
function abort() {
- state.selectedElement.setData(undefined)
+ state.selectedElement.setData(undefined);
// When aborted, we force the contributors to place the pin _again_
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
- state.lastClickObject.features.setData([])
- preciseInputIsTapped = false
+ state.lastClickObject.features.setData([]);
+ preciseInputIsTapped = false;
}
async function confirm() {
- creating = true
- const location: { lon: number; lat: number } = preciseCoordinate.data
- const snapTo: WayId | undefined = snappedToObject.data
+ creating = true;
+ const location: { lon: number; lat: number } = preciseCoordinate.data;
+ const snapTo: WayId | undefined = snappedToObject.data;
const tags: Tag[] = selectedPreset.preset.tags.concat(
..._globalFilter.map((f) => f?.onNewPoint?.tags ?? [])
- )
- console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags)
+ );
+ console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags);
- let snapToWay: undefined | OsmWay = undefined
+ let snapToWay: undefined | OsmWay = undefined;
if (snapTo !== undefined) {
- const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0)
+ const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0);
if (downloaded !== "deleted") {
- snapToWay = downloaded
+ snapToWay = downloaded;
}
}
@@ -113,33 +113,42 @@
theme: state.layout?.id ?? "unkown",
changeType: "create",
snapOnto: snapToWay,
- })
- await state.changes.applyAction(newElementAction)
- state.newFeatures.features.ping()
+ reusePointWithinMeters: 1
+ });
+ await state.changes.applyAction(newElementAction);
+ state.newFeatures.features.ping();
// The 'changes' should have created a new point, which added this into the 'featureProperties'
- const newId = newElementAction.newElementId
- console.log("Applied pending changes, fetching store for", newId)
- const tagsStore = state.featureProperties.getStore(newId)
+ const newId = newElementAction.newElementId;
+ console.log("Applied pending changes, fetching store for", newId);
+ const tagsStore = state.featureProperties.getStore(newId);
+ if (!tagsStore) {
+ console.error("Bug: no tagsStore found for", newId);
+ }
{
// Set some metainfo
- const properties = tagsStore.data
+ const properties = tagsStore.data;
if (snapTo) {
// metatags (starting with underscore) are not uploaded, so we can safely mark this
- delete properties["_referencing_ways"]
- properties["_referencing_ways"] = `["${snapTo}"]`
+ delete properties["_referencing_ways"];
+ properties["_referencing_ways"] = `["${snapTo}"]`;
}
- properties["_backend"] = state.osmConnection.Backend()
- properties["_last_edit:timestamp"] = new Date().toISOString()
- const userdetails = state.osmConnection.userDetails.data
- properties["_last_edit:contributor"] = userdetails.name
- properties["_last_edit:uid"] = "" + userdetails.uid
- tagsStore.ping()
+ properties["_backend"] = state.osmConnection.Backend();
+ properties["_last_edit:timestamp"] = new Date().toISOString();
+ const userdetails = state.osmConnection.userDetails.data;
+ properties["_last_edit:contributor"] = userdetails.name;
+ properties["_last_edit:uid"] = "" + userdetails.uid;
+ tagsStore.ping();
}
- const feature = state.indexedFeatures.featuresById.data.get(newId)
- abort()
- state.selectedLayer.setData(selectedPreset.layer)
- state.selectedElement.setData(feature)
- tagsStore.ping()
+ const feature = state.indexedFeatures.featuresById.data.get(newId);
+ console.log("Selecting feature", feature, "and opening their popup");
+ abort();
+ state.selectedLayer.setData(selectedPreset.layer);
+ state.selectedElement.setData(feature);
+ tagsStore.ping();
+ }
+
+ function confirmSync() {
+ confirm().then(_ => console.debug("New point successfully handled")).catch(e => console.error("Handling the new point went wrong due to", e));
}
@@ -328,7 +337,7 @@
"absolute top-0 flex w-full justify-center p-12"
)}
>
-
+
diff --git a/src/UI/Popup/CreateNewNote.svelte b/src/UI/Popup/CreateNewNote.svelte
index 4bdb92b23c..10eda741a2 100644
--- a/src/UI/Popup/CreateNewNote.svelte
+++ b/src/UI/Popup/CreateNewNote.svelte
@@ -62,8 +62,13 @@
state.newFeatures.features.data.push(feature)
state.newFeatures.features.ping()
state.selectedElement?.setData(feature)
+ if(state.featureProperties.trackFeature){
+ state.featureProperties.trackFeature(feature)
+ }
comment.setData("")
created = true
+ state.selectedElement.setData(feature)
+ state.selectedLayer.setData(state.layerState.filteredLayers.get("note"))
}
diff --git a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte
index 8eada0a7fb..2cbfb0850a 100644
--- a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte
+++ b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte
@@ -38,7 +38,6 @@
const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined
let currentState: "start" | "confirm" | "applying" | "deleted" = "start"
$: {
- console.log("Current state is", currentState, $canBeDeleted, canBeDeletedReason)
deleteAbility.CheckDeleteability(true)
}
@@ -55,7 +54,6 @@
let actionToTake: OsmChangeAction
const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {}))
const deleteReason = changedProperties[DeleteConfig.deleteReasonKey]
- console.log("Deleting! Hard?:", canBeDeleted.data, deleteReason)
if (deleteReason) {
// This is a proper, hard deletion
actionToTake = new DeleteAction(
diff --git a/src/UI/Popup/LinkableImage.svelte b/src/UI/Popup/LinkableImage.svelte
index eeb3c19c7b..2aa280f986 100644
--- a/src/UI/Popup/LinkableImage.svelte
+++ b/src/UI/Popup/LinkableImage.svelte
@@ -6,7 +6,7 @@
import ToSvelte from "../Base/ToSvelte.svelte"
import { AttributedImage } from "../Image/AttributedImage"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
- import LinkPicture from "../../Logic/Osm/Actions/LinkPicture"
+ 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"
@@ -23,25 +23,24 @@
export let feature: Feature
export let layer: LayerConfig
- export let linkable = true
- let isLinked = false
+ export let linkable = true;
+ let isLinked = Object.values(tags.data).some(v => image.pictureUrl === v);
- const t = Translations.t.image.nearby
- const c = [lon, lat]
+ const t = Translations.t.image.nearby;
+ const c = [lon, lat];
let attributedImage = new AttributedImage({
url: image.thumbUrl ?? image.pictureUrl,
provider: AllImageProviders.byName(image.provider),
- date: new Date(image.date),
- })
- let distance = Math.round(
- GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
- )
+ date: new Date(image.date)
+ });
+ let distance = Math.round(GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c));
+
$: {
const currentTags = tags.data
const key = Object.keys(image.osmTags)[0]
const url = image.osmTags[key]
if (isLinked) {
- const action = new LinkPicture(currentTags.id, key, url, currentTags, {
+ const action = new LinkImageAction(currentTags.id, key, url, currentTags, {
theme: state.layout.id,
changeType: "link-image",
})
diff --git a/src/UI/Popup/NoteCommentElement.ts b/src/UI/Popup/NoteCommentElement.ts
index c72244924c..4d4e59c7eb 100644
--- a/src/UI/Popup/NoteCommentElement.ts
+++ b/src/UI/Popup/NoteCommentElement.ts
@@ -41,7 +41,7 @@ export default class NoteCommentElement extends Combine {
let userinfo = Stores.FromPromise(
Utils.downloadJsonCached(
- "https://www.openstreetmap.org/api/0.6/user/" + comment.uid,
+ "https://api.openstreetmap.org/api/0.6/user/" + comment.uid,
24 * 60 * 60 * 1000
)
)
@@ -56,7 +56,7 @@ export default class NoteCommentElement extends Combine {
)
const htmlElement = document.createElement("div")
- htmlElement.innerHTML = comment.html
+ htmlElement.innerHTML = Utils.purify(comment.html)
const images = Array.from(htmlElement.getElementsByTagName("a"))
.map((link) => link.href)
.filter((link) => {
diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts
index 198fc7a938..ed5a7acc4f 100644
--- a/src/UI/Popup/PlantNetDetectionViz.ts
+++ b/src/UI/Popup/PlantNetDetectionViz.ts
@@ -1,18 +1,13 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource"
-import Toggle from "../Input/Toggle"
-import Lazy from "../Base/Lazy"
import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
-import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch"
import Wikidata from "../../Logic/Web/Wikidata"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag"
-import { SubtleButton } from "../Base/SubtleButton"
-import Combine from "../Base/Combine"
-import Svg from "../../Svg"
-import Translations from "../i18n/Translations"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
+import SvelteUIElement from "../Base/SvelteUIElement"
+import PlantNet from "../PlantNet/PlantNet.svelte"
export class PlantNetDetectionViz implements SpecialVisualization {
funcName = "plantnet_detection"
@@ -37,45 +32,29 @@ export class PlantNetDetectionViz implements SpecialVisualization {
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
}
- const detect = new UIEventSource(false)
- const toggle = new Toggle(
- new Lazy(() => {
- const allProvidedImages: Store = AllImageProviders.LoadImagesFor(
- tags,
- imagePrefixes
- )
- const allImages: Store = allProvidedImages.map((pi) =>
- pi.map((pi) => pi.url)
- )
- return new PlantNetSpeciesSearch(allImages, async (selectedWikidata) => {
- selectedWikidata = Wikidata.ExtractKey(selectedWikidata)
- const change = new ChangeTagAction(
- tags.data.id,
- new And([
- new Tag("species:wikidata", selectedWikidata),
- new Tag("source:species:wikidata", "PlantNet.org AI"),
- ]),
- tags.data,
- {
- theme: state.layout.id,
- changeType: "plantnet-ai-detection",
- }
- )
- await state.changes.applyAction(change)
- })
- }),
- new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() =>
- detect.setData(true)
- ),
- detect
+ const allProvidedImages: Store = AllImageProviders.LoadImagesFor(
+ tags,
+ imagePrefixes
)
+ const imageUrls: Store = allProvidedImages.map((pi) => pi.map((pi) => pi.url))
- return new Combine([
- toggle,
- new Combine([
- Svg.plantnet_logo_svg().SetClass("w-10 h-10 p-1 mr-1 bg-white rounded-full"),
- Translations.t.plantDetection.poweredByPlantnet,
- ]).SetClass("flex p-2 bg-gray-200 rounded-xl self-end"),
- ]).SetClass("flex flex-col")
+ async function applySpecies(selectedWikidata) {
+ selectedWikidata = Wikidata.ExtractKey(selectedWikidata)
+ const change = new ChangeTagAction(
+ tags.data.id,
+ new And([
+ new Tag("species:wikidata", selectedWikidata),
+ new Tag("source:species:wikidata", "PlantNet.org AI"),
+ ]),
+ tags.data,
+ {
+ theme: state.layout.id,
+ changeType: "plantnet-ai-detection",
+ }
+ )
+ await state.changes.applyAction(change)
+ }
+
+ return new SvelteUIElement(PlantNet, { imageUrls, onConfirm: applySpecies })
}
}
diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte
index 9e031f169b..d50d0efcdd 100644
--- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte
+++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte
@@ -88,7 +88,6 @@
checkedMappings.push(unseenFreeformValues.length > 0)
}
}
- console.log("Inited 'checkMappings' to", checkedMappings)
if (confg.freeform?.key) {
if (!confg.multiAnswer) {
// Somehow, setting multi-answer freeform values is broken if this is not set
diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts
index 4cb3aeb02b..a4e00100b8 100644
--- a/src/UI/SpecialVisualization.ts
+++ b/src/UI/SpecialVisualization.ts
@@ -1,113 +1,118 @@
-import { Store, UIEventSource } from "../Logic/UIEventSource"
-import BaseUIElement from "./BaseUIElement"
-import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
-import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
-import { OsmConnection } from "../Logic/Osm/OsmConnection"
-import { Changes } from "../Logic/Osm/Changes"
-import { ExportableMap, MapProperties } from "../Models/MapProperties"
-import LayerState from "../Logic/State/LayerState"
-import { Feature, Geometry, Point } from "geojson"
-import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
-import { MangroveIdentity } from "../Logic/Web/MangroveReviews"
-import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
-import LayerConfig from "../Models/ThemeConfig/LayerConfig"
-import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
-import { MenuState } from "../Models/MenuState"
-import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"
-import { RasterLayerPolygon } from "../Models/RasterLayers"
+import { Store, UIEventSource } from "../Logic/UIEventSource";
+import BaseUIElement from "./BaseUIElement";
+import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
+import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource";
+import { OsmConnection } from "../Logic/Osm/OsmConnection";
+import { Changes } from "../Logic/Osm/Changes";
+import { ExportableMap, MapProperties } from "../Models/MapProperties";
+import LayerState from "../Logic/State/LayerState";
+import { Feature, Geometry, Point } from "geojson";
+import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
+import { MangroveIdentity } from "../Logic/Web/MangroveReviews";
+import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore";
+import LayerConfig from "../Models/ThemeConfig/LayerConfig";
+import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
+import { MenuState } from "../Models/MenuState";
+import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader";
+import { RasterLayerPolygon } from "../Models/RasterLayers";
+import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager";
+import { OsmTags } from "../Models/OsmFeature";
/**
* The state needed to render a special Visualisation.
*/
export interface SpecialVisualizationState {
- readonly guistate: MenuState
- readonly layout: LayoutConfig
- readonly featureSwitches: FeatureSwitchState
+ readonly guistate: MenuState;
+ readonly layout: LayoutConfig;
+ readonly featureSwitches: FeatureSwitchState;
- readonly layerState: LayerState
- readonly featureProperties: { getStore(id: string): UIEventSource> }
+ readonly layerState: LayerState;
+ readonly featureProperties: { getStore(id: string): UIEventSource>, trackFeature?(feature: { properties: OsmTags }) };
- readonly indexedFeatures: IndexedFeatureSource
+ readonly indexedFeatures: IndexedFeatureSource;
- /**
- * Some features will create a new element that should be displayed.
- * These can be injected by appending them to this featuresource (and pinging it)
- */
- readonly newFeatures: WritableFeatureSource
+ /**
+ * Some features will create a new element that should be displayed.
+ * These can be injected by appending them to this featuresource (and pinging it)
+ */
+ readonly newFeatures: WritableFeatureSource;
- readonly historicalUserLocations: WritableFeatureSource>
+ readonly historicalUserLocations: WritableFeatureSource>;
- readonly osmConnection: OsmConnection
- readonly featureSwitchUserbadge: Store
- readonly featureSwitchIsTesting: Store
- readonly changes: Changes
- readonly osmObjectDownloader: OsmObjectDownloader
- /**
- * State of the main map
- */
- readonly mapProperties: MapProperties & ExportableMap
+ readonly osmConnection: OsmConnection;
+ readonly featureSwitchUserbadge: Store;
+ readonly featureSwitchIsTesting: Store;
+ readonly changes: Changes;
+ readonly osmObjectDownloader: OsmObjectDownloader;
+ /**
+ * State of the main map
+ */
+ readonly mapProperties: MapProperties & ExportableMap;
- readonly selectedElement: UIEventSource
- /**
- * Works together with 'selectedElement' to indicate what properties should be displayed
- */
- readonly selectedLayer: UIEventSource
- readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
+ readonly selectedElement: UIEventSource;
+ /**
+ * Works together with 'selectedElement' to indicate what properties should be displayed
+ */
+ readonly selectedLayer: UIEventSource;
+ readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>;
- /**
- * If data is currently being fetched from external sources
- */
- readonly dataIsLoading: Store
- /**
- * Only needed for 'ReplaceGeometryAction'
- */
- readonly fullNodeDatabase?: FullNodeDatabaseSource
+ /**
+ * If data is currently being fetched from external sources
+ */
+ readonly dataIsLoading: Store;
+ /**
+ * Only needed for 'ReplaceGeometryAction'
+ */
+ readonly fullNodeDatabase?: FullNodeDatabaseSource;
- readonly perLayer: ReadonlyMap
- readonly userRelatedState: {
- readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
- readonly mangroveIdentity: MangroveIdentity
- readonly showAllQuestionsAtOnce: UIEventSource
- readonly preferencesAsTags: Store>
- readonly language: UIEventSource
- }
- readonly lastClickObject: WritableFeatureSource
+ readonly perLayer: ReadonlyMap;
+ readonly userRelatedState: {
+ readonly imageLicense: UIEventSource;
+ readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
+ readonly mangroveIdentity: MangroveIdentity
+ readonly showAllQuestionsAtOnce: UIEventSource
+ readonly preferencesAsTags: Store>
+ readonly language: UIEventSource
+ };
+ readonly lastClickObject: WritableFeatureSource;
- readonly availableLayers: Store
+ readonly availableLayers: Store;
+
+ readonly imageUploadManager: ImageUploadManager;
}
export interface SpecialVisualization {
- readonly funcName: string
- readonly docs: string | BaseUIElement
- readonly example?: string
+ readonly funcName: string;
+ readonly docs: string | BaseUIElement;
+ readonly example?: string;
- /**
- * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
- */
- readonly needsNodeDatabase?: boolean
- readonly args: {
- name: string
- defaultValue?: string
- doc: string
- required?: false | boolean
- }[]
- readonly getLayerDependencies?: (argument: string[]) => string[]
+ /**
+ * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
+ */
+ readonly needsNodeDatabase?: boolean;
+ readonly args: {
+ name: string
+ defaultValue?: string
+ doc: string
+ required?: false | boolean
+ }[];
+ readonly getLayerDependencies?: (argument: string[]) => string[];
- structuredExamples?(): { feature: Feature>; args: string[] }[]
+ structuredExamples?(): { feature: Feature>; args: string[] }[];
- constr(
- state: SpecialVisualizationState,
- tagSource: UIEventSource>,
- argument: string[],
- feature: Feature,
- layer: LayerConfig
- ): BaseUIElement
+ constr(
+ state: SpecialVisualizationState,
+ tagSource: UIEventSource>,
+ argument: string[],
+ feature: Feature,
+ layer: LayerConfig
+ ): BaseUIElement;
}
export type RenderingSpecification =
- | string
- | {
- func: SpecialVisualization
- args: string[]
- style: string
- }
+ | string
+ | {
+ func: SpecialVisualization
+ args: string[]
+ style: string
+}
diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts
index a1803e5390..23d1b57c8b 100644
--- a/src/UI/SpecialVisualizations.ts
+++ b/src/UI/SpecialVisualizations.ts
@@ -1,79 +1,71 @@
-import Combine from "./Base/Combine"
-import { FixedUiElement } from "./Base/FixedUiElement"
-import BaseUIElement from "./BaseUIElement"
-import Title from "./Base/Title"
-import Table from "./Base/Table"
-import {
- RenderingSpecification,
- SpecialVisualization,
- SpecialVisualizationState,
-} from "./SpecialVisualization"
-import { HistogramViz } from "./Popup/HistogramViz"
-import { MinimapViz } from "./Popup/MinimapViz"
-import { ShareLinkViz } from "./Popup/ShareLinkViz"
-import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
-import { MultiApplyViz } from "./Popup/MultiApplyViz"
-import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"
-import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
-import TagApplyButton from "./Popup/TagApplyButton"
-import { CloseNoteButton } from "./Popup/CloseNoteButton"
-import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
-import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"
-import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
-import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"
-import { ImageCarousel } from "./Image/ImageCarousel"
-import { ImageUploadFlow } from "./Image/ImageUploadFlow"
-import { VariableUiElement } from "./Base/VariableUIElement"
-import { Utils } from "../Utils"
-import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"
-import { Translation } from "./i18n/Translation"
-import Translations from "./i18n/Translations"
-import ReviewForm from "./Reviews/ReviewForm"
-import ReviewElement from "./Reviews/ReviewElement"
-import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
-import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"
-import { SubtleButton } from "./Base/SubtleButton"
-import Svg from "../Svg"
-import NoteCommentElement from "./Popup/NoteCommentElement"
-import ImgurUploader from "../Logic/ImageProviders/ImgurUploader"
-import FileSelectorButton from "./Input/FileSelectorButton"
-import { LoginToggle } from "./Popup/LoginButton"
-import Toggle from "./Input/Toggle"
-import { SubstitutedTranslation } from "./SubstitutedTranslation"
-import List from "./Base/List"
-import StatisticsPanel from "./BigComponents/StatisticsPanel"
-import AutoApplyButton from "./Popup/AutoApplyButton"
-import { LanguageElement } from "./Popup/LanguageElement"
-import FeatureReviews from "../Logic/Web/MangroveReviews"
-import Maproulette from "../Logic/Maproulette"
-import SvelteUIElement from "./Base/SvelteUIElement"
-import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
-import QuestionViz from "./Popup/QuestionViz"
-import { Feature, Point } from "geojson"
-import { GeoOperations } from "../Logic/GeoOperations"
-import CreateNewNote from "./Popup/CreateNewNote.svelte"
-import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
-import UserProfile from "./BigComponents/UserProfile.svelte"
-import LanguagePicker from "./LanguagePicker"
-import Link from "./Base/Link"
-import LayerConfig from "../Models/ThemeConfig/LayerConfig"
-import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
-import { OsmTags, WayId } from "../Models/OsmFeature"
-import MoveWizard from "./Popup/MoveWizard"
-import SplitRoadWizard from "./Popup/SplitRoadWizard"
-import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
-import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
-import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
-import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"
-import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"
-import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"
-import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
-import { OpenJosm } from "./BigComponents/OpenJosm"
-import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
-import FediverseValidator from "./InputElement/Validators/FediverseValidator"
-import SendEmail from "./Popup/SendEmail.svelte"
-import NearbyImages from "./Popup/NearbyImages.svelte"
-import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"
+import Combine from "./Base/Combine";
+import { FixedUiElement } from "./Base/FixedUiElement";
+import BaseUIElement from "./BaseUIElement";
+import Title from "./Base/Title";
+import Table from "./Base/Table";
+import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization";
+import { HistogramViz } from "./Popup/HistogramViz";
+import { MinimapViz } from "./Popup/MinimapViz";
+import { ShareLinkViz } from "./Popup/ShareLinkViz";
+import { UploadToOsmViz } from "./Popup/UploadToOsmViz";
+import { MultiApplyViz } from "./Popup/MultiApplyViz";
+import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz";
+import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz";
+import TagApplyButton from "./Popup/TagApplyButton";
+import { CloseNoteButton } from "./Popup/CloseNoteButton";
+import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis";
+import { Store, Stores, UIEventSource } from "../Logic/UIEventSource";
+import AllTagsPanel from "./Popup/AllTagsPanel.svelte";
+import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
+import { ImageCarousel } from "./Image/ImageCarousel";
+import { VariableUiElement } from "./Base/VariableUIElement";
+import { Utils } from "../Utils";
+import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata";
+import { Translation } from "./i18n/Translation";
+import Translations from "./i18n/Translations";
+import ReviewForm from "./Reviews/ReviewForm";
+import ReviewElement from "./Reviews/ReviewElement";
+import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization";
+import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
+import { SubtleButton } from "./Base/SubtleButton";
+import Svg from "../Svg";
+import NoteCommentElement from "./Popup/NoteCommentElement";
+import { SubstitutedTranslation } from "./SubstitutedTranslation";
+import List from "./Base/List";
+import StatisticsPanel from "./BigComponents/StatisticsPanel";
+import AutoApplyButton from "./Popup/AutoApplyButton";
+import { LanguageElement } from "./Popup/LanguageElement";
+import FeatureReviews from "../Logic/Web/MangroveReviews";
+import Maproulette from "../Logic/Maproulette";
+import SvelteUIElement from "./Base/SvelteUIElement";
+import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource";
+import QuestionViz from "./Popup/QuestionViz";
+import { Feature, Point } from "geojson";
+import { GeoOperations } from "../Logic/GeoOperations";
+import CreateNewNote from "./Popup/CreateNewNote.svelte";
+import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte";
+import UserProfile from "./BigComponents/UserProfile.svelte";
+import LanguagePicker from "./LanguagePicker";
+import Link from "./Base/Link";
+import LayerConfig from "../Models/ThemeConfig/LayerConfig";
+import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
+import { OsmTags, WayId } from "../Models/OsmFeature";
+import MoveWizard from "./Popup/MoveWizard";
+import SplitRoadWizard from "./Popup/SplitRoadWizard";
+import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz";
+import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte";
+import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte";
+import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz";
+import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz";
+import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz";
+import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte";
+import { OpenJosm } from "./BigComponents/OpenJosm";
+import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
+import FediverseValidator from "./InputElement/Validators/FediverseValidator";
+import SendEmail from "./Popup/SendEmail.svelte";
+import NearbyImages from "./Popup/NearbyImages.svelte";
+import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte";
+import UploadImage from "./Image/UploadImage.svelte";
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@@ -272,6 +264,7 @@ export default class SpecialVisualizations {
SpecialVisualizations.specialVisualizations
.map((sp) => sp.funcName + "()")
.join(", ")
+
}
}
@@ -538,7 +531,7 @@ export default class SpecialVisualizations {
const keys = args[0].split(";").map((k) => k.trim())
const wikiIds: Store = tagsSource.map((tags) => {
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
- return tags[key]?.split(";")?.map((id) => id.trim())
+ return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
})
return new SvelteUIElement(WikipediaPanel, {
wikiIds,
@@ -616,16 +609,18 @@ export default class SpecialVisualizations {
{
name: "image-key",
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
- defaultValue: "image",
+ required: false
},
{
name: "label",
doc: "The text to show on the button",
- defaultValue: "Add image",
+ required: false
},
],
constr: (state, tags, args) => {
- return new ImageUploadFlow(tags, state, args[0], args[1])
+ return new SvelteUIElement(UploadImage, {
+ state,tags, labelText: args[1], image: args[0]
+ })
},
},
{
@@ -864,43 +859,11 @@ export default class SpecialVisualizations {
},
],
constr: (state, tags, args) => {
- const isUploading = new UIEventSource(false)
- const t = Translations.t.notes
const id = tags.data[args[0] ?? "id"]
-
- const uploader = new ImgurUploader(async (url) => {
- isUploading.setData(false)
- await state.osmConnection.addCommentToNote(id, url)
- NoteCommentElement.addCommentTo(url, tags, state)
- })
-
- const label = new Combine([
- Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "),
- Translations.t.image.addPicture,
- ]).SetClass(
- "p-2 border-4 border-black rounded-full font-bold h-full align-center w-full flex justify-center"
- )
-
- const fileSelector = new FileSelectorButton(label)
- fileSelector.GetValue().addCallback((filelist) => {
- isUploading.setData(true)
- uploader.uploadMany("Image for osm.org/note/" + id, "CC0", filelist)
- })
- const ti = Translations.t.image
- const uploadPanel = new Combine([
- fileSelector,
- ti.respectPrivacy.SetClass("text-sm"),
- ]).SetClass("flex flex-col")
- return new LoginToggle(
- new Toggle(
- Translations.t.image.uploadingPicture.SetClass("alert"),
- uploadPanel,
- isUploading
- ),
- t.loginToAddPicture,
- state
- )
- },
+ tags = state.featureProperties.getStore(id)
+ console.log("Id is", id)
+ return new SvelteUIElement(UploadImage, {state, tags})
+ }
},
{
funcName: "title",
@@ -1171,7 +1134,7 @@ export default class SpecialVisualizations {
new Link(
Utils.SubstituteKeys(text, tags),
Utils.SubstituteKeys(href, tags),
- download === undefined,
+ download === undefined && !href.startsWith("#"),
Utils.SubstituteKeys(download, tags)
).SetClass(classnames)
)
diff --git a/src/UI/StylesheetTestGui.svelte b/src/UI/StylesheetTestGui.svelte
index cd44cf3289..452ebb378d 100644
--- a/src/UI/StylesheetTestGui.svelte
+++ b/src/UI/StylesheetTestGui.svelte
@@ -29,6 +29,10 @@
areas, where some buttons might appear.
+
+ Highly interactive area (mostly: active question)
+
+