From b59524733c8949f5389ed88ea1e885b83bc998c0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 04:50:44 +0100 Subject: [PATCH] Refactoring: move specialVisulations into groups --- src/UI/Base/LoginButton.svelte | 17 +- src/UI/BigComponents/Giggity.svelte | 106 --- src/UI/Popup/Notes/AddNoteCommentViz.ts | 22 - src/UI/Popup/ShareLinkViz.ts | 5 +- src/UI/Popup/SplitRoadWizard.svelte | 101 +-- .../FavouriteVisualisations.ts | 53 ++ .../ImageVisualisations.ts | 122 +++ .../NoteVisualisations.ts | 139 ++++ .../ReviewSpecialVisualisations.ts | 147 ++++ .../SettingsVisualisations.ts | 103 +++ .../UISpecialVisualisations.ts | 204 +++++ src/UI/SpecialVisualization.ts | 36 + src/UI/SpecialVisualizations.ts | 739 +----------------- 13 files changed, 894 insertions(+), 900 deletions(-) delete mode 100644 src/UI/BigComponents/Giggity.svelte delete mode 100644 src/UI/Popup/Notes/AddNoteCommentViz.ts create mode 100644 src/UI/SpecialVisualisations/FavouriteVisualisations.ts create mode 100644 src/UI/SpecialVisualisations/ImageVisualisations.ts create mode 100644 src/UI/SpecialVisualisations/NoteVisualisations.ts create mode 100644 src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts create mode 100644 src/UI/SpecialVisualisations/SettingsVisualisations.ts create mode 100644 src/UI/SpecialVisualisations/UISpecialVisualisations.ts diff --git a/src/UI/Base/LoginButton.svelte b/src/UI/Base/LoginButton.svelte index 72ef23b24..21d332726 100644 --- a/src/UI/Base/LoginButton.svelte +++ b/src/UI/Base/LoginButton.svelte @@ -2,8 +2,6 @@ import { OsmConnection } from "../../Logic/Osm/OsmConnection" import Translations from "../i18n/Translations.js" import Tr from "./Tr.svelte" - import Login from "../../assets/svg/Login.svelte" - import ArrowRightOnRectangle from "@babeard/svelte-heroicons/solid/ArrowRightOnRectangle" import ArrowLeftOnRectangle from "@babeard/svelte-heroicons/solid/ArrowLeftOnRectangle" export let osmConnection: OsmConnection @@ -12,11 +10,14 @@ if (osmConnection === undefined) { console.error("No osmConnection passed into loginButton") } + let isLoggedIn = osmConnection.isLoggedIn - +{#if !$isLoggedIn} + +{/if} diff --git a/src/UI/BigComponents/Giggity.svelte b/src/UI/BigComponents/Giggity.svelte deleted file mode 100644 index 6107b8ca7..000000000 --- a/src/UI/BigComponents/Giggity.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - -{#if $events === undefined} - Loading giggity events from {giggityUrl} -{:else if $events.length === 0} - No upcoming events in this room -{:else} -
-

Upcoming events

- {#each $events as event} -
- {#if event.url} -

{event.title}

- {:else} -

{event.title}

- {/if} -
{event.start}
- By {event.persons} -
- {event.abstract} -
- {event.url} -
- {/each} -
-{/if} diff --git a/src/UI/Popup/Notes/AddNoteCommentViz.ts b/src/UI/Popup/Notes/AddNoteCommentViz.ts deleted file mode 100644 index 0a223ef8b..000000000 --- a/src/UI/Popup/Notes/AddNoteCommentViz.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" -import { UIEventSource } from "../../../Logic/UIEventSource" -import Constants from "../../../Models/Constants" -import SvelteUIElement from "../../Base/SvelteUIElement" -import AddNoteComment from "./AddNoteComment.svelte" - -export class AddNoteCommentViz implements SpecialVisualization { - funcName = "add_note_comment" - needsUrls = [Constants.osmAuthConfig.url] - docs = "A textfield to add a comment to a node (with the option to close the note)." - args = [ - { - name: "Id-key", - doc: "The property name where the ID of the note to close can be found", - defaultValue: "id", - }, - ] - - public constr(state: SpecialVisualizationState, tags: UIEventSource>) { - return new SvelteUIElement(AddNoteComment, { state, tags }) - } -} diff --git a/src/UI/Popup/ShareLinkViz.ts b/src/UI/Popup/ShareLinkViz.ts index 123883c63..487573664 100644 --- a/src/UI/Popup/ShareLinkViz.ts +++ b/src/UI/Popup/ShareLinkViz.ts @@ -1,11 +1,12 @@ import { UIEventSource } from "../../Logic/UIEventSource" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import ShareButton from "../Base/ShareButton.svelte" -export class ShareLinkViz implements SpecialVisualization { +export class ShareLinkViz implements SpecialVisualizationSvelte { funcName = "share_link" + group = "default" docs = "Creates a link that (attempts to) open the native 'share'-screen" example = "{share_link()} to share the current page, {share_link()} to share the given url" diff --git a/src/UI/Popup/SplitRoadWizard.svelte b/src/UI/Popup/SplitRoadWizard.svelte index 4b0b79a9f..524625ae7 100644 --- a/src/UI/Popup/SplitRoadWizard.svelte +++ b/src/UI/Popup/SplitRoadWizard.svelte @@ -1,5 +1,5 @@ +{#if $id.startsWith("way/")} + + - - - - {#if step === "deleted"} - - {:else if step === "initial"} - - {:else if step === "loading_way"} - - {:else if step === "splitting"} -
-
- -
-
- { + {#if step === "deleted"} + + {:else if step === "initial"} + + {:else if step === "loading_way"} + + {:else if step === "splitting"} +
+
+ +
+
+ { splitPoints.set([]) step = "initial" }} - > - - - doSplit()} - > - - + > + + + doSplit()} + > + + +
-
- {:else if step === "has_been_split"} - - - {/if} - + {:else if step === "has_been_split"} + + + {/if} + +{/if} diff --git a/src/UI/SpecialVisualisations/FavouriteVisualisations.ts b/src/UI/SpecialVisualisations/FavouriteVisualisations.ts new file mode 100644 index 000000000..21ff2df87 --- /dev/null +++ b/src/UI/SpecialVisualisations/FavouriteVisualisations.ts @@ -0,0 +1,53 @@ +import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { UIEventSource } from "../../Logic/UIEventSource" +import { Feature } from "geojson" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import SvelteUIElement from "../Base/SvelteUIElement" +import MarkAsFavourite from "../Popup/MarkAsFavourite.svelte" +import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte" + +export class FavouriteVisualisations { + public static initList(): SpecialVisualizationSvelte[] { + return [{ + funcName: "favourite_status", + + docs: "A button that allows a (logged in) contributor to mark a location as a favourite location", + args: [], + group: "favourites", + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement { + return new SvelteUIElement(MarkAsFavourite, { + tags: tagSource, + state, + layer, + feature + }) + } + }, + { + funcName: "favourite_icon", + group: "favourites", + docs: "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon", + args: [], + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement { + return new SvelteUIElement(MarkAsFavouriteMini, { + tags: tagSource, + state, + layer, + feature + }) + } + }] + } +} diff --git a/src/UI/SpecialVisualisations/ImageVisualisations.ts b/src/UI/SpecialVisualisations/ImageVisualisations.ts new file mode 100644 index 000000000..f75f5c5cd --- /dev/null +++ b/src/UI/SpecialVisualisations/ImageVisualisations.ts @@ -0,0 +1,122 @@ +import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" +import SvelteUIElement from "../Base/SvelteUIElement" +import ImageCarousel from "../Image/ImageCarousel.svelte" +import { Imgur } from "../../Logic/ImageProviders/Imgur" +import UploadImage from "../Image/UploadImage.svelte" +import { CombinedFetcher } from "../../Logic/Web/NearbyImagesSearch" +import { UIEventSource } from "../../Logic/UIEventSource" +import { Feature } from "geojson" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { GeoOperations } from "../../Logic/GeoOperations" +import NearbyImages from "../Image/NearbyImages.svelte" +import NearbyImagesCollapsed from "../Image/NearbyImagesCollapsed.svelte" + +class NearbyImageVis implements SpecialVisualizationSvelte { + // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests + args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [ + { + name: "mode", + defaultValue: "closed", + doc: "Either `open` or `closed`. If `open`, then the image carousel will always be shown" + }, + { + name: "readonly", + required: false, + doc: "If 'readonly' or 'yes', will not show the 'link'-button" + } + ] + group: "images" + docs = + "A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature" + funcName = "nearby_images" + needsUrls = CombinedFetcher.apiUrls + + constr( + state: SpecialVisualizationState, + tags: UIEventSource>, + args: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement { + const isOpen = args[0] === "open" + const readonly = args[1] === "readonly" || args[1] === "yes" + const [lon, lat] = GeoOperations.centerpointCoordinates(feature) + return new SvelteUIElement(isOpen ? NearbyImages : NearbyImagesCollapsed, { + tags, + state, + lon, + lat, + feature, + layer, + linkable: !readonly + }) + } +} + +export class ImageVisualisations { + + static initList(): SpecialVisualizationSvelte[] { + return [ + new NearbyImageVis(), + { + funcName: "image_carousel", + group: "images", + docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)", + args: [ + { + name: "image_key", + defaultValue: AllImageProviders.defaultKeys.join(","), + doc: "The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Multiple values are allowed if ';'-separated " + } + ], + needsUrls: AllImageProviders.apiUrls, + constr: (state, tags, args) => { + let imagePrefixes: string[] = undefined + if (args.length > 0) { + imagePrefixes = [].concat(...args.map((a) => a.split(","))) + } + const images = AllImageProviders.loadImagesFor(tags, imagePrefixes) + const estimated = tags.mapD(tags => AllImageProviders.estimateNumberOfImages(tags, imagePrefixes)) + return new SvelteUIElement(ImageCarousel, { state, tags, images, estimated }) + } + }, + { + funcName: "image_upload", + group: "images", + docs: "Creates a button where a user can upload an image to IMGUR", + needsUrls: [Imgur.apiUrl, ...Imgur.supportingUrls], + args: [ + { + name: "image-key", + doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", + required: false + }, + { + name: "label", + doc: "The text to show on the button", + required: false + }, + { + name: "disable_blur", + doc: "If set to 'true' or 'yes', then face blurring will be disabled. To be used sparingly", + required: false + } + ], + constr: (state, tags, args, feature) => { + const targetKey = args[0] === "" ? undefined : args[0] + const noBlur = args[3]?.toLowerCase()?.trim() + return new SvelteUIElement(UploadImage, { + state, + tags, + targetKey, + feature, + labelText: args[1], + image: args[2], + noBlur: noBlur === "true" || noBlur === "yes" + }) + } + }] + } + +} diff --git a/src/UI/SpecialVisualisations/NoteVisualisations.ts b/src/UI/SpecialVisualisations/NoteVisualisations.ts new file mode 100644 index 000000000..6f4ccd5d8 --- /dev/null +++ b/src/UI/SpecialVisualisations/NoteVisualisations.ts @@ -0,0 +1,139 @@ +import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import Constants from "../../Models/Constants" +import { UIEventSource } from "../../Logic/UIEventSource" +import { Feature } from "geojson" +import { GeoOperations } from "../../Logic/GeoOperations" +import SvelteUIElement from "../Base/SvelteUIElement" +import CreateNewNote from "../Popup/Notes/CreateNewNote.svelte" +import { Utils } from "../../Utils" +import CloseNoteButton from "../Popup/Notes/CloseNoteButton.svelte" +import Translations from "../i18n/Translations" +import AddNoteComment from "../Popup/Notes/AddNoteComment.svelte" +import { Imgur } from "../../Logic/ImageProviders/Imgur" +import UploadImage from "../Image/UploadImage.svelte" + +class CloseNoteViz implements SpecialVisualizationSvelte { + public readonly funcName = "close_note" + public readonly needsUrls = [Constants.osmAuthConfig.url] + public readonly docs = + "Button to close a note. A predefined text can be defined to close the note with. If the note is already closed, will show a small text." + public readonly args = [ + { + name: "text", + doc: "Text to show on this button", + required: true + }, + { + name: "icon", + doc: "Icon to show", + defaultValue: "checkmark.svg" + }, + { + name: "idkey", + doc: "The property name where the ID of the note to close can be found", + defaultValue: "id" + }, + { + name: "comment", + doc: "Text to add onto the note when closing" + }, + { + name: "minZoom", + doc: "If set, only show the closenote button if zoomed in enough" + }, + { + name: "zoomButton", + doc: "Text to show if not zoomed in enough" + } + ] + public readonly group: "notes" + + public constr( + state: SpecialVisualizationState, + tags: UIEventSource>, + args: string[] + ): SvelteUIElement { + const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs( + this.args, + args + ) + + return new SvelteUIElement(CloseNoteButton, { + state, + tags, + icon, + idkey, + message: comment, + text: Translations.T(text), + minzoom: minZoom, + zoomMoreMessage: zoomButton + }) + } +} + + +class AddNoteCommentViz implements SpecialVisualizationSvelte { + funcName = "add_note_comment" + needsUrls = [Constants.osmAuthConfig.url] + docs = "A textfield to add a comment to a node (with the option to close the note)." + args = [ + { + name: "Id-key", + doc: "The property name where the ID of the note to close can be found", + defaultValue: "id" + } + ] + public readonly group: "notes" + + + public constr(state: SpecialVisualizationState, tags: UIEventSource>): SvelteUIElement { + return new SvelteUIElement(AddNoteComment, { state, tags }) + } +} + + +export class NoteVisualisations { + public static initList(): SpecialVisualizationSvelte[] { + return [new AddNoteCommentViz(), + { + funcName: "open_note", + args: [], + group: "notes", + needsUrls: [Constants.osmAuthConfig.url], + docs: "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled", + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature + ): SvelteUIElement { + const [lon, lat] = GeoOperations.centerpointCoordinates(feature) + return new SvelteUIElement(CreateNewNote, { + state, + coordinate: new UIEventSource({ lon, lat }) + }) + } + }, + { + funcName: "add_image_to_note", + docs: "Adds an image to a node", + args: [ + { + name: "Id-key", + doc: "The property name where the ID of the note to close can be found", + defaultValue: "id" + } + ], + group: "notes", + needsUrls: [Imgur.apiUrl, ...Imgur.supportingUrls], + + constr: (state, tags, args, feature, layer) => { + const id = tags.data[args[0] ?? "id"] + tags = state.featureProperties.getStore(id) + return new SvelteUIElement(UploadImage, { state, tags, layer, feature }) + } + }, + new CloseNoteViz() + ] + } +} diff --git a/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts b/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts new file mode 100644 index 000000000..7678f6f72 --- /dev/null +++ b/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts @@ -0,0 +1,147 @@ +import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { MangroveReviews } from "mangrove-reviews-typescript" +import FeatureReviews from "../../Logic/Web/MangroveReviews" +import SvelteUIElement from "../Base/SvelteUIElement" +import StarsBarIcon from "../Reviews/StarsBarIcon.svelte" +import ReviewForm from "../Reviews/ReviewForm.svelte" +import AllReviews from "../Reviews/AllReviews.svelte" +import { UIEventSource } from "../../Logic/UIEventSource" +import ImportReviewIdentity from "../Reviews/ImportReviewIdentity.svelte" + +export class ReviewSpecialVisualisations { + public static initList(): SpecialVisualizationSvelte[] { + return [{ + funcName: "rating", + group: "reviews", + docs: "Shows stars which represent the average rating on mangrove.", + needsUrls: [MangroveReviews.ORIGINAL_API], + args: [ + { + name: "subjectKey", + defaultValue: "name", + doc: "The key to use to determine the subject. If the value is specified, the subject will be tags[subjectKey] and will use this to filter the reviews." + }, + { + name: "fallback", + doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value" + } + ], + constr: (state, tags, args, feature) => { + const nameKey = args[0] ?? "name" + const fallbackName = args[1] + const reviews = FeatureReviews.construct( + feature, + tags, + state.userRelatedState.mangroveIdentity, + { + nameKey: nameKey, + fallbackName + }, + state.featureSwitchIsTesting + ) + return new SvelteUIElement(StarsBarIcon, { + score: reviews.average + }) + } + }, + { + funcName: "create_review", + group: "reviews", + + docs: "Invites the contributor to leave a review. Somewhat small UI-element until interacted", + needsUrls: [MangroveReviews.ORIGINAL_API], + args: [ + { + name: "subjectKey", + defaultValue: "name", + doc: "The key to use to determine the subject. If specified, the subject will be tags[subjectKey]" + }, + { + name: "fallback", + doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value" + }, + { + name: "question", + doc: "The question to ask during the review" + } + ], + constr: (state, tags, args, feature, layer) => { + const nameKey = args[0] ?? "name" + const fallbackName = args[1] + const question = args[2] + const reviews = FeatureReviews.construct( + feature, + tags, + state.userRelatedState?.mangroveIdentity, + { + nameKey: nameKey, + fallbackName + }, + state.featureSwitchIsTesting + ) + return new SvelteUIElement(ReviewForm, { + reviews, + state, + tags, + feature, + layer, + question + }) + } + }, + { + funcName: "list_reviews", + group: "reviews", + + docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten", + needsUrls: [MangroveReviews.ORIGINAL_API], + args: [ + { + name: "subjectKey", + defaultValue: "name", + doc: "The key to use to determine the subject. If specified, the subject will be tags[subjectKey]" + }, + { + name: "fallback", + doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value" + } + ], + constr: (state, tags, args, feature, layer) => { + const nameKey = args[0] ?? "name" + const fallbackName = args[1] + const reviews = FeatureReviews.construct( + feature, + tags, + state.userRelatedState?.mangroveIdentity, + { + nameKey: nameKey, + fallbackName + }, + state.featureSwitchIsTesting + ) + return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer }) + } + }, + { + funcName: "import_mangrove_key", + group: "settings", + + docs: "Only makes sense in the usersettings. Allows to import a mangrove public key and to use this to make reviews", + args: [ + { + name: "text", + doc: "The text that is shown on the button" + } + ], + needsUrls: [], + constr( + state: SpecialVisualizationState, + _: UIEventSource>, + argument: string[] + ): SvelteUIElement { + const [text] = argument + return new SvelteUIElement(ImportReviewIdentity, { state, text }) + } + }] + } +} diff --git a/src/UI/SpecialVisualisations/SettingsVisualisations.ts b/src/UI/SpecialVisualisations/SettingsVisualisations.ts new file mode 100644 index 000000000..c882c38dc --- /dev/null +++ b/src/UI/SpecialVisualisations/SettingsVisualisations.ts @@ -0,0 +1,103 @@ +import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import SvelteUIElement from "../Base/SvelteUIElement" +import DisabledQuestions from "../Popup/DisabledQuestions.svelte" +import Constants from "../../Models/Constants" +import LogoutButton from "../Base/LogoutButton.svelte" +import LoginButton from "../Base/LoginButton.svelte" +import ThemeViewState from "../../Models/ThemeViewState" +import OrientationDebugPanel from "../Debug/OrientationDebugPanel.svelte" +import AllTagsPanel from "../Popup/AllTagsPanel.svelte" +import { UIEventSource } from "../../Logic/UIEventSource" +import { Feature } from "geojson" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import ClearCaches from "../Popup/ClearCaches.svelte" + +export class SettingsVisualisations { + public static initList(): SpecialVisualizationSvelte[] { + return [ + { + funcName: "disabled_questions", + group: "settings", + docs: "Shows which questions are disabled for every layer. Used in 'settings'", + needsUrls: [], + args: [], + constr(state) { + return new SvelteUIElement(DisabledQuestions, { state }) + } + }, + { + funcName: "gyroscope_all_tags", + group: "settings", + docs: "Shows the current tags of the GPS-representing object, used for debugging", + args: [], + constr(): SvelteUIElement { + return new SvelteUIElement(OrientationDebugPanel, {}) + } + }, + { + funcName: "gps_all_tags", + group: "settings", + docs: "Shows the current tags of the GPS-representing object, used for debugging", + args: [], + constr( + state: SpecialVisualizationState + ): SvelteUIElement { + const tags = (( + state + )).geolocation.currentUserLocation.features.map( + (features) => features[0]?.properties + ) + return new SvelteUIElement(AllTagsPanel, { + state, + tags + }) + } + }, + { + funcName: "clear_caches", + docs: "A button which clears the locally downloaded data and the service worker. Login status etc will be kept", + args: [ + { + name: "text", + required: true, + doc: "The text to show on the button" + } + ], + group: "settings", + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement { + return new SvelteUIElement(ClearCaches, { + msg: argument[0] ?? "Clear local caches" + }) + } + }, + { + funcName: "login_button", + args: [], + docs: "Show a login button", + needsUrls: [], + group: "settings", + constr(state: SpecialVisualizationState): SvelteUIElement { + return new SvelteUIElement(LoginButton, { osmConnection: state.osmConnection }) + } + }, + + { + funcName: "logout", + args: [], + needsUrls: [Constants.osmAuthConfig.url], + docs: "Shows a button where the user can log out", + group: "settings", + constr(state: SpecialVisualizationState): SvelteUIElement { + return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection }) + } + } + + ] + } +} diff --git a/src/UI/SpecialVisualisations/UISpecialVisualisations.ts b/src/UI/SpecialVisualisations/UISpecialVisualisations.ts new file mode 100644 index 000000000..73c5cd1d2 --- /dev/null +++ b/src/UI/SpecialVisualisations/UISpecialVisualisations.ts @@ -0,0 +1,204 @@ +import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import SvelteUIElement from "../Base/SvelteUIElement" +import { UIEventSource } from "../../Logic/UIEventSource" +import { Feature } from "geojson" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import Questionbox from "../Popup/TagRendering/Questionbox.svelte" +import MinimapViz from "../Popup/MinimapViz.svelte" +import SplitRoadWizard from "../Popup/SplitRoadWizard.svelte" +import MoveWizard from "../Popup/MoveWizard.svelte" +import DeleteWizard from "../Popup/DeleteFlow/DeleteWizard.svelte" +import QrCode from "../Popup/QrCode.svelte" +import NothingKnown from "../Popup/NothingKnown.svelte" +import { ShareLinkViz } from "../Popup/ShareLinkViz" + +/** + * Thin wrapper around QuestionBox.svelte to include it into the special Visualisations + */ +class QuestionViz implements SpecialVisualizationSvelte { + funcName = "questions" + needsUrls = [] + docs = + "The special element which shows the questions which are unkown. Added by default if not yet there" + args = [ + { + name: "labels", + doc: "One or more ';'-separated labels. If these are given, only questions with these labels will be given. Use `unlabeled` for all questions that don't have an explicit label. If none given, all questions will be shown" + }, + { + name: "blacklisted-labels", + doc: "One or more ';'-separated labels of questions which should _not_ be included" + } + ] + svelteBased = true + group: "default" + + constr( + state: SpecialVisualizationState, + tags: UIEventSource>, + args: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement { + + const labels = args[0] + ?.split(";") + ?.map((s) => s.trim()) + ?.filter((s) => s !== "") + const blacklist = args[1] + ?.split(";") + ?.map((s) => s.trim()) + ?.filter((s) => s !== "") + return new SvelteUIElement(Questionbox, { + layer, + tags, + selectedElement: feature, + state, + onlyForLabels: labels, + notForLabels: blacklist + }) + } +} + +export class UISpecialVisualisations { + public static initList(): SpecialVisualizationSvelte [] { + return [new QuestionViz(), + { + funcName: "minimap", + docs: "A small map showing the selected feature.", + needsUrls: [], + group: "default", + + args: [ + { + doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close", + name: "zoomlevel", + defaultValue: "18" + }, + { + doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled)", + name: "idKey", + defaultValue: "id" + } + ], + example: + "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`", + + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + args: string[], + feature: Feature + ): SvelteUIElement { + return new SvelteUIElement(MinimapViz, { state, args, feature, tagSource }) + } + }, + { + funcName: "split_button", + docs: "Adds a button which allows to split a way", + args: [], + group: "default", + + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource> + ): SvelteUIElement { + return new SvelteUIElement(SplitRoadWizard, { id: tagSource.map(pr => pr.id), state }) + } + }, + { + funcName: "move_button", + docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config", + args: [], + group: "default", + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement { + if (feature.geometry.type !== "Point") { + return undefined + } + + return new SvelteUIElement(MoveWizard, { + state, + featureToMove: feature, + layer + }) + } + }, + { + funcName: "delete_button", + docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config", + args: [], + group: "default", + + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement { + if (!layer.deletion) { + return undefined + } + return new SvelteUIElement(DeleteWizard, { + tags: tagSource, + deleteConfig: layer.deletion, + state, + feature, + layer + }) + } + }, + { + funcName: "qr_code", + args: [], + group: "default", + docs: "Generates a QR-code to share the selected object", + constr( + state: SpecialVisualizationState, + tags: UIEventSource>, + argument: string[], + feature: Feature + ): SvelteUIElement { + return new SvelteUIElement(QrCode, { state, tags, feature }) + } + }, + { + funcName: "if_nothing_known", + args: [ + { + name: "text", + doc: "Text to show", + required: true + }, + { name: "cssClasses", doc: "Classes to apply onto the text" } + ], + group: "default", + docs: "Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question", + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement { + const text = argument[0] + const cssClasses = argument[1] + return new SvelteUIElement(NothingKnown, { + state, + tags: tagSource, + layer, + text, + cssClasses + }) + } + }, + new ShareLinkViz() + ] + } +} diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index d032165ac..8b97c64a7 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -21,6 +21,7 @@ import ShowDataLayer from "./Map/ShowDataLayer" import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch" import UserRelatedState from "../Logic/State/UserRelatedState" import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" +import SvelteUIElement from "./Base/SvelteUIElement" /** * The state needed to render a special Visualisation. @@ -83,6 +84,7 @@ export interface SpecialVisualizationState { export interface SpecialVisualization { readonly funcName: string readonly docs: string | BaseUIElement + readonly group?: string readonly example?: string readonly needsUrls?: string[] | ((args: string[]) => string | string[]) @@ -109,6 +111,40 @@ export interface SpecialVisualization { ): BaseUIElement } + +export interface SpecialVisualizationSvelte { + readonly funcName: string + readonly docs: string + /** + * The 'group' is merely what association it has in the docs + */ + readonly group: string + readonly example?: string + readonly needsUrls?: string[] | ((args: string[]) => 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[] }[] + + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): SvelteUIElement +} + export type RenderingSpecification = | string | { diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 8844bedbf..18bd5d793 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -5,17 +5,13 @@ import Title from "./Base/Title" import { default as FeatureTitle } from "./Popup/Title.svelte" import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization" import { HistogramViz } from "./Popup/HistogramViz" -import MinimapViz from "./Popup/MinimapViz.svelte" -import { ShareLinkViz } from "./Popup/ShareLinkViz" import { UploadToOsmViz } from "./Popup/UploadToOsmViz" import { MultiApplyViz } from "./Popup/MultiApplyViz" -import { AddNoteCommentViz } from "./Popup/Notes/AddNoteCommentViz" import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" import TagApplyButton from "./Popup/TagApplyButton" import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" import { ImmutableStore, Store, Stores, UIEventSource } from "../Logic/UIEventSource" import AllTagsPanel from "./Popup/AllTagsPanel.svelte" -import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" import { VariableUiElement } from "./Base/VariableUIElement" import { Utils } from "../Utils" import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" @@ -27,13 +23,11 @@ import List from "./Base/List" import StatisticsPanel from "./BigComponents/StatisticsPanel" import AutoApplyButton from "./Popup/AutoApplyButton" import { LanguageElement } from "./Popup/LanguageElement/LanguageElement" -import FeatureReviews from "../Logic/Web/MangroveReviews" import Maproulette, { MaprouletteTask } from "../Logic/Maproulette" import SvelteUIElement from "./Base/SvelteUIElement" import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import { Feature, GeoJsonProperties, LineString } from "geojson" import { GeoOperations } from "../Logic/GeoOperations" -import CreateNewNote from "./Popup/Notes/CreateNewNote.svelte" import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" @@ -43,101 +37,40 @@ import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svel 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 OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" import SendEmail from "./Popup/SendEmail.svelte" -import UploadImage from "./Image/UploadImage.svelte" -import { Imgur } from "../Logic/ImageProviders/Imgur" import Constants from "../Models/Constants" -import { MangroveReviews } from "mangrove-reviews-typescript" import Wikipedia from "../Logic/Web/Wikipedia" -import AllReviews from "./Reviews/AllReviews.svelte" -import StarsBarIcon from "./Reviews/StarsBarIcon.svelte" -import ReviewForm from "./Reviews/ReviewForm.svelte" -import Questionbox from "./Popup/TagRendering/Questionbox.svelte" import { TagUtils } from "../Logic/Tags/TagUtils" -import Giggity from "./BigComponents/Giggity.svelte" -import ThemeViewState from "../Models/ThemeViewState" import LanguagePicker from "./InputElement/LanguagePicker.svelte" -import LogoutButton from "./Base/LogoutButton.svelte" import OpenJosm from "./Base/OpenJosm.svelte" -import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte" -import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte" import NextChangeViz from "./OpeningHours/NextChangeViz.svelte" -import NearbyImages from "./Image/NearbyImages.svelte" -import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte" -import MoveWizard from "./Popup/MoveWizard.svelte" import { Unit } from "../Models/Unit" -import OrientationDebugPanel from "./Debug/OrientationDebugPanel.svelte" import MaprouletteSetStatus from "./MapRoulette/MaprouletteSetStatus.svelte" import DirectionIndicator from "./Base/DirectionIndicator.svelte" import ComparisonTool from "./Comparison/ComparisonTool.svelte" import SpecialTranslation from "./Popup/TagRendering/SpecialTranslation.svelte" import SpecialVisualisationUtils from "./SpecialVisualisationUtils" -import LoginButton from "./Base/LoginButton.svelte" import Toggle from "./Input/Toggle" -import ImportReviewIdentity from "./Reviews/ImportReviewIdentity.svelte" import LinkedDataLoader from "../Logic/Web/LinkedDataLoader" -import SplitRoadWizard from "./Popup/SplitRoadWizard.svelte" import DynLink from "./Base/DynLink.svelte" import Locale from "./i18n/Locale" import LanguageUtils from "../Utils/LanguageUtils" import MarkdownUtils from "../Utils/MarkdownUtils" import Trash from "@babeard/svelte-heroicons/mini/Trash" -import NothingKnown from "./Popup/NothingKnown.svelte" -import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch" import { And } from "../Logic/Tags/And" -import CloseNoteButton from "./Popup/Notes/CloseNoteButton.svelte" import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte" -import QrCode from "./Popup/QrCode.svelte" -import ClearCaches from "./Popup/ClearCaches.svelte" import GroupedView from "./Popup/GroupedView.svelte" import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" import NoteCommentElement from "./Popup/Notes/NoteCommentElement.svelte" -import DisabledQuestions from "./Popup/DisabledQuestions.svelte" import FediverseLink from "./Popup/FediverseLink.svelte" -import ImageCarousel from "./Image/ImageCarousel.svelte" +import { ImageVisualisations } from "./SpecialVisualisations/ImageVisualisations" +import { NoteVisualisations } from "./SpecialVisualisations/NoteVisualisations" +import { FavouriteVisualisations } from "./SpecialVisualisations/FavouriteVisualisations" +import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisualisations" +import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations" +import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations" -class NearbyImageVis implements SpecialVisualization { - // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests - args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [ - { - name: "mode", - defaultValue: "closed", - doc: "Either `open` or `closed`. If `open`, then the image carousel will always be shown", - }, - { - name: "readonly", - required: false, - doc: "If 'readonly' or 'yes', will not show the 'link'-button", - }, - ] - docs = - "A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature" - funcName = "nearby_images" - needsUrls = CombinedFetcher.apiUrls - - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - const isOpen = args[0] === "open" - const readonly = args[1] === "readonly" || args[1] === "yes" - const [lon, lat] = GeoOperations.centerpointCoordinates(feature) - return new SvelteUIElement(isOpen ? NearbyImages : NearbyImagesCollapsed, { - tags, - state, - lon, - lat, - feature, - layer, - linkable: !readonly, - }) - } -} class StealViz implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -212,109 +145,7 @@ class StealViz implements SpecialVisualization { } } -class CloseNoteViz implements SpecialVisualization { - public readonly funcName = "close_note" - public readonly needsUrls = [Constants.osmAuthConfig.url] - public readonly docs = - "Button to close a note. A predefined text can be defined to close the note with. If the note is already closed, will show a small text." - public readonly args = [ - { - name: "text", - doc: "Text to show on this button", - required: true, - }, - { - name: "icon", - doc: "Icon to show", - defaultValue: "checkmark.svg", - }, - { - name: "idkey", - doc: "The property name where the ID of the note to close can be found", - defaultValue: "id", - }, - { - name: "comment", - doc: "Text to add onto the note when closing", - }, - { - name: "minZoom", - doc: "If set, only show the closenote button if zoomed in enough", - }, - { - name: "zoomButton", - doc: "Text to show if not zoomed in enough", - }, - ] - public constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[] - ): SvelteUIElement { - const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs( - this.args, - args - ) - - return new SvelteUIElement(CloseNoteButton, { - state, - tags, - icon, - idkey, - message: comment, - text: Translations.T(text), - minzoom: minZoom, - zoomMoreMessage: zoomButton, - }) - } -} - -/** - * Thin wrapper around QuestionBox.svelte to include it into the special Visualisations - */ -export class QuestionViz implements SpecialVisualization { - funcName = "questions" - needsUrls = [] - docs = - "The special element which shows the questions which are unkown. Added by default if not yet there" - args = [ - { - name: "labels", - doc: "One or more ';'-separated labels. If these are given, only questions with these labels will be given. Use `unlabeled` for all questions that don't have an explicit label. If none given, all questions will be shown", - }, - { - name: "blacklisted-labels", - doc: "One or more ';'-separated labels of questions which should _not_ be included", - }, - ] - svelteBased = true - - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - const labels = args[0] - ?.split(";") - ?.map((s) => s.trim()) - ?.filter((s) => s !== "") - const blacklist = args[1] - ?.split(";") - ?.map((s) => s.trim()) - ?.filter((s) => s !== "") - return new SvelteUIElement(Questionbox, { - layer, - tags, - selectedElement: feature, - state, - onlyForLabels: labels, - notForLabels: blacklist, - }) - } -} export default class SpecialVisualizations { public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList() @@ -421,7 +252,12 @@ export default class SpecialVisualizations { private static initList(): SpecialVisualization[] { const specialVisualizations: SpecialVisualization[] = [ - new QuestionViz(), + ...ImageVisualisations.initList(), + ...NoteVisualisations.initList(), + ...FavouriteVisualisations.initList(), + ...UISpecialVisualisations.initList(), + ...SettingsVisualisations.initList(), + ...ReviewSpecialVisualisations.initList(), { funcName: "add_new_point", docs: "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`", @@ -456,115 +292,11 @@ export default class SpecialVisualizations { ) }, }, - { - funcName: "logout", - args: [], - needsUrls: [Constants.osmAuthConfig.url], - docs: "Shows a button where the user can log out", - constr(state: SpecialVisualizationState): BaseUIElement { - return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection }) - }, - }, new HistogramViz(), new StealViz(), - { - funcName: "minimap", - docs: "A small map showing the selected feature.", - needsUrls: [], - args: [ - { - doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close", - name: "zoomlevel", - defaultValue: "18", - }, - { - doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled)", - name: "idKey", - defaultValue: "id", - }, - ], - example: - "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`", - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature - ): SvelteUIElement { - return new SvelteUIElement(MinimapViz, { state, args, feature, tagSource }) - }, - }, - { - funcName: "split_button", - docs: "Adds a button which allows to split a way", - args: [], - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource> - ): BaseUIElement { - return new VariableUiElement( - tagSource - .map((tags) => tags.id) - .map((id) => { - if (id.startsWith("way/")) { - return new SvelteUIElement(SplitRoadWizard, { id, state }) - } - return undefined - }) - ) - }, - }, - { - funcName: "move_button", - docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config", - args: [], - - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - if (feature.geometry.type !== "Point") { - return undefined - } - - return new SvelteUIElement(MoveWizard, { - state, - featureToMove: feature, - layer, - }) - }, - }, - { - funcName: "delete_button", - docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config", - args: [], - - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - if (!layer.deletion) { - return undefined - } - return new SvelteUIElement(DeleteWizard, { - tags: tagSource, - deleteConfig: layer.deletion, - state, - feature, - layer, - }) - }, - }, - new ShareLinkViz(), { funcName: "export_as_gpx", docs: "Exports the selected feature as GPX-file", @@ -598,26 +330,7 @@ export default class SpecialVisualizations { }, new UploadToOsmViz(), new MultiApplyViz(), - new AddNoteCommentViz(), - { - funcName: "open_note", - args: [], - needsUrls: [Constants.osmAuthConfig.url], - docs: "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled", - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature - ): BaseUIElement { - const [lon, lat] = GeoOperations.centerpointCoordinates(feature) - return new SvelteUIElement(CreateNewNote, { - state, - coordinate: new UIEventSource({ lon, lat }), - }) - }, - }, - new CloseNoteViz(), + new PlantNetDetectionViz(), new TagApplyButton(), @@ -625,7 +338,6 @@ export default class SpecialVisualizations { new PointImportButtonViz(), new WayImportButtonViz(), new ConflateImportButtonViz(), - new NearbyImageVis(), { funcName: "wikipedia", @@ -700,172 +412,10 @@ export default class SpecialVisualizations { layer: LayerConfig ) => new SvelteUIElement(AllTagsPanel, { tags, layer }), }, - { - funcName: "image_carousel", - docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)", - args: [ - { - name: "image_key", - defaultValue: AllImageProviders.defaultKeys.join(","), - doc: "The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Multiple values are allowed if ';'-separated ", - }, - ], - needsUrls: AllImageProviders.apiUrls, - constr: (state, tags, args) => { - let imagePrefixes: string[] = undefined - if (args.length > 0) { - imagePrefixes = [].concat(...args.map((a) => a.split(","))) - } - const images = AllImageProviders.loadImagesFor(tags, imagePrefixes) - const estimated = tags.mapD(tags => AllImageProviders.estimateNumberOfImages(tags, imagePrefixes)) - return new SvelteUIElement(ImageCarousel, { state, tags, images, estimated }) - }, - }, - { - funcName: "image_upload", - docs: "Creates a button where a user can upload an image to IMGUR", - needsUrls: [Imgur.apiUrl, ...Imgur.supportingUrls], - args: [ - { - name: "image-key", - doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", - required: false, - }, - { - name: "label", - doc: "The text to show on the button", - required: false, - }, - { - name: "disable_blur", - doc: "If set to 'true' or 'yes', then face blurring will be disabled. To be used sparingly", - required: false, - }, - ], - constr: (state, tags, args, feature) => { - const targetKey = args[0] === "" ? undefined : args[0] - const noBlur = args[3]?.toLowerCase()?.trim() - return new SvelteUIElement(UploadImage, { - state, - tags, - targetKey, - feature, - labelText: args[1], - image: args[2], - noBlur: noBlur === "true" || noBlur === "yes", - }) - }, - }, - { - funcName: "rating", - docs: "Shows stars which represent the average rating on mangrove.", - needsUrls: [MangroveReviews.ORIGINAL_API], - args: [ - { - name: "subjectKey", - defaultValue: "name", - doc: "The key to use to determine the subject. If the value is specified, the subject will be tags[subjectKey] and will use this to filter the reviews.", - }, - { - name: "fallback", - doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value", - }, - ], - constr: (state, tags, args, feature) => { - const nameKey = args[0] ?? "name" - const fallbackName = args[1] - const reviews = FeatureReviews.construct( - feature, - tags, - state.userRelatedState.mangroveIdentity, - { - nameKey: nameKey, - fallbackName, - }, - state.featureSwitchIsTesting - ) - return new SvelteUIElement(StarsBarIcon, { - score: reviews.average, - }) - }, - }, - - { - funcName: "create_review", - docs: "Invites the contributor to leave a review. Somewhat small UI-element until interacted", - needsUrls: [MangroveReviews.ORIGINAL_API], - args: [ - { - name: "subjectKey", - defaultValue: "name", - doc: "The key to use to determine the subject. If specified, the subject will be tags[subjectKey]", - }, - { - name: "fallback", - doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value", - }, - { - name: "question", - doc: "The question to ask during the review", - }, - ], - constr: (state, tags, args, feature, layer) => { - const nameKey = args[0] ?? "name" - const fallbackName = args[1] - const question = args[2] - const reviews = FeatureReviews.construct( - feature, - tags, - state.userRelatedState?.mangroveIdentity, - { - nameKey: nameKey, - fallbackName, - }, - state.featureSwitchIsTesting - ) - return new SvelteUIElement(ReviewForm, { - reviews, - state, - tags, - feature, - layer, - question, - }) - }, - }, - { - funcName: "list_reviews", - docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten", - needsUrls: [MangroveReviews.ORIGINAL_API], - args: [ - { - name: "subjectKey", - defaultValue: "name", - doc: "The key to use to determine the subject. If specified, the subject will be tags[subjectKey]", - }, - { - name: "fallback", - doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value", - }, - ], - constr: (state, tags, args, feature, layer) => { - const nameKey = args[0] ?? "name" - const fallbackName = args[1] - const reviews = FeatureReviews.construct( - feature, - tags, - state.userRelatedState?.mangroveIdentity, - { - nameKey: nameKey, - fallbackName, - }, - state.featureSwitchIsTesting - ) - return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer }) - }, - }, { funcName: "reviews", + group: "reviews", + example: "`{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used", docs: "A pragmatic combination of `create_review` and `list_reviews`", @@ -873,16 +423,16 @@ export default class SpecialVisualizations { { name: "subjectKey", defaultValue: "name", - doc: "The key to use to determine the subject. If specified, the subject will be tags[subjectKey]", + doc: "The key to use to determine the subject. If specified, the subject will be tags[subjectKey]" }, { name: "fallback", - doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value", + doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value" }, { name: "question", - doc: "The question to ask in the review form. Optional", - }, + doc: "The question to ask in the review form. Optional" + } ], constr( state: SpecialVisualizationState, @@ -897,29 +447,12 @@ export default class SpecialVisualizations { .constr(state, tagSource, args, feature, layer), SpecialVisualizations.specialVisualisationsDict .get("list_reviews") - .constr(state, tagSource, args, feature, layer), + .constr(state, tagSource, args, feature, layer) ]) - }, - }, - { - funcName: "import_mangrove_key", - docs: "Only makes sense in the usersettings. Allows to import a mangrove public key and to use this to make reviews", - args: [ - { - name: "text", - doc: "The text that is shown on the button", - }, - ], - needsUrls: [], - constr( - state: SpecialVisualizationState, - _: UIEventSource>, - argument: string[] - ): BaseUIElement { - const [text] = argument - return new SvelteUIElement(ImportReviewIdentity, { state, text }) - }, + } }, + + { funcName: "opening_hours_table", docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.", @@ -1119,24 +652,6 @@ export default class SpecialVisualizations { }) ), }, - { - funcName: "add_image_to_note", - docs: "Adds an image to a node", - args: [ - { - name: "Id-key", - doc: "The property name where the ID of the note to close can be found", - defaultValue: "id", - }, - ], - needsUrls: [Imgur.apiUrl, ...Imgur.supportingUrls], - - constr: (state, tags, args, feature, layer) => { - const id = tags.data[args[0] ?? "id"] - tags = state.featureProperties.getStore(id) - return new SvelteUIElement(UploadImage, { state, tags, layer, feature }) - }, - }, { funcName: "title", args: [], @@ -1591,96 +1106,7 @@ export default class SpecialVisualizations { ) }, }, - { - funcName: "giggity", - args: [ - { - name: "giggityUrl", - required: true, - doc: "The URL of the giggity-XML", - }, - ], - docs: "Shows events that are happening based on a Giggity URL", - needsUrls: (args) => args[0], - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - const giggityUrl = argument[0] - return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl }) - }, - }, - { - funcName: "gps_all_tags", - - docs: "Shows the current tags of the GPS-representing object, used for debugging", - args: [], - constr( - state: SpecialVisualizationState, - _: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - const tags = (( - state - )).geolocation.currentUserLocation.features.map( - (features) => features[0]?.properties - ) - return new Combine([ - new SvelteUIElement(OrientationDebugPanel, {}), - new SvelteUIElement(AllTagsPanel, { - state, - tags, - }), - ]) - }, - }, - { - funcName: "favourite_status", - - docs: "A button that allows a (logged in) contributor to mark a location as a favourite location", - args: [], - - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - return new SvelteUIElement(MarkAsFavourite, { - tags: tagSource, - state, - layer, - feature, - }) - }, - }, - { - funcName: "favourite_icon", - - docs: "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon", - args: [], - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - return new SvelteUIElement(MarkAsFavouriteMini, { - tags: tagSource, - state, - layer, - feature, - }) - }, - }, { funcName: "direction_indicator", args: [], @@ -1696,19 +1122,7 @@ export default class SpecialVisualizations { return new SvelteUIElement(DirectionIndicator, { state, feature }) }, }, - { - funcName: "qr_code", - args: [], - docs: "Generates a QR-code to share the selected object", - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature - ): SvelteUIElement { - return new SvelteUIElement(QrCode, { state, tags, feature }) - }, - }, + { funcName: "direction_absolute", docs: "Converts compass degrees (with 0° being north, 90° being east, ...) into a human readable, translated direction such as 'north', 'northeast'", @@ -1784,25 +1198,6 @@ export default class SpecialVisualizations { }) }, }, - { - funcName: "login_button", - args: [], - docs: "Show a login button", - needsUrls: [], - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - return new Toggle( - undefined, - new SvelteUIElement(LoginButton, { osmConnection: state.osmConnection }), - state.osmConnection.isLoggedIn - ) - }, - }, { funcName: "linked_data_from_website", docs: "Attempts to load (via a proxy) the specified website and parsed ld+json from there. Suitable data will be offered to import into OSM", @@ -1937,35 +1332,7 @@ export default class SpecialVisualizations { ) }, }, - { - funcName: "if_nothing_known", - args: [ - { - name: "text", - doc: "Text to show", - required: true, - }, - { name: "cssClasses", doc: "Classes to apply onto the text" }, - ], - docs: "Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question", - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - const text = argument[0] - const cssClasses = argument[1] - return new SvelteUIElement(NothingKnown, { - state, - tags: tagSource, - layer, - text, - cssClasses, - }) - }, - }, + { funcName: "preset_description", docs: "Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty", @@ -1992,28 +1359,7 @@ export default class SpecialVisualizations { return new SvelteUIElement(PendingChangesIndicator, { state, compact: false }) }, }, - { - funcName: "clear_caches", - docs: "A button which clears the locally downloaded data and the service worker. Login status etc will be kept", - args: [ - { - name: "text", - required: true, - doc: "The text to show on the button", - }, - ], - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - return new SvelteUIElement(ClearCaches, { - msg: argument[0] ?? "Clear local caches", - }) - }, - }, + { funcName: "group", docs: "A collapsable group (accordion)", @@ -2080,38 +1426,7 @@ export default class SpecialVisualizations { }) }, }, - { - funcName: "clear_all", - docs: "Clears all user preferences", - needsUrls: [], - args: [ - { - name: "text", - doc: "Text to show on the button", - }, - ], - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { - const text = argument[0] - return new SubtleButton(undefined, text).onClick(() => { - state.osmConnection.preferencesHandler.ClearPreferences() - }) - }, - }, - { - funcName: "disabled_questions", - docs: "Shows which questions are disabled for every layer. Used in 'settings'", - needsUrls: [], - args: [], - constr(state) { - return new SvelteUIElement(DisabledQuestions, { state }) - }, - }, + ] specialVisualizations.push(new AutoApplyButton(specialVisualizations))