Merge branch 'develop'

This commit is contained in:
Pieter Vander Vennet 2025-08-19 23:35:31 +02:00
commit b8847189c2
87 changed files with 1010 additions and 781 deletions

View file

@ -12,9 +12,16 @@
"hidden"
],
"render": {
"en": "Dietary options",
"cs": "Dietní možnosti",
"nl": "Dieetopties"
"special": {
"class": "inline-flex float-right",
"labels": "diets_content",
"type": "show_icons"
},
"before": {
"en": "Dietary options",
"cs": "Dietní možnosti",
"nl": "Dieetopties"
}
}
},
{

View file

@ -131,6 +131,9 @@
},
"tags": [
"historic=locomotive"
],
"snapToLayer": [
"railway"
]
},
{
@ -144,6 +147,9 @@
},
"tags": [
"historic=railway_car"
],
"snapToLayer": [
"railway"
]
},
{
@ -157,6 +163,9 @@
},
"tags": [
"historic=minecart"
],
"snapToLayer": [
"railway"
]
}
],

View file

@ -430,10 +430,10 @@
}
},
{
"id": "favourite_icon",
"condition": "_favourite=yes",
"description": "Only for rendering",
"icon": "circle:white;heart:red",
"condition": "_favourite=yes",
"id": "favourite_icon",
"metacondition": "__showTimeSensitiveIcons!=no"
},
{

View file

@ -217,8 +217,8 @@
},
{
"id": "debug",
"render": "{all_tags()}",
"metacondition": "__featureSwitchIsDebugging=true"
"metacondition": "__featureSwitchIsDebugging=true",
"render": "{all_tags()}"
}
],
"filter": [

View file

@ -114,9 +114,9 @@
"lineRendering": [],
"tagRenderings": [
{
"classes": "p-0",
"id": "conversation",
"render": "{visualize_note_comments()}",
"classes": "p-0"
"render": "{visualize_note_comments()}"
},
{
"id": "add_image",

View file

@ -66,16 +66,16 @@
],
"tagRenderings": [
{
"id": "country_name",
"condition": "level=country",
"description": "The name of the country",
"render": "{nameEn} {emojiFlag}",
"condition": "level=country"
"id": "country_name",
"render": "{nameEn} {emojiFlag}"
},
{
"id": "community_links",
"condition": "_community_links~*",
"description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
"render": "{_community_links}",
"condition": "_community_links~*"
"id": "community_links",
"render": "{_community_links}"
}
],
"filter": [

View file

@ -0,0 +1,63 @@
{
"id": "railway",
"name": {
"en": "Railway"
},
"description": {
"en": "Railways and disused railways"
},
"source": {
"osmTags": {
"or": [
"railway=rail",
"railway=tram",
"railway=subway",
"railway=light_rail",
"railway=disused",
"disused:railway=rail",
"disused:railway=tram",
"disused:railway=subway",
"disused:railway=light_rail",
"abandoned:railway=rail",
"abandoned:railway=tram",
"abandoned:railway=subway",
"abandoned:railway=light_rail"
]
}
},
"minzoom": 14,
"title": {
"render": {
"en": "Railway"
},
"icon": "./assets/svg/train.svg"
},
"pointRendering": [
{
"location": [
"point"
],
"marker": [
{
"icon": "circle"
}
]
}
],
"lineRendering": [
{
"width": 1,
"color": "black"
}
],
"tagRenderings": [
"images"
],
"allowMove": false,
"#": "In first instance to snap historic rolling stock",
"snapName": {
"en": "railway track"
},
"credits": "mnalis ALTernative",
"credits:uid": 172435
}

View file

@ -24,8 +24,8 @@
inkscape:deskcolor="#d1d1d1"
showguides="true"
inkscape:zoom="31.809268"
inkscape:cx="10.374335"
inkscape:cy="10.327179"
inkscape:cx="7.4506587"
inkscape:cy="14.445475"
inkscape:window-width="1920"
inkscape:window-height="1005"
inkscape:window-x="0"
@ -35,13 +35,13 @@
<g
id="g6"
transform="matrix(1.3906291,1.4182437,-1.4182437,1.3906291,10.496254,-26.783855)"
style="fill:#000000;fill-opacity:1">
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:0.82973289;stroke-width:0.40276519;stroke-dasharray:none">
<path
fill="#dd2e44"
d="M 18.378552,14.993974 9.879,9.886 l 5.123325,8.551246 1.328258,-2.074158 z"
id="path5"
sodipodi:nodetypes="ccccc"
style="fill:#000000;fill-opacity:1" />
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:0.82973289;stroke-width:0.40276519;stroke-dasharray:none" />
</g>
<path
style="font-size:10.2423px;line-height:1;font-family:KacstDigital;-inkscape-font-specification:'KacstDigital, Normal';fill:#ffffff;fill-opacity:1;stroke:#fffbff;stroke-width:0.46;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

@ -43,11 +43,26 @@
"id~relation/.*"
]
},
"building~*"
{
"or": [
"building~*",
"building:part~*"
]
}
]
}
},
"title": "OSM-gebouw",
"title": {
"render": "OSM-building",
"mappings": [
{
"if": "building:part~*",
"then": {
"en": "Building part"
}
}
]
},
"tagRenderings": [
{
"id": "building type",
@ -106,10 +121,12 @@
"if": "building=yes",
"then": "A building - no specification"
}
]
],
"condition": "building:part="
},
{
"id": "grb-housenumber",
"condition": "building:part=",
"render": {
"nl": "Het huisnummer is <b>{addr:housenumber}</b>"
},
@ -134,6 +151,7 @@
]
},
{
"condition": "building:part=",
"id": "grb-unit",
"question": "Wat is de wooneenheid-aanduiding?",
"render": {
@ -150,6 +168,7 @@
]
},
{
"condition": "building:part=",
"id": "grb-street",
"render": {
"nl": "De straat is <b>{addr:street}</b>"
@ -242,7 +261,53 @@
]
}
],
"pointRendering": null,
"pointRendering": [
{
"marker": [
{
"icon": "circle",
"color": {
"render": "#00c",
"mappings": [
{
"if": "fixme~*",
"then": "#ff00ff"
},
{
"if": "building=house",
"then": "#a00"
},
{
"if": "building=shed",
"then": "#563e02"
},
{
"if": {
"or": [
"building=garage",
"building=garages"
]
},
"then": "#f9bfbb"
},
{
"if": "building=yes",
"then": "#0774f2"
},
{
"if": "building:part~*",
"then": "#f8fc25"
}
]
}
}
],
"location": [
"centroid"
],
"iconSize": "10,10"
}
],
"lineRendering": [
{
"width": {
@ -281,6 +346,10 @@
{
"if": "building=yes",
"then": "#0774f2"
},
{
"if": "building:part~*",
"then": "#f8fc25"
}
]
}

View file

@ -18,6 +18,14 @@
},
"icon": "./assets/layers/historic_rolling_stock/steam_locomotive.svg",
"layers": [
"historic_rolling_stock"
"historic_rolling_stock",
{
"builtin": "railway",
"override": {
"name": null,
"presets": null,
"shownByDefault": false
}
}
]
}

View file

@ -38,6 +38,9 @@
"zh_Hant": "顯示由MapComplete進行的變動"
},
"icon": "./assets/svg/logo.svg",
"startZoom": 1,
"startLat": 0,
"startLon": 0,
"hideFromOverview": true,
"layers": [
{

View file

@ -58,6 +58,14 @@
"drinking_water",
"birdhide",
"nature_reserve",
{
"builtin": [
"shelter"
],
"override": {
"minzoom": 11
}
},
{
"builtin": [
"map",
@ -66,7 +74,6 @@
"picnic_table",
"toilet",
"guidepost",
"shelter",
"bbq",
"firepit",
"insect_hotel",

View file

@ -100,6 +100,48 @@
"name": null
}
},
{
"builtin": "cafe_pub",
"override": {
"id": "cafe_pub_dog_friendly",
"name=": {
"en": "Dog friendly drinking places",
"da": "Hundevenlige værtshuse"
},
"pointRendering": [
{
"iconBadges+": [
"icons.dogicon"
]
}
],
"=presets": [],
"source": {
"osmTags": {
"and+": [
{
"or": [
"dog=unleashed",
"dog=leashed",
"dog=yes"
]
}
]
}
}
}
},
{
"builtin": "cafe_pub",
"override": {
"minzoom": 18,
"isCounted": false,
"filter": {
"sameAs": "cafe_pub_dog_friendly"
},
"name=": null
}
},
{
"builtin": "shops",
"override": {
@ -227,4 +269,4 @@
}
}
]
}
}

View file

@ -26,9 +26,9 @@
"_car_width:=2 /* The width that a single car needs */",
"_cyclistWidth:=1.5 /* The width a single cyclist needs to be safely overtaken */",
"_pedestrianWidth:=0.75 /* The width a pedestrian needs if sidewalks are missing */",
"_has_left_parking=(feat.properties['parking:lane:left'] ?? feat.properties['parking:lane:both']) === 'parallel'",
"_has_right_parking=(feat.properties['parking:lane:right'] ?? feat.properties['parking:lane:both']) === 'parallel'",
"_has_other_parking= ['parking:lane:left','parking:lane:right','parking:lane:both'].some(key => ['perpendicular','diagonal'].indexOf(feat.properties[key]) >= 0)",
"_has_left_parking=['lane','yes', 'parallel'].indexOf(feat.properties['parking:left'] ?? feat.properties['parking:both']) >= 0",
"_has_right_parking=['lane','yes','parallel'].indexOf(feat.properties['parking:right'] ?? feat.properties['parking:both']) >= 0",
"_has_other_parking= ['parking:left:orientation','parking:right:orientation','parking:both:orientation'].some(key => ['perpendicular','diagonal'].indexOf(feat.properties[key]) >= 0)",
"_parallel_parking_count=get(feat)('_has_right_parking') + get(feat)('_has_left_parking') /* in javascript logic: true + true == 2*/",
"_width:needed:parking=get(feat)('_parallel_parking_count') * get(feat)('_car_width')",
"_has_sidewalk_left=['left','both'].indexOf(feat.properties['sidewalk']) >= 0",
@ -154,7 +154,6 @@
},
{
"id": "has_sidewalks",
"condition": "id=disabled",
"question": {
"nl": "Heeft deze straat voetpaden?"
},
@ -184,7 +183,9 @@
}
}
]
}
},
"questions",
"{nearby_images(closed,readonly)}"
],
"pointRendering": [
{
@ -239,6 +240,67 @@
}
}
]
},
{
"id": "street_no_width",
"description": "Typical city streets with width",
"name": {
"nl": "Straten zonder breedte"
},
"source": {
"osmTags": {
"and": [
"width:carriageway=",
{
"or": [
"highway=residential",
"highway=unclassified",
"highway=tertiary",
"highway=living_street"
]
}
]
}
},
"minzoom": 19,
"title": {
"render": {
"nl": "{name}"
},
"mappings": [
{
"if": "name=",
"then": {
"nl": "Naamloos segment"
}
}
]
},
"tagRenderings": [
{
"id": "carriageway_width",
"render": "Deze straat is <b>{width:carriageway}m</b> breed",
"question": "Hoe breed is deze straat?",
"freeform": {
"key": "width:carriageway",
"type": "distance",
"helperArgs": {
"zoom": 21,
"background": "map"
}
}
}
],
"pointRendering": null,
"lineRendering": [
{
"color": "#ff00ff",
"width": "4"
}
],
"allowMove": false,
"allowSplit": true,
"allowDeletion": false
}
],
"lockLocation": [

View file

@ -678,6 +678,7 @@
},
"input_helpers": {
"distance": {
"measureAgain": "Start new measurement from current location",
"setFirst": "Measure from current location"
}
},
@ -755,6 +756,22 @@
"typeText": "Type some text to add a comment",
"warnAnonymous": "You are not logged in. We won't be able to contact you to resolve your issue."
},
"offline": {
"actions": "Actions",
"autoCheckmark": "Automatically download the basemap when browsing around",
"autoExplanation": "If checked, MapComplete will automatically download the basemap to the cache for the area. This results in bigger initial data loads, but requires less internet over the long run. If you plan to visit a region with less connectivity, you can also select the area you want to download below.",
"autoExplanationIntro": "What does automatically downloading basemaps mean?",
"date": "Map generation data",
"delete": "Delete basemap",
"deleteAll": "Delete all basemaps",
"download": "Download area",
"installing": "Data is being downloaded",
"localOnMap": "Offline basemaps on the map",
"name": "Name",
"overview": "Offline basemaps overview",
"range": "Zoom ranges",
"size": "Size"
},
"plantDetection": {
"back": "Back to species overview",
"button": "Automatically detect the plant species using the AI of Plantnet.org",

View file

@ -4751,7 +4751,9 @@
"diets": {
"tagRenderings": {
"diets_title": {
"render": "Dietní možnosti"
"render": {
"before": "Dietní možnosti"
}
},
"gluten_free": {
"mappings": {

View file

@ -4768,7 +4768,9 @@
"diets": {
"tagRenderings": {
"diets_title": {
"render": "Dietary options"
"render": {
"before": "Dietary options"
}
},
"gluten_free": {
"mappings": {
@ -10092,6 +10094,14 @@
}
}
},
"railway": {
"description": "Railways and disused railways",
"name": "Railway",
"snapName": "railway track",
"title": {
"render": "Railway"
}
},
"railway_platforms": {
"description": "Find every platform in the station, and the train routes that use them.",
"name": "Railway Platforms",

View file

@ -4561,7 +4561,9 @@
"diets": {
"tagRenderings": {
"diets_title": {
"render": "Dieetopties"
"render": {
"before": "Dieetopties"
}
},
"gluten_free": {
"mappings": {

View file

@ -794,7 +794,7 @@
"name": "Restaurants que accepten gossos"
}
},
"7": {
"9": {
"override": {
"name": "Botigues amigues dels gossos"
}

View file

@ -1092,12 +1092,17 @@
"pets": {
"description": "Na této mapě najdete různá zajímavá místa pro vaše domácí mazlíčky: veterináře, psí parky, obchody pro zvířata, restaurace pro psy, ...",
"layers": {
"11": {
"override": {
"name=": "Odpadkové koše se sáčky na exkrementy"
}
},
"4": {
"override": {
"name": "Restaurace vhodné pro vstup se psy"
}
},
"6": {
"8": {
"override": {
"=presets": {
"0": {
@ -1107,14 +1112,9 @@
"name": "Obchody se zvířecími potřebami"
}
},
"7": {
"override": {
"name": "Obchody vhodné pro vstup se psy"
}
},
"9": {
"override": {
"name=": "Odpadkové koše se sáčky na exkrementy"
"name": "Obchody vhodné pro vstup se psy"
}
}
},

View file

@ -1092,12 +1092,22 @@
"pets": {
"description": "På dette kort finder du forskellige interessante steder for dine kæledyr: dyrlæger, hundeparker, dyrehandlere, hundevenlige restauranter, ...",
"layers": {
"11": {
"override": {
"name=": "Affaldskurve med dispensere til hundeposer"
}
},
"4": {
"override": {
"name": "Hundevenlige madsteder"
}
},
"6": {
"override": {
"name=": "Hundevenlige værtshuse"
}
},
"8": {
"override": {
"=presets": {
"0": {
@ -1107,14 +1117,9 @@
"name": "Dyrehandlere"
}
},
"7": {
"override": {
"name": "hundevenlig butik"
}
},
"9": {
"override": {
"name=": "Affaldskurve med dispensere til hundeposer"
"name": "hundevenlig butik"
}
}
},

View file

@ -1046,19 +1046,19 @@
"pets": {
"description": "Diese Karte zeigt interessante Orte für Haustierbesitzer: Tierärzte, Hundeparks, Tiergeschäfte, hundefreundliche Restaurants, ...",
"layers": {
"11": {
"override": {
"name=": "Mülleimer mit Spender für Kotbeutel"
}
},
"4": {
"override": {
"name": "Hundefreundliche Restaurants"
}
},
"7": {
"override": {
"name": "Hundefreundliche Geschäfte"
}
},
"9": {
"override": {
"name=": "Mülleimer mit Spender für Kotbeutel"
"name": "Hundefreundliche Geschäfte"
}
}
},

View file

@ -646,6 +646,13 @@
"grb-reference": {
"render": "Has been imported from GRB, reference number is {source:geometry:ref}"
}
},
"title": {
"mappings": {
"0": {
"then": "Building part"
}
}
}
},
"1": {
@ -1092,12 +1099,22 @@
"pets": {
"description": "On this map, you'll find various interesting places for you pets: veterinarians, dog parks, pet shops, dog-friendly restaurants, ...",
"layers": {
"11": {
"override": {
"name=": "Waste baskets with excrement bag dispensers"
}
},
"4": {
"override": {
"name": "Dog friendly eateries"
}
},
"6": {
"override": {
"name=": "Dog friendly drinking places"
}
},
"8": {
"override": {
"=presets": {
"0": {
@ -1107,14 +1124,9 @@
"name": "Pet stores"
}
},
"7": {
"override": {
"name": "Dog-friendly shops"
}
},
"9": {
"override": {
"name=": "Waste baskets with excrement bag dispensers"
"name": "Dog-friendly shops"
}
}
},

View file

@ -1025,7 +1025,7 @@
"name": "Restaurantes que admiten perros"
}
},
"7": {
"9": {
"override": {
"name": "Tiendas que admiten perros"
}

View file

@ -873,7 +873,7 @@
"name": "Restaurants acceptant les chiens"
}
},
"7": {
"9": {
"override": {
"name": "Magasins acceptant les chiens"
}

View file

@ -1083,12 +1083,17 @@
"pets": {
"description": "Su questa mappa, troverai vari luoghi interessanti per i tuoi animali domestici: veterinari, parchi per cani, negozi di animali, ristoranti che accettano cani, ...",
"layers": {
"11": {
"override": {
"name=": "Cestini per rifiuti con dispenser di sacchetti per escrementi"
}
},
"4": {
"override": {
"name": "Ristoranti adatti ai cani"
}
},
"6": {
"8": {
"override": {
"=presets": {
"0": {
@ -1098,14 +1103,9 @@
"name": "Negozi di animali"
}
},
"7": {
"override": {
"name": "Negozi adatti ai cani"
}
},
"9": {
"override": {
"name=": "Cestini per rifiuti con dispenser di sacchetti per escrementi"
"name": "Negozi adatti ai cani"
}
}
},

View file

@ -1011,7 +1011,7 @@
"name": "반려견 친화적 식당"
}
},
"7": {
"9": {
"override": {
"name": "반려견 친화적 상점"
}

View file

@ -368,7 +368,7 @@
"name": "Hundevennlige spisesteder"
}
},
"7": {
"9": {
"override": {
"name": "Hundevennlige butikker"
}

View file

@ -1079,12 +1079,17 @@
"pets": {
"description": "Deze kaart helpt je op weg met je huisdier: dierenartsen, hondenloopzones, dierenwinkels, hondenvriendelijke restaurants, ...",
"layers": {
"11": {
"override": {
"name=": "Vuilnisbakken met verdelers voor hondenpoepzakjes"
}
},
"4": {
"override": {
"name": "Hondvriendelijke eetgelegenheden"
}
},
"6": {
"8": {
"override": {
"=presets": {
"0": {
@ -1094,14 +1099,9 @@
"name": "Dierenwinkels"
}
},
"7": {
"override": {
"name": "Hondvriendelijke winkels"
}
},
"9": {
"override": {
"name=": "Vuilnisbakken met verdelers voor hondenpoepzakjes"
"name": "Hondvriendelijke winkels"
}
}
},

View file

@ -147,7 +147,7 @@
},
"pets": {
"layers": {
"7": {
"9": {
"override": {
"name": "کُتیاں دی اِجازت دیاں دکاناں"
}

View file

@ -750,7 +750,7 @@
"name": "Restauracje przyjazne psom"
}
},
"7": {
"9": {
"override": {
"name": "Sklepy przyjazne psom"
}

View file

@ -556,19 +556,19 @@
"pets": {
"description": "На цій мапі ви знайдете різні цікаві місця для ваших домашніх улюбленців: ветеринари, парки для собак, зоомагазини, ресторани, дружні до собак, …",
"layers": {
"11": {
"override": {
"name=": "Кошики для сміття з дозаторами для пакетів для екскрементів"
}
},
"4": {
"override": {
"name": "Заклади харчування, дружні до собак"
}
},
"7": {
"override": {
"name": "Магазини, дружні до собак"
}
},
"9": {
"override": {
"name=": "Кошики для сміття з дозаторами для пакетів для екскрементів"
"name": "Магазини, дружні до собак"
}
}
},

View file

@ -1092,12 +1092,17 @@
"pets": {
"description": "在這份地圖上,你會找到與寵物有關的有趣地位:獸醫、寵物公園、寵物用品店、寵物友善餐廳、…",
"layers": {
"11": {
"override": {
"name=": "帶有糞便袋分配器的垃圾籃"
}
},
"4": {
"override": {
"name": "寵物友善餐廳"
}
},
"6": {
"8": {
"override": {
"=presets": {
"0": {
@ -1107,14 +1112,9 @@
"name": "寵物用品店"
}
},
"7": {
"override": {
"name": "寵物友善商家"
}
},
"9": {
"override": {
"name=": "帶有糞便袋分配器的垃圾籃"
"name": "寵物友善商家"
}
}
},

View file

@ -37,15 +37,8 @@ import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable"
import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers"
import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages"
import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson"
import {
LayerConfigDependencyGraph,
LevelInfo,
} from "../src/Models/ThemeConfig/LayerConfigDependencyGraph"
import { Lists } from "../src/Utils/Lists"
import {
LayerConfigDependencyGraph,
LevelInfo,
} from "../src/Models/ThemeConfig/LayerConfigDependencyGraph"
import { LayerConfigDependencyGraph, LevelInfo } from "../src/Models/ThemeConfig/LayerConfigDependencyGraph"
import { AddContextToTranslations } from "../src/Models/ThemeConfig/Conversion/AddContextToTranslations"
// This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
@ -121,7 +114,7 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye
static singleton = new AddIconSummary()
constructor() {
super("AddIconSummary", "Adds an icon summary for quick reference")
super("AddIconSummary", "Adds an icon summary ('_layerIcon') for quick reference. This previews how the layer should be shown in e.g. the filter menu")
}
convert(json: { raw: LayerConfigJson; parsed: LayerConfig }) {
@ -706,8 +699,8 @@ class LayerOverviewUtils extends Script {
)
const path = "assets/layers/questions/questions.json"
const sharedQuestionsRaw = this.parseLayer(doesImageExist, prepareLayer, path).raw
const sharedQuestions = new AddContextToTranslations("").convertStrict(
const sharedQuestionsRaw: LayerConfigJson = this.parseLayer(doesImageExist, prepareLayer, path).raw
const sharedQuestions: LayerConfigJson = new AddContextToTranslations<LayerConfigJson>("").convertStrict(
sharedQuestionsRaw,
ConversionContext.construct(["layers:questions"], [])
)

View file

@ -567,6 +567,10 @@ function MergeTranslation(source: any, target: any, language: string, context: s
const sourceV = source[key]
const targetV = target[keyRemapping?.get(key) ?? key]
if(targetV === undefined){
throw `Merging translations failed; targetV is undefined for context ${context}; but we are trying to add ${sourceV} (${key}) to it`
}
if (typeof sourceV === "string") {
if (sourceV === "") {
console.log("Ignoring empty string in the translations")

View file

@ -209,6 +209,9 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
continue
}
const store = featureProperties.getStore(id)
if(store === undefined){
continue
}
const origValue = store.data._favourite
if (detected.indexOf(id) >= 0) {
if (origValue !== "yes") {

View file

@ -2,7 +2,7 @@ import { Utils } from "../../Utils"
import polygon_features from "../../assets/polygon-features.json"
import { OsmFeature, OsmId, OsmTags, WayId } from "../../Models/OsmFeature"
import OsmToGeoJson from "osmtogeojson"
import { Feature, LineString, Polygon } from "geojson"
import { Feature, LineString, Point, Polygon } from "geojson"
import Constants from "../../Models/Constants"
export abstract class OsmObject {
@ -131,7 +131,7 @@ export abstract class OsmObject {
*/
public abstract centerpoint(): [number, number]
public abstract asGeoJson(): any
public abstract asGeoJson(): OsmFeature
abstract SaveExtraData(element: any, allElements: OsmObject[] | any)
@ -228,7 +228,7 @@ ${tags} </node>
return [this.lat, this.lon]
}
asGeoJson(): OsmFeature {
asGeoJson(): Feature<Point, OsmTags> {
return {
type: "Feature",
properties: this.tags,
@ -305,7 +305,7 @@ ${nds}${tags} </way>
this.nodes = element.nodes
}
public asGeoJson(): Feature<Polygon | LineString> & { properties: { id: WayId } } {
public asGeoJson(): Feature<Polygon | LineString, OsmTags> & { properties: { id: WayId } } {
const coordinates: [number, number][] | [number, number][][] = this.coordinates.map(
([lat, lon]) => [lon, lat]
)
@ -384,7 +384,7 @@ ${members}${tags} </relation>
this.geojson = geojson
}
asGeoJson(): any {
asGeoJson(): OsmFeature {
if (this.geojson !== undefined) {
return this.geojson
}

View file

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

View file

@ -4,13 +4,17 @@ import { AndroidPolyfill } from "./AndroidPolyfill"
import { IndexedFeatureSource } from "../FeatureSource/FeatureSource"
import { Feature } from "geojson"
import { Store, UIEventSource } from "../UIEventSource"
import ThemeViewState from "../../Models/ThemeViewState"
import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
import ThemeSource from "../FeatureSource/Sources/ThemeSource"
export default class ThemeViewStateHashActor {
private readonly _state: {
private readonly _state: Readonly<{
indexedFeatures: IndexedFeatureSource
selectedElement: UIEventSource<Feature>
guistate: MenuState
}
guistate: MenuState,
osmObjectDownloader: OsmObjectDownloader
}>
private isUpdatingHash = false
public static readonly documentation = [
@ -35,12 +39,13 @@ export default class ThemeViewStateHashActor {
* As such, we use a change in the hash to close the appropriate windows
*
*/
constructor(state: {
constructor(state: Readonly<{
featureSwitches: { featureSwitchBackToThemeOverview: Store<boolean> }
indexedFeatures: IndexedFeatureSource
indexedFeatures: IndexedFeatureSource & ThemeSource
selectedElement: UIEventSource<Feature>
guistate: MenuState
}) {
guistate: MenuState,
osmObjectDownloader: OsmObjectDownloader
}> ) {
this._state = state
AndroidPolyfill.onBackButton(() => this.back(), {
returnToIndex: state.featureSwitches.featureSwitchBackToThemeOverview,
@ -50,6 +55,17 @@ export default class ThemeViewStateHashActor {
const containsMenu = this.loadStateFromHash(hashOnLoad)
// First of all, try to recover the selected element
if (!containsMenu && hashOnLoad?.length > 0) {
if (hashOnLoad.startsWith("node/") || hashOnLoad.startsWith("way/") || hashOnLoad.startsWith("relation/")) {
// This is an OSM-element. Let's download it and add it to the indexedFeatures
console.log("Directly downloading item from hash")
state.osmObjectDownloader.DownloadObjectAsync(hashOnLoad)
.then(osmObj => {
if (osmObj === "deleted") {
return
}
state.indexedFeatures.addItem(osmObj.asGeoJson())
})
}
state.indexedFeatures.featuresById.addCallbackAndRunD(() => {
// once that we have found a matching element, we can be sure the indexedFeaturesource was popuplated and that the job is done
return this.loadSelectedElementFromHash(hashOnLoad)

View file

@ -90,6 +90,18 @@ export class MenuState {
if (!visitedBefore.data && shouldShowWelcomeMessage) {
this.pageStates.about_theme.set(true)
visitedBefore.set(true)
this._selectedElement.addCallbackD(() => {
if(this.pageStates.about_theme.data){
this.pageStates.about_theme.set(false)
this._selectedElement.addCallbackAndRun(selected => {
if(!selected){
this.pageStates.about_theme.set(true)
return true
}
})
return true
}
})
}
}

View file

@ -299,7 +299,7 @@ export interface LayerConfigJson {
/**
* Advanced option - might be set by the theme compiler
*
* If true, this data will _always_ be loaded, even if the theme is disabled by a filter or hidden.
* If true, this data will _always_ be loaded, even if the layer is disabled by a filter or hidden.
* The opposite of `doNotDownload`
*
* question: Should this layer be forcibly loaded?

View file

@ -523,7 +523,9 @@ export default class TagRenderingConfig {
/**
* Gets all the render values. Will return multiple render values if 'multianswer' is enabled.
* The result will equal [GetRenderValue] if not 'multiAnswer'
* The result will equal [GetRenderValue] if not 'multiAnswer'.
*
* If an iconsource is given, will return an icon too (but not necessarly an iconSize)
* @param tags
* @constructor
*/

View file

@ -21,6 +21,7 @@ import SelectedElementTagsUpdater from "../../Logic/Actors/SelectedElementTagsUp
import NoElementsInViewDetector, {
FeatureViewState,
} from "../../Logic/Actors/NoElementsInViewDetector"
import { features } from "monaco-editor/esm/metadata"
export class WithChangesState extends WithLayoutSourceState {
readonly changes: Changes
@ -226,15 +227,10 @@ export class WithChangesState extends WithLayoutSourceState {
metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement,
fetchStore: (id) => this.featureProperties.getStore(id),
onClick: feature => {
this.setSelectedElement(feature)
}
})
/*new ShowDataLayer(map, {
layer: fs.layer.layerDef,
features: filtered,
doShowLayer,
metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement,
fetchStore: (id) => this.featureProperties.getStore(id),
})*/
})
return filteringFeatureSource
}

View file

@ -51,12 +51,7 @@ export class WithImageState extends WithGuiState implements SpecialVisualization
* Setup various services for which no reference are needed
*/
private initActors() {
new ThemeViewStateHashActor({
featureSwitches: this.featureSwitches,
selectedElement: this.selectedElement,
indexedFeatures: this.indexedFeatures,
guistate: this.guistate,
})
new ThemeViewStateHashActor(this)
new PendingChangesUploader(this.changes, this.selectedElement, this.imageUploadManager)
}
}

View file

@ -130,7 +130,8 @@ export class WithLayoutSourceState extends WithSelectedElementState {
}
protected setSelectedElement(feature: Feature) {
// The given feature might be a partial one from the cache
// The given feature might be a partial one from the cache or might be a centroid point instead of the way
// We lookup a version by IDP
if (feature !== undefined) {
feature =
this.indexedFeatures.featuresById.data?.get(feature?.properties?.id) ?? feature

View file

@ -4,7 +4,7 @@ import FavouritesFeatureSource from "../../Logic/FeatureSource/Sources/Favourite
import Constants from "../Constants"
import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import { Feature } from "geojson"
import { Feature, Geometry } from "geojson"
import { BBox } from "../../Logic/BBox"
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
import MetaTagging from "../../Logic/MetaTagging"
@ -22,6 +22,7 @@ import {
} from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions"
import { ClusterGrouping } from "../../Logic/FeatureSource/TiledFeatureSource/ClusteringFeatureSource"
import { OsmTags } from "../OsmFeature"
export class WithSpecialLayers extends WithChangesState {
readonly favourites: FavouritesFeatureSource
@ -180,7 +181,7 @@ export class WithSpecialLayers extends WithChangesState {
})
)
// show last click = new point/note marker
const features = new StaticFeatureSource(lastClickFiltered)
const features: StaticFeatureSource<Feature<Geometry, Record<string, string> & {id: string}>> = new StaticFeatureSource(lastClickFiltered)
this.featureProperties.trackFeatureSource(features)
new ShowDataLayer(this.map, {
features,

View file

@ -51,6 +51,8 @@
}
})
let hotkeys = Hotkeys._docs
let showBackground = state.featureSwitches.featureSwitchBackgroundSelection
let showFilters = state.featureSwitches.featureSwitchFilter
</script>
<div class:h-0={!onlyLink} class:h-full={onlyLink} class="overflow-hidden">
@ -75,10 +77,12 @@
<ThemeIntroPanel {state} />
</Page>
<FilterPage {onlyLink} {state} />
<RasterLayerOverview {onlyLink} {state} />
{#if $showFilters}
<FilterPage {onlyLink} {state} />
{/if}
{#if $showBackground}
<RasterLayerOverview {onlyLink} {state} />
{/if}
<Page {onlyLink} shown={pg.share}>
<svelte:fragment slot="header">
<Share />

View file

@ -38,7 +38,7 @@
import SidebarUnit from "../Base/SidebarUnit.svelte"
import Squares2x2 from "@babeard/svelte-heroicons/mini/Squares2x2"
import EnvelopeOpen from "@babeard/svelte-heroicons/mini/EnvelopeOpen"
import { UIEventSource } from "../../Logic/UIEventSource"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle"
import { AndroidPolyfill } from "../../Logic/Web/AndroidPolyfill"
import Forgejo from "../../assets/svg/Forgejo.svelte"
@ -74,7 +74,7 @@
let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true)
let featureSwitches = state.featureSwitches
let showHome = featureSwitches?.featureSwitchBackToThemeOverview
let showHome = featureSwitches?.featureSwitchBackToThemeOverview ?? new ImmutableStore(true)
let pg = state.guistate.pageStates
export let onlyLink: boolean
const t = Translations.t.general.menu
@ -218,11 +218,12 @@
<Tr t={Translations.t.general.menu.aboutMapComplete} />
</h3>
{#if $showHome}
<a class="flex" href={($isAndroid ? "https://mapcomplete.org" : ".") + "/studio.html"}>
<Pencil class="mr-2 h-6 w-6" />
<Tr t={Translations.t.general.morescreen.createYourOwnTheme} />
</a>
{/if}
<a class="flex" href="mailto:info@mapcomplete.org">
<EnvelopeOpen class="h-6 w-6" />
<Tr t={Translations.t.general.attribution.emailCreators} />

View file

@ -20,6 +20,9 @@
import type { AreaDescription } from "../../Logic/OfflineBasemapManager"
import { OfflineBasemapManager } from "../../Logic/OfflineBasemapManager"
import Checkbox from "../Base/Checkbox.svelte"
import Translations from "../i18n/Translations"
import { default as Trans } from "../Base/Tr.svelte"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
export let state: ThemeViewState & SpecialVisualizationState = undefined
export let autoDownload = state.autoDownloadOfflineBasemap
@ -147,25 +150,29 @@
],
}),
})
const t = Translations.t.offline
</script>
<div class="max-h-leave-room flex h-full flex-col">
<div class="max-h-leave-room flex h-full flex-col overflow-auto ">
<Checkbox selected={autoDownload}>
Automatically download the basemap when browsing around
<Trans t={t.autoCheckmark} />
</Checkbox>
<AccordionSingle noBorder>
<Trans slot="header" cls="text-sm" t={t.autoExplanationIntro} />
<div class="low-interaction">
<Trans t={t.autoExplanation} />
</div>
</AccordionSingle>
<div>
If checked, MapComplete will automatically download the basemap to the cache for the area. This
results in bigger initial data loads, but requires less internet over the long run. If you plan
to visit a region with less connectivity, you can also select the area you want to download
below.
</div>
{#if $installed === undefined}
<Loading />
{:else}
<div class="h-full overflow-auto pb-16">
<div class="pb-16">
<Accordion class="" inactiveClass="text-black">
<AccordionItem paddingDefault="p-2">
<div slot="header">Map</div>
<Trans slot="header" t={t.localOnMap} />
<div class="leave-room relative">
<div class="absolute left-0 top-0 h-full w-full rounded-lg">
<MaplibreMap {map} {mapProperties} />
@ -176,7 +183,9 @@
<div class="mb-16 h-32 w-16" />
{#if $focusTileIsInstalling}
<div class="normal-background rounded-lg">
<Loading>Data is being downloaded</Loading>
<Loading>
<Trans t={t.installing} />
</Loading>
</div>
{:else}
<button
@ -185,7 +194,7 @@
class:disabled={$focusTileIsInstalled}
>
<DownloadIcon class="h-8 w-8" />
Download
<Trans t={t.download} />
</button>
{/if}
</div>
@ -193,8 +202,7 @@
</AccordionItem>
<AccordionItem paddingDefault="p-2">
<div slot="header">Offline tile management</div>
<Trans t={t.overview} slot="header" />
<div class="leave-room">
{Utils.toHumanByteSize(Utils.sum($installed.map((area) => area.size)))}
<button
@ -203,15 +211,25 @@
}}
>
<TrashIcon class="w-6" />
Delete all
<Trans t={t.deleteAll} />
</button>
<table class="w-full">
<tr>
<th>Name</th>
<th>Map generation date</th>
<th>Size</th>
<th>Zoom ranges</th>
<th>Actions</th>
<th>
<Trans t={t.name} />
</th>
<th>
<Trans t={t.date} />
</th>
<th>
<Trans t={t.size} />
</th>
<th>
<Trans t={t.range} />
</th>
<th>
<Trans t={t.actions} />
</th>
</tr>
{#each $installed ?? [] as area}
<tr>
@ -229,7 +247,8 @@
<td>
<button on:click={() => del(area)}>
<TrashIcon class="w-6" />
Delete this map
<Trans t={t.delete} />
</button>
</td>
</tr>

View file

@ -77,7 +77,7 @@
Feature<
Point,
{
id: number
id: string
index: number
dist: number
location: number
@ -101,8 +101,8 @@
})
let id = 0
adaptor.lastClickLocation.addCallbackD(({ lon, lat }) => {
let projected: Feature<Point, { index: number; id?: number; reuse?: string }> =
GeoOperations.nearestPoint(wayGeojson, [lon, lat])
let projected: Feature<Point, { index: number; id: string; reuse?: string }> =
<any>GeoOperations.nearestPoint(wayGeojson, [lon, lat])
console.log("Added splitpoint", projected, id)
@ -126,6 +126,7 @@
},
properties: {
index: i + 1,
id: "" + id,
reuse: "yes",
},
}
@ -139,14 +140,14 @@
},
properties: {
index: i,
id: "" + id,
reuse: "yes",
},
}
}
projected.properties["id"] = id
id++
splitPoints.data.push(<any>projected)
splitPoints.data.push(projected)
splitPoints.ping()
})
</script>

View file

@ -7,7 +7,7 @@
import { UIEventSource, Store } from "../../../Logic/UIEventSource"
import type { MapProperties } from "../../../Models/MapProperties"
import ThemeViewState from "../../../Models/ThemeViewState"
import type { Feature } from "geojson"
import type { Feature, LineString } from "geojson"
import type { RasterLayerPolygon } from "../../../Models/RasterLayers"
import { RasterLayerUtils } from "../../../Models/RasterLayers"
import { eliCategory } from "../../../Models/RasterLayerProperties"
@ -22,13 +22,16 @@
import Tr from "../../Base/Tr.svelte"
import { onDestroy } from "svelte"
export let value: UIEventSource<number>
export let value: UIEventSource<string>
export let feature: Feature
export let args: { background?: string; zoom?: number }
export let state: ThemeViewState = undefined
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let center = GeoOperations.centerpointCoordinates(feature)
if (feature.geometry.type === "LineString") {
center = <[number, number]>GeoOperations.nearestPoint(<Feature<LineString>>feature, center).geometry.coordinates
}
export let initialCoordinate: { lon: number; lat: number } = { lon: center[0], lat: center[1] }
let mapLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(
initialCoordinate
@ -66,7 +69,7 @@
// A bit of a double task: calculate the actual value _and_ the map rendering
const end = mapLocation.data
const distance = GeoOperations.distanceBetween([start.lon, start.lat], [end.lon, end.lat])
value.set(distance.toFixed(2))
value.set(distance.toFixed(1))
return <Feature[]>[
{
@ -93,6 +96,7 @@
layer: new LayerConfig(conflation),
features: new StaticFeatureSource(lengthFeature),
})
const t = Translations.t.input_helpers.distance
</script>
<div class="relative h-64 w-full">
@ -102,6 +106,6 @@
</div>
</div>
<button class="primary" on:click={() => selectStart()}>
<Tr t={Translations.t.input_helpers.distance.setFirst} />
<button class:primary={$start === undefined} on:click={() => selectStart()}>
<Tr t={$start === undefined ? t.setFirst : t.measureAgain} />
</button>

View file

@ -50,6 +50,7 @@
mla.lastClickLocation.addCallbackAndRunD((lastClick) => {
dispatch("click", lastClick)
})
mla.installQuicklocation()
mapProperties.location.syncWith(value)
if (onCreated) {
onCreated(value, map, mla)

View file

@ -81,6 +81,7 @@ export default class Validators {
new ColorValidator(),
new DirectionValidator(),
new DistanceValidator(),
new SlopeValidator(),
new UrlValidator(),
@ -107,7 +108,6 @@ export default class Validators {
new NameSuggestionIndexValidator(),
new DistanceValidator(),
]
private static _byType = Validators._byTypeConstructor()

View file

@ -757,4 +757,28 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
})
})
}
/**
* In general, the 'location'-attribute is only updated when the map stops moving.
* In some cases, we'd like to update the map faster, especially when the map is used for an input-element
* such as distance, snap-to, ...
*
* In that case, calling this method will install an extra handler on 'drag', updating the location faster.
* To avoid rendering artefacts or too frequenting pinging, this is ratelimited to one update every 'rateLimitMs' milliseconds
*/
public installQuicklocation(ratelimitMs = 50) {
this._maplibreMap.addCallbackAndRunD(map => {
let lastUpdate = new Date().getTime()
map.on("drag", e => {
let now = new Date().getTime()
if(now - lastUpdate < ratelimitMs){
return
}
lastUpdate = now;
const center = map.getCenter()
this.location.set({lon: center.lng, lat: center.lat})
})
})
}
}

View file

@ -59,14 +59,14 @@ class SingleBackgroundHandler {
return
}
console.debug(
"Removing raster layer",
this._targetLayer.properties.id,
"map moved and not been used for",
SingleBackgroundHandler.DEACTIVATE_AFTER
)
try {
if (map.getLayer(<string>this._targetLayer.properties.id)) {
console.debug(
"Removing raster layer",
this._targetLayer.properties.id,
"map moved and not been used for",
SingleBackgroundHandler.DEACTIVATE_AFTER
)
map.removeLayer(<string>this._targetLayer.properties.id)
}
} catch (e) {

View file

@ -459,11 +459,6 @@ export default class ShowDataLayer {
preprocessPoints,
} = this._options
let onClick = this._options.onClick
if (!onClick && selectedElement && layer.title !== undefined) {
onClick = (feature: Feature) => {
selectedElement?.setData(feature)
}
}
if (drawLines !== false) {
for (let i = 0; i < layer.lineRendering.length; i++) {
const lineRenderingConfig = layer.lineRendering[i]

View file

@ -7,7 +7,7 @@ export interface ShowDataLayerOptions {
/**
* Features to show
*/
features: FeatureSource<Feature<Geometry, OsmTags>>
features: FeatureSource<Feature<Geometry, Record<string, any> & {id: string}>>
/**
* Indication of the current selected element; overrides some filters.
* When a feature is tapped, the feature will be put in there

View file

@ -2,8 +2,9 @@ import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
import { Changes } from "../../Logic/Osm/Changes"
import {
SpecialVisualisationArg,
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
@ -31,12 +32,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
public readonly funcName: string = "auto_apply"
public readonly needsUrls = []
public readonly group = "data_import"
public readonly args: {
name: string
defaultValue?: string
doc: string
required?: boolean
}[] = [
public readonly args: SpecialVisualisationArg[] = [
{
name: "target_layer",
doc: "The layer that the target features will reside in",
@ -54,6 +50,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
},
{
name: "text",
type:"translation",
doc: "The text to show on the button",
required: true,
},
@ -86,15 +83,11 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
4. At last, add this component`
}
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[]
): SvelteUIElement {
const target_layer_id = argument[0]
const targetTagRendering = argument[2]
const text = argument[3]
const icon = argument[4]
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
const target_layer_id = args[0]
const targetTagRendering = args[2]
const text = args[3]
const icon = args[4]
const options = {
target_layer_id,
targetTagRendering,
@ -105,7 +98,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
const to_parse: UIEventSource<string[]> = new UIEventSource<string[]>(undefined)
Stores.chronic(500, () => to_parse.data === undefined)
.map(() => {
const applicable = <string | string[]>tagSource.data[argument[1]]
const applicable = <string | string[]>tags.data[args[1]]
if (typeof applicable === "string") {
return <string[]>JSON.parse(applicable)
} else {

View file

@ -1,11 +1,5 @@
import {
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { Feature, LineString } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations"
import SvelteUIElement from "../Base/SvelteUIElement"
import ExportFeatureButton from "./ExportFeatureButton.svelte"
@ -17,13 +11,7 @@ class ExportAsGpxVis extends SpecialVisualizationSvelte {
args = []
needsUrls = []
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
) {
constr({ tags, feature, layer }: SpecialVisualisationParams) {
if (feature.geometry.type !== "LineString") {
return undefined
}
@ -48,7 +36,7 @@ class ExportAsGeojsonVis extends SpecialVisualizationSvelte {
docs = "Exports the selected feature as GeoJson-file"
args = []
constr(state, tags, args, feature, layer) {
constr({ tags, feature, layer }: SpecialVisualisationParams) {
const t = Translations.t.general.download
return new SvelteUIElement(ExportFeatureButton, {
tags,
@ -64,7 +52,7 @@ class ExportAsGeojsonVis extends SpecialVisualizationSvelte {
}
export class DataExportVisualisations {
public static initList(): SpecialVisualization[] {
public static initList(): SpecialVisualizationSvelte[] {
return [new ExportAsGpxVis(), new ExportAsGeojsonVis()]
}
}

View file

@ -1,11 +1,11 @@
import {
SpecialVisualisationArg,
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { HistogramViz } from "./HistogramViz"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
import { Store } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
import SvelteUIElement from "../Base/SvelteUIElement"
import DirectionIndicator from "../Base/DirectionIndicator.svelte"
@ -20,15 +20,18 @@ import NextChangeViz from "../OpeningHours/NextChangeViz.svelte"
import { Unit } from "../../Models/Unit"
import AllFeaturesStatistics from "../Statistics/AllFeaturesStatistics.svelte"
import { LanguageElement } from "./LanguageElement/LanguageElement"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import { And } from "../../Logic/Tags/And"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte"
import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte"
import CollectionTimes from "../CollectionTimes/CollectionTimes.svelte"
import Tr from "../Base/Tr.svelte"
import Combine from "../Base/Combine"
import Marker from "../Map/Marker.svelte"
import { twJoin } from "tailwind-merge"
class DirectionIndicatorVis extends SpecialVisualization {
class DirectionIndicatorVis extends SpecialVisualizationSvelte {
funcName = "direction_indicator"
args = []
@ -36,13 +39,8 @@ class DirectionIndicatorVis extends SpecialVisualization {
"Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object"
group = "data"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature
): BaseUIElement {
return new SvelteUIElement(DirectionIndicator, { state, feature })
constr(params: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(DirectionIndicator, params)
}
}
@ -65,17 +63,15 @@ class DirectionAbsolute extends SpecialVisualization {
]
group = "data"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[]
): BaseUIElement {
constr({
tags,
args,
}: SpecialVisualisationParams): BaseUIElement {
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
const offset = args[1] === "" ? 0 : Number(args[1])
return new VariableUiElement(
tagSource
.map((tags) => {
tags.map((tags) => {
console.log("Direction value", tags[key], key)
return tags[key]
})
@ -117,14 +113,12 @@ class OpeningHoursTableVis extends SpecialVisualizationSvelte {
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)}`"
constr(state, tagSource: UIEventSource<any>, args) {
constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement {
const [key, prefix, postfix] = args
const openingHoursStore: Store<opening_hours | "error" | undefined> =
OH.CreateOhObjectStore(tagSource, key, prefix, postfix)
OH.CreateOhObjectStore(tags, key, prefix, postfix)
return new SvelteUIElement(OpeningHoursWithError, {
tags: tagSource,
key,
opening_hours_obj: openingHoursStore,
tags, key, opening_hours_obj: openingHoursStore,
})
}
}
@ -152,11 +146,7 @@ class OpeningHoursState extends SpecialVisualizationSvelte {
},
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
const keyToUse = args[0]
const prefix = args[1]
const postfix = args[2]
@ -187,10 +177,10 @@ class Canonical extends SpecialVisualization {
},
]
constr(state, tagSource, args) {
constr({ state, tags, args }: SpecialVisualisationParams) {
const key = args[0]
return new VariableUiElement(
tagSource
tags
.map((tags) => tags[key])
.map((value) => {
if (value === undefined) {
@ -203,7 +193,7 @@ class Canonical extends SpecialVisualization {
if (unit === undefined) {
return value
}
const getCountry = () => tagSource.data._country
const getCountry = () => tags.data._country
return unit.asHumanLongValue(value, getCountry)
})
)
@ -217,26 +207,24 @@ class StatisticsVis extends SpecialVisualizationSvelte {
"Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer"
args = []
constr(state) {
return new SvelteUIElement(AllFeaturesStatistics, { state })
constr(params: SpecialVisualisationParams) {
return new SvelteUIElement(AllFeaturesStatistics, params)
}
}
class PresetDescription extends SpecialVisualization {
class PresetDescription extends SpecialVisualizationSvelte {
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 = []
group = "UI"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>
): BaseUIElement {
const translation = tagSource.map((tags) => {
constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement {
const translation = tags.map((tags) => {
const layer = state.theme.getMatchingLayer(tags)
return layer?.getMostMatchingPreset(tags)?.description
})
return new VariableUiElement(translation)
return new SvelteUIElement(Tr, { t: translation })
}
}
@ -244,14 +232,9 @@ class PresetTypeSelect extends SpecialVisualizationSvelte {
funcName = "preset_type_select"
docs = "An editable tag rendering which allows to change the type"
args = []
group = "ui"
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
selectedElement: Feature,
layer: LayerConfig
): SvelteUIElement {
constr({ state, tags, feature, layer }: SpecialVisualisationParams,): SvelteUIElement {
const t = Translations.t.preset_type
if (layer._basedOn !== layer.id) {
console.warn("Trying to use the _original_ layer")
@ -277,7 +260,7 @@ class PresetTypeSelect extends SpecialVisualizationSvelte {
return new SvelteUIElement(TagRenderingEditable, {
config,
tags,
selectedElement,
selectedElement: feature,
state,
layer,
})
@ -290,8 +273,8 @@ class AllTagsVis extends SpecialVisualizationSvelte {
args = []
group = "data"
constr(state, tags: UIEventSource<Record<string, string>>, _, __, layer: LayerConfig) {
return new SvelteUIElement(AllTagsPanel, { tags, layer })
constr(params: SpecialVisualisationParams) {
return new SvelteUIElement(AllTagsPanel, params)
}
}
@ -302,23 +285,18 @@ class PointsInTimeVis extends SpecialVisualization {
args = [
{
name: "key",
type:"key",
required: true,
doc: "The key out of which the points_in_time will be parsed",
},
]
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
constr({ tags, args }: SpecialVisualisationParams): BaseUIElement {
const key = args[0]
const points_in_time = tagSource.map((tags) => tags[key])
const points_in_time = tags.map((tags) => tags[key])
const times = points_in_time.map(
(times) => OH.createOhObject(<any>tagSource.data, times, tagSource.data["_country"], 1),
[tagSource]
(times) => OH.createOhObject(<any>tags.data, times, tags.data["_country"], 1),
[tags]
)
return new VariableUiElement(
times.map((times) => new SvelteUIElement(CollectionTimes, { times }))
@ -326,6 +304,60 @@ class PointsInTimeVis extends SpecialVisualization {
}
}
class KnownIcons extends SpecialVisualization {
docs = "Displays all icons from the specified tagRenderings (if they are known and have an icon) together, e.g. to give a summary of the dietary options"
needsUrls = []
group = "UI"
funcName = "show_icons"
args: SpecialVisualisationArg[] = [{
name: "labels",
doc: "A ';'-separated list of labels and/or ids of tagRenderings",
type: "key",
required: true,
}, {
name: "class",
doc: "CSS-classes of the container, space-separated",
type: "css",
required: false,
defaultValue: "inline-flex mx-4",
}]
private static readonly emojiHeights = {
small: "2rem",
medium: "3rem",
large: "5rem",
}
constr(options: SpecialVisualisationParams): BaseUIElement {
const labels = new Set(options.args[0].split(";").map(s => s.trim()))
const matchingTrs = options.layer.tagRenderings.filter(
tr => labels.has(tr.id) || tr.labels.some(l => labels.has(l)),
)
return new VariableUiElement(options.tags.map(tags =>
new Combine(matchingTrs.map(tr => {
const mapping = tr.GetRenderValueWithImage(tags)
if (!mapping?.icon) {
return undefined
}
return new SvelteUIElement(Marker, {
emojiHeight: KnownIcons.emojiHeights[mapping.iconClass] ?? "2rem",
clss: `mapping-icon-${mapping.iconClass ?? "small"}`,
icons: mapping.icon,
size: twJoin(
"shrink-0",
`mapping-icon-${mapping.iconClass ?? "small"}-height mapping-icon-${
mapping.iconClass ?? "small"
}-width`),
})
})
).SetClass(options.args[1] ?? "inline-flex mx-4")
))
}
}
export class DataVisualisations {
public static initList(): SpecialVisualization[] {
return [
@ -341,6 +373,7 @@ export class DataVisualisations {
new PresetDescription(),
new PresetTypeSelect(),
new AllTagsVis(),
new KnownIcons(),
]
}
}

View file

@ -1,5 +1,5 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { Feature } from "geojson"
import SvelteUIElement from "../Base/SvelteUIElement"
import Histogram from "../BigComponents/Histogram.svelte"
@ -14,6 +14,7 @@ export class HistogramViz extends SpecialVisualization {
args = [
{
name: "key",
type:"key",
doc: "The key to be read and to generate a histogram from",
required: true,
},
@ -35,12 +36,8 @@ export class HistogramViz extends SpecialVisualization {
]
}
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[]
) {
const values: Store<string[]> = tagSource.map((tags) => {
constr( {tags, args}: SpecialVisualisationParams): SvelteUIElement {
const values: Store<string[]> = tags.map((tags) => {
const value = tags[args[0]]
try {
if (value === "" || value === undefined) {

View file

@ -1,7 +1,11 @@
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
import {
SpecialVisualisationArg,
SpecialVisualisationParams,
SpecialVisualizationSvelte,
SpecialVisualizationUtils,
} from "../../SpecialVisualization"
import { UIEventSource } from "../../../Logic/UIEventSource"
import { Feature, Geometry, LineString, Polygon } from "geojson"
import BaseUIElement from "../../BaseUIElement"
import { ImportFlowArguments, ImportFlowUtils } from "./ImportFlow"
import Translations from "../../i18n/Translations"
import { Utils } from "../../../Utils"
@ -14,6 +18,7 @@ import { Changes } from "../../../Logic/Osm/Changes"
import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
import { OsmTags } from "../../../Models/OsmFeature"
import Tr from "../../Base/Tr.svelte"
export interface ConflateFlowArguments extends ImportFlowArguments {
way_to_conflate: string
@ -22,21 +27,17 @@ export interface ConflateFlowArguments extends ImportFlowArguments {
snap_onto_layers?: string
}
export default class ConflateImportButtonViz extends SpecialVisualization implements AutoAction {
export default class ConflateImportButtonViz extends SpecialVisualizationSvelte implements AutoAction {
supportsAutoAction: boolean = true
needsUrls = []
group = "data_import"
public readonly funcName: string = "conflate_button"
public readonly args: {
name: string
defaultValue?: string
doc: string
required?: boolean
}[] = [
public readonly args: SpecialVisualisationArg[] = [
...ImportFlowUtils.generalArguments,
{
name: "way_to_conflate",
type:"key",
doc: "The key, of which the corresponding value is the id of the OSM-way that must be conflated; typically a calculatedTag",
},
]
@ -84,32 +85,25 @@ export default class ConflateImportButtonViz extends SpecialVisualization implem
await state.changes.applyAction(action)
}
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature
): BaseUIElement {
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement {
const canBeImported =
feature.geometry.type === "LineString" ||
(feature.geometry.type === "Polygon" && feature.geometry.coordinates.length === 1)
if (!canBeImported) {
return Translations.t.general.add.import.wrongTypeToConflate.SetClass("alert")
return new SvelteUIElement(Tr, { t: Translations.t.general.add.import.wrongTypeToConflate, cls: "alert" })
}
const args: ConflateFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, args)
const idOfWayToReplaceGeometry = tagSource.data[args.way_to_conflate]
const argsParsed: ConflateFlowArguments = <any>SpecialVisualizationUtils.parseArgs(this.args, args)
const tagsToApply = ImportFlowUtils.getTagsToApply(<UIEventSource<OsmTags>>tags, argsParsed)
const idOfWayToReplaceGeometry = tags.data[argsParsed.way_to_conflate]
const importFlow = new ConflateImportFlowState(
state,
<Feature<LineString | Polygon>>feature,
args,
argsParsed,
tagsToApply,
tagSource,
tags,
idOfWayToReplaceGeometry
)
return new SvelteUIElement(WayImportFlow, {
importFlow,
})
return new SvelteUIElement(WayImportFlow, { importFlow })
}
getLayerDependencies = (args: string[]) =>

View file

@ -66,13 +66,14 @@ ${Utils.special_visualizations_importRequirementDocs}
* Given the tagsstore of the point which represents the challenge, creates a new store with tags that should be applied onto the newly created point,
*/
public static getTagsToApply(
originalFeatureTags: UIEventSource<OsmTags>,
originalFeatureTags: Store<OsmTags>,
args: { tags: string }
): Store<Tag[]> {
if (originalFeatureTags === undefined) {
return undefined
}
let newTags: Store<Tag[]>
// Listing of the keys that should be transferred
const tags = args.tags
if (
tags.indexOf(" ") < 0 &&
@ -81,12 +82,6 @@ ${Utils.special_visualizations_importRequirementDocs}
) {
// This is a property to expand...
const items: string = originalFeatureTags.data[tags]
console.debug(
"The import button is using tags from properties[" +
tags +
"] of this object, namely ",
items
)
if (items.startsWith("{")) {
// This is probably a JSON

View file

@ -1,7 +1,5 @@
import { Feature, Point } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource"
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
import BaseUIElement from "../../BaseUIElement"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../../SpecialVisualization"
import SvelteUIElement from "../../Base/SvelteUIElement"
import PointImportFlow from "./PointImportFlow.svelte"
import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlowState"
@ -9,12 +7,14 @@ import { Utils } from "../../../Utils"
import { ImportFlowUtils } from "./ImportFlow"
import Translations from "../../i18n/Translations"
import { GeoOperations } from "../../../Logic/GeoOperations"
import Tr from "../../Base/Tr.svelte"
import { UIEventSource } from "../../../Logic/UIEventSource"
import { OsmTags } from "../../../Models/OsmFeature"
/**
* The wrapper to make the special visualisation for the PointImportFlow
*/
export class PointImportButtonViz extends SpecialVisualization {
export class PointImportButtonViz extends SpecialVisualizationSvelte {
public readonly funcName = "import_button"
public readonly docs: string =
"This button will copy the point from an external dataset into OpenStreetMap" +
@ -47,29 +47,24 @@ export class PointImportButtonViz extends SpecialVisualization {
public needsUrls = []
group = "data_import"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature
): BaseUIElement {
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement {
const to_point_index = this.args.findIndex((arg) => arg.name === "to_point")
const summarizePointArg = argument[to_point_index].toLowerCase()
const summarizePointArg = args[to_point_index].toLowerCase()
if (feature.geometry.type !== "Point") {
if (summarizePointArg !== "no" && summarizePointArg !== "false") {
feature = GeoOperations.centerpoint(feature)
} else {
return Translations.t.general.add.import.wrongType.SetClass("alert")
return new SvelteUIElement(Tr, { t: Translations.t.general.add.import.wrongType.SetClass("alert") })
}
}
const baseArgs: PointImportFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, baseArgs)
const baseArgs: PointImportFlowArguments = <any>Utils.ParseVisArgs(this.args, args)
const tagsToApply = ImportFlowUtils.getTagsToApply(<UIEventSource<OsmTags>>tags, baseArgs)
const importFlow = new PointImportFlowState(
state,
<Feature<Point>>feature,
baseArgs,
tagsToApply,
tagSource
tags
)
return new SvelteUIElement(PointImportFlow, {

View file

@ -1,10 +1,12 @@
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
import {
SpecialVisualisationParams,
SpecialVisualizationSvelte,
SpecialVisualizationUtils,
} from "../../SpecialVisualization"
import { AutoAction } from "../AutoApplyButtonVis"
import { Feature, LineString, Polygon } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource"
import BaseUIElement from "../../BaseUIElement"
import { ImportFlowUtils } from "./ImportFlow"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import SvelteUIElement from "../../Base/SvelteUIElement"
import WayImportFlow from "./WayImportFlow.svelte"
import WayImportFlowState, { WayImportFlowArguments } from "./WayImportFlowState"
@ -13,12 +15,11 @@ import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
import { Changes } from "../../../Logic/Osm/Changes"
import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource"
import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { OsmTags } from "../../../Models/OsmFeature"
/**
* Wrapper around 'WayImportFlow' to make it a special visualisation
*/
export default class WayImportButtonViz extends SpecialVisualization implements AutoAction {
export default class WayImportButtonViz extends SpecialVisualizationSvelte implements AutoAction {
public readonly funcName: string = "import_way_button"
needsUrls = []
group = "data_import"
@ -60,25 +61,20 @@ export default class WayImportButtonViz extends SpecialVisualization implements
public readonly supportsAutoAction = true
public readonly needsNodeDatabase = true
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature,
_: LayerConfig
): BaseUIElement {
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement {
const geometry = feature.geometry
if (!(geometry.type == "LineString" || geometry.type === "Polygon")) {
throw "Invalid type to import " + geometry.type
throw "Invalid type to import, expected linestring of polygon but got " + geometry.type
}
const args: WayImportFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, args)
const parsedArgs: WayImportFlowArguments = <any>SpecialVisualizationUtils.parseArgs(this.args, args)
console.log("Parsed args are", parsedArgs)
const tagsToApply = ImportFlowUtils.getTagsToApply(tags, parsedArgs)
const importFlow = new WayImportFlowState(
state,
<Feature<LineString | Polygon>>feature,
args,
parsedArgs,
tagsToApply,
tagSource
tags,
)
return new SvelteUIElement(WayImportFlow, {
importFlow,

View file

@ -1,46 +1,49 @@
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
import BaseUIElement from "../../BaseUIElement"
import { UIEventSource } from "../../../Logic/UIEventSource"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../../SpecialVisualization"
import SvelteUIElement from "../../Base/SvelteUIElement"
import { Feature } from "geojson"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { default as LanguageElementSvelte } from "./LanguageElement.svelte"
export class LanguageElement extends SpecialVisualization {
export class LanguageElement extends SpecialVisualizationSvelte {
funcName: string = "language_chooser"
needsUrls = []
docs: string | BaseUIElement =
docs: string =
"The language element allows to show and pick all known (modern) languages. The key can be set"
args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
args: { name: string; defaultValue?: string; doc: string; required?: boolean; type?: string }[] = [
{
name: "key",
required: true,
type:"key",
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `<key>:nl=yes` if _nl_ is picked ",
},
{
name: "question",
required: true,
type: "translation",
doc: "What to ask if no questions are known",
},
{
name: "render_list_item",
type: "translation",
doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).",
defaultValue: "{language()}",
},
{
name: "render_single_language",
type: "translation",
doc: "What will be shown if the feature only supports a single language",
required: true,
},
{
type: "translation",
name: "render_all",
doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
doc: "The full rendering. U0se `{list}` to show where the list of languages must come. Optional if mode=single",
defaultValue: "{list()}",
},
{
name: "no_known_languages",
type: "translation",
doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead",
},
]
@ -59,14 +62,16 @@ export class LanguageElement extends SpecialVisualization {
`
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
{
state,
tags,
args,
feature,
layer,
}: SpecialVisualisationParams,
): SvelteUIElement {
let [key, question, item_render, single_render, all_render, on_no_known_languages] =
argument
args
if (item_render === undefined || item_render.trim() === "") {
item_render = "{language()}"
}
@ -94,7 +99,7 @@ export class LanguageElement extends SpecialVisualization {
return new SvelteUIElement(LanguageElementSvelte, {
key,
tags: tagSource,
tags,
state,
feature,
layer,

View file

@ -1,7 +1,6 @@
import { GeoOperations } from "../../Logic/GeoOperations"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { Feature } from "geojson"
import { ImmutableStore } from "../../Logic/UIEventSource"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement"
import MapillaryLink from "../BigComponents/MapillaryLink.svelte"
@ -20,12 +19,7 @@ export class MapillaryLinkVis extends SpecialVisualizationSvelte {
},
]
public constr(
state: SpecialVisualizationState,
tagsSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature
): SvelteUIElement {
public constr({ args, feature }: SpecialVisualisationParams): SvelteUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
let zoom = Number(args[0])
if (isNaN(zoom)) {

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import type { Feature, Geometry } from "geojson"
import { GeoOperations } from "../../Logic/GeoOperations"
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
import type { SpecialVisualizationState } from "../SpecialVisualization"
@ -8,22 +8,23 @@
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import MaplibreMap from "../Map/MaplibreMap.svelte"
import DelayedComponent from "../Base/DelayedComponent.svelte"
import type { OsmTags } from "../../Models/OsmFeature"
import { onDestroy } from "svelte"
export let state: SpecialVisualizationState
export let tagSource: UIEventSource<Record<string, string>>
export let tags: UIEventSource<Record<string, string>>
export let defaultzoom: number
export let idkeys: string[]
export let feature: Feature
export let clss: string = "h-40 rounded"
const keys = idkeys
let featuresToShow: Store<Feature[]> = state.indexedFeatures.featuresById.map(
let featuresToShow: Store<Feature<Geometry, OsmTags>[]> = state.indexedFeatures.featuresById.map(
(featuresById) => {
if (featuresById === undefined) {
return []
}
const properties = tagSource.data
const features: Feature[] = []
const properties = tags.data
const features: Feature<Geometry, OsmTags>[] = []
for (const key of keys) {
const value = properties[key]
if (value === undefined || value === null) {
@ -44,13 +45,13 @@
console.warn("No feature found for id ", id)
continue
}
features.push(feature)
features.push(<Feature<Geometry, OsmTags>> feature)
}
}
return features
},
[tagSource],
onDestroy
[tags],
onDestroy,
)
let mlmap = new UIEventSource(undefined)
@ -70,7 +71,7 @@
mlmap,
new StaticFeatureSource(featuresToShow),
state.theme.layers,
{ zoomToFeatures: true }
{ zoomToFeatures: true },
)
</script>

View file

@ -1,6 +1,6 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Store } from "../../Logic/UIEventSource"
import { MultiApplyParams } from "./MultiApply"
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement"
import MultiApplyButton from "./MultiApplyButton.svelte"
@ -19,7 +19,9 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
doc: "One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features.",
required: true,
},
{ name: "text", doc: "The text to show on the button" },
{ name: "text",
type: "translation",
doc: "The text to show on the button" },
{
name: "autoapply",
doc: "A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown",
@ -34,17 +36,13 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
example =
"{multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)}"
constr(
state: SpecialVisualizationState,
tagsSource: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
const featureIdsKey = args[0]
const keysToApply = args[1].split(";")
const text = args[2]
const autoapply = args[3]?.toLowerCase() === "true"
const overwrite = args[4]?.toLowerCase() === "true"
const featureIds: Store<string[]> = tagsSource.map((tags) => {
const featureIds: Store<string[]> = tags.map((tags) => {
const ids = tags[featureIdsKey]
try {
if (ids === undefined) {
@ -69,7 +67,7 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
text,
autoapply,
overwrite,
tagsSource,
tagsSource: tags,
state,
}
return new SvelteUIElement(MultiApplyButton, { params })

View file

@ -1,11 +1,11 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Store } from "../../Logic/UIEventSource"
import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import Wikidata from "../../Logic/Web/Wikidata"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement"
import PlantNet from "../PlantNet/PlantNet.svelte"
import { default as PlantNetCode } from "../../Logic/Web/PlantNet"
@ -31,16 +31,13 @@ export class PlantNetDetectionViz extends SpecialVisualizationSvelte {
args = [
{
name: "image_key",
type:"key",
defaultValue: AllImageProviders.defaultKeys.join(","),
doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ",
},
]
public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
public constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
let imagePrefixes: string[] = undefined
if (args.length > 0) {
imagePrefixes = [].concat(...args.map((a) => a.split(",")))

View file

@ -1,6 +1,5 @@
import { UIEventSource } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement"
import ShareButton from "../Base/ShareButton.svelte"
@ -17,25 +16,25 @@ export class ShareLinkViz extends SpecialVisualizationSvelte {
},
{
name: "text",
type:"translation",
doc: "The text to show on the button. If none is given, will act as a titleIcon",
},
]
needsUrls = []
public constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[]
public constr({
state,
tags,
args}:SpecialVisualisationParams
) {
const text = args[1]
const generateShareData = () => {
const title = state?.theme?.title?.txt ?? "MapComplete"
const matchingLayer: LayerConfig = state?.theme?.getMatchingLayer(tagSource?.data)
const matchingLayer: LayerConfig = state?.theme?.getMatchingLayer(tags?.data)
let name =
matchingLayer?.title?.GetRenderValue(tagSource.data)?.Subs(tagSource.data)?.txt ??
tagSource.data?.name ??
matchingLayer?.title?.GetRenderValue(tags.data)?.Subs(tags.data)?.txt ??
tags.data?.name ??
"POI"
if (name) {
name = `${name} (${title})`

View file

@ -51,7 +51,7 @@
{
try {
return specpart.func
.constr(state, tags, specpart.args, feature, layer)
.constr({state, tags, args : specpart.args, feature, layer})
?.SetClass(specpart.style)
} catch (e) {
console.error(

View file

@ -1,4 +1,9 @@
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import {
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { GeoOperations } from "../../Logic/GeoOperations"
import Constants from "../../Models/Constants"
@ -9,18 +14,14 @@ import { ServerSourceInfo } from "../../Models/SourceOverview"
/**
* Wrapper around 'UploadTraceToOsmUI'
*/
export class UploadToOsmViz extends SpecialVisualization {
export class UploadToOsmViz extends SpecialVisualizationSvelte {
funcName = "upload_to_osm"
docs =
"Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored."
args = []
needsUrls: ServerSourceInfo[] = [Constants.osmAuthConfig]
constr(
state: SpecialVisualizationState,
_: UIEventSource<Record<string, string>>,
__: string[]
) {
constr({ state }: SpecialVisualisationParams): SvelteUIElement {
const locations = state.historicalUserLocations.features.data
return new SvelteUIElement(UploadTraceToOsmUI, {
state,

View file

@ -1,4 +1,5 @@
import {
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
@ -47,6 +48,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
args = [
{
name: "message",
type: "translation",
doc: "A message to show to the user",
},
{
@ -56,6 +58,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
},
{
name: "message_confirm",
type: "translation",
doc: "What to show when the task is closed, either by the user or was already closed.",
},
{
@ -65,17 +68,19 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
},
{
name: "maproulette_id",
type:"key",
doc: "The property name containing the maproulette id",
defaultValue: "mr_taskId",
},
{
name: "ask_feedback",
type: "translation",
doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added",
defaultValue: "",
},
]
constr(state, tagsSource, args) {
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
let [message, image, message_closed, statusToSet, maproulette_id_key, askFeedback] = args
if (image === "") {
image = "confirm"
@ -86,7 +91,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
statusToSet = statusToSet ?? "1"
return new SvelteUIElement(MaprouletteSetStatus, {
state,
tags: tagsSource,
tags,
message,
image,
message_closed,
@ -106,6 +111,7 @@ class LinkedDataFromWebsite extends SpecialVisualization {
{
name: "key",
defaultValue: "website",
type:"key",
doc: "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>",
},
{
@ -144,21 +150,15 @@ class LinkedDataFromWebsite extends SpecialVisualization {
},
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
if (state.theme.enableMorePrivacy) {
return undefined
}
const key = argument[0] ?? "website"
const useProxy = argument[1] !== "no"
const readonly = argument[3] === "readonly"
const isClosed = (argument[4] ?? "yes") === "yes"
const key = args[0] ?? "website"
const useProxy = args[1] !== "no"
const readonly = args[3] === "readonly"
const isClosed = (args[4] ?? "yes") === "yes"
const downloadInformation = new UIEventSource(false)
const countryStore: Store<string | undefined> = tags.mapD((tags) => tags._country)
const sourceUrl: Store<string | undefined> = tags.mapD((tags) => {
@ -240,7 +240,7 @@ class LinkedDataFromWebsite extends SpecialVisualization {
}
}
class CompareData extends SpecialVisualization {
class CompareData extends SpecialVisualizationSvelte {
funcName = "compare_data"
group = "data_import"
needsUrls = (args) => args[1].split(";")
@ -248,6 +248,7 @@ class CompareData extends SpecialVisualization {
{
name: "url",
required: true,
type:"key",
doc: "The attribute containing the url where to fetch more data",
},
{
@ -264,20 +265,14 @@ class CompareData extends SpecialVisualization {
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"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
const url = args[0]
const readonly = args[3] === "yes"
const externalData = UIEventSource.fromPromiseWithErr(Utils.downloadJson(url))
return new SvelteUIElement(ComparisonTool, {
url,
state,
tags: tagSource,
tags,
layer,
feature,
readonly,
@ -286,7 +281,7 @@ class CompareData extends SpecialVisualization {
}
}
export class DataImportSpecialVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
public static initList(): SpecialVisualizationSvelte[] {
return [
new TagApplyViz(),
new PointImportButtonViz(),

View file

@ -1,7 +1,4 @@
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement"
import MarkAsFavourite from "../Popup/MarkAsFavourite.svelte"
import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte"
@ -14,19 +11,8 @@ class FavouriteStatus extends SpecialVisualizationSvelte {
args = []
group = "favourites"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
return new SvelteUIElement(MarkAsFavourite, {
tags: tagSource,
state,
layer,
feature,
})
constr(params: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(MarkAsFavourite, params)
}
}
@ -37,19 +23,8 @@ class FavouriteIcon extends SpecialVisualizationSvelte {
"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<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
return new SvelteUIElement(MarkAsFavouriteMini, {
tags: tagSource,
state,
layer,
feature,
})
constr(params: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(MarkAsFavouriteMini, params)
}
}

View file

@ -1,12 +1,9 @@
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import SvelteUIElement from "../Base/SvelteUIElement"
import ImageCarousel from "../Image/ImageCarousel.svelte"
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"
@ -32,13 +29,7 @@ class NearbyImageVis extends SpecialVisualizationSvelte {
funcName = "nearby_images"
needsUrls = CombinedFetcher.apiUrls
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
const isOpen = args[0] === "open"
const readonly = args[1] === "readonly" || args[1] === "yes"
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
@ -69,7 +60,7 @@ class ImageCarouselVis extends SpecialVisualizationSvelte {
]
needsUrls = AllImageProviders.apiUrls
constr(state, tags, args, feature) {
constr({state, tags, args, feature}: SpecialVisualisationParams) {
let imagePrefixes: string[] = undefined
if (args.length > 0) {
imagePrefixes = [].concat(...args.map((a) => a.split(";")))
@ -95,8 +86,8 @@ class ImageUpload extends SpecialVisualizationSvelte {
needsUrls = [Constants.panoramax, Constants.osmAuthConfig]
args = [
{
type: "key",
name: "image_key",
type: "key",
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
defaultValue: "panoramax",
required: false,
@ -105,6 +96,7 @@ class ImageUpload extends SpecialVisualizationSvelte {
name: "label",
doc: "The text to show on the button",
required: false,
type: "translation"
},
{
name: "disable_blur",
@ -113,7 +105,7 @@ class ImageUpload extends SpecialVisualizationSvelte {
},
]
constr(state, tags, args, feature) {
constr({state, tags, args, feature}: SpecialVisualisationParams) {
const targetKey = args[0] === "" ? undefined : args[0]
const noBlur = args[3]?.toLowerCase()?.trim()
return new SvelteUIElement(UploadImage, {

View file

@ -1,8 +1,4 @@
import {
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization"
import Constants from "../../Models/Constants"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
@ -28,6 +24,7 @@ class CloseNoteViz extends SpecialVisualizationSvelte {
{
name: "text",
doc: "Text to show on this button",
type: "translation",
required: true,
},
{
@ -39,6 +36,7 @@ class CloseNoteViz extends SpecialVisualizationSvelte {
name: "idkey",
doc: "The property name where the ID of the note to close can be found",
defaultValue: "id",
type:"key"
},
{
name: "comment",
@ -55,11 +53,7 @@ class CloseNoteViz extends SpecialVisualizationSvelte {
]
public readonly group = "notes"
public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
public constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs(
this.args,
args
@ -87,14 +81,12 @@ class AddNoteCommentViz extends SpecialVisualizationSvelte {
name: "Id-key",
doc: "The property name where the ID of the note to close can be found",
defaultValue: "id",
type:"key"
},
]
public readonly group = "notes"
public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>
): SvelteUIElement {
public constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(AddNoteComment, { state, tags })
}
}
@ -107,13 +99,8 @@ class OpenNote extends SpecialVisualizationSvelte {
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<Record<string, string>>,
argument: string[],
feature: Feature
): SvelteUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
constr({ state, feature }: SpecialVisualisationParams): SvelteUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(<Feature>feature)
return new SvelteUIElement(CreateNewNote, {
state,
coordinate: new UIEventSource({ lon, lat }),
@ -129,12 +116,13 @@ class AddImageToNote extends SpecialVisualizationSvelte {
name: "Id-key",
doc: "The property name where the ID of the note to close can be found",
defaultValue: "id",
type:"key"
},
]
group = "notes"
needsUrls = []
constr(state, tags, args, feature) {
constr({ state, tags, args, feature }: SpecialVisualisationParams) {
const id = tags.data[args[0] ?? "id"]
tags = state.featureProperties.getStore(id)
return new SvelteUIElement(UploadImage, { state, tags, feature })
@ -150,6 +138,7 @@ class VisualiseNoteComment extends SpecialVisualization {
name: "commentsKey",
doc: "The property name of the comments, which should be stringified json",
defaultValue: "comments",
type:"key"
},
{
name: "start",
@ -159,7 +148,7 @@ class VisualiseNoteComment extends SpecialVisualization {
]
needsUrls = [Constants.osmAuthConfig]
constr(state, tags, args) {
constr({ state, tags, args }: SpecialVisualisationParams) {
return new VariableUiElement(
tags
.map((tags) => tags[args[0]])
@ -186,7 +175,7 @@ class VisualiseNoteComment extends SpecialVisualization {
}
export class NoteVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
public static initList(): SpecialVisualization[] {
return [
new AddNoteCommentViz(),
new CloseNoteViz(),

View file

@ -1,4 +1,5 @@
import {
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
@ -16,6 +17,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import BaseUIElement from "../BaseUIElement"
import Combine from "../Base/Combine"
import { ServerSourceInfo } from "../../Models/SourceOverview"
import { WithUserRelatedState } from "../../Models/ThemeViewState/WithUserRelatedState"
class CreateReview extends SpecialVisualizationSvelte {
public static MangroveReviewInfo: ServerSourceInfo = {
@ -43,6 +45,7 @@ class CreateReview extends SpecialVisualizationSvelte {
{
name: "subjectKey",
defaultValue: "name",
type:"key",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
},
{
@ -51,11 +54,13 @@ class CreateReview extends SpecialVisualizationSvelte {
},
{
name: "question",
type: "translation",
doc: "The question to ask during the review",
},
]
constr(state, tags, args, feature, layer) {
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams) {
const nameKey = args[0] ?? "name"
const fallbackName = args[1]
const question = args[2]
@ -67,7 +72,7 @@ class CreateReview extends SpecialVisualizationSvelte {
nameKey: nameKey,
fallbackName,
},
state
<SpecialVisualizationState & WithUserRelatedState>state,
)
return new SvelteUIElement(ReviewForm, {
reviews,
@ -91,6 +96,7 @@ class ListReview extends SpecialVisualizationSvelte {
{
name: "subjectKey",
defaultValue: "name",
type: "key",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
},
{
@ -99,7 +105,7 @@ class ListReview extends SpecialVisualizationSvelte {
},
]
constr(state, tags, args, feature) {
constr({ state, tags, args, feature }: SpecialVisualisationParams) {
const nameKey = args[0] ?? "name"
const fallbackName = args[1]
const reviews = FeatureReviews.construct(
@ -125,6 +131,7 @@ class Rating extends SpecialVisualizationSvelte {
{
name: "subjectKey",
defaultValue: "name",
type:"key",
doc: "The key to use to determine the subject. If the value is specified, the subject will be <b>tags[subjectKey]</b> and will use this to filter the reviews.",
},
{
@ -133,7 +140,7 @@ class Rating extends SpecialVisualizationSvelte {
},
]
constr(state, tags, args, feature) {
constr({ state, tags, args, feature }: SpecialVisualisationParams) {
const nameKey = args[0] ?? "name"
const fallbackName = args[1]
const reviews = FeatureReviews.construct(
@ -161,16 +168,13 @@ class ImportMangroveKey extends SpecialVisualizationSvelte {
args = [
{
name: "text",
type: "translation",
doc: "The text that is shown on the button",
},
]
constr(
state: SpecialVisualizationState,
_: UIEventSource<Record<string, string>>,
argument: string[]
): SvelteUIElement {
const [text] = argument
constr({ state, args }: SpecialVisualisationParams): SvelteUIElement {
const [text] = args
return new SvelteUIElement(ImportReviewIdentity, { state, text })
}
}
@ -186,6 +190,7 @@ class Reviews extends SpecialVisualization {
{
name: "subjectKey",
defaultValue: "name",
type:"key",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
},
{
@ -194,27 +199,22 @@ class Reviews extends SpecialVisualization {
},
{
name: "question",
type: "translation",
doc: "The question to ask in the review form. Optional",
},
]
needsUrls = [CreateReview.MangroveReviewInfo]
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
constr(params: SpecialVisualisationParams): BaseUIElement {
return new Combine([
new CreateReview().constr(state, tagSource, args, feature, layer),
new ListReview().constr(state, tagSource, args, feature),
new CreateReview().constr(params),
new ListReview().constr(params),
])
}
}
export class ReviewSpecialVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
public static initList(): SpecialVisualization[] {
return [
new Rating(),
new CreateReview(),

View file

@ -1,4 +1,8 @@
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import {
SpecialVisualisationParams,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement"
import Constants from "../../Models/Constants"
import LogoutButton from "../Base/LogoutButton.svelte"
@ -25,7 +29,7 @@ class LanguagePickerVis extends SpecialVisualizationSvelte {
group = "settings"
docs = "A component to set the language of the user interface"
constr(state: SpecialVisualizationState): SvelteUIElement {
constr({ state }: SpecialVisualisationParams): SvelteUIElement {
const availableLanguages = Locale.showLinkToWeblate.map((showTranslations) =>
showTranslations
? LanguageUtils.usedLanguagesSorted
@ -68,7 +72,7 @@ class GpsAllTags extends SpecialVisualizationSvelte {
docs = "Shows the current tags of the GPS-representing object, used for debugging"
args = []
constr(state: SpecialVisualizationState): SvelteUIElement {
constr({ state }: SpecialVisualisationParams): SvelteUIElement {
const tags = (<ThemeViewState>state).geolocation.currentUserLocation.features.map(
(features) => features[0]?.properties
)
@ -85,7 +89,7 @@ class StorageAllTags extends SpecialVisualizationSvelte {
docs = "Shows the current state of storage"
args = []
constr(state: SpecialVisualizationState): SvelteUIElement {
constr({ state }: SpecialVisualisationParams): SvelteUIElement {
const data = {}
for (const key in localStorage) {
data[key] = localStorage[key]
@ -111,18 +115,15 @@ export class ClearCachesVis extends SpecialVisualizationSvelte {
{
name: "text",
required: true,
type: "translation",
doc: "The text to show on the button",
},
]
group = "settings"
constr(
_: SpecialVisualizationState,
__: UIEventSource<Record<string, string>>,
argument: string[]
): SvelteUIElement {
constr({ args }: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(ClearCaches, {
msg: argument[0] ?? "Clear local caches",
msg: args[0] ?? "Clear local caches",
})
}
}
@ -136,13 +137,14 @@ class LoginButtonVis extends SpecialVisualizationSvelte {
},
{
name: "message",
type: "translation",
doc: "Message to display on the button",
},
]
docs = "Show a login button"
group = "settings"
constr(state: SpecialVisualizationState, _, args): SvelteUIElement {
constr({ state, args }: SpecialVisualisationParams): SvelteUIElement {
const force = args[0].toLowerCase()
let msg = args[1]
if (msg === "") {
@ -160,6 +162,7 @@ class QrLogin extends SpecialVisualizationSvelte {
funcName = "qr_login"
args = [
{
type: "translation",
name: "text",
doc: "Extra text on the side of the QR-code",
},
@ -172,16 +175,10 @@ class QrLogin extends SpecialVisualizationSvelte {
"A QR-code which shares the current URL and adds the login token. Anyone with this login token will have the same permissions as you currently have. Logging out from this session will also log them out"
group = "settings"
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
const shared_oauth_cookie = state.osmConnection.getToken()
const sideText = argument[0]
const sideTextClass = argument[1] ?? ""
const sideText = args[0]
const sideTextClass = args[1] ?? ""
return new SvelteUIElement(QrCode, {
state,
tags,
@ -199,8 +196,8 @@ class Logout extends SpecialVisualizationSvelte {
docs = "Shows a button where the user can log out"
group = "settings"
constr(state: SpecialVisualizationState): SvelteUIElement {
return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection })
constr({ state }: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(LogoutButton,state)
}
}
@ -210,7 +207,7 @@ class PendingChanges extends SpecialVisualizationSvelte {
group = "settings"
args = []
constr(state: SpecialVisualizationState): SvelteUIElement {
constr({ state }: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(PendingChangesIndicator, { state, compact: false })
}
}
@ -221,8 +218,8 @@ class ClearLocationHistoryVis extends SpecialVisualizationSvelte {
docs = "A button to remove the travelled track information from the device"
args = []
constr(state) {
return new SvelteUIElement(ClearGPSHistory, { state })
constr(params: SpecialVisualisationParams) {
return new SvelteUIElement(ClearGPSHistory, params)
}
}
@ -230,7 +227,6 @@ export class SettingsVisualisations {
public static initList(): SpecialVisualizationSvelte[] {
return [
new LanguagePickerVis(),
new DisabledQuestionsVis(),
new GyroscopeAllTags(),
new GpsAllTags(),

View file

@ -1,5 +1,5 @@
import { AutoAction } from "../Popup/AutoApplyButtonVis"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { Utils } from "../../Utils"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Tag } from "../../Logic/Tags/Tag"
@ -26,6 +26,7 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
},
{
name: "message",
type:"translation",
doc: "The text to show to the contributor",
},
{
@ -173,12 +174,7 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
}
}
public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature
): SvelteUIElement {
public constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement {
const tagsToApply: Store<Tag[]> = TagApplyViz.generateTagsToApply(args[0], tags)
const msg = args[1]
let image = args[2]?.trim()

View file

@ -1,4 +1,5 @@
import {
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
@ -27,6 +28,7 @@ class StealViz extends SpecialVisualization {
args = [
{
name: "featureId",
type:"key",
doc: "The key of the attribute which contains the id of the feature from which to use the tags",
required: true,
},
@ -38,7 +40,7 @@ class StealViz extends SpecialVisualization {
]
needsUrls = []
constr(state: SpecialVisualizationState, featureTags, args) {
constr({ state, tags, args }: SpecialVisualisationParams) {
const [featureIdKey, layerAndtagRenderingIds] = args
const tagRenderings: [LayerConfig, TagRenderingConfig][] = []
for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) {
@ -51,7 +53,7 @@ class StealViz extends SpecialVisualization {
throw "Could not create stolen tagrenddering: tagRenderings not found"
}
return new VariableUiElement(
featureTags.map(
tags.map(
(tags) => {
const featureId = tags[featureIdKey]
if (featureId === undefined) {
@ -117,6 +119,7 @@ class Multi extends SpecialVisualization {
args = [
{
name: "key",
type:"key",
doc: "The property to read and to interpret as a list of properties",
required: true,
},
@ -131,18 +134,12 @@ class Multi extends SpecialVisualization {
},
]
constr(
state: SpecialVisualizationState,
featureTags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
) {
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams) {
const [key, tr, classesRaw] = args
const classes = classesRaw ?? ""
const translation = new Translation({ "*": tr })
return new VariableUiElement(
featureTags.map((tags) => {
tags.map((tags) => {
let properties: object[]
if (typeof tags[key] === "string") {
properties = JSON.parse(tags[key])
@ -196,20 +193,14 @@ class Group extends SpecialVisualizationSvelte {
},
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
selectedElement: Feature,
layer: LayerConfig
): SvelteUIElement {
const [header, labelsStr, blacklistStr] = argument
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
const [header, labelsStr, blacklistStr] = args
const labels = labelsStr.split(";").map((x) => x.trim())
const blacklist = blacklistStr?.split(";")?.map((x) => x.trim()) ?? []
return new SvelteUIElement(GroupedView, {
state,
tags,
selectedElement,
selectedElement: feature,
layer,
header,
labels,
@ -222,19 +213,19 @@ class OpenInId extends SpecialVisualizationSvelte {
funcName = "open_in_iD"
docs = "Opens the current view in the iD-editor"
args = []
group = "tagrendering_manipulation"
group = "web_and_communication"
constr(state, feature): SvelteUIElement {
constr({state, feature}: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(OpenIdEditor, {
mapProperties: state.mapProperties,
objectId: feature.data.id,
objectId: feature.properties.id,
})
}
}
class OpenInJosm extends SpecialVisualizationSvelte {
funcName = "open_in_josm"
group = "tagrendering_manipulation"
group = "web_and_communication"
docs = "Opens the current view in the JOSM-editor"
args = []
needsUrls = [
@ -252,12 +243,12 @@ class OpenInJosm extends SpecialVisualizationSvelte {
},
]
constr(state): SvelteUIElement {
return new SvelteUIElement(OpenJosm, { state })
constr(params: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(OpenJosm, params)
}
}
export default class TagrenderingManipulationSpecialVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
public static initList(): SpecialVisualization[] {
return [new StealViz(), new Multi(), new Group(), new OpenInId(), new OpenInJosm()]
}
}

View file

@ -1,4 +1,5 @@
import {
SpecialVisualisationParams,
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
@ -49,13 +50,7 @@ class QuestionViz extends SpecialVisualizationSvelte {
]
group = "default"
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
const labels = args[0]
?.split(";")
?.map((s) => s.trim())
@ -99,6 +94,7 @@ class Minimap extends SpecialVisualizationSvelte {
doc: "The key of one or more properties of the feature, semi-colon separated. 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. ",
name: "idKey",
defaultValue: "id",
type:"key"
},
{
name: "class",
@ -109,12 +105,7 @@ class Minimap extends SpecialVisualizationSvelte {
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<Record<string, string>>,
args: string[],
feature: Feature
): SvelteUIElement {
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
const minzoom = Number(args[0] ?? 18)
const ids = args[1]?.split(";")?.map((s) => s.trim()) ?? ["id"]
const clss = args[2]
@ -124,7 +115,7 @@ class Minimap extends SpecialVisualizationSvelte {
idkeys: ids,
clss,
feature,
tagSource,
tags,
})
}
}
@ -135,12 +126,9 @@ class SplitButton extends SpecialVisualizationSvelte {
args = []
group = "default"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>
): SvelteUIElement {
constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(SplitRoadWizard, {
id: tagSource.map((pr) => pr.id),
id: tags.map((pr) => pr.id),
state,
})
}
@ -153,13 +141,7 @@ class MoveButton extends SpecialVisualizationSvelte {
args = []
group = "default"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
constr({ state, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
if (feature.geometry.type !== "Point") {
return undefined
}
@ -179,18 +161,12 @@ class DeleteButton extends SpecialVisualizationSvelte {
args = []
group = "default"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
constr({ state, tags, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
if (!layer.deletion) {
return undefined
}
return new SvelteUIElement(DeleteWizard, {
tags: tagSource,
tags,
deleteConfig: layer.deletion,
state,
feature,
@ -203,7 +179,7 @@ class QrCodeVis extends SpecialVisualizationSvelte {
funcName = "qr_code"
args = [
{
name: "text",
name: "text",type:"translation",
doc: "Extra text on the side of the QR-code",
},
{
@ -214,14 +190,9 @@ class QrCodeVis extends SpecialVisualizationSvelte {
group = "default"
docs = "Generates a QR-code to share the selected object"
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature
): SvelteUIElement {
const sideText = argument[0]
const sideTextClass = argument[1] ?? ""
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement {
const sideText = args[0]
const sideTextClass = args[1] ?? ""
return new SvelteUIElement(QrCode, {
state,
tags,
@ -238,6 +209,7 @@ class IfNothingKnown extends SpecialVisualizationSvelte {
{
name: "text",
doc: "Text to show",
type:"translation",
required: true,
},
{ name: "cssClasses", doc: "Classes to apply onto the text" },
@ -246,18 +218,12 @@ class IfNothingKnown extends SpecialVisualizationSvelte {
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<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
const text = argument[0]
const cssClasses = argument[1]
constr({ state, tags, args, layer }: SpecialVisualisationParams): SvelteUIElement {
const text = args[0]
const cssClasses = args[1]
return new SvelteUIElement(NothingKnown, {
state,
tags: tagSource,
tags,
layer,
text,
cssClasses,
@ -272,7 +238,7 @@ class AddNewPointVis extends SpecialVisualizationSvelte {
args = []
group = "default"
constr(state: SpecialVisualizationState, _, __, feature: GeoJSON): SvelteUIElement {
constr({ state, feature }: SpecialVisualisationParams): SvelteUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(AddNewPoint, {
state,
@ -295,14 +261,10 @@ class Translated extends SpecialVisualization {
},
]
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[]
): BaseUIElement {
constr({ tags, args }: SpecialVisualisationParams): BaseUIElement {
return new VariableUiElement(
tagSource.map((tags) => {
const v = tags[argument[0] ?? "value"]
tags.map((tags) => {
const v = tags[args[0] ?? "value"]
try {
const tr = typeof v === "string" ? JSON.parse(v) : v
return new Translation(tr).SetClass("font-bold")
@ -325,14 +287,8 @@ class TitleVis extends SpecialVisualizationSvelte {
example =
"`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`."
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
_: string[],
feature: Feature,
layer: LayerConfig
) {
return new SvelteUIElement(FeatureTitle, { state, tags, feature, layer })
constr(params: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(FeatureTitle, params)
}
}
@ -346,16 +302,11 @@ class BracedVis extends SpecialVisualization {
name: "text",
required: true,
doc: "The value to show",
type:"translation"
},
]
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
constr({ args }: SpecialVisualisationParams): BaseUIElement {
return new FixedUiElement("{" + args[0] + "}")
}
}
@ -365,19 +316,9 @@ class CreateCopyVis extends SpecialVisualizationSvelte {
funcName = "create_copy"
docs = "Allow to create a copy of the current element"
args = []
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
try {
console.log(">>> create_copy invoked")
return new SvelteUIElement(CreateCopy, { state, tags, argument, feature, layer })
} catch (e) {
console.error(">>> failed", e)
}
constr(params: SpecialVisualisationParams): SvelteUIElement {
return new SvelteUIElement(CreateCopy, params)
}
}

View file

@ -1,10 +1,5 @@
import {
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
import SvelteUIElement from "../Base/SvelteUIElement"
import FediverseLink from "../Popup/FediverseLink.svelte"
import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata"
@ -18,35 +13,33 @@ import SendEmail from "../Popup/SendEmail.svelte"
import DynLink from "../Base/DynLink.svelte"
import { Lists } from "../../Utils/Lists"
class FediverseLinkVis extends SpecialVisualization {
class FediverseLinkVis extends SpecialVisualizationSvelte {
funcName = "fediverse_link"
group = "web_and_communication"
docs = "Converts a fediverse username or link into a clickable link"
args = [
{
name: "key",
type:"key",
doc: "The attribute-name containing the link",
required: true,
},
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[]
): BaseUIElement {
const key = argument[0]
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
const key = args[0]
return new SvelteUIElement(FediverseLink, { key, tags, state })
}
}
class WikipediaVis extends SpecialVisualization {
class WikipediaVis extends SpecialVisualizationSvelte {
funcName = "wikipedia"
group = "web_and_communication"
docs = "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag."
args = [
{
name: "keyToShowWikipediaFor",
type:"key",
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",
defaultValue: "wikidata;wikipedia",
},
@ -56,9 +49,9 @@ class WikipediaVis extends SpecialVisualization {
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"
constr(_, tagsSource, args) {
constr({ tags, args }: SpecialVisualisationParams) {
const keys = args[0].split(";").map((k) => k.trim())
const wikiIds: Store<string[]> = tagsSource.map((tags) => {
const wikiIds: Store<string[]> = tags.map((tags) => {
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
})
@ -76,6 +69,7 @@ class WikidatalabelVis extends SpecialVisualization {
args = [
{
name: "keyToShowWikidataFor",
type:"key",
doc: "Use the wikidata entry from this key to show the label",
defaultValue: "wikidata",
},
@ -85,8 +79,8 @@ class WikidatalabelVis extends SpecialVisualization {
example =
"`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself"
constr(_, tagsSource, args) {
const id = tagsSource
constr({ tags, args }: SpecialVisualisationParams) {
const id = tags
.map((tags) => tags[args[0]])
.map((wikidata) => {
const wikidataIds = Lists.noEmpty(
@ -117,28 +111,32 @@ class SendEmailVis extends SpecialVisualizationSvelte {
{
name: "to",
doc: "Who to send the email to?",
type:"key",
required: true,
},
{
name: "subject",
type: "translation",
doc: "The subject of the email",
required: true,
},
{
name: "body",
type: "translation",
doc: "The text in the email",
required: true,
},
{
name: "button_text",
type: "translation",
doc: "The text shown on the button in the UI",
required: true,
},
]
constr(__, tags, args) {
return new SvelteUIElement(SendEmail, { args, tags })
constr(params: SpecialVisualisationParams) {
return new SvelteUIElement(SendEmail, params)
}
}
@ -151,11 +149,12 @@ class LinkVis extends SpecialVisualizationSvelte {
{
name: "text",
doc: "Text to be shown",
type: "translation",
required: true,
},
{
name: "href",
doc: "The URL to link to. Note that this will be URI-encoded before ",
doc: "The URL to link to. Note that this will be URI-encoded before and (as everything) supports substitutions of attributes",
required: true,
},
{
@ -176,31 +175,27 @@ class LinkVis extends SpecialVisualizationSvelte {
},
]
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[]
): SvelteUIElement {
constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement {
let [text, href, classnames, download, ariaLabel, icon] = args
if (download === "") {
download = undefined
}
const newTab = download === undefined && !href.startsWith("#")
const textStore = tagSource.map((tags) => Utils.SubstituteKeys(text, tags))
const hrefStore = tagSource.map((tags) => Utils.SubstituteKeys(href, tags))
const textStore = tags.map((tags) => Utils.SubstituteKeys(text, tags))
const hrefStore = tags.map((tags) => Utils.SubstituteKeys(href, tags))
return new SvelteUIElement(DynLink, {
text: textStore,
href: hrefStore,
classnames: new ImmutableStore(classnames),
download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)),
ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)),
download: tags.map((tags) => Utils.SubstituteKeys(download, tags)),
ariaLabel: tags.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)),
newTab: new ImmutableStore(newTab),
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)),
icon: tags.map((tags) => Utils.SubstituteKeys(icon, tags)),
})
}
}
export class WebAndCommunicationSpecialVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
public static initList(): SpecialVisualization[] {
return [
new FediverseLinkVis(),
new WikipediaVis(),

View file

@ -28,6 +28,7 @@ import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropert
import SvelteUIElement from "./Base/SvelteUIElement"
import { Utils } from "../Utils"
import { ServerSourceInfo } from "../Models/SourceOverview"
import { Translation } from "./i18n/Translation"
/**
* The state needed to render a special Visualisation.
@ -87,9 +88,34 @@ export interface SpecialVisualizationState {
reportError(message: string | Error | XMLHttpRequest, extramessage?: string): Promise<void>
}
export interface SpecialVisualisationArg {
name: string
defaultValue?: string
doc: string
required?: false | boolean
type?: "key" | "translation" | string
}
export class SpecialVisualizationUtils {
static parseArgs(specs: { name: string; defaultValue?: string }[], args: string[]){
return Utils.ParseVisArgs(specs, args)
}
}
export interface SpecialVisualisationParams {
state: SpecialVisualizationState
tags: UIEventSource<Record<string, string>>
args: string[]
feature: Feature
layer: LayerConfig
}
export abstract class SpecialVisualization {
readonly funcName: string
readonly docs: string | BaseUIElement
readonly docs: string
/**
* The 'group' is merely what association it has in the docs
*/
@ -107,34 +133,16 @@ export abstract class SpecialVisualization {
/**
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
*/
readonly args: {
name: string
defaultValue?: string
doc: string
required?: false | boolean
type?: "key" | string
}[]
readonly args: SpecialVisualisationArg[]
readonly getLayerDependencies?: (argument: string[]) => string[]
structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]
abstract constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement
abstract constr(options: SpecialVisualisationParams): BaseUIElement
}
export abstract class SpecialVisualizationSvelte extends SpecialVisualization {
abstract constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement
abstract constr(options: SpecialVisualisationParams): SvelteUIElement
}
export type RenderingSpecification =

View file

@ -1,4 +1,4 @@
import { RenderingSpecification, SpecialVisualization } from "./SpecialVisualization"
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationSvelte } from "./SpecialVisualization"
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
import { MultiApplyViz } from "./Popup/MultiApplyViz"
import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis"
@ -176,22 +176,26 @@ export default class SpecialVisualizations {
}
private static initList(): SpecialVisualization[] {
const specialVisualizations: SpecialVisualization[] = [
const specialVisualizationsSv: SpecialVisualizationSvelte[] = [
...ImageVisualisations.initList(),
...NoteVisualisations.initList(),
...FavouriteVisualisations.initList(),
...UISpecialVisualisations.initList(),
...SettingsVisualisations.initList(),
...ReviewSpecialVisualisations.initList(),
...DataImportSpecialVisualisations.initList(),
...TagrenderingManipulationSpecialVisualisations.initList(),
...WebAndCommunicationSpecialVisualisations.initList(),
...DataVisualisations.initList(),
...DataExportVisualisations.initList(),
new UploadToOsmViz(),
new MultiApplyViz(),
]
const specialVisualizations: SpecialVisualization[] = [
...NoteVisualisations.initList(),
...UISpecialVisualisations.initList(),
...ReviewSpecialVisualisations.initList(),
...TagrenderingManipulationSpecialVisualisations.initList(),
...WebAndCommunicationSpecialVisualisations.initList(),
...DataVisualisations.initList(),
...specialVisualizationsSv
]
specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations))
if (Utils.runningFromConsole) {

View file

@ -1,6 +1,5 @@
import DOMPurify from "dompurify"
import { Lists } from "./Utils/Lists"
import { Strings } from "./Utils/Strings"
export class Utils {
/**
@ -292,7 +291,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
seen.add(ks)
uniq.push(img)
}
} else {
} else if(ks){
const ksNoNull = Lists.noNull(ks)
const hasBeenSeen = ksNoNull.some((k) => seen.has(k))
if (!hasBeenSeen) {

View file

@ -1,4 +1,4 @@
<script>
export let color = "#000000"
</script>
<svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown on:focus viewBox="0 0 19.998842 21.478794" version="1.1" id="svg6" sodipodi:docname="north_arrow.svg" width="19.998842" height="21.478794" inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs id="defs6" /> <sodipodi:namedview id="namedview6" pagecolor="#ffffff" bordercolor="#999999" borderopacity="1" inkscape:showpageshadow="2" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showguides="true" inkscape:zoom="31.809268" inkscape:cx="10.374335" inkscape:cy="10.327179" inkscape:window-width="1920" inkscape:window-height="1005" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg6" /> <g id="g6" transform="matrix(1.3906291,1.4182437,-1.4182437,1.3906291,10.496254,-26.783855)" style="fill:{color};fill-opacity:1"> <path fill="#dd2e44" d="M 18.378552,14.993974 9.879,9.886 l 5.123325,8.551246 1.328258,-2.074158 z" id="path5" sodipodi:nodetypes="ccccc" style="fill:{color};fill-opacity:1" /> </g> <path style="font-size:10.2423px;line-height:1;font-family:KacstDigital;-inkscape-font-specification:'KacstDigital, Normal';fill:#ffffff;fill-opacity:1;stroke:#fffbff;stroke-width:0.46;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" d="M 8.3448502,13.390069 H 9.141387 l 1.938629,3.657626 v -3.657626 h 0.573976 v 4.372166 H 10.857453 L 8.9188256,14.104609 v 3.657626 H 8.3448502 Z" id="text6" aria-label="N" /> </svg>
<svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown on:focus viewBox="0 0 19.998842 21.478794" version="1.1" id="svg6" sodipodi:docname="north_arrow.svg" width="19.998842" height="21.478794" inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs id="defs6" /> <sodipodi:namedview id="namedview6" pagecolor="#ffffff" bordercolor="#999999" borderopacity="1" inkscape:showpageshadow="2" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showguides="true" inkscape:zoom="31.809268" inkscape:cx="7.4506587" inkscape:cy="14.445475" inkscape:window-width="1920" inkscape:window-height="1005" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg6" /> <g id="g6" transform="matrix(1.3906291,1.4182437,-1.4182437,1.3906291,10.496254,-26.783855)" style="fill:{color};fill-opacity:1;stroke:#ffffff;stroke-opacity:0.82973289;stroke-width:0.40276519;stroke-dasharray:none"> <path fill="#dd2e44" d="M 18.378552,14.993974 9.879,9.886 l 5.123325,8.551246 1.328258,-2.074158 z" id="path5" sodipodi:nodetypes="ccccc" style="fill:{color};fill-opacity:1;stroke:#ffffff;stroke-opacity:0.82973289;stroke-width:0.40276519;stroke-dasharray:none" /> </g> <path style="font-size:10.2423px;line-height:1;font-family:KacstDigital;-inkscape-font-specification:'KacstDigital, Normal';fill:#ffffff;fill-opacity:1;stroke:#fffbff;stroke-width:0.46;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" d="M 8.3448502,13.390069 H 9.141387 l 1.938629,3.657626 v -3.657626 h 0.573976 v 4.372166 H 10.857453 L 8.9188256,14.104609 v 3.657626 H 8.3448502 Z" id="text6" aria-label="N" /> </svg>