Refactoring: port ExportAsGpxViz.svelte to svelte, rename to 'ExportFeatureButton'

This commit is contained in:
Pieter Vander Vennet 2024-11-01 18:51:31 +01:00
parent f195322e4e
commit 4dc48bc57a
4 changed files with 199 additions and 199 deletions

View file

@ -1,6 +1,6 @@
import GeoJsonSource from "./GeoJsonSource" import GeoJsonSource from "./GeoJsonSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { UpdatableFeatureSource } from "../FeatureSource" import { FeatureSource, UpdatableFeatureSource } from "../FeatureSource"
import { Or } from "../../Tags/Or" import { Or } from "../../Tags/Or"
import FeatureSwitchState from "../../State/FeatureSwitchState" import FeatureSwitchState from "../../State/FeatureSwitchState"
import OverpassFeatureSource from "./OverpassFeatureSource" import OverpassFeatureSource from "./OverpassFeatureSource"
@ -64,8 +64,8 @@ export default class ThemeSource extends FeatureSourceMerger {
const mvtSources: UpdatableFeatureSource[] = osmLayers const mvtSources: UpdatableFeatureSource[] = osmLayers
.filter((f) => mvtAvailableLayers.has(f.id)) .filter((f) => mvtAvailableLayers.has(f.id))
.map((l) => ThemeSource.setupMvtSource(l, mapProperties, isDisplayed(l.id))) .map((l) => ThemeSource.setupMvtSource(l, mapProperties, isDisplayed(l.id)))
const nonMvtSources = [] const nonMvtSources: FeatureSource[] = []
const nonMvtLayers = osmLayers.filter((l) => !mvtAvailableLayers.has(l.id)) const nonMvtLayers: LayerConfig[] = osmLayers.filter((l) => !mvtAvailableLayers.has(l.id))
const isLoading = new UIEventSource(false) const isLoading = new UIEventSource(false)

View file

@ -1,49 +0,0 @@
import Translations from "../i18n/Translations"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import Combine from "../Base/Combine"
import { GeoOperations } from "../../Logic/GeoOperations"
import { Utils } from "../../Utils"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature, LineString } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ToSvelte from "../Base/ToSvelte.svelte"
import SvelteUIElement from "../Base/SvelteUIElement"
import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray"
export class ExportAsGpxViz implements SpecialVisualization {
funcName = "export_as_gpx"
docs = "Exports the selected feature as GPX-file"
args = []
needsUrls = []
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
) {
const t = Translations.t.general.download
if (feature.geometry.type !== "LineString") {
return undefined
}
return new SubtleButton(
new SvelteUIElement(ArrowDownTray),
new Combine([
t.downloadFeatureAsGpx.SetClass("font-bold text-lg"),
t.downloadGpxHelper.SetClass("subtle"),
]).SetClass("flex flex-col")
)
.SetClass("w-full")
.onClick(() => {
console.log("Exporting as GPX!")
const tags = tagSource.data
const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
const gpx = GeoOperations.toGpx(<Feature<LineString>>feature, title)
Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
mimetype: "{gpx=application/gpx+xml}",
})
})
}
}

View file

@ -0,0 +1,43 @@
<script lang="ts">
import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import { Store } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { GeoOperations } from "../../Logic/GeoOperations"
import type { Feature, LineString } from "geojson"
import { Utils } from "../../Utils"
import { Translation } from "../i18n/Translation"
const t = Translations.t.general.download
export let tags: Store<Record<string, string>>
export let layer: LayerConfig
export let feature: Feature
export let mimetype: string
export let extension: string
export let maintext: Translation
export let helpertext: Translation
export let construct: (feature: Feature, title: string) => (Blob | string)
function exportGpx() {
console.log("Exporting as GPX!")
const tgs = tags.data
const title = layer.title?.GetRenderValue(tgs)?.Subs(tgs)?.txt ?? "gpx_track"
const data = construct(feature, title)
Utils.offerContentsAsDownloadableFile(data, title + "_mapcomplete_export."+extension, {
mimetype,
})
}
</script>
<button class="w-full" on:click={() => exportGpx()}>
<ArrowDownTray class="w-11 h-11 mr-2"/>
<div class="flex flex-col items-start w-full">
<Tr t={maintext} cls="font-bold text-lg" />
<Tr t={helpertext} cls="subtle text-start" />
</div>
</button>

View file

@ -2,11 +2,7 @@ import Combine from "./Base/Combine"
import { FixedUiElement } from "./Base/FixedUiElement" import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement" import BaseUIElement from "./BaseUIElement"
import Title from "./Base/Title" import Title from "./Base/Title"
import { import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
RenderingSpecification,
SpecialVisualization,
SpecialVisualizationState,
} from "./SpecialVisualization"
import { HistogramViz } from "./Popup/HistogramViz" import { HistogramViz } from "./Popup/HistogramViz"
import MinimapViz from "./Popup/MinimapViz.svelte" import MinimapViz from "./Popup/MinimapViz.svelte"
import { ShareLinkViz } from "./Popup/ShareLinkViz" import { ShareLinkViz } from "./Popup/ShareLinkViz"
@ -35,13 +31,13 @@ import FeatureReviews from "../Logic/Web/MangroveReviews"
import Maproulette, { MaprouletteTask } from "../Logic/Maproulette" import Maproulette, { MaprouletteTask } from "../Logic/Maproulette"
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 } from "geojson" import { Feature, GeoJsonProperties, LineString } from "geojson"
import { GeoOperations } from "../Logic/GeoOperations" import { GeoOperations } from "../Logic/GeoOperations"
import CreateNewNote from "./Popup/Notes/CreateNewNote.svelte" import CreateNewNote from "./Popup/Notes/CreateNewNote.svelte"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
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 { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" import ExportFeatureButton from "./Popup/ExportFeatureButton.svelte"
import WikipediaPanel from "./Wikipedia/WikipediaPanel.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 { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"
@ -49,7 +45,6 @@ import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"
import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"
import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import FediverseValidator from "./InputElement/Validators/FediverseValidator"
import SendEmail from "./Popup/SendEmail.svelte" import SendEmail from "./Popup/SendEmail.svelte"
import UploadImage from "./Image/UploadImage.svelte" import UploadImage from "./Image/UploadImage.svelte"
import { Imgur } from "../Logic/ImageProviders/Imgur" import { Imgur } from "../Logic/ImageProviders/Imgur"
@ -73,7 +68,6 @@ import NearbyImages from "./Image/NearbyImages.svelte"
import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte" import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte"
import MoveWizard from "./Popup/MoveWizard.svelte" import MoveWizard from "./Popup/MoveWizard.svelte"
import { Unit } from "../Models/Unit" import { Unit } from "../Models/Unit"
import Link from "./Base/Link.svelte"
import OrientationDebugPanel from "./Debug/OrientationDebugPanel.svelte" import OrientationDebugPanel from "./Debug/OrientationDebugPanel.svelte"
import MaprouletteSetStatus from "./MapRoulette/MaprouletteSetStatus.svelte" import MaprouletteSetStatus from "./MapRoulette/MaprouletteSetStatus.svelte"
import DirectionIndicator from "./Base/DirectionIndicator.svelte" import DirectionIndicator from "./Base/DirectionIndicator.svelte"
@ -127,7 +121,7 @@ class NearbyImageVis implements SpecialVisualization {
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): SvelteUIElement { ): SvelteUIElement {
const isOpen = args[0] === "open" const isOpen = args[0] === "open"
const readonly = args[1] === "readonly" const readonly = args[1] === "readonly"
@ -194,7 +188,7 @@ class StealViz implements SpecialVisualization {
selectedElement: otherFeature, selectedElement: otherFeature,
state, state,
layer, layer,
}) }),
) )
} }
if (elements.length === 1) { if (elements.length === 1) {
@ -202,8 +196,8 @@ class StealViz implements SpecialVisualization {
} }
return new Combine(elements).SetClass("flex flex-col") return new Combine(elements).SetClass("flex flex-col")
}, },
[state.indexedFeatures.featuresById] [state.indexedFeatures.featuresById],
) ),
) )
} }
@ -255,11 +249,11 @@ class CloseNoteViz implements SpecialVisualization {
public constr( public constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
args: string[] args: string[],
): SvelteUIElement { ): SvelteUIElement {
const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs( const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs(
this.args, this.args,
args args,
) )
return new SvelteUIElement(CloseNoteButton, { return new SvelteUIElement(CloseNoteButton, {
@ -300,7 +294,7 @@ export class QuestionViz implements SpecialVisualization {
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): SvelteUIElement { ): SvelteUIElement {
const labels = args[0] const labels = args[0]
?.split(";") ?.split(";")
@ -332,7 +326,7 @@ export default class SpecialVisualizations {
for (const specialVisualization of SpecialVisualizations.specialVisualizations) { for (const specialVisualization of SpecialVisualizations.specialVisualizations) {
SpecialVisualizations.specialVisualisationsDict.set( SpecialVisualizations.specialVisualisationsDict.set(
specialVisualization.funcName, specialVisualization.funcName,
specialVisualization specialVisualization,
) )
} }
} }
@ -352,15 +346,15 @@ export default class SpecialVisualizations {
viz.docs, viz.docs,
viz.args.length > 0 viz.args.length > 0
? MarkdownUtils.table( ? MarkdownUtils.table(
["name", "default", "description"], ["name", "default", "description"],
viz.args.map((arg) => { viz.args.map((arg) => {
let defaultArg = arg.defaultValue ?? "_undefined_" let defaultArg = arg.defaultValue ?? "_undefined_"
if (defaultArg == "") { if (defaultArg == "") {
defaultArg = "_empty string_" defaultArg = "_empty string_"
} }
return [arg.name, defaultArg, arg.doc] return [arg.name, defaultArg, arg.doc]
}) }),
) )
: undefined, : undefined,
"#### Example usage of " + viz.funcName, "#### Example usage of " + viz.funcName,
"<code>" + example + "</code>", "<code>" + example + "</code>",
@ -369,18 +363,18 @@ export default class SpecialVisualizations {
public static constructSpecification( public static constructSpecification(
template: string, template: string,
extraMappings: SpecialVisualization[] = [] extraMappings: SpecialVisualization[] = [],
): RenderingSpecification[] { ): RenderingSpecification[] {
return SpecialVisualisationUtils.constructSpecification( return SpecialVisualisationUtils.constructSpecification(
template, template,
SpecialVisualizations.specialVisualisationsDict, SpecialVisualizations.specialVisualisationsDict,
extraMappings extraMappings,
) )
} }
public static HelpMessage(): string { public static HelpMessage(): string {
const helpTexts: string[] = SpecialVisualizations.specialVisualizations.map((viz) => const helpTexts: string[] = SpecialVisualizations.specialVisualizations.map((viz) =>
SpecialVisualizations.DocumentationFor(viz) SpecialVisualizations.DocumentationFor(viz),
) )
const firstPart = new Combine([ const firstPart = new Combine([
@ -413,10 +407,10 @@ export default class SpecialVisualizations {
}, },
}, },
null, null,
" " " ",
) ),
).SetClass("code"), ).SetClass("code"),
'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)",
]) ])
.SetClass("flex flex-col") .SetClass("flex flex-col")
.AsMarkdown() .AsMarkdown()
@ -454,10 +448,10 @@ export default class SpecialVisualizations {
assignTo: state.userRelatedState.language, assignTo: state.userRelatedState.language,
availableLanguages: languages, availableLanguages: languages,
preferredLanguages: state.osmConnection.userDetails.map( preferredLanguages: state.osmConnection.userDetails.map(
(ud) => ud.languages (ud) => ud.languages,
), ),
}) })
}) }),
) )
}, },
}, },
@ -496,7 +490,7 @@ export default class SpecialVisualizations {
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature feature: Feature,
): SvelteUIElement { ): SvelteUIElement {
return new SvelteUIElement(MinimapViz, { state, args, feature, tagSource }) return new SvelteUIElement(MinimapViz, { state, args, feature, tagSource })
}, },
@ -508,7 +502,7 @@ export default class SpecialVisualizations {
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>> tagSource: UIEventSource<Record<string, string>>,
): BaseUIElement { ): BaseUIElement {
return new VariableUiElement( return new VariableUiElement(
tagSource tagSource
@ -518,7 +512,7 @@ export default class SpecialVisualizations {
return new SvelteUIElement(SplitRoadWizard, { id, state }) return new SvelteUIElement(SplitRoadWizard, { id, state })
} }
return undefined return undefined
}) }),
) )
}, },
}, },
@ -532,7 +526,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
if (feature.geometry.type !== "Point") { if (feature.geometry.type !== "Point") {
return undefined return undefined
@ -555,7 +549,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
if (!layer.deletion) { if (!layer.deletion) {
return undefined return undefined
@ -570,7 +564,34 @@ export default class SpecialVisualizations {
}, },
}, },
new ShareLinkViz(), new ShareLinkViz(),
new ExportAsGpxViz(), {
funcName: "export_as_gpx",
docs: "Exports the selected feature as GPX-file",
args: [],
needsUrls: [],
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig,
) {
if (feature.geometry.type !== "LineString") {
return undefined
}
const t = Translations.t.general.download
return new SvelteUIElement(ExportFeatureButton, {
tags, feature, layer,
mimetype: "{gpx=application/gpx+xml}",
extension: "gpx",
construct: (feature: Feature<LineString>, title: string) => GeoOperations.toGpx(feature, title),
helpertext: t.downloadGpxHelper,
maintext: t.downloadFeatureAsGpx,
})
},
},
new UploadToOsmViz(), new UploadToOsmViz(),
new MultiApplyViz(), new MultiApplyViz(),
new AddNoteCommentViz(), new AddNoteCommentViz(),
@ -583,7 +604,7 @@ export default class SpecialVisualizations {
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature feature: Feature,
): BaseUIElement { ): BaseUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature) const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(CreateNewNote, { return new SvelteUIElement(CreateNewNote, {
@ -646,7 +667,7 @@ export default class SpecialVisualizations {
.map((tags) => tags[args[0]]) .map((tags) => tags[args[0]])
.map((wikidata) => { .map((wikidata) => {
wikidata = Utils.NoEmpty( wikidata = Utils.NoEmpty(
wikidata?.split(";")?.map((wd) => wd.trim()) ?? [] wikidata?.split(";")?.map((wd) => wd.trim()) ?? [],
)[0] )[0]
const entry = Wikidata.LoadWikidataEntry(wikidata) const entry = Wikidata.LoadWikidataEntry(wikidata)
return new VariableUiElement( return new VariableUiElement(
@ -656,9 +677,9 @@ export default class SpecialVisualizations {
} }
const response = <WikidataResponse>e["success"] const response = <WikidataResponse>e["success"]
return Translation.fromMap(response.labels) return Translation.fromMap(response.labels)
}) }),
) )
}) }),
), ),
}, },
new MapillaryLinkVis(), new MapillaryLinkVis(),
@ -672,7 +693,7 @@ export default class SpecialVisualizations {
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
_, _,
__, __,
layer: LayerConfig layer: LayerConfig,
) => new SvelteUIElement(AllTagsPanel, { tags, layer }), ) => new SvelteUIElement(AllTagsPanel, { tags, layer }),
}, },
{ {
@ -694,7 +715,7 @@ export default class SpecialVisualizations {
return new ImageCarousel( return new ImageCarousel(
AllImageProviders.LoadImagesFor(tags, imagePrefixes), AllImageProviders.LoadImagesFor(tags, imagePrefixes),
tags, tags,
state state,
) )
}, },
}, },
@ -758,7 +779,7 @@ export default class SpecialVisualizations {
nameKey: nameKey, nameKey: nameKey,
fallbackName, fallbackName,
}, },
state.featureSwitchIsTesting state.featureSwitchIsTesting,
) )
return new SvelteUIElement(StarsBarIcon, { return new SvelteUIElement(StarsBarIcon, {
score: reviews.average, score: reviews.average,
@ -792,7 +813,7 @@ export default class SpecialVisualizations {
nameKey: nameKey, nameKey: nameKey,
fallbackName, fallbackName,
}, },
state.featureSwitchIsTesting state.featureSwitchIsTesting,
) )
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer }) return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
}, },
@ -823,7 +844,7 @@ export default class SpecialVisualizations {
nameKey: nameKey, nameKey: nameKey,
fallbackName, fallbackName,
}, },
state.featureSwitchIsTesting state.featureSwitchIsTesting,
) )
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer }) return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
}, },
@ -849,7 +870,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
return new Combine([ return new Combine([
SpecialVisualizations.specialVisualisationsDict["create_review"].constr( SpecialVisualizations.specialVisualisationsDict["create_review"].constr(
@ -857,14 +878,14 @@ export default class SpecialVisualizations {
tagSource, tagSource,
args, args,
feature, feature,
layer layer,
), ),
SpecialVisualizations.specialVisualisationsDict["list_reviews"].constr( SpecialVisualizations.specialVisualisationsDict["list_reviews"].constr(
state, state,
tagSource, tagSource,
args, args,
feature, feature,
layer layer,
), ),
]) ])
}, },
@ -882,7 +903,7 @@ export default class SpecialVisualizations {
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
_: UIEventSource<Record<string, string>>, _: UIEventSource<Record<string, string>>,
argument: string[] argument: string[],
): BaseUIElement { ): BaseUIElement {
const [text] = argument const [text] = argument
return new SvelteUIElement(ImportReviewIdentity, { state, text }) return new SvelteUIElement(ImportReviewIdentity, { state, text })
@ -939,7 +960,7 @@ export default class SpecialVisualizations {
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
args: string[] args: string[],
): SvelteUIElement { ): SvelteUIElement {
const keyToUse = args[0] const keyToUse = args[0]
const prefix = args[1] const prefix = args[1]
@ -976,17 +997,17 @@ export default class SpecialVisualizations {
return undefined return undefined
} }
const allUnits: Unit[] = [].concat( const allUnits: Unit[] = [].concat(
...(state?.theme?.layers?.map((lyr) => lyr.units) ?? []) ...(state?.theme?.layers?.map((lyr) => lyr.units) ?? []),
) )
const unit = allUnits.filter((unit) => const unit = allUnits.filter((unit) =>
unit.isApplicableToKey(key) unit.isApplicableToKey(key),
)[0] )[0]
if (unit === undefined) { if (unit === undefined) {
return value return value
} }
const getCountry = () => tagSource.data._country const getCountry = () => tagSource.data._country
return unit.asHumanLongValue(value, getCountry) return unit.asHumanLongValue(value, getCountry)
}) }),
) )
}, },
}, },
@ -995,31 +1016,16 @@ export default class SpecialVisualizations {
docs: "Exports the selected feature as GeoJson-file", docs: "Exports the selected feature as GeoJson-file",
args: [], args: [],
constr: (state, tagSource, tagsSource, feature, layer) => { constr: (state, tags, args, feature, layer) => {
const t = Translations.t.general.download const t = Translations.t.general.download
return new SvelteUIElement(ExportFeatureButton, {
return new SubtleButton( tags, feature, layer,
new SvelteUIElement(ArrowDownTray), mimetype: "application/vnd.geo+json",
new Combine([ extension: "geojson",
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"), construct: (feature: Feature<LineString>) => JSON.stringify(feature, null, " "),
t.downloadGeoJsonHelper.SetClass("subtle"), maintext: t.downloadFeatureAsGeojson,
]).SetClass("flex flex-col") helpertext: t.downloadGeoJsonHelper,
) })
.onClick(() => {
console.log("Exporting as Geojson")
const tags = tagSource.data
const title =
layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson"
const data = JSON.stringify(feature, null, " ")
Utils.offerContentsAsDownloadableFile(
data,
title + "_mapcomplete_export.geojson",
{
mimetype: "application/vnd.geo+json",
}
)
})
.SetClass("w-full")
}, },
}, },
{ {
@ -1052,7 +1058,7 @@ export default class SpecialVisualizations {
constr: (state) => { constr: (state) => {
return new SubtleButton( return new SubtleButton(
new SvelteUIElement(Trash).SetClass("h-6"), new SvelteUIElement(Trash).SetClass("h-6"),
Translations.t.general.removeLocationHistory Translations.t.general.removeLocationHistory,
).onClick(() => { ).onClick(() => {
state.historicalUserLocations.features.setData([]) state.historicalUserLocations.features.setData([])
state.selectedElement.setData(undefined) state.selectedElement.setData(undefined)
@ -1093,10 +1099,10 @@ export default class SpecialVisualizations {
new SvelteUIElement(NoteCommentElement, { new SvelteUIElement(NoteCommentElement, {
comment, comment,
state, state,
}) }),
) ),
).SetClass("flex flex-col") ).SetClass("flex flex-col")
}) }),
), ),
}, },
{ {
@ -1129,7 +1135,7 @@ export default class SpecialVisualizations {
tagsSource: UIEventSource<Record<string, string>>, tagsSource: UIEventSource<Record<string, string>>,
_: string[], _: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
) => ) =>
new VariableUiElement( new VariableUiElement(
tagsSource.map((tags) => { tagsSource.map((tags) => {
@ -1149,7 +1155,7 @@ export default class SpecialVisualizations {
}) })
.SetClass("px-1") .SetClass("px-1")
.setSpan() .setSpan()
}) }),
), ),
}, },
{ {
@ -1165,8 +1171,8 @@ export default class SpecialVisualizations {
const challenge = Stores.FromPromise( const challenge = Stores.FromPromise(
Utils.downloadJsonCached<MaprouletteTask>( Utils.downloadJsonCached<MaprouletteTask>(
`${Maproulette.defaultEndpoint}/challenge/${parentId}`, `${Maproulette.defaultEndpoint}/challenge/${parentId}`,
24 * 60 * 60 * 1000 24 * 60 * 60 * 1000,
) ),
) )
return new VariableUiElement( return new VariableUiElement(
@ -1191,7 +1197,7 @@ export default class SpecialVisualizations {
} else { } else {
return [title, new List(listItems)] return [title, new List(listItems)]
} }
}) }),
) )
}, },
docs: "Fetches the metadata of MapRoulette campaign that this task is part of and shows those details (namely `title`, `description` and `instruction`).\n\nThis reads the property `mr_challengeId` to detect the parent campaign.", docs: "Fetches the metadata of MapRoulette campaign that this task is part of and shows those details (namely `title`, `description` and `instruction`).\n\nThis reads the property `mr_challengeId` to detect the parent campaign.",
@ -1205,15 +1211,15 @@ export default class SpecialVisualizations {
"\n" + "\n" +
"```json\n" + "```json\n" +
"{\n" + "{\n" +
' "id": "mark_duplicate",\n' + " \"id\": \"mark_duplicate\",\n" +
' "render": {\n' + " \"render\": {\n" +
' "special": {\n' + " \"special\": {\n" +
' "type": "maproulette_set_status",\n' + " \"type\": \"maproulette_set_status\",\n" +
' "message": {\n' + " \"message\": {\n" +
' "en": "Mark as not found or false positive"\n' + " \"en\": \"Mark as not found or false positive\"\n" +
" },\n" + " },\n" +
' "status": "2",\n' + " \"status\": \"2\",\n" +
' "image": "close"\n' + " \"image\": \"close\"\n" +
" }\n" + " }\n" +
" }\n" + " }\n" +
"}\n" + "}\n" +
@ -1289,7 +1295,7 @@ export default class SpecialVisualizations {
(l) => (l) =>
l.name !== null && l.name !== null &&
l.title && l.title &&
state.perLayer.get(l.id) !== undefined state.perLayer.get(l.id) !== undefined,
) )
.map( .map(
(l) => { (l) => {
@ -1299,8 +1305,8 @@ export default class SpecialVisualizations {
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox) const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
return new StatisticsPanel(fsBboxed) return new StatisticsPanel(fsBboxed)
}, },
[state.mapProperties.bounds] [state.mapProperties.bounds],
) ),
) )
}, },
}, },
@ -1370,7 +1376,7 @@ export default class SpecialVisualizations {
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
args: string[] args: string[],
): SvelteUIElement { ): SvelteUIElement {
let [text, href, classnames, download, ariaLabel, icon] = args let [text, href, classnames, download, ariaLabel, icon] = args
if (download === "") { if (download === "") {
@ -1408,7 +1414,7 @@ export default class SpecialVisualizations {
}, },
}, },
null, null,
" " " ",
) + ) +
"\n```", "\n```",
args: [ args: [
@ -1432,7 +1438,7 @@ export default class SpecialVisualizations {
featureTags: UIEventSource<Record<string, string>>, featureTags: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
) { ) {
const [key, tr, classesRaw] = args const [key, tr, classesRaw] = args
let classes = classesRaw ?? "" let classes = classesRaw ?? ""
@ -1450,7 +1456,7 @@ export default class SpecialVisualizations {
"Could not create a special visualization for multi(", "Could not create a special visualization for multi(",
args.join(", ") + ")", args.join(", ") + ")",
"no properties found for object", "no properties found for object",
feature.properties.id feature.properties.id,
) )
return undefined return undefined
} }
@ -1466,7 +1472,7 @@ export default class SpecialVisualizations {
elements.push(subsTr) elements.push(subsTr)
} }
return elements return elements
}) }),
) )
}, },
}, },
@ -1486,7 +1492,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
return new VariableUiElement( return new VariableUiElement(
tagSource.map((tags) => { tagSource.map((tags) => {
@ -1498,7 +1504,7 @@ export default class SpecialVisualizations {
console.error("Cannot create a translation for", v, "due to", e) console.error("Cannot create a translation for", v, "due to", e)
return JSON.stringify(v) return JSON.stringify(v)
} }
}) }),
) )
}, },
}, },
@ -1518,10 +1524,10 @@ export default class SpecialVisualizations {
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
const key = argument[0] const key = argument[0]
return new SvelteUIElement(FediverseLink, {key, tags, state}) return new SvelteUIElement(FediverseLink, { key, tags, state })
}, },
}, },
@ -1541,7 +1547,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
return new FixedUiElement("{" + args[0] + "}") return new FixedUiElement("{" + args[0] + "}")
}, },
@ -1562,7 +1568,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
const key = argument[0] ?? "value" const key = argument[0] ?? "value"
return new VariableUiElement( return new VariableUiElement(
@ -1580,12 +1586,12 @@ export default class SpecialVisualizations {
} catch (e) { } catch (e) {
return new FixedUiElement( return new FixedUiElement(
"Could not parse this tag: " + "Could not parse this tag: " +
JSON.stringify(value) + JSON.stringify(value) +
" due to " + " due to " +
e e,
).SetClass("alert") ).SetClass("alert")
} }
}) }),
) )
}, },
}, },
@ -1606,7 +1612,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
const giggityUrl = argument[0] const giggityUrl = argument[0]
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl }) return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
@ -1622,12 +1628,12 @@ export default class SpecialVisualizations {
_: UIEventSource<Record<string, string>>, _: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
const tags = (<ThemeViewState>( const tags = (<ThemeViewState>(
state state
)).geolocation.currentUserLocation.features.map( )).geolocation.currentUserLocation.features.map(
(features) => features[0]?.properties (features) => features[0]?.properties,
) )
return new Combine([ return new Combine([
new SvelteUIElement(OrientationDebugPanel, {}), new SvelteUIElement(OrientationDebugPanel, {}),
@ -1649,7 +1655,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
return new SvelteUIElement(MarkAsFavourite, { return new SvelteUIElement(MarkAsFavourite, {
tags: tagSource, tags: tagSource,
@ -1669,7 +1675,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
return new SvelteUIElement(MarkAsFavouriteMini, { return new SvelteUIElement(MarkAsFavouriteMini, {
tags: tagSource, tags: tagSource,
@ -1689,7 +1695,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
return new SvelteUIElement(DirectionIndicator, { state, feature }) return new SvelteUIElement(DirectionIndicator, { state, feature })
}, },
@ -1702,7 +1708,7 @@ export default class SpecialVisualizations {
state: SpecialVisualizationState, state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature feature: Feature,
): SvelteUIElement { ): SvelteUIElement {
return new SvelteUIElement(QrCode, { state, tags, feature }) return new SvelteUIElement(QrCode, { state, tags, feature })
}, },
@ -1721,7 +1727,7 @@ export default class SpecialVisualizations {
constr( constr(
state: SpecialVisualizationState, state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
args: string[] args: string[],
): BaseUIElement { ): BaseUIElement {
const key = args[0] === "" ? "_direction:centerpoint" : args[0] const key = args[0] === "" ? "_direction:centerpoint" : args[0]
return new VariableUiElement( return new VariableUiElement(
@ -1732,11 +1738,11 @@ export default class SpecialVisualizations {
}) })
.mapD((value) => { .mapD((value) => {
const dir = GeoOperations.bearingToHuman( const dir = GeoOperations.bearingToHuman(
GeoOperations.parseBearing(value) GeoOperations.parseBearing(value),
) )
console.log("Human dir", dir) console.log("Human dir", dir)
return Translations.t.general.visualFeedback.directionsAbsolute[dir] return Translations.t.general.visualFeedback.directionsAbsolute[dir]
}) }),
) )
}, },
}, },
@ -1766,7 +1772,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
const url = args[0] const url = args[0]
const readonly = args[3] === "yes" const readonly = args[3] === "yes"
@ -1792,12 +1798,12 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
args: string[], args: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
return new Toggle( return new Toggle(
undefined, undefined,
new SvelteUIElement(LoginButton, { osmConnection: state.osmConnection }), new SvelteUIElement(LoginButton, { osmConnection: state.osmConnection }),
state.osmConnection.isLoggedIn state.osmConnection.isLoggedIn,
) )
}, },
}, },
@ -1835,7 +1841,7 @@ export default class SpecialVisualizations {
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
const key = argument[0] ?? "website" const key = argument[0] ?? "website"
const useProxy = argument[1] !== "no" const useProxy = argument[1] !== "no"
@ -1862,11 +1868,11 @@ export default class SpecialVisualizations {
const features = const features =
await LinkedDataLoader.fetchVeloparkEntry( await LinkedDataLoader.fetchVeloparkEntry(
url, url,
loadAll loadAll,
) )
const feature = const feature =
features.find( features.find(
(f) => f.properties["ref:velopark"] === url (f) => f.properties["ref:velopark"] === url,
) ?? features[0] ) ?? features[0]
const properties = feature.properties const properties = feature.properties
properties["ref:velopark"] = url properties["ref:velopark"] = url
@ -1876,7 +1882,7 @@ export default class SpecialVisualizations {
console.error(e) console.error(e)
throw e throw e
} }
})() })(),
) )
} }
return Stores.FromPromiseWithErr( return Stores.FromPromiseWithErr(
@ -1885,27 +1891,27 @@ export default class SpecialVisualizations {
return await LinkedDataLoader.fetchJsonLd( return await LinkedDataLoader.fetchJsonLd(
url, url,
{ country }, { country },
useProxy ? "proxy" : "fetch-lod" useProxy ? "proxy" : "fetch-lod",
) )
} catch (e) { } catch (e) {
console.log( console.log(
"Could not get with proxy/download LOD, attempting to download directly. Error for ", "Could not get with proxy/download LOD, attempting to download directly. Error for ",
url, url,
"is", "is",
e e,
) )
return await LinkedDataLoader.fetchJsonLd( return await LinkedDataLoader.fetchJsonLd(
url, url,
{ country }, { country },
"fetch-raw" "fetch-raw",
) )
} }
})() })(),
) )
}) })
externalData.addCallbackAndRunD((lod) => externalData.addCallbackAndRunD((lod) =>
console.log("linked_data_from_website received the following data:", lod) console.log("linked_data_from_website received the following data:", lod),
) )
return new Toggle( return new Toggle(
@ -1920,7 +1926,7 @@ export default class SpecialVisualizations {
collapsed: isClosed, collapsed: isClosed,
}), }),
undefined, undefined,
url.map((url) => !!url) url.map((url) => !!url),
) )
}, },
}, },
@ -1940,7 +1946,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
const text = argument[0] const text = argument[0]
const cssClasses = argument[1] const cssClasses = argument[1]
@ -1962,7 +1968,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig 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)
@ -1980,7 +1986,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
return new SvelteUIElement(PendingChangesIndicator, { state, compact: false }) return new SvelteUIElement(PendingChangesIndicator, { state, compact: false })
}, },
@ -2000,7 +2006,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): SvelteUIElement { ): SvelteUIElement {
return new SvelteUIElement<any, any, any>(ClearCaches, { return new SvelteUIElement<any, any, any>(ClearCaches, {
msg: argument[0] ?? "Clear local caches", msg: argument[0] ?? "Clear local caches",
@ -2025,7 +2031,7 @@ export default class SpecialVisualizations {
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
selectedElement: Feature, selectedElement: Feature,
layer: LayerConfig layer: LayerConfig,
): SvelteUIElement { ): SvelteUIElement {
const [header, labelsStr] = argument const [header, labelsStr] = argument
const labels = labelsStr.split(";").map((x) => x.trim()) const labels = labelsStr.split(";").map((x) => x.trim())
@ -2048,7 +2054,7 @@ export default class SpecialVisualizations {
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
selectedElement: Feature, selectedElement: Feature,
layer: LayerConfig layer: LayerConfig,
): SvelteUIElement { ): SvelteUIElement {
const t = Translations.t.preset_type const t = Translations.t.preset_type
const question: QuestionableTagRenderingConfigJson = { const question: QuestionableTagRenderingConfigJson = {
@ -2088,7 +2094,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>, tagSource: UIEventSource<Record<string, string>>,
argument: string[], argument: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
): BaseUIElement { ): BaseUIElement {
const text = argument[0] const text = argument[0]
return new SubtleButton(undefined, text).onClick(() => { return new SubtleButton(undefined, text).onClick(() => {
@ -2110,7 +2116,7 @@ export default class SpecialVisualizations {
"Invalid special visualisation found: funcName is undefined or doesn't match " + "Invalid special visualisation found: funcName is undefined or doesn't match " +
regex + regex +
invalid.map((sp) => sp.i).join(", ") + invalid.map((sp) => sp.i).join(", ") +
'. Did you perhaps type \n funcName: "funcname" // type declaration uses COLON\ninstead of:\n funcName = "funcName" // value definition uses EQUAL' ". Did you perhaps type \n funcName: \"funcname\" // type declaration uses COLON\ninstead of:\n funcName = \"funcName\" // value definition uses EQUAL"
) )
} }