Chore: housekeeping

This commit is contained in:
Pieter Vander Vennet 2025-02-10 02:04:58 +01:00
parent cd9e03dd6f
commit b300fffdc5
156 changed files with 4436 additions and 1318 deletions

View file

@ -19,273 +19,272 @@ import { Utils } from "../../Utils"
export class DataImportSpecialVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
return [
new TagApplyButton(),
new PointImportButtonViz(),
new WayImportButtonViz(),
new ConflateImportButtonViz(),
new PlantNetDetectionViz(),
{
funcName: "maproulette_set_status",
group: "data_import",
docs: "Change the status of the given MapRoulette task",
needsUrls: [Maproulette.defaultEndpoint],
example:
" The following example sets the status to '2' (false positive)\n" +
"\n" +
"```json\n" +
"{\n" +
" \"id\": \"mark_duplicate\",\n" +
" \"render\": {\n" +
" \"special\": {\n" +
" \"type\": \"maproulette_set_status\",\n" +
" \"message\": {\n" +
" \"en\": \"Mark as not found or false positive\"\n" +
" },\n" +
" \"status\": \"2\",\n" +
" \"image\": \"close\"\n" +
" }\n" +
" }\n" +
"}\n" +
"```",
args: [
{
name: "message",
doc: "A message to show to the user"
},
{
name: "image",
doc: "Image to show",
defaultValue: "confirm"
},
{
name: "message_confirm",
doc: "What to show when the task is closed, either by the user or was already closed."
},
{
name: "status",
doc: "A statuscode to apply when the button is clicked. 1 = `close`, 2 = `false_positive`, 3 = `skip`, 4 = `deleted`, 5 = `already fixed` (on the map, e.g. for duplicates), 6 = `too hard`",
defaultValue: "1"
},
{
name: "maproulette_id",
doc: "The property name containing the maproulette id",
defaultValue: "mr_taskId"
},
{
name: "ask_feedback",
doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added",
defaultValue: ""
}
],
return [
new TagApplyButton(),
new PointImportButtonViz(),
new WayImportButtonViz(),
new ConflateImportButtonViz(),
new PlantNetDetectionViz(),
{
funcName: "maproulette_set_status",
group: "data_import",
docs: "Change the status of the given MapRoulette task",
needsUrls: [Maproulette.defaultEndpoint],
example:
" The following example sets the status to '2' (false positive)\n" +
"\n" +
"```json\n" +
"{\n" +
' "id": "mark_duplicate",\n' +
' "render": {\n' +
' "special": {\n' +
' "type": "maproulette_set_status",\n' +
' "message": {\n' +
' "en": "Mark as not found or false positive"\n' +
" },\n" +
' "status": "2",\n' +
' "image": "close"\n' +
" }\n" +
" }\n" +
"}\n" +
"```",
args: [
{
name: "message",
doc: "A message to show to the user",
},
{
name: "image",
doc: "Image to show",
defaultValue: "confirm",
},
{
name: "message_confirm",
doc: "What to show when the task is closed, either by the user or was already closed.",
},
{
name: "status",
doc: "A statuscode to apply when the button is clicked. 1 = `close`, 2 = `false_positive`, 3 = `skip`, 4 = `deleted`, 5 = `already fixed` (on the map, e.g. for duplicates), 6 = `too hard`",
defaultValue: "1",
},
{
name: "maproulette_id",
doc: "The property name containing the maproulette id",
defaultValue: "mr_taskId",
},
{
name: "ask_feedback",
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) => {
let [
message,
image,
message_closed,
statusToSet,
maproulette_id_key,
askFeedback
] = args
if (image === "") {
image = "confirm"
}
if (maproulette_id_key === "" || maproulette_id_key === undefined) {
maproulette_id_key = "mr_taskId"
}
statusToSet = statusToSet ?? "1"
return new SvelteUIElement(MaprouletteSetStatus, {
state,
tags: tagsSource,
message,
image,
message_closed,
statusToSet,
maproulette_id_key,
askFeedback
})
}
},
{
funcName: "linked_data_from_website",
group: "data_import",
docs: "Attempts to load (via a proxy) the specified website and parsed ld+json from there. Suitable data will be offered to import into OSM. Note: this element is added by default",
args: [
{
name: "key",
defaultValue: "website",
doc: "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>"
},
{
name: "useProxy",
defaultValue: "yes",
doc: "If 'yes', uses the provided proxy server. This proxy server will scrape HTML and search for a script with `lang='ld+json'`. If `no`, the data will be downloaded and expects a linked-data-json directly"
},
{
name: "host",
doc: "If not using a proxy, define what host the website is allowed to connect to"
},
{
name: "mode",
doc: "If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM"
},
{
name: "collapsed",
defaultValue: "yes",
doc: "If the containing accordion should be closed"
}
],
needsUrls: [Constants.linkedDataProxy, "http://www.schema.org"],
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const key = argument[0] ?? "website"
const useProxy = argument[1] !== "no"
const readonly = argument[3] === "readonly"
const isClosed = (argument[4] ?? "yes") === "yes"
const countryStore: Store<string | undefined> = tags.mapD(
(tags) => tags._country
)
const sourceUrl: Store<string | undefined> = tags.mapD((tags) => {
if (!tags[key] || tags[key] === "undefined") {
return null
constr: (state, tagsSource, args) => {
let [
message,
image,
message_closed,
statusToSet,
maproulette_id_key,
askFeedback,
] = args
if (image === "") {
image = "confirm"
}
return tags[key]
})
const externalData: Store<{ success: GeoJsonProperties } | { error }> =
sourceUrl.bindD(
(url) => {
const country = countryStore.data
if (url.startsWith("https://data.velopark.be/")) {
if (maproulette_id_key === "" || maproulette_id_key === undefined) {
maproulette_id_key = "mr_taskId"
}
statusToSet = statusToSet ?? "1"
return new SvelteUIElement(MaprouletteSetStatus, {
state,
tags: tagsSource,
message,
image,
message_closed,
statusToSet,
maproulette_id_key,
askFeedback,
})
},
},
{
funcName: "linked_data_from_website",
group: "data_import",
docs: "Attempts to load (via a proxy) the specified website and parsed ld+json from there. Suitable data will be offered to import into OSM. Note: this element is added by default",
args: [
{
name: "key",
defaultValue: "website",
doc: "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>",
},
{
name: "useProxy",
defaultValue: "yes",
doc: "If 'yes', uses the provided proxy server. This proxy server will scrape HTML and search for a script with `lang='ld+json'`. If `no`, the data will be downloaded and expects a linked-data-json directly",
},
{
name: "host",
doc: "If not using a proxy, define what host the website is allowed to connect to",
},
{
name: "mode",
doc: "If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM",
},
{
name: "collapsed",
defaultValue: "yes",
doc: "If the containing accordion should be closed",
},
],
needsUrls: [Constants.linkedDataProxy, "http://www.schema.org"],
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const key = argument[0] ?? "website"
const useProxy = argument[1] !== "no"
const readonly = argument[3] === "readonly"
const isClosed = (argument[4] ?? "yes") === "yes"
const countryStore: Store<string | undefined> = tags.mapD(
(tags) => tags._country
)
const sourceUrl: Store<string | undefined> = tags.mapD((tags) => {
if (!tags[key] || tags[key] === "undefined") {
return null
}
return tags[key]
})
const externalData: Store<{ success: GeoJsonProperties } | { error }> =
sourceUrl.bindD(
(url) => {
const country = countryStore.data
if (url.startsWith("https://data.velopark.be/")) {
return Stores.FromPromiseWithErr(
(async () => {
try {
const loadAll =
layer.id.toLowerCase().indexOf("maproulette") >=
0 // Dirty hack
const features =
await LinkedDataLoader.fetchVeloparkEntry(
url,
loadAll
)
const feature =
features.find(
(f) => f.properties["ref:velopark"] === url
) ?? features[0]
const properties = feature.properties
properties["ref:velopark"] = url
console.log(
"Got properties from velopark:",
properties
)
return properties
} catch (e) {
console.error(e)
throw e
}
})()
)
}
if (country === undefined) {
return undefined
}
return Stores.FromPromiseWithErr(
(async () => {
try {
const loadAll =
layer.id.toLowerCase().indexOf("maproulette") >=
0 // Dirty hack
const features =
await LinkedDataLoader.fetchVeloparkEntry(
url,
loadAll
)
const feature =
features.find(
(f) => f.properties["ref:velopark"] === url
) ?? features[0]
const properties = feature.properties
properties["ref:velopark"] = url
console.log(
"Got properties from velopark:",
properties
return await LinkedDataLoader.fetchJsonLd(
url,
{ country },
useProxy ? "proxy" : "fetch-lod"
)
return properties
} catch (e) {
console.error(e)
throw e
console.log(
"Could not get with proxy/download LOD, attempting to download directly. Error for ",
url,
"is",
e
)
return await LinkedDataLoader.fetchJsonLd(
url,
{ country },
"fetch-raw"
)
}
})()
)
}
if (country === undefined) {
return undefined
}
return Stores.FromPromiseWithErr(
(async () => {
try {
return await LinkedDataLoader.fetchJsonLd(
url,
{ country },
useProxy ? "proxy" : "fetch-lod"
)
} catch (e) {
console.log(
"Could not get with proxy/download LOD, attempting to download directly. Error for ",
url,
"is",
e
)
return await LinkedDataLoader.fetchJsonLd(
url,
{ country },
"fetch-raw"
)
}
})()
)
},
[countryStore]
},
[countryStore]
)
externalData.addCallbackAndRunD((lod) =>
console.log("linked_data_from_website received the following data:", lod)
)
externalData.addCallbackAndRunD((lod) =>
console.log("linked_data_from_website received the following data:", lod)
)
return new Toggle(
new SvelteUIElement(ComparisonTool, {
feature,
return new Toggle(
new SvelteUIElement(ComparisonTool, {
feature,
state,
tags,
layer,
externalData,
sourceUrl,
readonly,
collapsed: isClosed,
}),
undefined,
sourceUrl.map((url) => !!url)
)
},
},
{
funcName: "compare_data",
group: "data_import",
needsUrls: (args) => args[1].split(";"),
args: [
{
name: "url",
required: true,
doc: "The attribute containing the url where to fetch more data",
},
{
name: "host",
required: true,
doc: "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. ",
},
{
name: "readonly",
required: false,
doc: "If 'yes', will not show 'apply'-buttons",
},
],
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 {
const url = args[0]
const readonly = args[3] === "yes"
const externalData = Stores.FromPromiseWithErr(Utils.downloadJson(url))
return new SvelteUIElement(ComparisonTool, {
url,
state,
tags,
tags: tagSource,
layer,
externalData,
sourceUrl,
feature,
readonly,
collapsed: isClosed
}),
undefined,
sourceUrl.map((url) => !!url)
)
}
},
{
funcName: "compare_data",
group: "data_import",
needsUrls: (args) => args[1].split(";"),
args: [
{
name: "url",
required: true,
doc: "The attribute containing the url where to fetch more data"
externalData,
})
},
{
name: "host",
required: true,
doc: "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. "
},
{
name: "readonly",
required: false,
doc: "If 'yes', will not show 'apply'-buttons"
}
],
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 {
const url = args[0]
const readonly = args[3] === "yes"
const externalData = Stores.FromPromiseWithErr(Utils.downloadJson(url))
return new SvelteUIElement(ComparisonTool, {
url,
state,
tags: tagSource,
layer,
feature,
readonly,
externalData
})
}
}
]
}
},
]
}
}

View file

@ -8,27 +8,28 @@ import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte"
export class FavouriteVisualisations {
public static initList(): SpecialVisualizationSvelte[] {
return [{
funcName: "favourite_status",
return [
{
funcName: "favourite_status",
docs: "A button that allows a (logged in) contributor to mark a location as a favourite location",
args: [],
group: "favourites",
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
return new SvelteUIElement(MarkAsFavourite, {
tags: tagSource,
state,
layer,
feature
})
}
},
docs: "A button that allows a (logged in) contributor to mark a location as a favourite location",
args: [],
group: "favourites",
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
return new SvelteUIElement(MarkAsFavourite, {
tags: tagSource,
state,
layer,
feature,
})
},
},
{
funcName: "favourite_icon",
group: "favourites",
@ -45,9 +46,10 @@ export class FavouriteVisualisations {
tags: tagSource,
state,
layer,
feature
feature,
})
}
}]
},
},
]
}
}

View file

@ -18,13 +18,13 @@ class NearbyImageVis implements SpecialVisualizationSvelte {
{
name: "mode",
defaultValue: "closed",
doc: "Either `open` or `closed`. If `open`, then the image carousel will always be shown"
doc: "Either `open` or `closed`. If `open`, then the image carousel will always be shown",
},
{
name: "readonly",
required: false,
doc: "If 'readonly' or 'yes', will not show the 'link'-button"
}
doc: "If 'readonly' or 'yes', will not show the 'link'-button",
},
]
group: "images"
docs =
@ -49,13 +49,12 @@ class NearbyImageVis implements SpecialVisualizationSvelte {
lat,
feature,
layer,
linkable: !readonly
linkable: !readonly,
})
}
}
export class ImageVisualisations {
static initList(): SpecialVisualizationSvelte[] {
return [
new NearbyImageVis(),
@ -67,8 +66,8 @@ export class ImageVisualisations {
{
name: "image_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 "
}
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 ",
},
],
needsUrls: AllImageProviders.apiUrls,
constr: (state, tags, args) => {
@ -77,9 +76,11 @@ export class ImageVisualisations {
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
}
const images = AllImageProviders.loadImagesFor(tags, imagePrefixes)
const estimated = tags.mapD(tags => AllImageProviders.estimateNumberOfImages(tags, imagePrefixes))
const estimated = tags.mapD((tags) =>
AllImageProviders.estimateNumberOfImages(tags, imagePrefixes)
)
return new SvelteUIElement(ImageCarousel, { state, tags, images, estimated })
}
},
},
{
funcName: "image_upload",
@ -90,18 +91,18 @@ export class ImageVisualisations {
{
name: "image-key",
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
required: false
required: false,
},
{
name: "label",
doc: "The text to show on the button",
required: false
required: false,
},
{
name: "disable_blur",
doc: "If set to 'true' or 'yes', then face blurring will be disabled. To be used sparingly",
required: false
}
required: false,
},
],
constr: (state, tags, args, feature) => {
const targetKey = args[0] === "" ? undefined : args[0]
@ -113,10 +114,10 @@ export class ImageVisualisations {
feature,
labelText: args[1],
image: args[2],
noBlur: noBlur === "true" || noBlur === "yes"
noBlur: noBlur === "true" || noBlur === "yes",
})
}
}]
},
},
]
}
}

View file

@ -1,4 +1,8 @@
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import {
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import Constants from "../../Models/Constants"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
@ -24,30 +28,30 @@ class CloseNoteViz implements SpecialVisualizationSvelte {
{
name: "text",
doc: "Text to show on this button",
required: true
required: true,
},
{
name: "icon",
doc: "Icon to show",
defaultValue: "checkmark.svg"
defaultValue: "checkmark.svg",
},
{
name: "idkey",
doc: "The property name where the ID of the note to close can be found",
defaultValue: "id"
defaultValue: "id",
},
{
name: "comment",
doc: "Text to add onto the note when closing"
doc: "Text to add onto the note when closing",
},
{
name: "minZoom",
doc: "If set, only show the closenote button if zoomed in enough"
doc: "If set, only show the closenote button if zoomed in enough",
},
{
name: "zoomButton",
doc: "Text to show if not zoomed in enough"
}
doc: "Text to show if not zoomed in enough",
},
]
public readonly group: "notes"
@ -69,12 +73,11 @@ class CloseNoteViz implements SpecialVisualizationSvelte {
message: comment,
text: Translations.T(text),
minzoom: minZoom,
zoomMoreMessage: zoomButton
zoomMoreMessage: zoomButton,
})
}
}
class AddNoteCommentViz implements SpecialVisualizationSvelte {
funcName = "add_note_comment"
needsUrls = [Constants.osmAuthConfig.url]
@ -83,21 +86,23 @@ class AddNoteCommentViz implements SpecialVisualizationSvelte {
{
name: "Id-key",
doc: "The property name where the ID of the note to close can be found",
defaultValue: "id"
}
defaultValue: "id",
},
]
public readonly group: "notes"
public constr(state: SpecialVisualizationState, tags: UIEventSource<Record<string, string>>): SvelteUIElement {
public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>
): SvelteUIElement {
return new SvelteUIElement(AddNoteComment, { state, tags })
}
}
export class NoteVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
return [new AddNoteCommentViz(),
return [
new AddNoteCommentViz(),
new CloseNoteViz(),
{
funcName: "open_note",
@ -114,9 +119,9 @@ export class NoteVisualisations {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(CreateNewNote, {
state,
coordinate: new UIEventSource({ lon, lat })
coordinate: new UIEventSource({ lon, lat }),
})
}
},
},
{
funcName: "add_image_to_note",
@ -125,8 +130,8 @@ export class NoteVisualisations {
{
name: "Id-key",
doc: "The property name where the ID of the note to close can be found",
defaultValue: "id"
}
defaultValue: "id",
},
],
group: "notes",
needsUrls: [Imgur.apiUrl, ...Imgur.supportingUrls],
@ -135,7 +140,7 @@ export class NoteVisualisations {
const id = tags.data[args[0] ?? "id"]
tags = state.featureProperties.getStore(id)
return new SvelteUIElement(UploadImage, { state, tags, layer, feature })
}
},
},
{
funcName: "visualize_note_comments",
@ -145,13 +150,13 @@ export class NoteVisualisations {
{
name: "commentsKey",
doc: "The property name of the comments, which should be stringified json",
defaultValue: "comments"
defaultValue: "comments",
},
{
name: "start",
doc: "Drop the first 'start' comments",
defaultValue: "0"
}
defaultValue: "0",
},
],
needsUrls: [Constants.osmAuthConfig.url],
constr: (state, tags, args) =>
@ -171,13 +176,13 @@ export class NoteVisualisations {
(comment) =>
new SvelteUIElement(NoteCommentElement, {
comment,
state
state,
})
)
).SetClass("flex flex-col")
})
)
}
),
},
]
}
}

View file

@ -24,16 +24,16 @@ export class ReviewSpecialVisualisations {
{
name: "subjectKey",
defaultValue: "name",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>"
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
},
{
name: "fallback",
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value"
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
},
{
name: "question",
doc: "The question to ask during the review"
}
doc: "The question to ask during the review",
},
],
constr: (state, tags, args, feature, layer) => {
const nameKey = args[0] ?? "name"
@ -45,7 +45,7 @@ export class ReviewSpecialVisualisations {
state.userRelatedState?.mangroveIdentity,
{
nameKey: nameKey,
fallbackName
fallbackName,
},
state.featureSwitchIsTesting
)
@ -55,9 +55,9 @@ export class ReviewSpecialVisualisations {
tags,
feature,
layer,
question
question,
})
}
},
}
const listReviews: SpecialVisualization & { group } = {
funcName: "list_reviews",
@ -69,12 +69,12 @@ export class ReviewSpecialVisualisations {
{
name: "subjectKey",
defaultValue: "name",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>"
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
},
{
name: "fallback",
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value"
}
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
},
],
constr: (state, tags, args, feature, layer) => {
const nameKey = args[0] ?? "name"
@ -85,12 +85,12 @@ export class ReviewSpecialVisualisations {
state.userRelatedState?.mangroveIdentity,
{
nameKey: nameKey,
fallbackName
fallbackName,
},
state.featureSwitchIsTesting
)
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
}
},
}
return [
{
@ -102,12 +102,12 @@ export class ReviewSpecialVisualisations {
{
name: "subjectKey",
defaultValue: "name",
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."
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.",
},
{
name: "fallback",
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value"
}
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
},
],
constr: (state, tags, args, feature) => {
const nameKey = args[0] ?? "name"
@ -118,14 +118,14 @@ export class ReviewSpecialVisualisations {
state.userRelatedState.mangroveIdentity,
{
nameKey: nameKey,
fallbackName
fallbackName,
},
state.featureSwitchIsTesting
)
return new SvelteUIElement(StarsBarIcon, {
score: reviews.average
score: reviews.average,
})
}
},
},
createReview,
listReviews,
@ -137,8 +137,8 @@ export class ReviewSpecialVisualisations {
args: [
{
name: "text",
doc: "The text that is shown on the button"
}
doc: "The text that is shown on the button",
},
],
needsUrls: [],
constr(
@ -148,7 +148,7 @@ export class ReviewSpecialVisualisations {
): SvelteUIElement {
const [text] = argument
return new SvelteUIElement(ImportReviewIdentity, { state, text })
}
},
},
{
funcName: "reviews",
@ -161,16 +161,16 @@ export class ReviewSpecialVisualisations {
{
name: "subjectKey",
defaultValue: "name",
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>"
doc: "The key to use to determine the subject. If specified, the subject will be <b>tags[subjectKey]</b>",
},
{
name: "fallback",
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value"
doc: "The identifier to use, if <i>tags[subjectKey]</i> as specified above is not available. This is effectively a fallback value",
},
{
name: "question",
doc: "The question to ask in the review form. Optional"
}
doc: "The question to ask in the review form. Optional",
},
],
constr(
state: SpecialVisualizationState,
@ -182,10 +182,10 @@ export class ReviewSpecialVisualisations {
return new Combine([
createReview.constr(state, tagSource, args, feature, layer),
listReviews.constr(state, tagSource, args, feature, layer)
listReviews.constr(state, tagSource, args, feature, layer),
])
}
}
},
},
]
}
}

View file

@ -24,17 +24,16 @@ export class SettingsVisualisations {
docs: "A component to set the language of the user interface",
constr(state: SpecialVisualizationState): SvelteUIElement {
const availableLanguages = Locale.showLinkToWeblate.map((showTranslations) =>
showTranslations
? LanguageUtils.usedLanguagesSorted
: state.theme.language)
showTranslations ? LanguageUtils.usedLanguagesSorted : state.theme.language
)
return new SvelteUIElement(LanguagePicker, {
assignTo: state.userRelatedState.language,
availableLanguages,
preferredLanguages: state.osmConnection.userDetails.map(
(ud) => ud?.languages ?? []
)
),
})
}
},
},
{
@ -45,7 +44,7 @@ export class SettingsVisualisations {
args: [],
constr(state) {
return new SvelteUIElement(DisabledQuestions, { state })
}
},
},
{
funcName: "gyroscope_all_tags",
@ -54,16 +53,14 @@ export class SettingsVisualisations {
args: [],
constr(): SvelteUIElement {
return new SvelteUIElement(OrientationDebugPanel, {})
}
},
},
{
funcName: "gps_all_tags",
group: "settings",
docs: "Shows the current tags of the GPS-representing object, used for debugging",
args: [],
constr(
state: SpecialVisualizationState
): SvelteUIElement {
constr(state: SpecialVisualizationState): SvelteUIElement {
const tags = (<ThemeViewState>(
state
)).geolocation.currentUserLocation.features.map(
@ -71,25 +68,23 @@ export class SettingsVisualisations {
)
return new SvelteUIElement(AllTagsPanel, {
state,
tags
tags,
})
}
},
},
{
funcName: "storage_all_tags",
group: "settings",
docs: "Shows the current state of storage",
args: [],
constr(
state: SpecialVisualizationState
): SvelteUIElement {
constr(state: SpecialVisualizationState): SvelteUIElement {
const data = {}
for (const key in localStorage) {
data[key] = localStorage[key]
}
const tags = new ImmutableStore(data)
return new SvelteUIElement(AllTagsPanel, { state, tags })
}
},
},
{
funcName: "clear_caches",
@ -98,8 +93,8 @@ export class SettingsVisualisations {
{
name: "text",
required: true,
doc: "The text to show on the button"
}
doc: "The text to show on the button",
},
],
group: "settings",
constr(
@ -108,9 +103,9 @@ export class SettingsVisualisations {
argument: string[]
): SvelteUIElement {
return new SvelteUIElement(ClearCaches, {
msg: argument[0] ?? "Clear local caches"
msg: argument[0] ?? "Clear local caches",
})
}
},
},
{
funcName: "login_button",
@ -120,7 +115,7 @@ export class SettingsVisualisations {
group: "settings",
constr(state: SpecialVisualizationState): SvelteUIElement {
return new SvelteUIElement(LoginButton, { osmConnection: state.osmConnection })
}
},
},
{
@ -131,7 +126,7 @@ export class SettingsVisualisations {
group: "settings",
constr(state: SpecialVisualizationState): SvelteUIElement {
return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection })
}
},
},
{
funcName: "pending_changes",
@ -140,8 +135,8 @@ export class SettingsVisualisations {
args: [],
constr(state: SpecialVisualizationState): SvelteUIElement {
return new SvelteUIElement(PendingChangesIndicator, { state, compact: false })
}
}
},
},
]
}
}

View file

@ -15,7 +15,6 @@ import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.sve
import Combine from "../Base/Combine"
class StealViz implements SpecialVisualization {
funcName = "steal"
group = "tagrendering_manipulation"
@ -24,13 +23,13 @@ class StealViz implements SpecialVisualization {
{
name: "featureId",
doc: "The key of the attribute which contains the id of the feature from which to use the tags",
required: true
required: true,
},
{
name: "tagRenderingId",
doc: "The layer-id and tagRenderingId to render. Can be multiple value if ';'-separated (in which case every value must also contain the layerId, e.g. `layerId.tagRendering0; layerId.tagRendering1`). Note: this can cause layer injection",
required: true
}
required: true,
},
]
needsUrls = []
svelteBased = true
@ -64,7 +63,7 @@ class StealViz implements SpecialVisualization {
tags: otherTags,
selectedElement: otherFeature,
state,
layer
layer,
})
)
}
@ -89,7 +88,6 @@ class StealViz implements SpecialVisualization {
}
export default class TagrenderingManipulationSpecialVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
return [
new StealViz(),
@ -106,10 +104,10 @@ export default class TagrenderingManipulationSpecialVisualisations {
type: "multi",
key: "_doors_from_building_properties",
tagrendering: {
en: "The building containing this feature has a <a href='#{id}'>door</a> of width {entrance:width}"
}
}
}
en: "The building containing this feature has a <a href='#{id}'>door</a> of width {entrance:width}",
},
},
},
},
null,
" "
@ -119,17 +117,17 @@ export default class TagrenderingManipulationSpecialVisualisations {
{
name: "key",
doc: "The property to read and to interpret as a list of properties",
required: true
required: true,
},
{
name: "tagrendering",
doc: "An entire tagRenderingConfig",
required: true
required: true,
},
{
name: "classes",
doc: "CSS-classes to apply on every individual item. Seperated by `space`"
}
doc: "CSS-classes to apply on every individual item. Seperated by `space`",
},
],
constr(
state: SpecialVisualizationState,
@ -147,7 +145,7 @@ export default class TagrenderingManipulationSpecialVisualisations {
if (typeof tags[key] === "string") {
properties = JSON.parse(tags[key])
} else {
properties = <object[]><unknown>tags[key]
properties = <object[]>(<unknown>tags[key])
}
if (!properties) {
console.debug(
@ -165,7 +163,7 @@ export default class TagrenderingManipulationSpecialVisualisations {
tags: new ImmutableStore(property),
state,
feature,
layer
layer,
// clss: classes ?? "",
}).SetClass(classes)
elements.push(subsTr)
@ -173,7 +171,7 @@ export default class TagrenderingManipulationSpecialVisualisations {
return elements
})
)
}
},
},
{
funcName: "group",
@ -182,12 +180,12 @@ export default class TagrenderingManipulationSpecialVisualisations {
args: [
{
name: "header",
doc: "The _identifier_ of a single tagRendering. This will be used as header"
doc: "The _identifier_ of a single tagRendering. This will be used as header",
},
{
name: "labels",
doc: "A `;`-separated list of either identifiers or label names. All tagRenderings matching this value will be shown in the accordion"
}
doc: "A `;`-separated list of either identifiers or label names. All tagRenderings matching this value will be shown in the accordion",
},
],
constr(
state: SpecialVisualizationState,
@ -204,9 +202,9 @@ export default class TagrenderingManipulationSpecialVisualisations {
selectedElement,
layer,
header,
labels
labels,
})
}
},
},
{
funcName: "open_in_iD",
@ -216,9 +214,9 @@ export default class TagrenderingManipulationSpecialVisualisations {
constr: (state, feature): SvelteUIElement => {
return new SvelteUIElement(OpenIdEditor, {
mapProperties: state.mapProperties,
objectId: feature.data.id
objectId: feature.data.id,
})
}
},
},
{
funcName: "open_in_josm",
@ -229,9 +227,8 @@ export default class TagrenderingManipulationSpecialVisualisations {
constr: (state): SvelteUIElement => {
return new SvelteUIElement(OpenJosm, { state })
}
}
},
},
]
}
}

View file

@ -25,12 +25,12 @@ class QuestionViz implements SpecialVisualizationSvelte {
args = [
{
name: "labels",
doc: "One or more ';'-separated labels. If these are given, only questions with these labels will be given. Use `unlabeled` for all questions that don't have an explicit label. If none given, all questions will be shown"
doc: "One or more ';'-separated labels. If these are given, only questions with these labels will be given. Use `unlabeled` for all questions that don't have an explicit label. If none given, all questions will be shown",
},
{
name: "blacklisted-labels",
doc: "One or more ';'-separated labels of questions which should _not_ be included"
}
doc: "One or more ';'-separated labels of questions which should _not_ be included",
},
]
svelteBased = true
group: "default"
@ -42,7 +42,6 @@ class QuestionViz implements SpecialVisualizationSvelte {
feature: Feature,
layer: LayerConfig
): SvelteUIElement {
const labels = args[0]
?.split(";")
?.map((s) => s.trim())
@ -57,14 +56,15 @@ class QuestionViz implements SpecialVisualizationSvelte {
selectedElement: feature,
state,
onlyForLabels: labels,
notForLabels: blacklist
notForLabels: blacklist,
})
}
}
export class UISpecialVisualisations {
public static initList(): SpecialVisualizationSvelte [] {
return [new QuestionViz(),
public static initList(): SpecialVisualizationSvelte[] {
return [
new QuestionViz(),
{
funcName: "minimap",
docs: "A small map showing the selected feature.",
@ -75,13 +75,13 @@ export class UISpecialVisualisations {
{
doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close",
name: "zoomlevel",
defaultValue: "18"
defaultValue: "18",
},
{
doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled)",
name: "idKey",
defaultValue: "id"
}
defaultValue: "id",
},
],
example:
"`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`",
@ -93,7 +93,7 @@ export class UISpecialVisualisations {
feature: Feature
): SvelteUIElement {
return new SvelteUIElement(MinimapViz, { state, args, feature, tagSource })
}
},
},
{
funcName: "split_button",
@ -105,8 +105,11 @@ export class UISpecialVisualisations {
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>
): SvelteUIElement {
return new SvelteUIElement(SplitRoadWizard, { id: tagSource.map(pr => pr.id), state })
}
return new SvelteUIElement(SplitRoadWizard, {
id: tagSource.map((pr) => pr.id),
state,
})
},
},
{
funcName: "move_button",
@ -127,9 +130,9 @@ export class UISpecialVisualisations {
return new SvelteUIElement(MoveWizard, {
state,
featureToMove: feature,
layer
layer,
})
}
},
},
{
funcName: "delete_button",
@ -152,9 +155,9 @@ export class UISpecialVisualisations {
deleteConfig: layer.deletion,
state,
feature,
layer
layer,
})
}
},
},
{
funcName: "qr_code",
@ -168,7 +171,7 @@ export class UISpecialVisualisations {
feature: Feature
): SvelteUIElement {
return new SvelteUIElement(QrCode, { state, tags, feature })
}
},
},
{
funcName: "if_nothing_known",
@ -176,9 +179,9 @@ export class UISpecialVisualisations {
{
name: "text",
doc: "Text to show",
required: true
required: true,
},
{ name: "cssClasses", doc: "Classes to apply onto the text" }
{ name: "cssClasses", doc: "Classes to apply onto the text" },
],
group: "default",
docs: "Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question",
@ -196,9 +199,9 @@ export class UISpecialVisualisations {
tags: tagSource,
layer,
text,
cssClasses
cssClasses,
})
}
},
},
new ShareLinkViz(),
{
@ -210,10 +213,10 @@ export class UISpecialVisualisations {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(AddNewPoint, {
state,
coordinate: { lon, lat }
coordinate: { lon, lat },
})
}
}
},
},
]
}
}

View file

@ -14,9 +14,8 @@ import SendEmail from "../Popup/SendEmail.svelte"
import DynLink from "../Base/DynLink.svelte"
export class WebAndCommunicationSpecialVisualisations {
public static initList(): (SpecialVisualization & { group }) [] {
public static initList(): (SpecialVisualization & { group })[] {
return [
{
funcName: "fediverse_link",
group: "web_and_communication",
@ -25,8 +24,8 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "key",
doc: "The attribute-name containing the link",
required: true
}
required: true,
},
],
constr(
@ -36,7 +35,7 @@ export class WebAndCommunicationSpecialVisualisations {
): BaseUIElement {
const key = argument[0]
return new SvelteUIElement(FediverseLink, { key, tags, state })
}
},
},
{
funcName: "wikipedia",
@ -46,8 +45,8 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "keyToShowWikipediaFor",
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"
}
defaultValue: "wikidata;wikipedia",
},
],
needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls],
@ -60,9 +59,9 @@ export class WebAndCommunicationSpecialVisualisations {
return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
})
return new SvelteUIElement(WikipediaPanel, {
wikiIds
wikiIds,
})
}
},
},
{
funcName: "wikidata_label",
@ -73,8 +72,8 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "keyToShowWikidataFor",
doc: "Use the wikidata entry from this key to show the label",
defaultValue: "wikidata"
}
defaultValue: "wikidata",
},
],
needsUrls: Wikidata.neededUrls,
example:
@ -98,7 +97,7 @@ export class WebAndCommunicationSpecialVisualisations {
})
)
})
)
),
},
new MapillaryLinkVis(),
{
@ -109,29 +108,29 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "to",
doc: "Who to send the email to?",
required: true
required: true,
},
{
name: "subject",
doc: "The subject of the email",
required: true
required: true,
},
{
name: "body",
doc: "The text in the email",
required: true
required: true,
},
{
name: "button_text",
doc: "The text shown on the button in the UI",
required: true
}
required: true,
},
],
constr(__, tags, args) {
return new SvelteUIElement(SendEmail, { args, tags })
}
},
},
{
funcName: "link",
@ -141,29 +140,29 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "text",
doc: "Text to be shown",
required: true
required: true,
},
{
name: "href",
doc: "The URL to link to. Note that this will be URI-encoded before ",
required: true
required: true,
},
{
name: "class",
doc: "CSS-classes to add to the element"
doc: "CSS-classes to add to the element",
},
{
name: "download",
doc: "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button."
doc: "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button.",
},
{
name: "arialabel",
doc: "If set, this text will be used as aria-label"
doc: "If set, this text will be used as aria-label",
},
{
name: "icon",
doc: "If set, show this icon next to the link. You might want to combine this with `class: button`"
}
doc: "If set, show this icon next to the link. You might want to combine this with `class: button`",
},
],
constr(
@ -185,10 +184,10 @@ export class WebAndCommunicationSpecialVisualisations {
download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)),
ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)),
newTab: new ImmutableStore(newTab),
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags))
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)),
}).setSpan()
}
}
},
},
]
}
}