2020-10-14 12:15:09 +02:00
import Combine from "./Base/Combine"
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-01-22 01:06:58 +01: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"
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
2022-11-02 13:47:34 +01:00
import TagApplyButton from "./Popup/TagApplyButton"
2023-06-14 20:39:36 +02:00
import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
2024-01-31 12:09:15 +01:00
import { ImmutableStore , Store , Stores , UIEventSource } from "../Logic/UIEventSource"
2023-03-24 19:21:15 +01:00
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
2023-06-14 20:39:36 +02:00
import { VariableUiElement } from "./Base/VariableUIElement"
import { Utils } from "../Utils"
import Wikidata , { WikidataResponse } from "../Logic/Web/Wikidata"
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"
2023-06-14 20:39:36 +02:00
import { SubtleButton } from "./Base/SubtleButton"
2022-11-02 13:47:34 +01:00
import StatisticsPanel from "./BigComponents/StatisticsPanel"
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"
2023-06-14 20:39:36 +02:00
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
2024-11-01 18:51:31 +01:00
import { Feature , GeoJsonProperties , 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-04-21 16:02:36 +02:00
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
2023-06-14 20:39:36 +02:00
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 OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
2023-09-15 01:53:50 +02:00
import SendEmail from "./Popup/SendEmail.svelte"
2023-09-27 22:21:35 +02:00
import Constants from "../Models/Constants"
import Wikipedia from "../Logic/Web/Wikipedia"
2023-10-22 01:30:05 +02:00
import { TagUtils } from "../Logic/Tags/TagUtils"
2023-11-19 04:38:34 +01:00
import OpenJosm from "./Base/OpenJosm.svelte"
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-13 05:24:56 +01:00
import ComparisonTool from "./Comparison/ComparisonTool.svelte"
2024-01-25 03:13:18 +01:00
import SpecialTranslation from "./Popup/TagRendering/SpecialTranslation.svelte"
2024-01-25 13:41:33 +01:00
import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
2024-02-14 12:20:29 +01:00
import Toggle from "./Input/Toggle"
2024-02-26 02:24:46 +01:00
import LinkedDataLoader from "../Logic/Web/LinkedDataLoader"
2024-04-10 15:29:48 +02:00
import DynLink from "./Base/DynLink.svelte"
2024-05-07 00:42:52 +02:00
import MarkdownUtils from "../Utils/MarkdownUtils"
2024-06-16 19:00:43 +02:00
import Trash from "@babeard/svelte-heroicons/mini/Trash"
2024-07-26 18:14:17 +02:00
import { And } from "../Logic/Tags/And"
2024-08-10 12:09:55 +02:00
import GroupedView from "./Popup/GroupedView.svelte"
2024-08-12 23:49:46 +02:00
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
2024-09-30 23:30:39 +02:00
import NoteCommentElement from "./Popup/Notes/NoteCommentElement.svelte"
2024-10-29 23:46:52 +01:00
import FediverseLink from "./Popup/FediverseLink.svelte"
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-28 23:37:42 +01:00
import { MapRouletteSpecialVisualisations } from "./SpecialVisualisations/MapRouletteSpecialVisualisations"
2022-05-06 12:41:24 +02:00
2023-04-13 22:44:35 +02:00
class StealViz implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
funcName = "steal"
docs = "Shows a tagRendering from a different object as if this was the object itself"
args = [
{
name : "featureId" ,
doc : "The key of the attribute which contains the id of the feature from which to use the tags" ,
2025-01-27 23:20:21 +01:00
required : true
2023-04-13 22:44:35 +02:00
} ,
{
name : "tagRenderingId" ,
doc : "The layer-id and tagRenderingId to render. Can be multiple value if ';'-separated (in which case every value must also contain the layerId, e.g. `layerId.tagRendering0; layerId.tagRendering1`). Note: this can cause layer injection" ,
2025-01-27 23:20:21 +01:00
required : true
}
2023-04-13 22:44:35 +02:00
]
2023-09-27 22:21:35 +02:00
needsUrls = [ ]
2023-12-31 20:57:45 +01:00
svelteBased = true
2023-04-14 02:42:57 +02:00
2023-04-13 22:44:35 +02:00
constr ( state : SpecialVisualizationState , featureTags , args ) {
const [ featureIdKey , layerAndtagRenderingIds ] = args
const tagRenderings : [ LayerConfig , TagRenderingConfig ] [ ] = [ ]
for ( const layerAndTagRenderingId of layerAndtagRenderingIds . split ( ";" ) ) {
const [ layerId , tagRenderingId ] = layerAndTagRenderingId . trim ( ) . split ( "." )
2024-10-17 04:06:03 +02:00
const layer = state . theme . layers . find ( ( l ) = > l . id === layerId )
2023-04-13 22:44:35 +02:00
const tagRendering = layer . tagRenderings . find ( ( tr ) = > tr . id === tagRenderingId )
tagRenderings . push ( [ layer , tagRendering ] )
}
if ( tagRenderings . length === 0 ) {
throw "Could not create stolen tagrenddering: tagRenderings not found"
}
return new VariableUiElement (
2023-06-14 20:39:36 +02:00
featureTags . map (
( tags ) = > {
const featureId = tags [ featureIdKey ]
if ( featureId === undefined ) {
return undefined
}
const otherTags = state . featureProperties . getStore ( featureId )
const otherFeature = state . indexedFeatures . featuresById . data . get ( featureId )
const elements : BaseUIElement [ ] = [ ]
for ( const [ layer , tagRendering ] of tagRenderings ) {
elements . push (
new SvelteUIElement ( TagRenderingEditable , {
config : tagRendering ,
tags : otherTags ,
selectedElement : otherFeature ,
state ,
2025-01-27 23:20:21 +01:00
layer
2025-01-18 00:30:06 +01:00
} )
2023-06-14 20:39:36 +02:00
)
}
if ( elements . length === 1 ) {
return elements [ 0 ]
}
return new Combine ( elements ) . SetClass ( "flex flex-col" )
} ,
2025-01-18 00:30:06 +01:00
[ state . indexedFeatures . featuresById ]
)
2023-04-13 22:44:35 +02:00
)
}
getLayerDependencies ( args ) : string [ ] {
2024-04-12 15:33:15 +02:00
const [ , tagRenderingId ] = args
2023-04-13 22:44:35 +02:00
if ( tagRenderingId . indexOf ( "." ) < 0 ) {
throw "Error: argument 'layerId.tagRenderingId' of special visualisation 'steal' should contain a dot"
}
2024-04-12 15:33:15 +02:00
const [ layerId ] = tagRenderingId . split ( "." )
2023-04-13 22:44:35 +02:00
return [ layerId ]
}
}
2023-04-14 02:42:57 +02:00
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-01-27 23:20:21 +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-01-27 23:20:21 +01:00
"<code>" + example + "</code>"
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 {
2023-02-14 00:09:04 +01:00
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-01-28 23:37:42 +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." ,
2025-01-27 23:20:21 +01:00
"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" ,
2025-01-28 23:37:42 +01:00
"reviews" : "Elements relating to seeing and adding ratings and reviews with Mangrove.reviews" ,
"maproulette" : "Elements to close a maproulette task"
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 ] ) {
throw "\n\n >>>> ERROR <<<< Unknown visualisation group type: " + viz . group + "\n\n\n"
}
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" ,
nl : "een boodschap in een andere taal"
} ,
other_arg_name : "more args"
} ,
before : {
en : "Some text to prefix before the special element (e.g. a title)" ,
nl : "Een tekst om voor het element te zetten (bv. een titel)"
} ,
after : {
en : "Some text to put after the element, e.g. a footer"
}
}
} ,
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-27 23:20:21 +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" ,
"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)"
] . 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-28 23:37:42 +01:00
. . . MapRouletteSpecialVisualisations . initList ( ) ,
2023-12-31 20:57:45 +01:00
2022-10-28 04:33:05 +02:00
new HistogramViz ( ) ,
new StealViz ( ) ,
2024-07-16 14:43:40 +02:00
2023-12-31 20:57:45 +01:00
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-01-27 23:20:21 +01:00
maintext : t.downloadFeatureAsGpx
2024-11-01 18:51:31 +01:00
} )
2025-01-27 23:20:21 +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
2022-10-28 04:33:05 +02:00
new PlantNetDetectionViz ( ) ,
2023-03-28 05:13:48 +02:00
new TagApplyButton ( ) ,
2023-06-01 02:52:21 +02:00
new PointImportButtonViz ( ) ,
2023-05-30 02:52:22 +02:00
new WayImportButtonViz ( ) ,
2023-06-01 02:52:21 +02:00
new ConflateImportButtonViz ( ) ,
2023-03-28 05:13:48 +02:00
2021-12-12 02:59:24 +01:00
{
funcName : "wikipedia" ,
2023-04-21 16:02:36 +02:00
docs : "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag." ,
2021-12-12 02:59:24 +01:00
args : [
2021-06-23 02:15:28 +02:00
{
2021-12-12 02:59:24 +01:00
name : "keyToShowWikipediaFor" ,
2022-05-01 20:56:16 +02:00
doc : "Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used" ,
2025-01-27 23:20:21 +01:00
defaultValue : "wikidata;wikipedia"
}
2021-12-12 02:59:24 +01:00
] ,
2023-09-27 22:21:35 +02:00
needsUrls : [ . . . Wikidata . neededUrls , . . . Wikipedia . neededUrls ] ,
2023-12-31 20:57:45 +01:00
2021-12-12 02:59:24 +01:00
example :
"`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height" ,
2023-05-06 01:23:55 +02:00
constr : ( _ , tagsSource , args ) = > {
2022-05-01 20:56:16 +02:00
const keys = args [ 0 ] . split ( ";" ) . map ( ( k ) = > k . trim ( ) )
2023-04-21 16:02:36 +02:00
const wikiIds : Store < string [ ] > = tagsSource . map ( ( tags ) = > {
const key = keys . find ( ( k ) = > tags [ k ] !== undefined && tags [ k ] !== "" )
2023-09-20 01:47:32 +02:00
return tags [ key ] ? . split ( ";" ) ? . map ( ( id ) = > id . trim ( ) ) ? ? [ ]
2023-04-21 16:02:36 +02:00
} )
return new SvelteUIElement ( WikipediaPanel , {
2025-01-27 23:20:21 +01:00
wikiIds
2023-04-21 16:02:36 +02:00
} )
2025-01-27 23:20:21 +01:00
}
2022-09-08 21:40:48 +02:00
} ,
2022-04-22 01:45:54 +02:00
{
funcName : "wikidata_label" ,
docs : "Shows the label of the corresponding wikidata-item" ,
args : [
{
name : "keyToShowWikidataFor" ,
doc : "Use the wikidata entry from this key to show the label" ,
2025-01-27 23:20:21 +01:00
defaultValue : "wikidata"
}
2022-04-22 01:45:54 +02:00
] ,
2023-09-27 22:21:35 +02:00
needsUrls : Wikidata.neededUrls ,
2022-04-22 01:45:54 +02:00
example :
"`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself" ,
constr : ( _ , tagsSource , args ) = >
new VariableUiElement (
tagsSource
. map ( ( tags ) = > tags [ args [ 0 ] ] )
. map ( ( wikidata ) = > {
wikidata = Utils . NoEmpty (
2025-01-18 00:30:06 +01:00
wikidata ? . split ( ";" ) ? . map ( ( wd ) = > wd . trim ( ) ) ? ? [ ]
2022-04-22 01:45:54 +02:00
) [ 0 ]
const entry = Wikidata . LoadWikidataEntry ( wikidata )
return new VariableUiElement (
entry . map ( ( e ) = > {
if ( e === undefined || e [ "success" ] === undefined ) {
return wikidata
}
const response = < WikidataResponse > e [ "success" ]
return Translation . fromMap ( response . labels )
2025-01-18 00:30:06 +01:00
} )
2022-09-08 21:40:48 +02:00
)
2025-01-18 00:30:06 +01:00
} )
2025-01-27 23:20:21 +01:00
)
2022-04-22 01:45:54 +02:00
} ,
2023-03-28 05:13:48 +02:00
new MapillaryLinkVis ( ) ,
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-01-27 23:20:21 +01:00
) = > new SvelteUIElement ( AllTagsPanel , { tags , layer } )
2023-03-28 05:13:48 +02:00
} ,
2024-08-13 22:17:36 +02:00
{
2024-08-14 13:53:56 +02:00
funcName : "reviews" ,
2025-01-27 04:50:44 +01:00
group : "reviews" ,
2024-08-13 22:17:36 +02:00
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" ,
2024-08-14 13:53:56 +02:00
docs : "A pragmatic combination of `create_review` and `list_reviews`" ,
2024-08-13 22:17:36 +02:00
args : [
{
name : "subjectKey" ,
defaultValue : "name" ,
2025-01-27 04:50:44 +01:00
doc : "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>"
2024-08-13 22:17:36 +02:00
} ,
{
name : "fallback" ,
2025-01-27 04:50:44 +01:00
doc : "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value"
2024-09-02 12:48:15 +02:00
} ,
2025-01-08 18:04:32 +01:00
{
name : "question" ,
2025-01-27 04:50:44 +01:00
doc : "The question to ask in the review form. Optional"
}
2024-08-13 22:17:36 +02:00
] ,
2024-08-14 13:53:56 +02:00
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
args : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2024-08-14 13:53:56 +02:00
) : BaseUIElement {
2024-08-13 22:17:36 +02:00
return new Combine ( [
2025-01-18 00:30:06 +01:00
SpecialVisualizations . specialVisualisationsDict
. get ( "create_review" )
. constr ( state , tagSource , args , feature , layer ) ,
SpecialVisualizations . specialVisualisationsDict
. get ( "list_reviews" )
2025-01-27 04:50:44 +01:00
. constr ( state , tagSource , args , feature , layer )
2024-08-13 22:17:36 +02:00
] )
2025-01-27 04:50:44 +01:00
}
2024-02-20 16:53:26 +01:00
} ,
2025-01-27 04:50:44 +01: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-01-27 23:20:21 +01:00
doc : "The tagkey from which the table is constructed."
2021-12-12 02:59:24 +01:00
} ,
{
name : "prefix" ,
defaultValue : "" ,
2025-01-27 23:20:21 +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-01-27 23:20:21 +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-01-27 23:20:21 +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" ,
defaultValue : "opening_hours" ,
2025-01-27 23:20:21 +01:00
doc : "The tagkey from which the opening hours are read."
2023-12-04 03:32:25 +01:00
} ,
{
name : "prefix" ,
defaultValue : "" ,
2025-01-27 23:20:21 +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-01-27 23:20:21 +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-01-27 23:20:21 +01:00
postfix
2023-12-04 03:32:25 +01:00
} )
2025-01-27 23:20:21 +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" ,
2022-03-29 00:20:10 +02:00
doc : "The key of the tag to give the canonical text for" ,
2025-01-27 23:20:21 +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-01-27 23:20:21 +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-01-27 23:20:21 +01:00
helpertext : t.downloadGeoJsonHelper
2024-11-01 18:51:31 +01:00
} )
2025-01-27 23:20:21 +01:00
}
2022-09-08 21:40:48 +02:00
} ,
2021-12-23 21:28:41 +01:00
{
funcName : "open_in_iD" ,
docs : "Opens the current view in the iD-editor" ,
args : [ ] ,
2023-12-31 20:57:45 +01:00
2022-01-08 04:22:50 +01:00
constr : ( state , feature ) = > {
2023-06-14 20:39:36 +02:00
return new SvelteUIElement ( OpenIdEditor , {
mapProperties : state.mapProperties ,
2025-01-27 23:20:21 +01:00
objectId : feature.data.id
2023-06-14 20:39:36 +02:00
} )
2025-01-27 23:20:21 +01:00
}
2022-09-08 21:40:48 +02:00
} ,
2022-06-08 12:53:04 +02:00
{
funcName : "open_in_josm" ,
docs : "Opens the current view in the JOSM-editor" ,
args : [ ] ,
2023-11-19 04:38:34 +01:00
needsUrls : [ "http://127.0.0.1:8111/load_and_zoom" ] ,
2023-09-27 22:21:35 +02:00
2023-03-28 05:13:48 +02:00
constr : ( state ) = > {
2023-11-19 04:38:34 +01:00
return new SvelteUIElement ( OpenJosm , { state } )
2025-01-27 23:20:21 +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 ) = > {
return new SubtleButton (
2025-01-02 03:02:06 +01:00
new SvelteUIElement ( Trash ) ,
2025-01-18 00:30:06 +01:00
Translations . t . general . removeLocationHistory
2021-12-12 02:59:24 +01:00
) . onClick ( ( ) = > {
state . historicalUserLocations . features . setData ( [ ] )
2023-06-07 02:42:49 +02:00
state . selectedElement . setData ( undefined )
2021-12-12 02:59:24 +01:00
} )
2025-01-27 23:20:21 +01:00
}
2022-09-08 21:40:48 +02:00
} ,
2022-01-08 14:08:04 +01:00
{
2022-01-08 04:22:50 +01:00
funcName : "visualize_note_comments" ,
2022-01-12 02:31:51 +01:00
docs : "Visualises the comments for notes" ,
2022-09-08 21:40:48 +02:00
args : [
{
2022-01-08 04:22:50 +01:00
name : "commentsKey" ,
doc : "The property name of the comments, which should be stringified json" ,
2025-01-27 23:20:21 +01:00
defaultValue : "comments"
2022-09-08 21:40:48 +02:00
} ,
{
2022-01-12 02:31:51 +01:00
name : "start" ,
2022-01-26 21:40:38 +01:00
doc : "Drop the first 'start' comments" ,
2025-01-27 23:20:21 +01:00
defaultValue : "0"
}
2022-09-08 21:40:48 +02:00
] ,
2023-09-27 22:21:35 +02:00
needsUrls : [ Constants . osmAuthConfig . url ] ,
2022-01-08 14:08:04 +01:00
constr : ( state , tags , args ) = >
new VariableUiElement (
2022-09-08 21:40:48 +02:00
tags
2022-07-27 23:59:04 +02:00
. map ( ( tags ) = > tags [ args [ 0 ] ] )
2022-01-08 04:22:50 +01:00
. map ( ( commentsStr ) = > {
2024-12-13 13:47:47 +01:00
const comments : { text : string } [ ] = JSON . parse ( commentsStr )
2022-01-12 02:31:51 +01:00
const startLoc = Number ( args [ 1 ] ? ? 0 )
2022-01-26 21:40:38 +01:00
if ( ! isNaN ( startLoc ) && startLoc > 0 ) {
2022-01-12 02:31:51 +01:00
comments . splice ( 0 , startLoc )
2022-09-08 21:40:48 +02:00
}
2022-07-29 20:04:36 +02:00
return new Combine (
2022-09-08 21:40:48 +02:00
comments
2022-01-08 04:22:50 +01:00
. filter ( ( c ) = > c . text !== "" )
2023-12-26 22:30:27 +01:00
. map (
2024-09-30 23:30:39 +02:00
( comment ) = >
2024-10-19 14:44:55 +02:00
new SvelteUIElement ( NoteCommentElement , {
comment ,
2025-01-27 23:20:21 +01:00
state
2025-01-18 00:30:06 +01:00
} )
)
2022-01-08 17:44:23 +01:00
) . SetClass ( "flex flex-col" )
2025-01-18 00:30:06 +01:00
} )
2025-01-27 23:20:21 +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-01-27 23:20:21 +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
2023-03-29 17:21:20 +02:00
constr : ( state ) = > {
2023-03-28 05:13:48 +02:00
return new Combine (
2024-10-17 04:06:03 +02:00
state . theme . layers
2024-04-13 02:40:21 +02:00
. filter (
( l ) = >
l . name !== null &&
l . title &&
2025-01-18 00:30:06 +01:00
state . perLayer . get ( l . id ) !== undefined
2024-04-13 02:40:21 +02:00
)
2023-03-28 05:13:48 +02:00
. map (
( l ) = > {
const fs = state . perLayer . get ( l . id )
2024-03-28 03:39:46 +01:00
console . log ( ">>>" , l . id , fs )
2023-04-27 00:58:21 +02:00
const bbox = state . mapProperties . bounds
2023-03-28 05:13:48 +02:00
const fsBboxed = new BBoxFeatureSourceForLayer ( fs , bbox )
return new StatisticsPanel ( fsBboxed )
} ,
2025-01-18 00:30:06 +01:00
[ state . mapProperties . bounds ]
)
2023-03-28 05:13:48 +02:00
)
2025-01-27 23:20:21 +01:00
}
2022-09-08 21:40:48 +02:00
} ,
2022-07-27 23:59:04 +02:00
{
funcName : "send_email" ,
docs : "Creates a `mailto`-link where some fields are already set and correctly escaped. The user will be promted to send the email" ,
args : [
2022-09-08 21:40:48 +02:00
{
2022-07-27 23:59:04 +02:00
name : "to" ,
doc : "Who to send the email to?" ,
2025-01-27 23:20:21 +01:00
required : true
2022-09-08 21:40:48 +02:00
} ,
{
2022-07-27 23:59:04 +02:00
name : "subject" ,
doc : "The subject of the email" ,
2025-01-27 23:20:21 +01:00
required : true
2022-09-08 21:40:48 +02:00
} ,
{
2022-07-27 23:59:04 +02:00
name : "body" ,
doc : "The text in the email" ,
2025-01-27 23:20:21 +01:00
required : true
2022-09-08 21:40:48 +02:00
} ,
2022-07-27 23:59:04 +02:00
2022-09-08 21:40:48 +02:00
{
2022-07-27 23:59:04 +02:00
name : "button_text" ,
doc : "The text shown on the button in the UI" ,
2025-01-27 23:20:21 +01:00
required : true
}
2022-09-08 21:40:48 +02:00
] ,
2023-09-27 22:21:35 +02:00
2023-03-28 05:13:48 +02:00
constr ( __ , tags , args ) {
2023-09-15 01:53:50 +02:00
return new SvelteUIElement ( SendEmail , { args , tags } )
2025-01-27 23:20:21 +01:00
}
2022-09-08 21:40:48 +02:00
} ,
2022-07-28 09:16:19 +02:00
{
2023-04-07 02:13:57 +02:00
funcName : "link" ,
2023-09-02 00:55:07 +02:00
docs : "Construct a link. By using the 'special' visualisation notation, translations should be easier" ,
2023-04-07 02:13:57 +02:00
args : [
{
name : "text" ,
doc : "Text to be shown" ,
2025-01-27 23:20:21 +01:00
required : true
2023-04-07 02:13:57 +02:00
} ,
{
name : "href" ,
2024-02-13 00:52:00 +01:00
doc : "The URL to link to. Note that this will be URI-encoded before " ,
2025-01-27 23:20:21 +01:00
required : true
2023-04-07 02:13:57 +02:00
} ,
2023-04-15 02:28:24 +02:00
{
name : "class" ,
2025-01-27 23:20:21 +01:00
doc : "CSS-classes to add to the element"
2023-04-15 02:28:24 +02:00
} ,
2023-09-21 02:31:35 +02:00
{
name : "download" ,
2025-01-27 23:20:21 +01:00
doc : "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button."
2023-09-21 02:31:35 +02:00
} ,
2023-12-14 18:25:35 +01:00
{
name : "arialabel" ,
2025-01-27 23:20:21 +01:00
doc : "If set, this text will be used as aria-label"
2024-08-09 16:24:38 +02:00
} ,
{
name : "icon" ,
2025-01-27 23:20:21 +01:00
doc : "If set, show this icon next to the link. You might want to combine this with `class: button`"
}
2023-04-07 02:13:57 +02:00
] ,
2023-12-31 20:57:45 +01:00
2023-04-07 02:13:57 +02:00
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
2025-01-18 00:30:06 +01:00
args : string [ ]
2024-08-28 12:02:04 +02:00
) : SvelteUIElement {
2024-08-09 16:24:38 +02:00
let [ text , href , classnames , download , ariaLabel , icon ] = args
2023-12-19 16:45:27 +01:00
if ( download === "" ) {
download = undefined
}
2023-12-14 18:25:35 +01:00
const newTab = download === undefined && ! href . startsWith ( "#" )
2024-04-13 02:40:21 +02:00
const textStore = tagSource . map ( ( tags ) = > Utils . SubstituteKeys ( text , tags ) )
2024-09-02 12:48:15 +02:00
const hrefStore = tagSource . map ( ( tags ) = > Utils . SubstituteKeys ( href , tags ) )
2024-04-10 15:29:48 +02:00
return new SvelteUIElement ( DynLink , {
2024-04-13 02:40:21 +02:00
text : textStore ,
2024-04-10 15:29:48 +02:00
href : hrefStore ,
classnames : new ImmutableStore ( classnames ) ,
2024-04-13 02:40:21 +02:00
download : tagSource.map ( ( tags ) = > Utils . SubstituteKeys ( download , tags ) ) ,
ariaLabel : tagSource.map ( ( tags ) = > Utils . SubstituteKeys ( ariaLabel , tags ) ) ,
2024-08-09 16:55:08 +02:00
newTab : new ImmutableStore ( newTab ) ,
2025-01-27 23:20:21 +01:00
icon : tagSource.map ( ( tags ) = > Utils . SubstituteKeys ( icon , tags ) )
2024-04-12 15:16:33 +02:00
} ) . setSpan ( )
2025-01-27 23:20:21 +01:00
}
2023-04-07 02:13:57 +02:00
} ,
{
2022-07-28 09:16:19 +02:00
funcName : "multi" ,
docs : "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering" ,
2022-07-29 20:04:36 +02:00
example :
"```json\n" +
JSON . stringify (
2022-07-28 09:16:19 +02:00
{
render : {
special : {
2022-07-29 20:04:36 +02:00
type : "multi" ,
key : "_doors_from_building_properties" ,
2023-02-09 02:45:19 +01:00
tagrendering : {
2025-01-27 23:20:21 +01:00
en : "The building containing this feature has a <a href='#{id}'>door</a> of width {entrance:width}"
}
}
}
2022-07-29 20:04:36 +02:00
} ,
2022-09-08 21:40:48 +02:00
null ,
2025-01-18 00:30:06 +01:00
" "
2022-09-08 21:40:48 +02:00
) +
2022-10-11 01:39:09 +02:00
"\n```" ,
2022-09-08 21:40:48 +02:00
args : [
2022-07-29 20:04:36 +02:00
{
2021-12-12 02:59:24 +01:00
name : "key" ,
2022-08-22 19:16:37 +02:00
doc : "The property to read and to interpret as a list of properties" ,
2025-01-27 23:20:21 +01:00
required : true
2022-09-08 21:40:48 +02:00
} ,
{
2022-07-29 20:04:36 +02:00
name : "tagrendering" ,
doc : "An entire tagRenderingConfig" ,
2025-01-27 23:20:21 +01:00
required : true
2025-01-02 03:38:39 +01:00
} ,
{
name : "classes" ,
2025-01-27 23:20:21 +01:00
doc : "CSS-classes to apply on every individual item. Seperated by `space`"
}
2022-09-08 21:40:48 +02:00
] ,
2024-01-25 03:13:18 +01:00
constr (
state : SpecialVisualizationState ,
featureTags : UIEventSource < Record < string , string > > ,
args : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2024-01-25 03:13:18 +01:00
) {
2025-01-02 03:38:39 +01:00
const [ key , tr , classesRaw ] = args
let classes = classesRaw ? ? ""
2023-06-14 20:39:36 +02:00
const translation = new Translation ( { "*" : tr } )
2022-07-29 20:04:36 +02:00
return new VariableUiElement (
featureTags . map ( ( tags ) = > {
2024-01-31 12:09:15 +01:00
let properties : object [ ]
2024-02-02 13:37:05 +01:00
if ( typeof tags [ key ] === "string" ) {
properties = JSON . parse ( tags [ key ] )
} else {
properties = < any > tags [ key ]
2024-01-31 12:09:15 +01:00
}
2024-04-13 02:40:21 +02:00
if ( ! properties ) {
console . debug (
"Could not create a special visualization for multi(" ,
args . join ( ", " ) + ")" ,
"no properties found for object" ,
2025-01-18 00:30:06 +01:00
feature . properties . id
2024-04-13 02:40:21 +02:00
)
2024-04-10 15:29:48 +02:00
return undefined
}
2022-07-29 20:04:36 +02:00
const elements = [ ]
for ( const property of properties ) {
2024-01-25 03:13:18 +01:00
const subsTr = new SvelteUIElement ( SpecialTranslation , {
t : translation ,
2024-01-31 12:09:15 +01:00
tags : new ImmutableStore ( property ) ,
2024-01-25 03:13:18 +01:00
state ,
feature ,
2025-01-27 23:20:21 +01:00
layer
2025-01-02 05:34:38 +01:00
// clss: classes ?? "",
2025-01-02 15:34:59 +01:00
} ) . SetClass ( classes )
2022-07-29 20:04:36 +02:00
elements . push ( subsTr )
}
2024-01-28 03:27:17 +01:00
return elements
2025-01-18 00:30:06 +01:00
} )
2022-09-08 21:40:48 +02:00
)
2025-01-27 23:20:21 +01:00
}
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" ,
doc : "The attribute to interpret as json" ,
2025-01-27 23:20:21 +01:00
defaultValue : "value"
}
2023-08-08 13:52:58 +02:00
] ,
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
argument : string [ ] ,
) : 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-01-27 23:20:21 +01:00
}
2023-08-08 13:52:58 +02:00
} ,
2023-08-10 14:10:06 +02:00
{
funcName : "fediverse_link" ,
docs : "Converts a fediverse username or link into a clickable link" ,
2023-08-10 16:25:25 +02:00
args : [
{
name : "key" ,
doc : "The attribute-name containing the link" ,
2025-01-27 23:20:21 +01:00
required : true
}
2023-08-10 16:25:25 +02:00
] ,
2023-12-31 20:57:45 +01:00
2023-08-10 16:25:25 +02:00
constr (
state : SpecialVisualizationState ,
2024-10-29 23:46:52 +01:00
tags : UIEventSource < Record < string , string > > ,
2023-08-10 16:25:25 +02:00
argument : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2023-08-10 16:25:25 +02:00
) : BaseUIElement {
2023-08-10 14:10:06 +02:00
const key = argument [ 0 ]
2024-11-01 18:51:31 +01:00
return new SvelteUIElement ( FediverseLink , { key , tags , state } )
2025-01-27 23:20:21 +01:00
}
2023-08-10 16:25:25 +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-01-27 23:20:21 +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-01-27 23:20:21 +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" ,
defaultValue : "value" ,
2025-01-27 23:20:21 +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 > > ,
argument : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
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-01-27 23:20:21 +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-01-27 23:20:21 +01:00
}
2023-10-22 01:30:05 +02:00
} ,
2023-12-31 20:57:45 +01: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 [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2023-12-24 05:01:10 +01:00
) : BaseUIElement {
return new SvelteUIElement ( DirectionIndicator , { state , feature } )
2025-01-27 23:20:21 +01:00
}
2023-12-24 05:01:10 +01:00
} ,
2025-01-27 04:50:44 +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" ,
doc : "The attribute containing the degrees" ,
2025-01-27 23:20:21 +01:00
defaultValue : "_direction:centerpoint"
}
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 ]
return new VariableUiElement (
tagSource
. map ( ( tags ) = > {
console . log ( "Direction value" , tags [ key ] , key )
return tags [ key ]
} )
. mapD ( ( value ) = > {
const dir = GeoOperations . bearingToHuman (
2025-01-18 00:30:06 +01:00
GeoOperations . parseBearing ( value )
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-01-27 23:20:21 +01:00
}
2023-12-31 14:09:25 +01:00
} ,
2024-01-13 05:24:56 +01:00
{
funcName : "compare_data" ,
2024-01-14 22:24:35 +01:00
needsUrls : ( args ) = > args [ 1 ] . split ( ";" ) ,
2024-01-16 04:20:25 +01:00
args : [
2024-01-13 05:24:56 +01:00
{
name : "url" ,
required : true ,
2025-01-27 23:20:21 +01:00
doc : "The attribute containing the url where to fetch more data"
2024-01-13 05:24:56 +01:00
} ,
2024-01-14 22:24:35 +01:00
{
2024-01-16 04:20:25 +01:00
name : "host" ,
required : true ,
2025-01-27 23:20:21 +01:00
doc : "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. "
2024-01-14 22:24:35 +01:00
} ,
2024-01-16 04:20:25 +01:00
{
2024-01-17 18:08:14 +01:00
name : "readonly" ,
2024-01-16 04:20:25 +01:00
required : false ,
2025-01-27 23:20:21 +01:00
doc : "If 'yes', will not show 'apply'-buttons"
}
2024-01-13 05:24:56 +01:00
] ,
docs : "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM" ,
2024-01-17 18:08:14 +01:00
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
args : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2024-01-17 18:08:14 +01:00
) : BaseUIElement {
2024-01-13 05:24:56 +01:00
const url = args [ 0 ]
2024-01-16 04:20:25 +01:00
const readonly = args [ 3 ] === "yes"
2024-02-26 02:24:46 +01:00
const externalData = Stores . FromPromiseWithErr ( Utils . downloadJson ( url ) )
2024-01-16 04:20:25 +01:00
return new SvelteUIElement ( ComparisonTool , {
url ,
state ,
tags : tagSource ,
layer ,
feature ,
2024-01-17 18:08:14 +01:00
readonly ,
2025-01-27 23:20:21 +01:00
externalData
2024-01-16 04:20:25 +01:00
} )
2025-01-27 23:20:21 +01:00
}
2024-01-16 04:20:25 +01:00
} ,
2024-02-22 18:58:34 +01:00
{
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" ,
args : [
{
name : "key" ,
defaultValue : "website" ,
2025-01-27 23:20:21 +01:00
doc : "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>"
2024-02-22 18:58:34 +01:00
} ,
2024-04-05 17:49:31 +02:00
{
name : "useProxy" ,
defaultValue : "yes" ,
2025-01-27 23:20:21 +01:00
doc : "If 'yes', uses the provided proxy server. This proxy server will scrape HTML and search for a script with `lang='ld+json'`. If `no`, the data will be downloaded and expects a linked-data-json directly"
2024-04-05 17:49:31 +02:00
} ,
{
name : "host" ,
2025-01-27 23:20:21 +01:00
doc : "If not using a proxy, define what host the website is allowed to connect to"
2024-04-05 17:49:31 +02:00
} ,
{
name : "mode" ,
2025-01-27 23:20:21 +01:00
doc : "If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM"
2024-06-18 03:33:11 +02:00
} ,
{
name : "collapsed" ,
defaultValue : "yes" ,
2025-01-27 23:20:21 +01:00
doc : "If the containing accordion should be closed"
}
2024-02-22 18:58:34 +01:00
] ,
2024-04-09 13:58:23 +02:00
needsUrls : [ Constants . linkedDataProxy , "http://www.schema.org" ] ,
2024-02-22 18:58:34 +01:00
constr (
state : SpecialVisualizationState ,
2024-02-26 02:24:46 +01:00
tags : UIEventSource < Record < string , string > > ,
2024-02-22 18:58:34 +01:00
argument : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
2024-02-22 18:58:34 +01:00
) : BaseUIElement {
const key = argument [ 0 ] ? ? "website"
2024-04-05 17:49:31 +02:00
const useProxy = argument [ 1 ] !== "no"
const readonly = argument [ 3 ] === "readonly"
2024-12-11 21:55:36 +01:00
const isClosed = ( argument [ 4 ] ? ? "yes" ) === "yes"
2024-04-05 17:49:31 +02:00
2024-12-11 21:55:36 +01:00
const countryStore : Store < string | undefined > = tags . mapD (
2025-01-18 00:30:06 +01:00
( tags ) = > tags . _country
2024-12-11 21:55:36 +01:00
)
const sourceUrl : Store < string | undefined > = tags . mapD ( ( tags ) = > {
if ( ! tags [ key ] || tags [ key ] === "undefined" ) {
return null
}
return tags [ key ]
} )
2024-06-16 16:06:26 +02:00
const externalData : Store < { success : GeoJsonProperties } | { error : any } > =
2024-12-11 21:55:36 +01:00
sourceUrl . bindD (
2024-12-17 04:23:24 +01:00
( url ) = > {
2024-12-11 21:55:36 +01:00
const country = countryStore . data
if ( url . startsWith ( "https://data.velopark.be/" ) ) {
return Stores . FromPromiseWithErr (
( async ( ) = > {
try {
const loadAll =
layer . id . toLowerCase ( ) . indexOf ( "maproulette" ) >=
0 // Dirty hack
const features =
await LinkedDataLoader . fetchVeloparkEntry (
url ,
2025-01-18 00:30:06 +01:00
loadAll
2024-12-11 21:55:36 +01:00
)
const feature =
features . find (
2025-01-18 00:30:06 +01:00
( f ) = > f . properties [ "ref:velopark" ] === url
2024-12-11 21:55:36 +01:00
) ? ? features [ 0 ]
const properties = feature . properties
properties [ "ref:velopark" ] = url
console . log (
"Got properties from velopark:" ,
2025-01-18 00:30:06 +01:00
properties
2024-12-11 21:55:36 +01:00
)
return properties
} catch ( e ) {
console . error ( e )
throw e
}
2025-01-18 00:30:06 +01:00
} ) ( )
2024-12-11 21:55:36 +01:00
)
}
if ( country === undefined ) {
return undefined
}
2024-06-16 16:06:26 +02:00
return Stores . FromPromiseWithErr (
( async ( ) = > {
try {
2024-12-11 21:55:36 +01:00
return await LinkedDataLoader . fetchJsonLd (
url ,
{ country } ,
2025-01-18 00:30:06 +01:00
useProxy ? "proxy" : "fetch-lod"
2024-12-11 21:55:36 +01:00
)
2024-06-16 16:06:26 +02:00
} catch ( e ) {
2024-12-11 21:55:36 +01:00
console . log (
"Could not get with proxy/download LOD, attempting to download directly. Error for " ,
url ,
"is" ,
2025-01-18 00:30:06 +01:00
e
2024-12-11 21:55:36 +01:00
)
return await LinkedDataLoader . fetchJsonLd (
url ,
{ country } ,
2025-01-18 00:30:06 +01:00
"fetch-raw"
2024-12-11 21:55:36 +01:00
)
2024-06-16 16:06:26 +02:00
}
2025-01-18 00:30:06 +01:00
} ) ( )
2024-06-16 16:06:26 +02:00
)
2024-12-11 21:55:36 +01:00
} ,
2025-01-18 00:30:06 +01:00
[ countryStore ]
2024-12-11 21:55:36 +01:00
)
2024-02-26 02:24:46 +01:00
2024-04-13 02:40:21 +02:00
externalData . addCallbackAndRunD ( ( lod ) = >
2025-01-18 00:30:06 +01:00
console . log ( "linked_data_from_website received the following data:" , lod )
2024-04-13 02:40:21 +02:00
)
2024-02-26 02:24:46 +01:00
return new Toggle (
new SvelteUIElement ( ComparisonTool , {
feature ,
state ,
tags ,
layer ,
externalData ,
2024-04-05 17:49:31 +02:00
sourceUrl ,
2024-06-19 03:22:57 +02:00
readonly ,
2025-01-27 23:20:21 +01:00
collapsed : isClosed
2024-04-13 02:40:21 +02:00
} ) ,
undefined ,
2025-01-18 00:30:06 +01:00
sourceUrl . map ( ( url ) = > ! ! url )
2024-02-26 02:24:46 +01:00
)
2025-01-27 23:20:21 +01:00
}
2024-06-16 16:06:26 +02:00
} ,
2025-01-27 04:50:44 +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 ,
tagSource : UIEventSource < Record < string , string > > ,
argument : string [ ] ,
feature : Feature ,
2025-01-18 00:30:06 +01:00
layer : LayerConfig
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-01-27 23:20:21 +01:00
}
2024-08-09 16:55:08 +02:00
} ,
2025-01-28 23:37:42 +01:00
2025-01-27 04:50:44 +01:00
2024-08-13 18:05:17 +02:00
{
funcName : "group" ,
docs : "A collapsable group (accordion)" ,
args : [
{
name : "header" ,
2025-01-27 23:20:21 +01:00
doc : "The _identifier_ of a single tagRendering. This will be used as header"
2024-08-13 18:05:17 +02:00
} ,
{
name : "labels" ,
2025-01-27 23:20:21 +01:00
doc : "A `;`-separated list of either identifiers or label names. All tagRenderings matching this value will be shown in the accordion"
}
2024-08-13 18:05:17 +02:00
] ,
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-13 18:05:17 +02:00
const [ header , labelsStr ] = argument
2024-08-14 13:53:56 +02:00
const labels = labelsStr . split ( ";" ) . map ( ( x ) = > x . trim ( ) )
2024-08-13 18:05:17 +02:00
return new SvelteUIElement < any , any , any > ( GroupedView , {
2024-08-14 13:53:56 +02:00
state ,
tags ,
selectedElement ,
layer ,
header ,
2025-01-27 23:20:21 +01:00
labels
2024-08-13 18:05:17 +02:00
} )
2025-01-27 23:20:21 +01: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-01-27 23:20:21 +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-01-27 23:20:21 +01:00
layer
2024-08-12 23:49:46 +02:00
} )
2025-01-27 23:20:21 +01:00
}
}
2025-01-27 04:50:44 +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-01-27 23:20:21 +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
}