2023-06-14 20:39:36 +02:00
import { FixedUiElement } from "./Base/FixedUiElement"
2021-06-11 22:51:45 +02:00
import BaseUIElement from "./BaseUIElement"
2025-01-08 18:04:32 +01:00
import { default as FeatureTitle } from "./Popup/Title.svelte"
2025-04-18 00:26:53 +02:00
import { RenderingSpecification , SpecialVisualization , SpecialVisualizationState } from "./SpecialVisualization"
2023-06-14 20:39:36 +02:00
import { HistogramViz } from "./Popup/HistogramViz"
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
import { MultiApplyViz } from "./Popup/MultiApplyViz"
2025-01-29 20:37:04 +01:00
import { UIEventSource } from "../Logic/UIEventSource"
2025-03-30 13:51:35 +02:00
import AllTagsPanel from "./Popup/AllTagsPanel/AllTagsPanel.svelte"
2023-06-14 20:39:36 +02:00
import { VariableUiElement } from "./Base/VariableUIElement"
2024-08-13 17:56:33 +02:00
import { Translation } from "./i18n/Translation"
2022-11-02 13:47:34 +01:00
import Translations from "./i18n/Translations"
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
import AutoApplyButton from "./Popup/AutoApplyButton"
2024-01-25 03:13:18 +01:00
import { LanguageElement } from "./Popup/LanguageElement/LanguageElement"
2023-02-15 18:24:08 +01:00
import SvelteUIElement from "./Base/SvelteUIElement"
2025-01-29 20:37:04 +01:00
import { Feature , LineString } from "geojson"
2023-06-14 20:39:36 +02:00
import { GeoOperations } from "../Logic/GeoOperations"
2023-04-13 22:44:35 +02:00
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
2024-11-01 18:51:31 +01:00
import ExportFeatureButton from "./Popup/ExportFeatureButton.svelte"
2023-06-14 20:39:36 +02:00
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
2023-09-27 22:21:35 +02:00
import Constants from "../Models/Constants"
2023-10-22 01:30:05 +02:00
import { TagUtils } from "../Logic/Tags/TagUtils"
2023-12-04 03:32:25 +01:00
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
2023-12-12 03:46:51 +01:00
import { Unit } from "../Models/Unit"
2023-12-24 05:01:10 +01:00
import DirectionIndicator from "./Base/DirectionIndicator.svelte"
2024-01-25 13:41:33 +01:00
import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
2024-05-07 00:42:52 +02:00
import MarkdownUtils from "../Utils/MarkdownUtils"
2024-07-26 18:14:17 +02:00
import { And } from "../Logic/Tags/And"
2024-08-12 23:49:46 +02:00
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
2025-01-27 04:50:44 +01:00
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"
2025-01-29 20:37:04 +01:00
import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations"
2025-04-18 00:26:53 +02:00
import TagrenderingManipulationSpecialVisualisations
from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
import {
WebAndCommunicationSpecialVisualisations
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
2025-02-12 16:51:37 +01:00
import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte"
2025-03-04 22:21:24 +01:00
import AllFeaturesStatistics from "./Statistics/AllFeaturesStatistics.svelte"
2023-10-06 03:34:26 +02:00
2020-10-09 20:10:21 +02:00
export default class SpecialVisualizations {
2022-11-02 13:47:34 +01:00
public static specialVisualizations : SpecialVisualization [ ] = SpecialVisualizations . initList ( )
2024-08-14 13:53:56 +02:00
public static specialVisualisationsDict : Map < string , SpecialVisualization > = new Map <
string ,
SpecialVisualization
> ( )
2024-08-13 17:56:33 +02:00
static {
for ( const specialVisualization of SpecialVisualizations . specialVisualizations ) {
2024-08-14 13:53:56 +02:00
SpecialVisualizations . specialVisualisationsDict . set (
specialVisualization . funcName ,
2025-01-18 00:30:06 +01:00
specialVisualization
2024-08-14 13:53:56 +02:00
)
2024-08-13 17:56:33 +02:00
}
}
2021-12-12 02:59:24 +01:00
2024-05-07 00:42:52 +02:00
public static DocumentationFor ( viz : string | SpecialVisualization ) : string {
2023-02-14 00:09:04 +01:00
if ( typeof viz === "string" ) {
viz = SpecialVisualizations . specialVisualizations . find ( ( sv ) = > sv . funcName === viz )
}
if ( viz === undefined ) {
2024-05-07 00:42:52 +02:00
return ""
2023-02-14 00:09:04 +01:00
}
2024-06-16 16:06:26 +02:00
const example =
viz . example ? ?
"`{" + viz . funcName + "(" + viz . args . map ( ( arg ) = > arg . defaultValue ) . join ( "," ) + ")}`"
2024-05-07 00:42:52 +02:00
return [
"### " + viz . funcName ,
2023-02-14 00:09:04 +01:00
viz . docs ,
viz . args . length > 0
2024-05-07 00:42:52 +02:00
? MarkdownUtils . table (
2025-02-10 02:04:58 +01:00
[ "name" , "default" , "description" ] ,
viz . args . map ( ( arg ) = > {
let defaultArg = arg . defaultValue ? ? "_undefined_"
if ( defaultArg == "" ) {
defaultArg = "_empty string_"
}
return [ arg . name , defaultArg , arg . doc ]
} )
)
2023-02-14 00:09:04 +01:00
: undefined ,
2024-05-07 00:42:52 +02:00
"#### Example usage of " + viz . funcName ,
2025-02-10 02:04:58 +01:00
example ,
2024-05-07 00:42:52 +02:00
] . join ( "\n\n" )
2023-02-14 00:09:04 +01:00
}
2024-01-25 13:41:33 +01:00
public static constructSpecification (
template : string ,
2025-01-18 00:30:06 +01:00
extraMappings : SpecialVisualization [ ] = [ ]
2024-01-25 13:41:33 +01:00
) : RenderingSpecification [ ] {
2024-08-14 13:53:56 +02:00
return SpecialVisualisationUtils . constructSpecification (
template ,
SpecialVisualizations . specialVisualisationsDict ,
2025-01-18 00:30:06 +01:00
extraMappings
2024-08-14 13:53:56 +02:00
)
2024-01-25 13:41:33 +01:00
}
2024-02-14 12:20:29 +01:00
2024-05-07 00:42:52 +02:00
public static HelpMessage ( ) : string {
2025-01-27 23:20:21 +01:00
const vis = [ . . . SpecialVisualizations . specialVisualizations ]
vis . sort ( ( a , b ) = > {
return a . funcName < b . funcName ? - 1 : 1
} )
vis . sort ( ( a , b ) = > {
if ( a . group === b . group ) {
return 0
}
return ( a . group ? ? "xxx" ) < ( b . group ? ? "xxx" ) ? - 1 : 1
} )
const groupExplanations : Record < string , string > = {
2025-02-10 02:04:58 +01:00
default :
"These special visualisations are (mostly) interactive components that most elements get by default. You'll normally won't need them in custom layers. There are also a few miscellaneous elements supporting the map UI." ,
favourites :
"Elements relating to marking an object as favourite (giving it a heart). Default element" ,
settings : "Elements part of the usersettings-ui" ,
images : "Elements related to adding or manipulating images. Normally also added by default, but in some cases a tweaked version is needed" ,
notes : "Elements relating to OpenStreetMap-notes, e.g. the component to close and/or add a comment" ,
reviews :
"Elements relating to seeing and adding ratings and reviews with Mangrove.reviews" ,
data_import :
"Elements to help with importing data to OSM. For example: buttons to import a feature, apply tags on an element, apply multiple tags on an element or to work with maproulette" ,
tagrendering_manipulation :
"Special visualisations which reuse other tagRenderings to show data, but with a twist." ,
web_and_communication :
"Tools to show data from external websites, which link to external websites or which link to external profiles" ,
2025-01-27 23:20:21 +01:00
}
const helpTexts : string [ ] = [ ]
let lastGroup : string = null
for ( const viz of vis ) {
if ( viz . group !== lastGroup ) {
lastGroup = viz . group
if ( viz . group === undefined ) {
helpTexts . push ( "## Unclassified elements\n\nVarious elements" )
} else {
helpTexts . push ( "## " + viz . group )
if ( ! groupExplanations [ viz . group ] ) {
2025-02-10 02:04:58 +01:00
throw (
"\n\n >>>> ERROR <<<< Unknown visualisation group type: " +
viz . group +
"\n\n\n"
)
2025-01-27 23:20:21 +01:00
}
helpTexts . push ( groupExplanations [ viz . group ] )
}
}
helpTexts . push ( SpecialVisualizations . DocumentationFor ( viz ) )
}
const example = JSON . stringify (
{
render : {
special : {
type : "some_special_visualisation" ,
argname : "some_arg" ,
message : {
en : "some other really long message" ,
2025-02-10 02:04:58 +01:00
nl : "een boodschap in een andere taal" ,
2025-01-27 23:20:21 +01:00
} ,
2025-02-10 02:04:58 +01:00
other_arg_name : "more args" ,
2025-01-27 23:20:21 +01:00
} ,
before : {
en : "Some text to prefix before the special element (e.g. a title)" ,
2025-02-10 02:04:58 +01:00
nl : "Een tekst om voor het element te zetten (bv. een titel)" ,
2025-01-27 23:20:21 +01:00
} ,
after : {
2025-02-10 02:04:58 +01:00
en : "Some text to put after the element, e.g. a footer" ,
} ,
} ,
2025-01-27 23:20:21 +01:00
} ,
null ,
" "
)
2023-02-14 00:09:04 +01:00
2025-01-27 23:20:21 +01:00
const firstPart = [
"# Special tag renderings" ,
2024-05-07 00:42:52 +02:00
"In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's." ,
2024-06-17 04:27:08 +02:00
"General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssClasses}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args" ,
2025-01-29 20:37:04 +01:00
"# Using expanded syntax" ,
2024-05-07 00:42:52 +02:00
` Instead of using \` {"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}} \` , one can also write ` ,
2025-01-27 23:20:21 +01:00
"```\n" + example + "\n```\n" ,
2025-02-10 02:04:58 +01:00
'In other words: use `{ "before": ..., "after": ..., "special": {"type": ..., "argname": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)' ,
"# Overview of all special components" ,
2025-01-27 23:20:21 +01:00
] . join ( "\n\n" )
2024-05-07 00:42:52 +02:00
return firstPart + "\n\n" + helpTexts . join ( "\n\n" )
2023-02-14 00:09:04 +01:00
}
2022-11-02 13:47:34 +01:00
private static initList ( ) : SpecialVisualization [ ] {
2022-01-08 04:22:50 +01:00
const specialVisualizations : SpecialVisualization [ ] = [
2025-01-27 04:50:44 +01:00
. . . ImageVisualisations . initList ( ) ,
. . . NoteVisualisations . initList ( ) ,
. . . FavouriteVisualisations . initList ( ) ,
. . . UISpecialVisualisations . initList ( ) ,
. . . SettingsVisualisations . initList ( ) ,
. . . ReviewSpecialVisualisations . initList ( ) ,
2025-01-29 20:37:04 +01:00
. . . DataImportSpecialVisualisations . initList ( ) ,
. . . TagrenderingManipulationSpecialVisualisations . initList ( ) ,
. . . WebAndCommunicationSpecialVisualisations . initList ( ) ,
2022-10-28 04:33:05 +02:00
new HistogramViz ( ) ,
2024-11-01 18:51:31 +01:00
{
funcName : "export_as_gpx" ,
docs : "Exports the selected feature as GPX-file" ,
args : [ ] ,
needsUrls : [ ] ,
constr (
state : SpecialVisualizationState ,
tags : UIEventSource < Record < string , string > > ,
argument : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2024-11-01 18:51:31 +01:00
) {
if ( feature . geometry . type !== "LineString" ) {
return undefined
}
const t = Translations . t . general . download
return new SvelteUIElement ( ExportFeatureButton , {
2024-11-07 11:19:15 +01:00
tags ,
feature ,
layer ,
2024-11-01 18:51:31 +01:00
mimetype : "{gpx=application/gpx+xml}" ,
extension : "gpx" ,
2024-11-07 11:19:15 +01:00
construct : ( feature : Feature < LineString > , title : string ) = >
GeoOperations . toGpx ( feature , title ) ,
2024-11-01 18:51:31 +01:00
helpertext : t.downloadGpxHelper ,
2025-02-10 02:04:58 +01:00
maintext : t.downloadFeatureAsGpx ,
2024-11-01 18:51:31 +01:00
} )
2025-02-10 02:04:58 +01:00
} ,
2024-11-01 18:51:31 +01:00
} ,
2022-10-28 04:33:05 +02:00
new UploadToOsmViz ( ) ,
new MultiApplyViz ( ) ,
2025-01-27 04:50:44 +01:00
2023-03-28 05:13:48 +02:00
new LanguageElement ( ) ,
{
funcName : "all_tags" ,
docs : "Prints all key-value pairs of the object - used for debugging" ,
args : [ ] ,
2024-01-22 03:42:00 +01:00
constr : (
state ,
tags : UIEventSource < Record < string , string > > ,
_ ,
__ ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2025-02-10 02:04:58 +01:00
) = > new SvelteUIElement ( AllTagsPanel , { tags , layer } ) ,
2023-03-28 05:13:48 +02:00
} ,
2021-12-12 02:59:24 +01:00
{
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'." ,
args : [
{
2021-06-20 03:09:55 +02:00
name : "key" ,
2021-12-12 02:59:24 +01:00
defaultValue : "opening_hours" ,
2025-04-21 02:51:41 +02:00
type : "key" ,
2025-02-10 02:04:58 +01:00
doc : "The tagkey from which the table is constructed." ,
2021-12-12 02:59:24 +01:00
} ,
{
name : "prefix" ,
defaultValue : "" ,
2025-02-10 02:04:58 +01:00
doc : "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__" ,
2021-12-12 02:59:24 +01:00
} ,
{
name : "postfix" ,
defaultValue : "" ,
2025-02-10 02:04:58 +01:00
doc : "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__" ,
} ,
2021-12-12 02:59:24 +01:00
] ,
2024-02-20 11:53:13 +01:00
needsUrls : [ Constants . countryCoderEndpoint ] ,
2021-12-12 02:59:24 +01:00
example :
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`" ,
2022-01-19 20:34:04 +01:00
constr : ( state , tagSource : UIEventSource < any > , args ) = > {
2023-12-15 18:14:21 +01:00
const [ key , prefix , postfix ] = args
2024-06-23 02:54:53 +02:00
return new OpeningHoursVisualization ( tagSource , key , prefix , postfix )
2025-02-10 02:04:58 +01:00
} ,
2022-09-08 21:40:48 +02:00
} ,
2023-12-04 03:32:25 +01:00
{
funcName : "opening_hours_state" ,
docs : "A small element, showing if the POI is currently open and when the next change is" ,
args : [
{
name : "key" ,
2025-04-21 02:51:41 +02:00
type : "key" ,
2023-12-04 03:32:25 +01:00
defaultValue : "opening_hours" ,
2025-02-10 02:04:58 +01:00
doc : "The tagkey from which the opening hours are read." ,
2023-12-04 03:32:25 +01:00
} ,
{
name : "prefix" ,
defaultValue : "" ,
2025-02-10 02:04:58 +01:00
doc : "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__" ,
2023-12-04 03:32:25 +01:00
} ,
{
name : "postfix" ,
defaultValue : "" ,
2025-02-10 02:04:58 +01:00
doc : "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__" ,
} ,
2023-12-04 03:32:25 +01:00
] ,
constr (
state : SpecialVisualizationState ,
tags : UIEventSource < Record < string , string > > ,
2025-01-18 00:30:06 +01:00
args : string [ ]
2023-12-31 20:57:45 +01:00
) : SvelteUIElement {
2023-12-04 03:32:25 +01:00
const keyToUse = args [ 0 ]
const prefix = args [ 1 ]
const postfix = args [ 2 ]
return new SvelteUIElement ( NextChangeViz , {
state ,
keyToUse ,
tags ,
prefix ,
2025-02-10 02:04:58 +01:00
postfix ,
2023-12-04 03:32:25 +01:00
} )
2025-02-10 02:04:58 +01:00
} ,
2023-12-04 03:32:25 +01:00
} ,
2021-12-12 02:59:24 +01:00
{
funcName : "canonical" ,
2023-12-31 20:57:45 +01:00
2022-07-26 12:05:34 +02:00
docs : "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. " ,
example :
"If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ..." ,
2021-12-12 02:59:24 +01:00
args : [
{
name : "key" ,
2025-04-21 02:51:41 +02:00
type : "key" ,
2022-03-29 00:20:10 +02:00
doc : "The key of the tag to give the canonical text for" ,
2025-02-10 02:04:58 +01:00
required : true ,
} ,
2021-12-12 02:59:24 +01:00
] ,
constr : ( state , tagSource , args ) = > {
const key = args [ 0 ]
return new VariableUiElement (
tagSource
. map ( ( tags ) = > tags [ key ] )
. map ( ( value ) = > {
if ( value === undefined ) {
return undefined
}
2023-12-12 03:46:51 +01:00
const allUnits : Unit [ ] = [ ] . concat (
2025-01-18 00:30:06 +01:00
. . . ( state ? . theme ? . layers ? . map ( ( lyr ) = > lyr . units ) ? ? [ ] )
2022-09-08 21:40:48 +02:00
)
2021-12-12 02:59:24 +01:00
const unit = allUnits . filter ( ( unit ) = >
2025-01-18 00:30:06 +01:00
unit . isApplicableToKey ( key )
2021-12-12 02:59:24 +01:00
) [ 0 ]
if ( unit === undefined ) {
return value
}
2023-12-12 03:46:51 +01:00
const getCountry = ( ) = > tagSource . data . _country
2024-02-12 14:48:05 +01:00
return unit . asHumanLongValue ( value , getCountry )
2025-01-18 00:30:06 +01:00
} )
2021-12-12 02:59:24 +01:00
)
2025-02-10 02:04:58 +01:00
} ,
2022-09-08 21:40:48 +02:00
} ,
2021-12-23 21:28:41 +01:00
{
funcName : "export_as_geojson" ,
docs : "Exports the selected feature as GeoJson-file" ,
args : [ ] ,
2023-12-31 20:57:45 +01:00
2024-11-01 18:51:31 +01:00
constr : ( state , tags , args , feature , layer ) = > {
2021-12-23 21:28:41 +01:00
const t = Translations . t . general . download
2024-11-01 18:51:31 +01:00
return new SvelteUIElement ( ExportFeatureButton , {
2024-11-07 11:19:15 +01:00
tags ,
feature ,
layer ,
2024-11-01 18:51:31 +01:00
mimetype : "application/vnd.geo+json" ,
extension : "geojson" ,
2024-11-07 11:19:15 +01:00
construct : ( feature : Feature < LineString > ) = >
JSON . stringify ( feature , null , " " ) ,
2024-11-01 18:51:31 +01:00
maintext : t.downloadFeatureAsGeojson ,
2025-02-10 02:04:58 +01:00
helpertext : t.downloadGeoJsonHelper ,
2024-11-01 18:51:31 +01:00
} )
2025-02-10 02:04:58 +01:00
} ,
2022-09-08 21:40:48 +02:00
} ,
2021-12-12 02:59:24 +01:00
{
funcName : "clear_location_history" ,
docs : "A button to remove the travelled track information from the device" ,
args : [ ] ,
2023-12-31 20:57:45 +01:00
2021-12-12 02:59:24 +01:00
constr : ( state ) = > {
2025-02-12 16:51:37 +01:00
return new SvelteUIElement ( ClearGPSHistory , { state } )
2025-02-10 02:04:58 +01:00
} ,
2022-09-08 21:40:48 +02:00
} ,
2022-02-16 02:24:15 +01:00
{
2022-03-10 23:20:50 +01:00
funcName : "title" ,
2022-02-16 02:24:15 +01:00
args : [ ] ,
2023-12-31 20:57:45 +01:00
2022-03-10 23:20:50 +01:00
docs : "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'" ,
example :
"`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`." ,
2024-01-25 03:13:18 +01:00
constr : (
state : SpecialVisualizationState ,
2025-01-08 18:04:32 +01:00
tags : UIEventSource < Record < string , string > > ,
2024-01-25 03:13:18 +01:00
_ : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2025-01-08 18:04:32 +01:00
) = > {
return new SvelteUIElement ( FeatureTitle , { state , tags , feature , layer } )
2025-02-10 02:04:58 +01:00
} ,
2022-05-06 12:41:24 +02:00
} ,
2022-07-25 18:55:15 +02:00
{
funcName : "statistics" ,
docs : "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer" ,
args : [ ] ,
2023-12-31 20:57:45 +01:00
2025-03-06 16:21:55 +01:00
constr : ( state ) = > new SvelteUIElement ( AllFeaturesStatistics , { state } ) ,
2022-09-08 21:40:48 +02:00
} ,
2023-08-08 13:52:58 +02:00
{
funcName : "translated" ,
docs : "If the given key can be interpreted as a JSON, only show the key containing the current language (or 'en'). This specialRendering is meant to be used by MapComplete studio and is not useful in map themes" ,
2023-12-31 20:57:45 +01:00
2023-08-08 13:52:58 +02:00
args : [
{
name : "key" ,
2025-04-21 02:51:41 +02:00
type : "key" ,
2023-08-08 13:52:58 +02:00
doc : "The attribute to interpret as json" ,
2025-02-10 02:04:58 +01:00
defaultValue : "value" ,
} ,
2023-08-08 13:52:58 +02:00
] ,
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
2025-02-10 02:04:58 +01:00
argument : string [ ]
2023-08-08 13:52:58 +02:00
) : BaseUIElement {
return new VariableUiElement (
tagSource . map ( ( tags ) = > {
const v = tags [ argument [ 0 ] ? ? "value" ]
try {
2023-10-17 00:32:54 +02:00
const tr = typeof v === "string" ? JSON . parse ( v ) : v
2023-08-08 13:52:58 +02:00
return new Translation ( tr ) . SetClass ( "font-bold" )
} catch ( e ) {
2023-10-24 22:01:10 +02:00
console . error ( "Cannot create a translation for" , v , "due to" , e )
2023-10-17 00:32:54 +02:00
return JSON . stringify ( v )
2023-08-08 13:52:58 +02:00
}
2025-01-18 00:30:06 +01:00
} )
2023-08-08 13:52:58 +02:00
)
2025-02-10 02:04:58 +01:00
} ,
2023-08-08 13:52:58 +02:00
} ,
2023-10-20 19:04:55 +02:00
{
funcName : "braced" ,
docs : "Show a literal text within braces" ,
2023-12-31 20:57:45 +01:00
2023-10-20 19:04:55 +02:00
args : [
{
name : "text" ,
required : true ,
2025-02-10 02:04:58 +01:00
doc : "The value to show" ,
} ,
2023-10-20 19:04:55 +02:00
] ,
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
args : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2023-10-20 19:04:55 +02:00
) : BaseUIElement {
return new FixedUiElement ( "{" + args [ 0 ] + "}" )
2025-02-10 02:04:58 +01:00
} ,
2023-10-20 19:04:55 +02:00
} ,
2023-10-22 01:30:05 +02:00
{
funcName : "tags" ,
docs : "Shows a (json of) tags in a human-readable way + links to the wiki" ,
2023-12-31 20:57:45 +01:00
2023-10-22 01:30:05 +02:00
args : [
{
name : "key" ,
2025-04-21 02:51:41 +02:00
type : "key" ,
2023-10-22 01:30:05 +02:00
defaultValue : "value" ,
2025-02-10 02:04:58 +01:00
doc : "The key to look for the tags" ,
} ,
2023-10-22 01:30:05 +02:00
] ,
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
2025-04-21 02:51:41 +02:00
argument : string [ ]
2023-10-22 01:30:05 +02:00
) : BaseUIElement {
const key = argument [ 0 ] ? ? "value"
return new VariableUiElement (
tagSource . map ( ( tags ) = > {
let value = tags [ key ]
if ( ! value ) {
return new FixedUiElement ( "No tags found" ) . SetClass ( "font-bold" )
}
if ( typeof value === "string" && value . startsWith ( "{" ) ) {
value = JSON . parse ( value )
}
try {
const parsed = TagUtils . Tag ( value )
return parsed . asHumanString ( true , false , { } )
} catch ( e ) {
return new FixedUiElement (
"Could not parse this tag: " +
2025-02-10 02:04:58 +01:00
JSON . stringify ( value ) +
" due to " +
e
2023-10-22 01:30:05 +02:00
) . SetClass ( "alert" )
}
2025-01-18 00:30:06 +01:00
} )
2023-10-22 01:30:05 +02:00
)
2025-02-10 02:04:58 +01:00
} ,
2023-10-22 01:30:05 +02:00
} ,
2023-12-24 05:01:10 +01:00
{
funcName : "direction_indicator" ,
args : [ ] ,
2023-12-31 20:57:45 +01:00
2023-12-24 05:01:10 +01:00
docs : "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object" ,
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
argument : string [ ] ,
2025-04-21 02:51:41 +02:00
feature : Feature
2023-12-24 05:01:10 +01:00
) : BaseUIElement {
return new SvelteUIElement ( DirectionIndicator , { state , feature } )
2025-02-10 02:04:58 +01:00
} ,
2023-12-24 05:01:10 +01:00
} ,
2023-12-31 14:09:25 +01:00
{
funcName : "direction_absolute" ,
docs : "Converts compass degrees (with 0° being north, 90° being east, ...) into a human readable, translated direction such as 'north', 'northeast'" ,
args : [
{
name : "key" ,
2025-04-21 02:51:41 +02:00
type : "key" ,
2023-12-31 14:09:25 +01:00
doc : "The attribute containing the degrees" ,
2025-02-10 02:04:58 +01:00
defaultValue : "_direction:centerpoint" ,
} ,
2025-04-18 00:26:53 +02:00
{
name : "offset" ,
doc : "Offset value that is added to the actual value, e.g. `180` to indicate the opposite (backward) direction" ,
defaultValue : "0"
}
2023-12-31 14:09:25 +01:00
] ,
2023-12-31 20:57:45 +01:00
2023-12-31 14:09:25 +01:00
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
2025-01-18 00:30:06 +01:00
args : string [ ]
2023-12-31 14:09:25 +01:00
) : BaseUIElement {
const key = args [ 0 ] === "" ? "_direction:centerpoint" : args [ 0 ]
2025-04-18 00:26:53 +02:00
const offset = args [ 1 ] === "" ? 0 : Number ( args [ 1 ] )
2023-12-31 14:09:25 +01:00
return new VariableUiElement (
tagSource
. map ( ( tags ) = > {
console . log ( "Direction value" , tags [ key ] , key )
return tags [ key ]
} )
. mapD ( ( value ) = > {
const dir = GeoOperations . bearingToHuman (
2025-04-18 00:26:53 +02:00
GeoOperations . parseBearing ( value ) + offset
2023-12-31 14:09:25 +01:00
)
console . log ( "Human dir" , dir )
return Translations . t . general . visualFeedback . directionsAbsolute [ dir ]
2025-01-18 00:30:06 +01:00
} )
2023-12-31 14:09:25 +01:00
)
2025-02-10 02:04:58 +01:00
} ,
2023-12-31 14:09:25 +01:00
} ,
2024-07-26 18:14:17 +02:00
{
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" ,
args : [ ] ,
2024-08-09 16:55:08 +02:00
constr (
state : SpecialVisualizationState ,
2025-02-10 02:04:58 +01:00
tagSource : UIEventSource < Record < string , string > >
2024-08-09 16:55:08 +02:00
) : BaseUIElement {
const translation = tagSource . map ( ( tags ) = > {
2024-10-17 04:06:03 +02:00
const layer = state . theme . getMatchingLayer ( tags )
2024-09-04 00:07:23 +02:00
return layer ? . getMostMatchingPreset ( tags ) ? . description
2024-07-26 18:14:17 +02:00
} )
return new VariableUiElement ( translation )
2025-02-10 02:04:58 +01:00
} ,
2024-08-09 16:55:08 +02:00
} ,
2024-08-02 13:31:45 +02:00
{
2024-08-12 23:49:46 +02:00
funcName : "preset_type_select" ,
docs : "An editable tag rendering which allows to change the type" ,
args : [ ] ,
2024-08-14 13:53:56 +02:00
constr (
state : SpecialVisualizationState ,
tags : UIEventSource < Record < string , string > > ,
argument : string [ ] ,
selectedElement : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2024-08-14 13:53:56 +02:00
) : SvelteUIElement {
2024-08-12 23:49:46 +02:00
const t = Translations . t . preset_type
const question : QuestionableTagRenderingConfigJson = {
id : layer.id + "-type" ,
question : t.question.translations ,
2024-08-23 13:00:26 +02:00
mappings : layer.presets.map ( ( pr ) = > ( {
if : new And ( pr . tags ) . asJson ( ) ,
icon : "auto" ,
then : ( pr . description ? t.typeDescription : t.typeTitle ) . Subs ( {
title : pr.title ,
2025-02-10 02:04:58 +01:00
description : pr.description ,
} ) . translations ,
} ) ) ,
2024-08-12 23:49:46 +02:00
}
const config = new TagRenderingConfig ( question )
return new SvelteUIElement ( TagRenderingEditable , {
config ,
2024-08-14 13:53:56 +02:00
tags ,
selectedElement ,
state ,
2025-02-10 02:04:58 +01:00
layer ,
2024-08-12 23:49:46 +02:00
} )
2025-02-10 02:04:58 +01:00
} ,
} ,
2020-10-17 02:37:53 +02:00
]
2022-01-08 04:22:50 +01:00
specialVisualizations . push ( new AutoApplyButton ( specialVisualizations ) )
2022-04-22 01:45:54 +02:00
2024-08-14 13:53:56 +02:00
const regex = /[a-zA-Z_]+/
2022-11-02 13:47:34 +01:00
const invalid = specialVisualizations
2023-06-14 20:39:36 +02:00
. map ( ( sp , i ) = > ( { sp , i } ) )
2024-08-13 17:56:33 +02:00
. filter ( ( sp ) = > sp . sp . funcName === undefined || ! sp . sp . funcName . match ( regex ) )
2024-08-23 13:00:26 +02:00
2022-11-02 13:47:34 +01:00
if ( invalid . length > 0 ) {
throw (
2024-08-14 13:53:56 +02:00
"Invalid special visualisation found: funcName is undefined or doesn't match " +
regex +
2022-11-02 13:47:34 +01:00
invalid . map ( ( sp ) = > sp . i ) . join ( ", " ) +
2025-02-10 02:04:58 +01:00
'. Did you perhaps type \n funcName: "funcname" // type declaration uses COLON\ninstead of:\n funcName = "funcName" // value definition uses EQUAL'
2022-11-02 13:47:34 +01:00
)
2022-10-28 04:33:05 +02:00
}
2024-08-23 13:13:41 +02:00
const allNames = specialVisualizations . map ( ( f ) = > f . funcName )
2024-08-23 13:00:26 +02:00
const seen = new Set < string > ( )
for ( let name of allNames ) {
name = name . toLowerCase ( )
2024-08-23 13:13:41 +02:00
if ( seen . has ( name ) ) {
throw "Invalid special visualisations: detected a duplicate name: " + name
2024-08-23 13:00:26 +02:00
}
seen . add ( name )
}
2022-01-08 04:22:50 +01:00
return specialVisualizations
2020-10-17 02:37:53 +02:00
}
2020-10-09 20:10:21 +02:00
}