Refactoring: move special visualisations into multiple files
This commit is contained in:
parent
f057b0f358
commit
b9bc3f5980
14 changed files with 839 additions and 801 deletions
|
@ -136,6 +136,11 @@ export class GenerateDocs extends Script {
|
||||||
async main(args: string[]) {
|
async main(args: string[]) {
|
||||||
console.log("Starting documentation generation...")
|
console.log("Starting documentation generation...")
|
||||||
ScriptUtils.fixUtils()
|
ScriptUtils.fixUtils()
|
||||||
|
|
||||||
|
this.WriteMarkdownFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage(), [
|
||||||
|
"src/UI/SpecialVisualizations.ts"
|
||||||
|
])
|
||||||
|
|
||||||
if (!existsSync("./Docs/Themes")) {
|
if (!existsSync("./Docs/Themes")) {
|
||||||
mkdirSync("./Docs/Themes")
|
mkdirSync("./Docs/Themes")
|
||||||
}
|
}
|
||||||
|
@ -166,13 +171,12 @@ export class GenerateDocs extends Script {
|
||||||
ScriptUtils.erasableLog("Written docs for theme", theme.id)
|
ScriptUtils.erasableLog("Written docs for theme", theme.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.WriteMarkdownFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage(), [
|
|
||||||
"src/UI/SpecialVisualizations.ts",
|
|
||||||
])
|
|
||||||
this.WriteMarkdownFile(
|
this.WriteMarkdownFile(
|
||||||
"./Docs/CalculatedTags.md",
|
"./Docs/CalculatedTags.md",
|
||||||
["# Metatags", SimpleMetaTaggers.HelpText(), ExtraFunctions.HelpText()].join("\n"),
|
["# Metatags", SimpleMetaTaggers.HelpText(), ExtraFunctions.HelpText()].join("\n"),
|
||||||
["src/Logic/SimpleMetaTagger.ts", "src/Logic/ExtraFunctions.ts"]
|
["src/Logic/SimpleMetaTagger.ts", "src/Logic/ExtraFunctions.ts"],
|
||||||
|
{ noTableOfContents: false }
|
||||||
)
|
)
|
||||||
this.WriteMarkdownFile("./Docs/SpecialInputElements.md", Validators.HelpText(), [
|
this.WriteMarkdownFile("./Docs/SpecialInputElements.md", Validators.HelpText(), [
|
||||||
"src/UI/InputElement/Validators.ts",
|
"src/UI/InputElement/Validators.ts",
|
||||||
|
@ -212,7 +216,7 @@ export class GenerateDocs extends Script {
|
||||||
markdown: string,
|
markdown: string,
|
||||||
autogenSource: string[],
|
autogenSource: string[],
|
||||||
options?: {
|
options?: {
|
||||||
noTableOfContents: boolean
|
noTableOfContents?: boolean
|
||||||
}
|
}
|
||||||
): void {
|
): void {
|
||||||
for (const source of autogenSource) {
|
for (const source of autogenSource) {
|
||||||
|
|
|
@ -1,42 +1,14 @@
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
|
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
|
||||||
export class ThemeMetaTagging {
|
export class ThemeMetaTagging {
|
||||||
public static readonly themeName = "usersettings"
|
public static readonly themeName = "usersettings"
|
||||||
|
|
||||||
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
|
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
|
||||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
|
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
|
||||||
feat.properties._description
|
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )
|
||||||
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
|
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) )
|
||||||
?.at(1)
|
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 )
|
||||||
Utils.AddLazyProperty(
|
feat.properties['__current_backgroun'] = 'initial_value'
|
||||||
feat.properties,
|
}
|
||||||
"_d",
|
}
|
||||||
() => feat.properties._description?.replace(/</g, "<")?.replace(/>/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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,6 +24,8 @@ export interface ConflateFlowArguments extends ImportFlowArguments {
|
||||||
export default class ConflateImportButtonViz implements SpecialVisualization, AutoAction {
|
export default class ConflateImportButtonViz implements SpecialVisualization, AutoAction {
|
||||||
supportsAutoAction: boolean = true
|
supportsAutoAction: boolean = true
|
||||||
needsUrls = []
|
needsUrls = []
|
||||||
|
group = "data_import"
|
||||||
|
|
||||||
public readonly funcName: string = "conflate_button"
|
public readonly funcName: string = "conflate_button"
|
||||||
public readonly args: {
|
public readonly args: {
|
||||||
name: string
|
name: string
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Feature, Point } from "geojson"
|
||||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
||||||
import BaseUIElement from "../../BaseUIElement"
|
import BaseUIElement from "../../BaseUIElement"
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
|
||||||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
import SvelteUIElement from "../../Base/SvelteUIElement"
|
||||||
import PointImportFlow from "./PointImportFlow.svelte"
|
import PointImportFlow from "./PointImportFlow.svelte"
|
||||||
import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlowState"
|
import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlowState"
|
||||||
|
@ -20,9 +19,11 @@ export class PointImportButtonViz implements SpecialVisualization {
|
||||||
public readonly example?: string
|
public readonly example?: string
|
||||||
public readonly args: { name: string; defaultValue?: string; doc: string; split?: boolean }[]
|
public readonly args: { name: string; defaultValue?: string; doc: string; split?: boolean }[]
|
||||||
public needsUrls = []
|
public needsUrls = []
|
||||||
|
group = "data_import"
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.funcName = "import_button"
|
this.funcName = "import_button"
|
||||||
|
|
||||||
this.docs =
|
this.docs =
|
||||||
"This button will copy the point from an external dataset into OpenStreetMap" +
|
"This button will copy the point from an external dataset into OpenStreetMap" +
|
||||||
ImportFlowUtils.documentationGeneral
|
ImportFlowUtils.documentationGeneral
|
||||||
|
|
|
@ -21,6 +21,8 @@ import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSou
|
||||||
export default class WayImportButtonViz implements AutoAction, SpecialVisualization {
|
export default class WayImportButtonViz implements AutoAction, SpecialVisualization {
|
||||||
public readonly funcName: string = "import_way_button"
|
public readonly funcName: string = "import_way_button"
|
||||||
needsUrls = []
|
needsUrls = []
|
||||||
|
group = "data_import"
|
||||||
|
|
||||||
public readonly docs: string =
|
public readonly docs: string =
|
||||||
"This button will copy the data from an external dataset into OpenStreetMap, copying the geometry and adding it as a 'line'" +
|
"This button will copy the data from an external dataset into OpenStreetMap, copying the geometry and adding it as a 'line'" +
|
||||||
ImportFlowUtils.documentationGeneral
|
ImportFlowUtils.documentationGeneral
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||||
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
|
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||||
import { Feature } from "geojson"
|
import { Feature } from "geojson"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||||
import MapillaryLink from "../BigComponents/MapillaryLink.svelte"
|
import MapillaryLink from "../BigComponents/MapillaryLink.svelte"
|
||||||
|
|
||||||
export class MapillaryLinkVis implements SpecialVisualization {
|
export class MapillaryLinkVis implements SpecialVisualizationSvelte {
|
||||||
funcName = "mapillary_link"
|
funcName = "mapillary_link"
|
||||||
|
group = "web_and_communication"
|
||||||
|
|
||||||
docs = "Adds a button to open mapillary on the specified location"
|
docs = "Adds a button to open mapillary on the specified location"
|
||||||
needsUrls = []
|
needsUrls = []
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ export class MapillaryLinkVis implements SpecialVisualization {
|
||||||
tagsSource: UIEventSource<Record<string, string>>,
|
tagsSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature
|
feature: Feature
|
||||||
): BaseUIElement {
|
): SvelteUIElement {
|
||||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||||
let zoom = Number(args[0])
|
let zoom = Number(args[0])
|
||||||
if (isNaN(zoom)) {
|
if (isNaN(zoom)) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { default as PlantNetCode } from "../../Logic/Web/PlantNet"
|
||||||
export class PlantNetDetectionViz implements SpecialVisualization {
|
export class PlantNetDetectionViz implements SpecialVisualization {
|
||||||
funcName = "plantnet_detection"
|
funcName = "plantnet_detection"
|
||||||
needsUrls = [PlantNetCode.baseUrl]
|
needsUrls = [PlantNetCode.baseUrl]
|
||||||
|
group = "data_import"
|
||||||
|
|
||||||
docs =
|
docs =
|
||||||
"Sends the images linked to the current object to plantnet.org and asks it what plant species is shown on it. The user can then select the correct species; the corresponding wikidata-identifier will then be added to the object (together with `source:species:wikidata=plantnet.org AI`). "
|
"Sends the images linked to the current object to plantnet.org and asks it what plant species is shown on it. The user can then select the correct species; the corresponding wikidata-identifier will then be added to the object (together with `source:species:wikidata=plantnet.org AI`). "
|
||||||
|
|
|
@ -20,6 +20,8 @@ import Icon from "../Map/Icon.svelte"
|
||||||
export default class TagApplyButton implements AutoAction, SpecialVisualization {
|
export default class TagApplyButton implements AutoAction, SpecialVisualization {
|
||||||
public readonly funcName = "tag_apply"
|
public readonly funcName = "tag_apply"
|
||||||
needsUrls = []
|
needsUrls = []
|
||||||
|
group = "data_import"
|
||||||
|
|
||||||
public readonly docs =
|
public readonly docs =
|
||||||
"Shows a big button; clicking this button will apply certain tags onto the feature.\n\nThe first argument takes a specification of which tags to add.\n" +
|
"Shows a big button; clicking this button will apply certain tags onto the feature.\n\nThe first argument takes a specification of which tags to add.\n" +
|
||||||
Utils.Special_visualizations_tagsToApplyHelpText
|
Utils.Special_visualizations_tagsToApplyHelpText
|
||||||
|
|
|
@ -1,14 +1,33 @@
|
||||||
import { SpecialVisualizationSvelte } from "../SpecialVisualization"
|
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import Maproulette from "../../Logic/Maproulette"
|
import Maproulette from "../../Logic/Maproulette"
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||||
import MaprouletteSetStatus from "../MapRoulette/MaprouletteSetStatus.svelte"
|
import MaprouletteSetStatus from "../MapRoulette/MaprouletteSetStatus.svelte"
|
||||||
|
import TagApplyButton from "../Popup/TagApplyButton"
|
||||||
|
import { PointImportButtonViz } from "../Popup/ImportButtons/PointImportButtonViz"
|
||||||
|
import WayImportButtonViz from "../Popup/ImportButtons/WayImportButtonViz"
|
||||||
|
import ConflateImportButtonViz from "../Popup/ImportButtons/ConflateImportButtonViz"
|
||||||
|
import { PlantNetDetectionViz } from "../Popup/PlantNetDetectionViz"
|
||||||
|
import Constants from "../../Models/Constants"
|
||||||
|
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import { Feature, GeoJsonProperties } from "geojson"
|
||||||
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
|
import BaseUIElement from "../BaseUIElement"
|
||||||
|
import LinkedDataLoader from "../../Logic/Web/LinkedDataLoader"
|
||||||
|
import Toggle from "../Input/Toggle"
|
||||||
|
import ComparisonTool from "../Comparison/ComparisonTool.svelte"
|
||||||
|
import { Utils } from "../../Utils"
|
||||||
|
|
||||||
export class MapRouletteSpecialVisualisations {
|
export class DataImportSpecialVisualisations {
|
||||||
public static initList(): SpecialVisualizationSvelte[] {
|
public static initList(): (SpecialVisualization & { group })[] {
|
||||||
return [
|
return [
|
||||||
{
|
new TagApplyButton(),
|
||||||
|
new PointImportButtonViz(),
|
||||||
|
new WayImportButtonViz(),
|
||||||
|
new ConflateImportButtonViz(),
|
||||||
|
new PlantNetDetectionViz(),
|
||||||
|
{
|
||||||
funcName: "maproulette_set_status",
|
funcName: "maproulette_set_status",
|
||||||
group: "maproulette",
|
group: "data_import",
|
||||||
docs: "Change the status of the given MapRoulette task",
|
docs: "Change the status of the given MapRoulette task",
|
||||||
needsUrls: [Maproulette.defaultEndpoint],
|
needsUrls: [Maproulette.defaultEndpoint],
|
||||||
example:
|
example:
|
||||||
|
@ -87,6 +106,186 @@ export class MapRouletteSpecialVisualisations {
|
||||||
askFeedback
|
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 {
|
||||||
|
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]
|
||||||
|
)
|
||||||
|
|
||||||
|
externalData.addCallbackAndRunD((lod) =>
|
||||||
|
console.log("linked_data_from_website received the following data:", lod)
|
||||||
|
)
|
||||||
|
|
||||||
|
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: tagSource,
|
||||||
|
layer,
|
||||||
|
feature,
|
||||||
|
readonly,
|
||||||
|
externalData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||||
import Constants from "../../Models/Constants"
|
import Constants from "../../Models/Constants"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { Feature } from "geojson"
|
import { Feature } from "geojson"
|
||||||
|
@ -11,6 +11,9 @@ import Translations from "../i18n/Translations"
|
||||||
import AddNoteComment from "../Popup/Notes/AddNoteComment.svelte"
|
import AddNoteComment from "../Popup/Notes/AddNoteComment.svelte"
|
||||||
import { Imgur } from "../../Logic/ImageProviders/Imgur"
|
import { Imgur } from "../../Logic/ImageProviders/Imgur"
|
||||||
import UploadImage from "../Image/UploadImage.svelte"
|
import UploadImage from "../Image/UploadImage.svelte"
|
||||||
|
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||||
|
import Combine from "../Base/Combine"
|
||||||
|
import NoteCommentElement from "../Popup/Notes/NoteCommentElement.svelte"
|
||||||
|
|
||||||
class CloseNoteViz implements SpecialVisualizationSvelte {
|
class CloseNoteViz implements SpecialVisualizationSvelte {
|
||||||
public readonly funcName = "close_note"
|
public readonly funcName = "close_note"
|
||||||
|
@ -93,8 +96,9 @@ class AddNoteCommentViz implements SpecialVisualizationSvelte {
|
||||||
|
|
||||||
|
|
||||||
export class NoteVisualisations {
|
export class NoteVisualisations {
|
||||||
public static initList(): SpecialVisualizationSvelte[] {
|
public static initList(): (SpecialVisualization & { group })[] {
|
||||||
return [new AddNoteCommentViz(),
|
return [new AddNoteCommentViz(),
|
||||||
|
new CloseNoteViz(),
|
||||||
{
|
{
|
||||||
funcName: "open_note",
|
funcName: "open_note",
|
||||||
args: [],
|
args: [],
|
||||||
|
@ -133,7 +137,47 @@ export class NoteVisualisations {
|
||||||
return new SvelteUIElement(UploadImage, { state, tags, layer, feature })
|
return new SvelteUIElement(UploadImage, { state, tags, layer, feature })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new CloseNoteViz()
|
{
|
||||||
|
funcName: "visualize_note_comments",
|
||||||
|
group: "notes",
|
||||||
|
docs: "Visualises the comments for notes",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "commentsKey",
|
||||||
|
doc: "The property name of the comments, which should be stringified json",
|
||||||
|
defaultValue: "comments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start",
|
||||||
|
doc: "Drop the first 'start' comments",
|
||||||
|
defaultValue: "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
needsUrls: [Constants.osmAuthConfig.url],
|
||||||
|
constr: (state, tags, args) =>
|
||||||
|
new VariableUiElement(
|
||||||
|
tags
|
||||||
|
.map((tags) => tags[args[0]])
|
||||||
|
.map((commentsStr) => {
|
||||||
|
const comments: { text: string }[] = JSON.parse(commentsStr)
|
||||||
|
const startLoc = Number(args[1] ?? 0)
|
||||||
|
if (!isNaN(startLoc) && startLoc > 0) {
|
||||||
|
comments.splice(0, startLoc)
|
||||||
|
}
|
||||||
|
return new Combine(
|
||||||
|
comments
|
||||||
|
.filter((c) => c.text !== "")
|
||||||
|
.map(
|
||||||
|
(comment) =>
|
||||||
|
new SvelteUIElement(NoteCommentElement, {
|
||||||
|
comment,
|
||||||
|
state
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).SetClass("flex flex-col")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import { MangroveReviews } from "mangrove-reviews-typescript"
|
import { MangroveReviews } from "mangrove-reviews-typescript"
|
||||||
import FeatureReviews from "../../Logic/Web/MangroveReviews"
|
import FeatureReviews from "../../Logic/Web/MangroveReviews"
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||||
|
@ -7,121 +7,128 @@ import ReviewForm from "../Reviews/ReviewForm.svelte"
|
||||||
import AllReviews from "../Reviews/AllReviews.svelte"
|
import AllReviews from "../Reviews/AllReviews.svelte"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import ImportReviewIdentity from "../Reviews/ImportReviewIdentity.svelte"
|
import ImportReviewIdentity from "../Reviews/ImportReviewIdentity.svelte"
|
||||||
|
import { Feature } from "geojson"
|
||||||
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
|
import BaseUIElement from "../BaseUIElement"
|
||||||
|
import Combine from "../Base/Combine"
|
||||||
|
|
||||||
export class ReviewSpecialVisualisations {
|
export class ReviewSpecialVisualisations {
|
||||||
public static initList(): SpecialVisualizationSvelte[] {
|
public static initList(): (SpecialVisualization & { group })[] {
|
||||||
return [{
|
const createReview: SpecialVisualization & { group } = {
|
||||||
funcName: "rating",
|
funcName: "create_review",
|
||||||
group: "reviews",
|
group: "reviews",
|
||||||
docs: "Shows stars which represent the average rating on mangrove.",
|
|
||||||
|
docs: "Invites the contributor to leave a review. Somewhat small UI-element until interacted",
|
||||||
needsUrls: [MangroveReviews.ORIGINAL_API],
|
needsUrls: [MangroveReviews.ORIGINAL_API],
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "subjectKey",
|
name: "subjectKey",
|
||||||
defaultValue: "name",
|
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 specified, the subject will be <b>tags[subjectKey]</b>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fallback",
|
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"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
constr: (state, tags, args, feature) => {
|
constr: (state, tags, args, feature, layer) => {
|
||||||
const nameKey = args[0] ?? "name"
|
const nameKey = args[0] ?? "name"
|
||||||
const fallbackName = args[1]
|
const fallbackName = args[1]
|
||||||
|
const question = args[2]
|
||||||
const reviews = FeatureReviews.construct(
|
const reviews = FeatureReviews.construct(
|
||||||
feature,
|
feature,
|
||||||
tags,
|
tags,
|
||||||
state.userRelatedState.mangroveIdentity,
|
state.userRelatedState?.mangroveIdentity,
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName
|
fallbackName
|
||||||
},
|
},
|
||||||
state.featureSwitchIsTesting
|
state.featureSwitchIsTesting
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(StarsBarIcon, {
|
return new SvelteUIElement(ReviewForm, {
|
||||||
score: reviews.average
|
reviews,
|
||||||
|
state,
|
||||||
|
tags,
|
||||||
|
feature,
|
||||||
|
layer,
|
||||||
|
question
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
const listReviews: SpecialVisualization & { group } = {
|
||||||
funcName: "create_review",
|
funcName: "list_reviews",
|
||||||
group: "reviews",
|
group: "reviews",
|
||||||
|
|
||||||
docs: "Invites the contributor to leave a review. Somewhat small UI-element until interacted",
|
docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten",
|
||||||
|
needsUrls: [MangroveReviews.ORIGINAL_API],
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "subjectKey",
|
||||||
|
defaultValue: "name",
|
||||||
|
doc: "The key to use to determine the subject. If specified, the subject will be <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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
constr: (state, tags, args, feature, layer) => {
|
||||||
|
const nameKey = args[0] ?? "name"
|
||||||
|
const fallbackName = args[1]
|
||||||
|
const reviews = FeatureReviews.construct(
|
||||||
|
feature,
|
||||||
|
tags,
|
||||||
|
state.userRelatedState?.mangroveIdentity,
|
||||||
|
{
|
||||||
|
nameKey: nameKey,
|
||||||
|
fallbackName
|
||||||
|
},
|
||||||
|
state.featureSwitchIsTesting
|
||||||
|
)
|
||||||
|
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
funcName: "rating",
|
||||||
|
group: "reviews",
|
||||||
|
docs: "Shows stars which represent the average rating on mangrove.",
|
||||||
needsUrls: [MangroveReviews.ORIGINAL_API],
|
needsUrls: [MangroveReviews.ORIGINAL_API],
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "subjectKey",
|
name: "subjectKey",
|
||||||
defaultValue: "name",
|
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 the value is specified, the subject will be <b>tags[subjectKey]</b> and will use this to filter the reviews."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fallback",
|
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"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
constr: (state, tags, args, feature, layer) => {
|
constr: (state, tags, args, feature) => {
|
||||||
const nameKey = args[0] ?? "name"
|
const nameKey = args[0] ?? "name"
|
||||||
const fallbackName = args[1]
|
const fallbackName = args[1]
|
||||||
const question = args[2]
|
|
||||||
const reviews = FeatureReviews.construct(
|
const reviews = FeatureReviews.construct(
|
||||||
feature,
|
feature,
|
||||||
tags,
|
tags,
|
||||||
state.userRelatedState?.mangroveIdentity,
|
state.userRelatedState.mangroveIdentity,
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName
|
fallbackName
|
||||||
},
|
},
|
||||||
state.featureSwitchIsTesting
|
state.featureSwitchIsTesting
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(ReviewForm, {
|
return new SvelteUIElement(StarsBarIcon, {
|
||||||
reviews,
|
score: reviews.average
|
||||||
state,
|
|
||||||
tags,
|
|
||||||
feature,
|
|
||||||
layer,
|
|
||||||
question
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
createReview,
|
||||||
funcName: "list_reviews",
|
listReviews,
|
||||||
group: "reviews",
|
|
||||||
|
|
||||||
docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten",
|
|
||||||
needsUrls: [MangroveReviews.ORIGINAL_API],
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "subjectKey",
|
|
||||||
defaultValue: "name",
|
|
||||||
doc: "The key to use to determine the subject. If specified, the subject will be <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"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
constr: (state, tags, args, feature, layer) => {
|
|
||||||
const nameKey = args[0] ?? "name"
|
|
||||||
const fallbackName = args[1]
|
|
||||||
const reviews = FeatureReviews.construct(
|
|
||||||
feature,
|
|
||||||
tags,
|
|
||||||
state.userRelatedState?.mangroveIdentity,
|
|
||||||
{
|
|
||||||
nameKey: nameKey,
|
|
||||||
fallbackName
|
|
||||||
},
|
|
||||||
state.featureSwitchIsTesting
|
|
||||||
)
|
|
||||||
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
funcName: "import_mangrove_key",
|
funcName: "import_mangrove_key",
|
||||||
group: "settings",
|
group: "settings",
|
||||||
|
@ -142,6 +149,43 @@ export class ReviewSpecialVisualisations {
|
||||||
const [text] = argument
|
const [text] = argument
|
||||||
return new SvelteUIElement(ImportReviewIdentity, { state, text })
|
return new SvelteUIElement(ImportReviewIdentity, { state, text })
|
||||||
}
|
}
|
||||||
}]
|
},
|
||||||
|
{
|
||||||
|
funcName: "reviews",
|
||||||
|
group: "reviews",
|
||||||
|
|
||||||
|
example:
|
||||||
|
"`{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used",
|
||||||
|
docs: "A pragmatic combination of `create_review` and `list_reviews`",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "subjectKey",
|
||||||
|
defaultValue: "name",
|
||||||
|
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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "question",
|
||||||
|
doc: "The question to ask in the review form. Optional"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
constr(
|
||||||
|
state: SpecialVisualizationState,
|
||||||
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
|
args: string[],
|
||||||
|
feature: Feature,
|
||||||
|
layer: LayerConfig
|
||||||
|
): BaseUIElement {
|
||||||
|
return new Combine([
|
||||||
|
createReview.constr(state, tagSource, args, feature, layer),
|
||||||
|
|
||||||
|
listReviews.constr(state, tagSource, args, feature, layer)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,237 @@
|
||||||
|
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import { Feature } from "geojson"
|
||||||
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
|
import { Translation } from "../i18n/Translation"
|
||||||
|
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||||
|
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||||
|
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
|
||||||
|
import GroupedView from "../Popup/GroupedView.svelte"
|
||||||
|
import OpenIdEditor from "../BigComponents/OpenIdEditor.svelte"
|
||||||
|
import OpenJosm from "../Base/OpenJosm.svelte"
|
||||||
|
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||||
|
import BaseUIElement from "../BaseUIElement"
|
||||||
|
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||||
|
import Combine from "../Base/Combine"
|
||||||
|
|
||||||
|
class StealViz implements SpecialVisualization {
|
||||||
|
|
||||||
|
funcName = "steal"
|
||||||
|
group = "tagrendering_manipulation"
|
||||||
|
|
||||||
|
docs = "Shows a tagRendering from a different object as if this was the object itself"
|
||||||
|
args = [
|
||||||
|
{
|
||||||
|
name: "featureId",
|
||||||
|
doc: "The key of the attribute which contains the id of the feature from which to use the tags",
|
||||||
|
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
needsUrls = []
|
||||||
|
svelteBased = true
|
||||||
|
|
||||||
|
constr(state: SpecialVisualizationState, featureTags, args) {
|
||||||
|
const [featureIdKey, layerAndtagRenderingIds] = args
|
||||||
|
const tagRenderings: [LayerConfig, TagRenderingConfig][] = []
|
||||||
|
for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) {
|
||||||
|
const [layerId, tagRenderingId] = layerAndTagRenderingId.trim().split(".")
|
||||||
|
const layer = state.theme.layers.find((l) => l.id === layerId)
|
||||||
|
const tagRendering = layer.tagRenderings.find((tr) => tr.id === tagRenderingId)
|
||||||
|
tagRenderings.push([layer, tagRendering])
|
||||||
|
}
|
||||||
|
if (tagRenderings.length === 0) {
|
||||||
|
throw "Could not create stolen tagrenddering: tagRenderings not found"
|
||||||
|
}
|
||||||
|
return new VariableUiElement(
|
||||||
|
featureTags.map(
|
||||||
|
(tags) => {
|
||||||
|
const featureId = tags[featureIdKey]
|
||||||
|
if (featureId === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const otherTags = state.featureProperties.getStore(featureId)
|
||||||
|
const otherFeature = state.indexedFeatures.featuresById.data.get(featureId)
|
||||||
|
const elements: BaseUIElement[] = []
|
||||||
|
for (const [layer, tagRendering] of tagRenderings) {
|
||||||
|
elements.push(
|
||||||
|
new SvelteUIElement(TagRenderingEditable, {
|
||||||
|
config: tagRendering,
|
||||||
|
tags: otherTags,
|
||||||
|
selectedElement: otherFeature,
|
||||||
|
state,
|
||||||
|
layer
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (elements.length === 1) {
|
||||||
|
return elements[0]
|
||||||
|
}
|
||||||
|
return new Combine(elements).SetClass("flex flex-col")
|
||||||
|
},
|
||||||
|
[state.indexedFeatures.featuresById]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayerDependencies(args): string[] {
|
||||||
|
const [, tagRenderingId] = args
|
||||||
|
if (tagRenderingId.indexOf(".") < 0) {
|
||||||
|
throw "Error: argument 'layerId.tagRenderingId' of special visualisation 'steal' should contain a dot"
|
||||||
|
}
|
||||||
|
const [layerId] = tagRenderingId.split(".")
|
||||||
|
return [layerId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TagrenderingManipulationSpecialVisualisations {
|
||||||
|
|
||||||
|
public static initList(): (SpecialVisualization & { group })[] {
|
||||||
|
return [
|
||||||
|
new StealViz(),
|
||||||
|
{
|
||||||
|
funcName: "multi",
|
||||||
|
group: "tagrendering_manipulation",
|
||||||
|
docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering",
|
||||||
|
example:
|
||||||
|
"```json\n" +
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
render: {
|
||||||
|
special: {
|
||||||
|
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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
" "
|
||||||
|
) +
|
||||||
|
"\n```",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "key",
|
||||||
|
doc: "The property to read and to interpret as a list of properties",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tagrendering",
|
||||||
|
doc: "An entire tagRenderingConfig",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "classes",
|
||||||
|
doc: "CSS-classes to apply on every individual item. Seperated by `space`"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
constr(
|
||||||
|
state: SpecialVisualizationState,
|
||||||
|
featureTags: UIEventSource<Record<string, string>>,
|
||||||
|
args: string[],
|
||||||
|
feature: Feature,
|
||||||
|
layer: LayerConfig
|
||||||
|
) {
|
||||||
|
const [key, tr, classesRaw] = args
|
||||||
|
const classes = classesRaw ?? ""
|
||||||
|
const translation = new Translation({ "*": tr })
|
||||||
|
return new VariableUiElement(
|
||||||
|
featureTags.map((tags) => {
|
||||||
|
let properties: object[]
|
||||||
|
if (typeof tags[key] === "string") {
|
||||||
|
properties = JSON.parse(tags[key])
|
||||||
|
} else {
|
||||||
|
properties = <object[]><unknown>tags[key]
|
||||||
|
}
|
||||||
|
if (!properties) {
|
||||||
|
console.debug(
|
||||||
|
"Could not create a special visualization for multi(",
|
||||||
|
args.join(", ") + ")",
|
||||||
|
"no properties found for object",
|
||||||
|
feature.properties.id
|
||||||
|
)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const elements = []
|
||||||
|
for (const property of properties) {
|
||||||
|
const subsTr = new SvelteUIElement(SpecialTranslation, {
|
||||||
|
t: translation,
|
||||||
|
tags: new ImmutableStore(property),
|
||||||
|
state,
|
||||||
|
feature,
|
||||||
|
layer
|
||||||
|
// clss: classes ?? "",
|
||||||
|
}).SetClass(classes)
|
||||||
|
elements.push(subsTr)
|
||||||
|
}
|
||||||
|
return elements
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "group",
|
||||||
|
group: "tagrendering_manipulation",
|
||||||
|
docs: "A collapsable group (accordion)",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
constr(
|
||||||
|
state: SpecialVisualizationState,
|
||||||
|
tags: UIEventSource<Record<string, string>>,
|
||||||
|
argument: string[],
|
||||||
|
selectedElement: Feature,
|
||||||
|
layer: LayerConfig
|
||||||
|
): SvelteUIElement {
|
||||||
|
const [header, labelsStr] = argument
|
||||||
|
const labels = labelsStr.split(";").map((x) => x.trim())
|
||||||
|
return new SvelteUIElement(GroupedView, {
|
||||||
|
state,
|
||||||
|
tags,
|
||||||
|
selectedElement,
|
||||||
|
layer,
|
||||||
|
header,
|
||||||
|
labels
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "open_in_iD",
|
||||||
|
docs: "Opens the current view in the iD-editor",
|
||||||
|
args: [],
|
||||||
|
group: "tagrendering_manipulation",
|
||||||
|
constr: (state, feature): SvelteUIElement => {
|
||||||
|
return new SvelteUIElement(OpenIdEditor, {
|
||||||
|
mapProperties: state.mapProperties,
|
||||||
|
objectId: feature.data.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "open_in_josm",
|
||||||
|
group: "tagrendering_manipulation",
|
||||||
|
docs: "Opens the current view in the JOSM-editor",
|
||||||
|
args: [],
|
||||||
|
needsUrls: ["http://127.0.0.1:8111/load_and_zoom"],
|
||||||
|
|
||||||
|
constr: (state): SvelteUIElement => {
|
||||||
|
return new SvelteUIElement(OpenJosm, { state })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,194 @@
|
||||||
import { SpecialVisualization } from "../SpecialVisualization"
|
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import BaseUIElement from "../BaseUIElement"
|
||||||
|
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||||
|
import FediverseLink from "../Popup/FediverseLink.svelte"
|
||||||
|
import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata"
|
||||||
|
import Wikipedia from "../../Logic/Web/Wikipedia"
|
||||||
|
import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte"
|
||||||
|
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||||
|
import { Utils } from "../../Utils"
|
||||||
|
import { Translation } from "../i18n/Translation"
|
||||||
|
import { MapillaryLinkVis } from "../Popup/MapillaryLinkVis"
|
||||||
|
import SendEmail from "../Popup/SendEmail.svelte"
|
||||||
|
import DynLink from "../Base/DynLink.svelte"
|
||||||
|
|
||||||
export class WebAndDataSpecialVisualisations {
|
export class WebAndCommunicationSpecialVisualisations {
|
||||||
public static initList(): (SpecialVisualization & { group }) [] {
|
public static initList(): (SpecialVisualization & { group }) [] {
|
||||||
return []
|
return [
|
||||||
|
|
||||||
|
{
|
||||||
|
funcName: "fediverse_link",
|
||||||
|
group: "web_and_communication",
|
||||||
|
docs: "Converts a fediverse username or link into a clickable link",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "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]
|
||||||
|
return new SvelteUIElement(FediverseLink, { key, tags, state })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "wikipedia",
|
||||||
|
group: "web_and_communication",
|
||||||
|
docs: "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag.",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls],
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
const keys = args[0].split(";").map((k) => k.trim())
|
||||||
|
const wikiIds: Store<string[]> = tagsSource.map((tags) => {
|
||||||
|
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
|
||||||
|
return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
|
||||||
|
})
|
||||||
|
return new SvelteUIElement(WikipediaPanel, {
|
||||||
|
wikiIds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "wikidata_label",
|
||||||
|
group: "web_and_communication",
|
||||||
|
|
||||||
|
docs: "Shows the label of the corresponding wikidata-item",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "keyToShowWikidataFor",
|
||||||
|
doc: "Use the wikidata entry from this key to show the label",
|
||||||
|
defaultValue: "wikidata"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
needsUrls: Wikidata.neededUrls,
|
||||||
|
example:
|
||||||
|
"`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself",
|
||||||
|
constr: (_, tagsSource, args) =>
|
||||||
|
new VariableUiElement(
|
||||||
|
tagsSource
|
||||||
|
.map((tags) => tags[args[0]])
|
||||||
|
.map((wikidata) => {
|
||||||
|
wikidata = Utils.NoEmpty(
|
||||||
|
wikidata?.split(";")?.map((wd) => wd.trim()) ?? []
|
||||||
|
)[0]
|
||||||
|
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
||||||
|
return new VariableUiElement(
|
||||||
|
entry.map((e) => {
|
||||||
|
if (e === undefined || e["success"] === undefined) {
|
||||||
|
return wikidata
|
||||||
|
}
|
||||||
|
const response = <WikidataResponse>e["success"]
|
||||||
|
return Translation.fromMap(response.labels)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
new MapillaryLinkVis(),
|
||||||
|
{
|
||||||
|
funcName: "send_email",
|
||||||
|
group: "web_and_communication",
|
||||||
|
docs: "Creates a `mailto`-link where some fields are already set and correctly escaped. The user will be promted to send the email",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "to",
|
||||||
|
doc: "Who to send the email to?",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subject",
|
||||||
|
doc: "The subject of the email",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "body",
|
||||||
|
doc: "The text in the email",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "button_text",
|
||||||
|
doc: "The text shown on the button in the UI",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
constr(__, tags, args) {
|
||||||
|
return new SvelteUIElement(SendEmail, { args, tags })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "link",
|
||||||
|
group: "web_and_communication",
|
||||||
|
docs: "Construct a link. By using the 'special' visualisation notation, translations should be easier",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "text",
|
||||||
|
doc: "Text to be shown",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "href",
|
||||||
|
doc: "The URL to link to. Note that this will be URI-encoded before ",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "class",
|
||||||
|
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."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arialabel",
|
||||||
|
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`"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
constr(
|
||||||
|
state: SpecialVisualizationState,
|
||||||
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
|
args: string[]
|
||||||
|
): 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))
|
||||||
|
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)),
|
||||||
|
newTab: new ImmutableStore(newTab),
|
||||||
|
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags))
|
||||||
|
}).setSpan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,9 @@ import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState
|
||||||
import { HistogramViz } from "./Popup/HistogramViz"
|
import { HistogramViz } from "./Popup/HistogramViz"
|
||||||
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
|
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
|
||||||
import { MultiApplyViz } from "./Popup/MultiApplyViz"
|
import { MultiApplyViz } from "./Popup/MultiApplyViz"
|
||||||
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
|
import { UIEventSource } from "../Logic/UIEventSource"
|
||||||
import TagApplyButton from "./Popup/TagApplyButton"
|
|
||||||
import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
|
|
||||||
import { ImmutableStore, Store, Stores, UIEventSource } from "../Logic/UIEventSource"
|
|
||||||
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
|
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
|
||||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
import { VariableUiElement } from "./Base/VariableUIElement"
|
||||||
import { Utils } from "../Utils"
|
|
||||||
import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"
|
|
||||||
import { Translation } from "./i18n/Translation"
|
import { Translation } from "./i18n/Translation"
|
||||||
import Translations from "./i18n/Translations"
|
import Translations from "./i18n/Translations"
|
||||||
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
|
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
|
||||||
|
@ -23,119 +18,34 @@ import AutoApplyButton from "./Popup/AutoApplyButton"
|
||||||
import { LanguageElement } from "./Popup/LanguageElement/LanguageElement"
|
import { LanguageElement } from "./Popup/LanguageElement/LanguageElement"
|
||||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||||
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
||||||
import { Feature, GeoJsonProperties, LineString } from "geojson"
|
import { Feature, LineString } from "geojson"
|
||||||
import { GeoOperations } from "../Logic/GeoOperations"
|
import { GeoOperations } from "../Logic/GeoOperations"
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
|
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
|
||||||
import ExportFeatureButton from "./Popup/ExportFeatureButton.svelte"
|
import ExportFeatureButton from "./Popup/ExportFeatureButton.svelte"
|
||||||
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
|
|
||||||
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
|
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"
|
||||||
import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"
|
|
||||||
import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"
|
|
||||||
import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"
|
|
||||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
|
||||||
import SendEmail from "./Popup/SendEmail.svelte"
|
|
||||||
import Constants from "../Models/Constants"
|
import Constants from "../Models/Constants"
|
||||||
import Wikipedia from "../Logic/Web/Wikipedia"
|
|
||||||
import { TagUtils } from "../Logic/Tags/TagUtils"
|
import { TagUtils } from "../Logic/Tags/TagUtils"
|
||||||
import OpenJosm from "./Base/OpenJosm.svelte"
|
|
||||||
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
|
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
|
||||||
import { Unit } from "../Models/Unit"
|
import { Unit } from "../Models/Unit"
|
||||||
import DirectionIndicator from "./Base/DirectionIndicator.svelte"
|
import DirectionIndicator from "./Base/DirectionIndicator.svelte"
|
||||||
import ComparisonTool from "./Comparison/ComparisonTool.svelte"
|
|
||||||
import SpecialTranslation from "./Popup/TagRendering/SpecialTranslation.svelte"
|
|
||||||
import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
|
import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
|
||||||
import Toggle from "./Input/Toggle"
|
|
||||||
import LinkedDataLoader from "../Logic/Web/LinkedDataLoader"
|
|
||||||
import DynLink from "./Base/DynLink.svelte"
|
|
||||||
import MarkdownUtils from "../Utils/MarkdownUtils"
|
import MarkdownUtils from "../Utils/MarkdownUtils"
|
||||||
import Trash from "@babeard/svelte-heroicons/mini/Trash"
|
import Trash from "@babeard/svelte-heroicons/mini/Trash"
|
||||||
import { And } from "../Logic/Tags/And"
|
import { And } from "../Logic/Tags/And"
|
||||||
import GroupedView from "./Popup/GroupedView.svelte"
|
|
||||||
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||||
import NoteCommentElement from "./Popup/Notes/NoteCommentElement.svelte"
|
|
||||||
import FediverseLink from "./Popup/FediverseLink.svelte"
|
|
||||||
import { ImageVisualisations } from "./SpecialVisualisations/ImageVisualisations"
|
import { ImageVisualisations } from "./SpecialVisualisations/ImageVisualisations"
|
||||||
import { NoteVisualisations } from "./SpecialVisualisations/NoteVisualisations"
|
import { NoteVisualisations } from "./SpecialVisualisations/NoteVisualisations"
|
||||||
import { FavouriteVisualisations } from "./SpecialVisualisations/FavouriteVisualisations"
|
import { FavouriteVisualisations } from "./SpecialVisualisations/FavouriteVisualisations"
|
||||||
import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisualisations"
|
import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisualisations"
|
||||||
import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations"
|
import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations"
|
||||||
import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations"
|
import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations"
|
||||||
import { MapRouletteSpecialVisualisations } from "./SpecialVisualisations/MapRouletteSpecialVisualisations"
|
import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations"
|
||||||
|
import TagrenderingManipulationSpecialVisualisations
|
||||||
|
from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
|
||||||
class StealViz implements SpecialVisualization {
|
import {
|
||||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
WebAndCommunicationSpecialVisualisations
|
||||||
|
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
|
||||||
funcName = "steal"
|
|
||||||
docs = "Shows a tagRendering from a different object as if this was the object itself"
|
|
||||||
args = [
|
|
||||||
{
|
|
||||||
name: "featureId",
|
|
||||||
doc: "The key of the attribute which contains the id of the feature from which to use the tags",
|
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
needsUrls = []
|
|
||||||
svelteBased = true
|
|
||||||
|
|
||||||
constr(state: SpecialVisualizationState, featureTags, args) {
|
|
||||||
const [featureIdKey, layerAndtagRenderingIds] = args
|
|
||||||
const tagRenderings: [LayerConfig, TagRenderingConfig][] = []
|
|
||||||
for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) {
|
|
||||||
const [layerId, tagRenderingId] = layerAndTagRenderingId.trim().split(".")
|
|
||||||
const layer = state.theme.layers.find((l) => l.id === layerId)
|
|
||||||
const tagRendering = layer.tagRenderings.find((tr) => tr.id === tagRenderingId)
|
|
||||||
tagRenderings.push([layer, tagRendering])
|
|
||||||
}
|
|
||||||
if (tagRenderings.length === 0) {
|
|
||||||
throw "Could not create stolen tagrenddering: tagRenderings not found"
|
|
||||||
}
|
|
||||||
return new VariableUiElement(
|
|
||||||
featureTags.map(
|
|
||||||
(tags) => {
|
|
||||||
const featureId = tags[featureIdKey]
|
|
||||||
if (featureId === undefined) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
const otherTags = state.featureProperties.getStore(featureId)
|
|
||||||
const otherFeature = state.indexedFeatures.featuresById.data.get(featureId)
|
|
||||||
const elements: BaseUIElement[] = []
|
|
||||||
for (const [layer, tagRendering] of tagRenderings) {
|
|
||||||
elements.push(
|
|
||||||
new SvelteUIElement(TagRenderingEditable, {
|
|
||||||
config: tagRendering,
|
|
||||||
tags: otherTags,
|
|
||||||
selectedElement: otherFeature,
|
|
||||||
state,
|
|
||||||
layer
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (elements.length === 1) {
|
|
||||||
return elements[0]
|
|
||||||
}
|
|
||||||
return new Combine(elements).SetClass("flex flex-col")
|
|
||||||
},
|
|
||||||
[state.indexedFeatures.featuresById]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
getLayerDependencies(args): string[] {
|
|
||||||
const [, tagRenderingId] = args
|
|
||||||
if (tagRenderingId.indexOf(".") < 0) {
|
|
||||||
throw "Error: argument 'layerId.tagRenderingId' of special visualisation 'steal' should contain a dot"
|
|
||||||
}
|
|
||||||
const [layerId] = tagRenderingId.split(".")
|
|
||||||
return [layerId]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default class SpecialVisualizations {
|
export default class SpecialVisualizations {
|
||||||
|
@ -180,7 +90,7 @@ export default class SpecialVisualizations {
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
"#### Example usage of " + viz.funcName,
|
"#### Example usage of " + viz.funcName,
|
||||||
"<code>" + example + "</code>"
|
example
|
||||||
].join("\n\n")
|
].join("\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +125,9 @@ export default class SpecialVisualizations {
|
||||||
"images": "Elements related to adding or manipulating images. Normally also added by default, but in some cases a tweaked version is needed",
|
"images": "Elements related to adding or manipulating images. Normally also added by default, but in some cases a tweaked version is needed",
|
||||||
"notes": "Elements relating to OpenStreetMap-notes, e.g. the component to close and/or add a comment",
|
"notes": "Elements relating to OpenStreetMap-notes, e.g. the component to close and/or add a comment",
|
||||||
"reviews": "Elements relating to seeing and adding ratings and reviews with Mangrove.reviews",
|
"reviews": "Elements relating to seeing and adding ratings and reviews with Mangrove.reviews",
|
||||||
"maproulette": "Elements to close a maproulette task"
|
"data_import": "Elements to help with importing data to OSM. For example: buttons to import a feature, apply tags on an element, apply multiple tags on an element or to work with maproulette",
|
||||||
|
"tagrendering_manipulation": "Special visualisations which reuse other tagRenderings to show data, but with a twist.",
|
||||||
|
"web_and_communication": "Tools to show data from external websites, which link to external websites or which link to external profiles"
|
||||||
}
|
}
|
||||||
|
|
||||||
const helpTexts: string[] = []
|
const helpTexts: string[] = []
|
||||||
|
@ -224,8 +136,6 @@ export default class SpecialVisualizations {
|
||||||
if (viz.group !== lastGroup) {
|
if (viz.group !== lastGroup) {
|
||||||
lastGroup = viz.group
|
lastGroup = viz.group
|
||||||
if (viz.group === undefined) {
|
if (viz.group === undefined) {
|
||||||
|
|
||||||
|
|
||||||
helpTexts.push("## Unclassified elements\n\nVarious elements")
|
helpTexts.push("## Unclassified elements\n\nVarious elements")
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -268,10 +178,11 @@ export default class SpecialVisualizations {
|
||||||
"# Special tag renderings",
|
"# Special tag renderings",
|
||||||
"In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
|
"In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
|
||||||
"General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssClasses}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args",
|
"General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssClasses}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args",
|
||||||
"#### Using expanded syntax",
|
"# Using expanded syntax",
|
||||||
`Instead of using \`{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}\`, one can also write`,
|
`Instead of using \`{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}\`, one can also write`,
|
||||||
"```\n" + example + "\n```\n",
|
"```\n" + example + "\n```\n",
|
||||||
"In other words: use `{ \"before\": ..., \"after\": ..., \"special\": {\"type\": ..., \"argname\": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)"
|
"In other words: use `{ \"before\": ..., \"after\": ..., \"special\": {\"type\": ..., \"argname\": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)",
|
||||||
|
"# Overview of all special components"
|
||||||
].join("\n\n")
|
].join("\n\n")
|
||||||
return firstPart + "\n\n" + helpTexts.join("\n\n")
|
return firstPart + "\n\n" + helpTexts.join("\n\n")
|
||||||
}
|
}
|
||||||
|
@ -284,12 +195,10 @@ export default class SpecialVisualizations {
|
||||||
...UISpecialVisualisations.initList(),
|
...UISpecialVisualisations.initList(),
|
||||||
...SettingsVisualisations.initList(),
|
...SettingsVisualisations.initList(),
|
||||||
...ReviewSpecialVisualisations.initList(),
|
...ReviewSpecialVisualisations.initList(),
|
||||||
...MapRouletteSpecialVisualisations.initList(),
|
...DataImportSpecialVisualisations.initList(),
|
||||||
|
...TagrenderingManipulationSpecialVisualisations.initList(),
|
||||||
|
...WebAndCommunicationSpecialVisualisations.initList(),
|
||||||
new HistogramViz(),
|
new HistogramViz(),
|
||||||
new StealViz(),
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "export_as_gpx",
|
funcName: "export_as_gpx",
|
||||||
docs: "Exports the selected feature as GPX-file",
|
docs: "Exports the selected feature as GPX-file",
|
||||||
|
@ -324,74 +233,6 @@ export default class SpecialVisualizations {
|
||||||
new UploadToOsmViz(),
|
new UploadToOsmViz(),
|
||||||
new MultiApplyViz(),
|
new MultiApplyViz(),
|
||||||
|
|
||||||
new PlantNetDetectionViz(),
|
|
||||||
|
|
||||||
new TagApplyButton(),
|
|
||||||
|
|
||||||
new PointImportButtonViz(),
|
|
||||||
new WayImportButtonViz(),
|
|
||||||
new ConflateImportButtonViz(),
|
|
||||||
|
|
||||||
{
|
|
||||||
funcName: "wikipedia",
|
|
||||||
docs: "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag.",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls],
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
const keys = args[0].split(";").map((k) => k.trim())
|
|
||||||
const wikiIds: Store<string[]> = tagsSource.map((tags) => {
|
|
||||||
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
|
|
||||||
return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
|
|
||||||
})
|
|
||||||
return new SvelteUIElement(WikipediaPanel, {
|
|
||||||
wikiIds
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
funcName: "wikidata_label",
|
|
||||||
docs: "Shows the label of the corresponding wikidata-item",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "keyToShowWikidataFor",
|
|
||||||
doc: "Use the wikidata entry from this key to show the label",
|
|
||||||
defaultValue: "wikidata"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
needsUrls: Wikidata.neededUrls,
|
|
||||||
example:
|
|
||||||
"`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself",
|
|
||||||
constr: (_, tagsSource, args) =>
|
|
||||||
new VariableUiElement(
|
|
||||||
tagsSource
|
|
||||||
.map((tags) => tags[args[0]])
|
|
||||||
.map((wikidata) => {
|
|
||||||
wikidata = Utils.NoEmpty(
|
|
||||||
wikidata?.split(";")?.map((wd) => wd.trim()) ?? []
|
|
||||||
)[0]
|
|
||||||
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
|
||||||
return new VariableUiElement(
|
|
||||||
entry.map((e) => {
|
|
||||||
if (e === undefined || e["success"] === undefined) {
|
|
||||||
return wikidata
|
|
||||||
}
|
|
||||||
const response = <WikidataResponse>e["success"]
|
|
||||||
return Translation.fromMap(response.labels)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
|
||||||
new MapillaryLinkVis(),
|
|
||||||
new LanguageElement(),
|
new LanguageElement(),
|
||||||
{
|
{
|
||||||
funcName: "all_tags",
|
funcName: "all_tags",
|
||||||
|
@ -405,47 +246,6 @@ export default class SpecialVisualizations {
|
||||||
layer: LayerConfig
|
layer: LayerConfig
|
||||||
) => new SvelteUIElement(AllTagsPanel, { tags, layer })
|
) => new SvelteUIElement(AllTagsPanel, { tags, layer })
|
||||||
},
|
},
|
||||||
{
|
|
||||||
funcName: "reviews",
|
|
||||||
group: "reviews",
|
|
||||||
|
|
||||||
example:
|
|
||||||
"`{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used",
|
|
||||||
docs: "A pragmatic combination of `create_review` and `list_reviews`",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "subjectKey",
|
|
||||||
defaultValue: "name",
|
|
||||||
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"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "question",
|
|
||||||
doc: "The question to ask in the review form. Optional"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
constr(
|
|
||||||
state: SpecialVisualizationState,
|
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
|
||||||
args: string[],
|
|
||||||
feature: Feature,
|
|
||||||
layer: LayerConfig
|
|
||||||
): BaseUIElement {
|
|
||||||
return new Combine([
|
|
||||||
SpecialVisualizations.specialVisualisationsDict
|
|
||||||
.get("create_review")
|
|
||||||
.constr(state, tagSource, args, feature, layer),
|
|
||||||
SpecialVisualizations.specialVisualisationsDict
|
|
||||||
.get("list_reviews")
|
|
||||||
.constr(state, tagSource, args, feature, layer)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "opening_hours_table",
|
funcName: "opening_hours_table",
|
||||||
docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.",
|
docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.",
|
||||||
|
@ -568,28 +368,7 @@ export default class SpecialVisualizations {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
funcName: "open_in_iD",
|
|
||||||
docs: "Opens the current view in the iD-editor",
|
|
||||||
args: [],
|
|
||||||
|
|
||||||
constr: (state, feature) => {
|
|
||||||
return new SvelteUIElement(OpenIdEditor, {
|
|
||||||
mapProperties: state.mapProperties,
|
|
||||||
objectId: feature.data.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
funcName: "open_in_josm",
|
|
||||||
docs: "Opens the current view in the JOSM-editor",
|
|
||||||
args: [],
|
|
||||||
needsUrls: ["http://127.0.0.1:8111/load_and_zoom"],
|
|
||||||
|
|
||||||
constr: (state) => {
|
|
||||||
return new SvelteUIElement(OpenJosm, { state })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
funcName: "clear_location_history",
|
funcName: "clear_location_history",
|
||||||
docs: "A button to remove the travelled track information from the device",
|
docs: "A button to remove the travelled track information from the device",
|
||||||
|
@ -605,46 +384,7 @@ export default class SpecialVisualizations {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
funcName: "visualize_note_comments",
|
|
||||||
docs: "Visualises the comments for notes",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "commentsKey",
|
|
||||||
doc: "The property name of the comments, which should be stringified json",
|
|
||||||
defaultValue: "comments"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "start",
|
|
||||||
doc: "Drop the first 'start' comments",
|
|
||||||
defaultValue: "0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
needsUrls: [Constants.osmAuthConfig.url],
|
|
||||||
constr: (state, tags, args) =>
|
|
||||||
new VariableUiElement(
|
|
||||||
tags
|
|
||||||
.map((tags) => tags[args[0]])
|
|
||||||
.map((commentsStr) => {
|
|
||||||
const comments: { text: string }[] = JSON.parse(commentsStr)
|
|
||||||
const startLoc = Number(args[1] ?? 0)
|
|
||||||
if (!isNaN(startLoc) && startLoc > 0) {
|
|
||||||
comments.splice(0, startLoc)
|
|
||||||
}
|
|
||||||
return new Combine(
|
|
||||||
comments
|
|
||||||
.filter((c) => c.text !== "")
|
|
||||||
.map(
|
|
||||||
(comment) =>
|
|
||||||
new SvelteUIElement(NoteCommentElement, {
|
|
||||||
comment,
|
|
||||||
state
|
|
||||||
})
|
|
||||||
)
|
|
||||||
).SetClass("flex flex-col")
|
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
funcName: "title",
|
funcName: "title",
|
||||||
args: [],
|
args: [],
|
||||||
|
@ -689,173 +429,8 @@ export default class SpecialVisualizations {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
funcName: "send_email",
|
|
||||||
docs: "Creates a `mailto`-link where some fields are already set and correctly escaped. The user will be promted to send the email",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "to",
|
|
||||||
doc: "Who to send the email to?",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "subject",
|
|
||||||
doc: "The subject of the email",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "body",
|
|
||||||
doc: "The text in the email",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "button_text",
|
|
||||||
doc: "The text shown on the button in the UI",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
constr(__, tags, args) {
|
|
||||||
return new SvelteUIElement(SendEmail, { args, tags })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
funcName: "link",
|
|
||||||
docs: "Construct a link. By using the 'special' visualisation notation, translations should be easier",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "text",
|
|
||||||
doc: "Text to be shown",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "href",
|
|
||||||
doc: "The URL to link to. Note that this will be URI-encoded before ",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "class",
|
|
||||||
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."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "arialabel",
|
|
||||||
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`"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
constr(
|
|
||||||
state: SpecialVisualizationState,
|
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
|
||||||
args: string[]
|
|
||||||
): 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))
|
|
||||||
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)),
|
|
||||||
newTab: new ImmutableStore(newTab),
|
|
||||||
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags))
|
|
||||||
}).setSpan()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
funcName: "multi",
|
|
||||||
docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering",
|
|
||||||
example:
|
|
||||||
"```json\n" +
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
render: {
|
|
||||||
special: {
|
|
||||||
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}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
" "
|
|
||||||
) +
|
|
||||||
"\n```",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "key",
|
|
||||||
doc: "The property to read and to interpret as a list of properties",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tagrendering",
|
|
||||||
doc: "An entire tagRenderingConfig",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "classes",
|
|
||||||
doc: "CSS-classes to apply on every individual item. Seperated by `space`"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
constr(
|
|
||||||
state: SpecialVisualizationState,
|
|
||||||
featureTags: UIEventSource<Record<string, string>>,
|
|
||||||
args: string[],
|
|
||||||
feature: Feature,
|
|
||||||
layer: LayerConfig
|
|
||||||
) {
|
|
||||||
const [key, tr, classesRaw] = args
|
|
||||||
let classes = classesRaw ?? ""
|
|
||||||
const translation = new Translation({ "*": tr })
|
|
||||||
return new VariableUiElement(
|
|
||||||
featureTags.map((tags) => {
|
|
||||||
let properties: object[]
|
|
||||||
if (typeof tags[key] === "string") {
|
|
||||||
properties = JSON.parse(tags[key])
|
|
||||||
} else {
|
|
||||||
properties = <any>tags[key]
|
|
||||||
}
|
|
||||||
if (!properties) {
|
|
||||||
console.debug(
|
|
||||||
"Could not create a special visualization for multi(",
|
|
||||||
args.join(", ") + ")",
|
|
||||||
"no properties found for object",
|
|
||||||
feature.properties.id
|
|
||||||
)
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
const elements = []
|
|
||||||
for (const property of properties) {
|
|
||||||
const subsTr = new SvelteUIElement(SpecialTranslation, {
|
|
||||||
t: translation,
|
|
||||||
tags: new ImmutableStore(property),
|
|
||||||
state,
|
|
||||||
feature,
|
|
||||||
layer
|
|
||||||
// clss: classes ?? "",
|
|
||||||
}).SetClass(classes)
|
|
||||||
elements.push(subsTr)
|
|
||||||
}
|
|
||||||
return elements
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
funcName: "translated",
|
funcName: "translated",
|
||||||
docs: "If the given key can be interpreted as a JSON, only show the key containing the current language (or 'en'). This specialRendering is meant to be used by MapComplete studio and is not useful in map themes",
|
docs: "If the given key can be interpreted as a JSON, only show the key containing the current language (or 'en'). This specialRendering is meant to be used by MapComplete studio and is not useful in map themes",
|
||||||
|
@ -886,28 +461,6 @@ export default class SpecialVisualizations {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
funcName: "fediverse_link",
|
|
||||||
docs: "Converts a fediverse username or link into a clickable link",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "key",
|
|
||||||
doc: "The attribute-name containing the link",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
constr(
|
|
||||||
state: SpecialVisualizationState,
|
|
||||||
tags: UIEventSource<Record<string, string>>,
|
|
||||||
argument: string[],
|
|
||||||
feature: Feature,
|
|
||||||
layer: LayerConfig
|
|
||||||
): BaseUIElement {
|
|
||||||
const key = argument[0]
|
|
||||||
return new SvelteUIElement(FediverseLink, { key, tags, state })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
funcName: "braced",
|
funcName: "braced",
|
||||||
docs: "Show a literal text within braces",
|
docs: "Show a literal text within braces",
|
||||||
|
@ -1022,182 +575,6 @@ export default class SpecialVisualizations {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
funcName: "compare_data",
|
|
||||||
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: tagSource,
|
|
||||||
layer,
|
|
||||||
feature,
|
|
||||||
readonly,
|
|
||||||
externalData
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
funcName: "linked_data_from_website",
|
|
||||||
docs: "Attempts to load (via a proxy) the specified website and parsed ld+json from there. Suitable data will be offered to import into OSM",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "key",
|
|
||||||
defaultValue: "website",
|
|
||||||
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: any }> =
|
|
||||||
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 {
|
|
||||||
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]
|
|
||||||
)
|
|
||||||
|
|
||||||
externalData.addCallbackAndRunD((lod) =>
|
|
||||||
console.log("linked_data_from_website received the following data:", lod)
|
|
||||||
)
|
|
||||||
|
|
||||||
return new Toggle(
|
|
||||||
new SvelteUIElement(ComparisonTool, {
|
|
||||||
feature,
|
|
||||||
state,
|
|
||||||
tags,
|
|
||||||
layer,
|
|
||||||
externalData,
|
|
||||||
sourceUrl,
|
|
||||||
readonly,
|
|
||||||
collapsed: isClosed
|
|
||||||
}),
|
|
||||||
undefined,
|
|
||||||
sourceUrl.map((url) => !!url)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "preset_description",
|
funcName: "preset_description",
|
||||||
|
@ -1206,9 +583,6 @@ export default class SpecialVisualizations {
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
|
||||||
feature: Feature,
|
|
||||||
layer: LayerConfig
|
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const translation = tagSource.map((tags) => {
|
const translation = tagSource.map((tags) => {
|
||||||
const layer = state.theme.getMatchingLayer(tags)
|
const layer = state.theme.getMatchingLayer(tags)
|
||||||
|
@ -1219,38 +593,6 @@ export default class SpecialVisualizations {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
funcName: "group",
|
|
||||||
docs: "A collapsable group (accordion)",
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
name: "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"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
constr(
|
|
||||||
state: SpecialVisualizationState,
|
|
||||||
tags: UIEventSource<Record<string, string>>,
|
|
||||||
argument: string[],
|
|
||||||
selectedElement: Feature,
|
|
||||||
layer: LayerConfig
|
|
||||||
): SvelteUIElement {
|
|
||||||
const [header, labelsStr] = argument
|
|
||||||
const labels = labelsStr.split(";").map((x) => x.trim())
|
|
||||||
return new SvelteUIElement<any, any, any>(GroupedView, {
|
|
||||||
state,
|
|
||||||
tags,
|
|
||||||
selectedElement,
|
|
||||||
layer,
|
|
||||||
header,
|
|
||||||
labels
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
funcName: "preset_type_select",
|
funcName: "preset_type_select",
|
||||||
docs: "An editable tag rendering which allows to change the type",
|
docs: "An editable tag rendering which allows to change the type",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue