Themes: various fixes to velopark, see #1783

This commit is contained in:
Pieter Vander Vennet 2024-02-14 12:20:29 +01:00
parent 3b549a5a0b
commit 2583feef65
7 changed files with 261 additions and 202 deletions

View file

@ -156,7 +156,7 @@ export class GeoOperations {
const intersection = GeoOperations.calculateIntersection(
feature,
otherFeature,
featureBBox
featureBBox,
)
if (intersection === null) {
continue
@ -195,7 +195,7 @@ export class GeoOperations {
console.error(
"Could not correctly calculate the overlap of ",
feature,
": unsupported type"
": unsupported type",
)
return result
}
@ -224,7 +224,7 @@ export class GeoOperations {
*/
public static inside(
pointCoordinate: [number, number] | Feature<Point>,
feature: Feature
feature: Feature,
): boolean {
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
@ -302,7 +302,7 @@ export class GeoOperations {
*/
public static nearestPoint(
way: Feature<LineString>,
point: [number, number]
point: [number, number],
): Feature<
Point,
{
@ -324,11 +324,11 @@ export class GeoOperations {
public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString>
public static forceLineString(
way: Feature<MultiLineString | MultiPolygon>
way: Feature<MultiLineString | MultiPolygon>,
): Feature<MultiLineString>
public static forceLineString(
way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
): Feature<LineString | MultiLineString> {
if (way.geometry.type === "Polygon") {
way = { ...way }
@ -352,8 +352,8 @@ export class GeoOperations {
const headerValuesOrdered: string[] = []
function addH(key: string) {
if(options?.ignoreTags){
if(key.match(options.ignoreTags)){
if (options?.ignoreTags) {
if (key.match(options.ignoreTags)) {
return
}
}
@ -455,7 +455,7 @@ export class GeoOperations {
*/
public static LineIntersections(
feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>,
): [number, number][] {
return turf
.lineIntersect(feature, otherFeature)
@ -492,7 +492,7 @@ export class GeoOperations {
locations:
| Feature<LineString>
| Feature<Point, { date?: string; altitude?: number | string }>[],
title?: string
title?: string,
) {
title = title?.trim()
if (title === undefined || title === "") {
@ -513,7 +513,7 @@ export class GeoOperations {
type: "Point",
coordinates: p,
},
}
},
)
}
for (const l of locationsWithMeta) {
@ -528,7 +528,7 @@ export class GeoOperations {
trackPoints.push(trkpt)
}
const header =
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
"<gpx version=\"1.1\" creator=\"mapcomplete.org\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"
return (
header +
"\n<name>" +
@ -546,7 +546,7 @@ export class GeoOperations {
*/
public static toGpxPoints(
locations: Feature<Point, { date?: string; altitude?: number | string }>[],
title?: string
title?: string,
) {
title = title?.trim()
if (title === undefined || title === "") {
@ -567,7 +567,7 @@ export class GeoOperations {
trackPoints.push(trkpt)
}
const header =
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
"<gpx version=\"1.1\" creator=\"mapcomplete.org\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"
return (
header +
"\n<name>" +
@ -655,7 +655,7 @@ export class GeoOperations {
},
},
distanceMeter,
{ units: "meters" }
{ units: "meters" },
).geometry.coordinates
}
@ -690,7 +690,7 @@ export class GeoOperations {
*/
static completelyWithin(
feature: Feature<Geometry, any>,
possiblyEnclosingFeature: Feature<Polygon | MultiPolygon, any>
possiblyEnclosingFeature: Feature<Polygon | MultiPolygon, any>,
): boolean {
return booleanWithin(feature, possiblyEnclosingFeature)
}
@ -746,7 +746,7 @@ export class GeoOperations {
*/
public static featureToCoordinateWithRenderingType(
feature: Feature,
location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string
location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | "polygon_centerpoint" | string,
): [number, number] | undefined {
switch (location) {
case "point":
@ -759,6 +759,11 @@ export class GeoOperations {
return undefined
}
return GeoOperations.centerpointCoordinates(feature)
case "polygon_centerpoint":
if (feature.geometry.type === "Polygon") {
return GeoOperations.centerpointCoordinates(feature)
}
return undefined
case "projected_centerpoint":
if (
feature.geometry.type === "LineString" ||
@ -767,7 +772,7 @@ export class GeoOperations {
const centerpoint = GeoOperations.centerpointCoordinates(feature)
const projected = GeoOperations.nearestPoint(
<Feature<LineString>>feature,
centerpoint
centerpoint,
)
return <[number, number]>projected.geometry.coordinates
}
@ -944,7 +949,7 @@ export class GeoOperations {
* GeoOperations.bearingToHuman(46) // => "NE"
*/
public static bearingToHuman(
bearing: number
bearing: number,
): "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW" {
while (bearing < 0) {
bearing += 360
@ -970,7 +975,7 @@ export class GeoOperations {
*
*/
public static bearingToHumanRelative(
bearing: number
bearing: number,
):
| "straight"
| "slight_right"
@ -995,12 +1000,12 @@ export class GeoOperations {
private static pointInPolygonCoordinates(
x: number,
y: number,
coordinates: [number, number][][]
coordinates: [number, number][][],
): boolean {
const inside = GeoOperations.pointWithinRing(
x,
y,
/*This is the outer ring of the polygon */ coordinates[0]
/*This is the outer ring of the polygon */ coordinates[0],
)
if (!inside) {
return false
@ -1009,7 +1014,7 @@ export class GeoOperations {
const inHole = GeoOperations.pointWithinRing(
x,
y,
coordinates[i] /* These are inner rings, aka holes*/
coordinates[i], /* These are inner rings, aka holes*/
)
if (inHole) {
return false
@ -1047,7 +1052,7 @@ export class GeoOperations {
feature,
otherFeature,
featureBBox: BBox,
otherFeatureBBox?: BBox
otherFeatureBBox?: BBox,
): number {
if (feature.geometry.type === "LineString") {
otherFeatureBBox = otherFeatureBBox ?? BBox.get(otherFeature)
@ -1096,7 +1101,7 @@ export class GeoOperations {
let intersection = turf.lineSlice(
turf.point(intersectionPointsArray[0]),
turf.point(intersectionPointsArray[1]),
feature
feature,
)
if (intersection == null) {
@ -1117,7 +1122,7 @@ export class GeoOperations {
otherFeature,
feature,
otherFeatureBBox,
featureBBox
featureBBox,
)
}
@ -1137,7 +1142,7 @@ export class GeoOperations {
console.log("Applying fallback intersection...")
const intersection = turf.intersect(
turf.truncate(feature),
turf.truncate(otherFeature)
turf.truncate(otherFeature),
)
if (intersection == null) {
return null

View file

@ -28,9 +28,9 @@ export default interface PointRenderingConfigJson {
/**
* question: At what location should this icon be shown?
* multianswer: true
* suggestions: return [{if: "value=point",then: "Show an icon for point (node) objects"},{if: "value=centroid",then: "Show an icon for line or polygon (way) objects at their centroid location"}, {if: "value=start",then: "Show an icon for line (way) objects at the start"},{if: "value=end",then: "Show an icon for line (way) object at the end"},{if: "value=projected_centerpoint",then: "Show an icon for line (way) object near the centroid location, but moved onto the line"}]
* suggestions: return [{if: "value=point",then: "Show an icon for point (node) objects"},{if: "value=centroid",then: "Show an icon for line or polygon (way) objects at their centroid location"}, {if: "value=start",then: "Show an icon for line (way) objects at the start"},{if: "value=end",then: "Show an icon for line (way) object at the end"},{if: "value=projected_centerpoint",then: "Show an icon for line (way) object near the centroid location, but moved onto the line. Does not show an item on polygons"}, ,{if: "value=polygon_centroid",then: "Show an icon at a polygon centroid (but not if it is a way)"}]
*/
location: ("point" | "centroid" | "start" | "end" | "projected_centerpoint" | string)[]
location: ("point" | "centroid" | "start" | "end" | "projected_centerpoint" | "polygon_centroid" | string)[]
/**
* The marker for an element.

View file

@ -38,9 +38,10 @@ export default class PointRenderingConfig extends WithContextLoader {
"start",
"end",
"projected_centerpoint",
"polygon_centroid"
])
public readonly location: Set<
"point" | "centroid" | "start" | "end" | "projected_centerpoint" | string
"point" | "centroid" | "start" | "end" | "projected_centerpoint" | "polygon_centroid" | string
>
public readonly marker: IconConfig[]

View file

@ -1,63 +1,82 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Loading from "../../assets/svg/Loading.svelte"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import Icon from "../Map/Icon.svelte"
import Maproulette from "../../Logic/Maproulette"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Loading from "../../assets/svg/Loading.svelte"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import Icon from "../Map/Icon.svelte"
import Maproulette from "../../Logic/Maproulette"
import LoginToggle from "../Base/LoginToggle.svelte"
/**
* A UI-element to change the status of a maproulette-task
*/
export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>
export let message: string
export let image: string
export let message_closed: string
export let statusToSet: string
export let maproulette_id_key: string
/**
* A UI-element to change the status of a maproulette-task
*/
export let state: SpecialVisualizationState
export let tags: UIEventSource<Record<string, string>>
export let message: string
export let image: string
export let message_closed: string
export let statusToSet: string
export let maproulette_id_key: string
let applying = false
let failed = false
export let askFeedback: string = ""
/** Current status of the task*/
let status: Store<number> = tags
.map((tgs) => {
if (tgs["status"]) {
return tgs["status"]
}
return Maproulette.codeToIndex(tgs["mr_taskStatus"])
})
.map(Number)
let applying = false
let failed = false
let feedback: string = ""
async function apply() {
const maproulette_id = tags.data[maproulette_id_key] ?? tags.data.mr_taskId ?? tags.data.id
try {
await Maproulette.singleton.closeTask(Number(maproulette_id), Number(statusToSet), {
tags: `MapComplete MapComplete:${state.layout.id}`,
})
tags.data["mr_taskStatus"] = Maproulette.STATUS_MEANING[Number(statusToSet)]
tags.data.status = statusToSet
tags.ping()
} catch (e) {
console.error(e)
failed = true
/** Current status of the task*/
let status: Store<number> = tags
.map((tgs) => {
if (tgs["status"]) {
return tgs["status"]
}
return Maproulette.codeToIndex(tgs["mr_taskStatus"])
})
.map(Number)
async function apply() {
const maproulette_id = tags.data[maproulette_id_key] ?? tags.data.mr_taskId ?? tags.data.id
try {
await Maproulette.singleton.closeTask(Number(maproulette_id), Number(statusToSet), {
tags: `MapComplete MapComplete:${state.layout.id}`,
comment: feedback
})
tags.data["mr_taskStatus"] = Maproulette.STATUS_MEANING[Number(statusToSet)]
tags.data.status = statusToSet
tags.ping()
} catch (e) {
console.error(e)
failed = true
}
}
}
</script>
{#if failed}
<div class="alert">ERROR - could not close the MapRoulette task</div>
{:else if applying}
<Loading>
<Tr t={Translations.t.general.loading} />
</Loading>
{:else if $status === Maproulette.STATUS_OPEN}
<button class="no-image-background w-full p-4 m-0" on:click={() => apply()}>
<Icon clss="w-8 h-8 mr-2 shrink-0" icon={image} />
{message}
</button>
{:else}
{message_closed}
{/if}
<LoginToggle ignoreLoading={true} {state}>
{#if failed}
<div class="alert">ERROR - could not close the MapRoulette task</div>
{:else if applying}
<Loading>
<Tr t={Translations.t.general.loading} />
</Loading>
{:else if $status === Maproulette.STATUS_OPEN}
{#if askFeedback !== "" && askFeedback !== undefined}
<div class="flex flex-col p-1 gap-y-1 interactive border border-gray-500 border-dashed">
<h3>{askFeedback}</h3>
<textarea bind:value={feedback}></textarea>
<button class="no-image-background w-full p-4 m-0" class:disabled={feedback===""} on:click={() => apply()}>
<Icon clss="w-8 h-8 mr-2 shrink-0" icon={image} />
{message}
</button>
{feedback}
</div>
{:else}
<button class="no-image-background w-full p-4 m-0" on:click={() => apply()}>
<Icon clss="w-8 h-8 mr-2 shrink-0" icon={image} />
{message}
</button>
{/if}
{:else}
{message_closed}
{/if}
</LoginToggle>

View file

@ -3,11 +3,7 @@ import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement"
import Title from "./Base/Title"
import Table from "./Base/Table"
import {
RenderingSpecification,
SpecialVisualization,
SpecialVisualizationState,
} from "./SpecialVisualization"
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
import { HistogramViz } from "./Popup/HistogramViz"
import { MinimapViz } from "./Popup/MinimapViz"
import { ShareLinkViz } from "./Popup/ShareLinkViz"
@ -90,6 +86,8 @@ import Qr from "../Utils/Qr"
import ComparisonTool from "./Comparison/ComparisonTool.svelte"
import SpecialTranslation from "./Popup/TagRendering/SpecialTranslation.svelte"
import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
import LoginButton from "./Base/LoginButton.svelte"
import Toggle from "./Input/Toggle"
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -116,7 +114,7 @@ class NearbyImageVis implements SpecialVisualization {
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
const isOpen = args[0] === "open"
const readonly = args[1] === "readonly"
@ -183,7 +181,7 @@ class StealViz implements SpecialVisualization {
selectedElement: otherFeature,
state,
layer,
})
}),
)
}
if (elements.length === 1) {
@ -191,8 +189,8 @@ class StealViz implements SpecialVisualization {
}
return new Combine(elements).SetClass("flex flex-col")
},
[state.indexedFeatures.featuresById]
)
[state.indexedFeatures.featuresById],
),
)
}
@ -231,7 +229,7 @@ export class QuestionViz implements SpecialVisualization {
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
const labels = args[0]
?.split(";")
@ -267,37 +265,38 @@ export default class SpecialVisualizations {
viz.docs,
viz.args.length > 0
? new Table(
["name", "default", "description"],
viz.args.map((arg) => {
let defaultArg = arg.defaultValue ?? "_undefined_"
if (defaultArg == "") {
defaultArg = "_empty string_"
}
return [arg.name, defaultArg, arg.doc]
})
)
["name", "default", "description"],
viz.args.map((arg) => {
let defaultArg = arg.defaultValue ?? "_undefined_"
if (defaultArg == "") {
defaultArg = "_empty string_"
}
return [arg.name, defaultArg, arg.doc]
}),
)
: undefined,
new Title("Example usage of " + viz.funcName, 4),
new FixedUiElement(
viz.example ??
"`{" +
viz.funcName +
"(" +
viz.args.map((arg) => arg.defaultValue).join(",") +
")}`"
"`{" +
viz.funcName +
"(" +
viz.args.map((arg) => arg.defaultValue).join(",") +
")}`",
).SetClass("literal-code"),
])
}
public static constructSpecification(
template: string,
extraMappings: SpecialVisualization[] = []
extraMappings: SpecialVisualization[] = [],
): RenderingSpecification[] {
return SpecialVisualisationUtils.constructSpecification(template, extraMappings)
}
public static HelpMessage() {
const helpTexts = SpecialVisualizations.specialVisualizations.map((viz) =>
SpecialVisualizations.DocumentationFor(viz)
SpecialVisualizations.DocumentationFor(viz),
)
return new Combine([
@ -331,10 +330,10 @@ export default class SpecialVisualizations {
},
},
null,
" "
)
" ",
),
).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"),
...helpTexts,
]).SetClass("flex flex-col")
@ -343,20 +342,20 @@ export default class SpecialVisualizations {
// noinspection JSUnusedGlobalSymbols
public static renderExampleOfSpecial(
state: SpecialVisualizationState,
s: SpecialVisualization
s: SpecialVisualization,
): BaseUIElement {
const examples =
s.structuredExamples === undefined
? []
: s.structuredExamples().map((e) => {
return s.constr(
state,
new UIEventSource<Record<string, string>>(e.feature.properties),
e.args,
e.feature,
undefined
)
})
return s.constr(
state,
new UIEventSource<Record<string, string>>(e.feature.properties),
e.args,
e.feature,
undefined,
)
})
return new Combine([new Title(s.funcName), s.docs, ...examples])
}
@ -396,7 +395,7 @@ export default class SpecialVisualizations {
assignTo: state.userRelatedState.language,
availableLanguages: state.layout.language,
preferredLanguages: state.osmConnection.userDetails.map(
(ud) => ud.languages
(ud) => ud.languages,
),
})
},
@ -421,7 +420,7 @@ export default class SpecialVisualizations {
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>
tagSource: UIEventSource<Record<string, string>>,
): BaseUIElement {
return new VariableUiElement(
tagSource
@ -431,7 +430,7 @@ export default class SpecialVisualizations {
return new SplitRoadWizard(<WayId>id, state)
}
return undefined
})
}),
)
},
},
@ -445,7 +444,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
if (feature.geometry.type !== "Point") {
return undefined
@ -468,7 +467,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
if (!layer.deletion) {
return undefined
@ -496,7 +495,7 @@ export default class SpecialVisualizations {
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature
feature: Feature,
): BaseUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(CreateNewNote, {
@ -560,7 +559,7 @@ export default class SpecialVisualizations {
.map((tags) => tags[args[0]])
.map((wikidata) => {
wikidata = Utils.NoEmpty(
wikidata?.split(";")?.map((wd) => wd.trim()) ?? []
wikidata?.split(";")?.map((wd) => wd.trim()) ?? [],
)[0]
const entry = Wikidata.LoadWikidataEntry(wikidata)
return new VariableUiElement(
@ -570,9 +569,9 @@ export default class SpecialVisualizations {
}
const response = <WikidataResponse>e["success"]
return Translation.fromMap(response.labels)
})
}),
)
})
}),
),
},
new MapillaryLinkVis(),
@ -586,7 +585,7 @@ export default class SpecialVisualizations {
tags: UIEventSource<Record<string, string>>,
_,
__,
layer: LayerConfig
layer: LayerConfig,
) => new SvelteUIElement(AllTagsPanel, { tags, layer }),
},
{
@ -608,7 +607,7 @@ export default class SpecialVisualizations {
return new ImageCarousel(
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
tags,
state
state,
)
},
},
@ -664,7 +663,7 @@ export default class SpecialVisualizations {
{
nameKey: nameKey,
fallbackName,
}
},
)
return new SvelteUIElement(StarsBarIcon, {
score: reviews.average,
@ -697,7 +696,7 @@ export default class SpecialVisualizations {
{
nameKey: nameKey,
fallbackName,
}
},
)
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
},
@ -729,7 +728,7 @@ export default class SpecialVisualizations {
{
nameKey: nameKey,
fallbackName,
}
},
)
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
},
@ -787,7 +786,7 @@ export default class SpecialVisualizations {
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): SvelteUIElement {
const keyToUse = args[0]
const prefix = args[1]
@ -824,17 +823,17 @@ export default class SpecialVisualizations {
return undefined
}
const allUnits: Unit[] = [].concat(
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? [])
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? []),
)
const unit = allUnits.filter((unit) =>
unit.isApplicableToKey(key)
unit.isApplicableToKey(key),
)[0]
if (unit === undefined) {
return value
}
const getCountry = () => tagSource.data._country
return unit.asHumanLongValue(value, getCountry)
})
}),
)
},
},
@ -851,7 +850,7 @@ export default class SpecialVisualizations {
new Combine([
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
t.downloadGeoJsonHelper.SetClass("subtle"),
]).SetClass("flex flex-col")
]).SetClass("flex flex-col"),
)
.onClick(() => {
console.log("Exporting as Geojson")
@ -864,7 +863,7 @@ export default class SpecialVisualizations {
title + "_mapcomplete_export.geojson",
{
mimetype: "application/vnd.geo+json",
}
},
)
})
.SetClass("w-full")
@ -900,7 +899,7 @@ export default class SpecialVisualizations {
constr: (state) => {
return new SubtleButton(
Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
Translations.t.general.removeLocationHistory
Translations.t.general.removeLocationHistory,
).onClick(() => {
state.historicalUserLocations.features.setData([])
state.selectedElement.setData(undefined)
@ -938,10 +937,10 @@ export default class SpecialVisualizations {
.filter((c) => c.text !== "")
.map(
(c, i) =>
new NoteCommentElement(c, state, i, comments.length)
)
new NoteCommentElement(c, state, i, comments.length),
),
).SetClass("flex flex-col")
})
}),
),
},
{
@ -975,7 +974,7 @@ export default class SpecialVisualizations {
tagsSource: UIEventSource<Record<string, string>>,
_: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
) =>
new VariableUiElement(
tagsSource.map((tags) => {
@ -993,7 +992,7 @@ export default class SpecialVisualizations {
feature,
layer,
}).SetClass("px-1")
})
}),
),
},
{
@ -1009,8 +1008,8 @@ export default class SpecialVisualizations {
let challenge = Stores.FromPromise(
Utils.downloadJsonCached(
`${Maproulette.defaultEndpoint}/challenge/${parentId}`,
24 * 60 * 60 * 1000
)
24 * 60 * 60 * 1000,
),
)
return new VariableUiElement(
@ -1035,7 +1034,7 @@ export default class SpecialVisualizations {
} else {
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.",
@ -1049,15 +1048,15 @@ export default class SpecialVisualizations {
"\n" +
"```json\n" +
"{\n" +
' "id": "mark_duplicate",\n' +
' "render": {\n' +
' "special": {\n' +
' "type": "maproulette_set_status",\n' +
' "message": {\n' +
' "en": "Mark as not found or false positive"\n' +
" \"id\": \"mark_duplicate\",\n" +
" \"render\": {\n" +
" \"special\": {\n" +
" \"type\": \"maproulette_set_status\",\n" +
" \"message\": {\n" +
" \"en\": \"Mark as not found or false positive\"\n" +
" },\n" +
' "status": "2",\n' +
' "image": "close"\n' +
" \"status\": \"2\",\n" +
" \"image\": \"close\"\n" +
" }\n" +
" }\n" +
"}\n" +
@ -1086,10 +1085,15 @@ export default class SpecialVisualizations {
doc: "The property name containing the maproulette id",
defaultValue: "mr_taskId",
},
{
name: "ask_feedback",
doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added",
defaultValue: ""
}
],
constr: (state, tagsSource, args) => {
let [message, image, message_closed, statusToSet, maproulette_id_key] = args
let [message, image, message_closed, statusToSet, maproulette_id_key, askFeedback] = args
if (image === "") {
image = "confirm"
}
@ -1105,6 +1109,7 @@ export default class SpecialVisualizations {
message_closed,
statusToSet,
maproulette_id_key,
askFeedback
})
},
},
@ -1124,8 +1129,8 @@ export default class SpecialVisualizations {
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
return new StatisticsPanel(fsBboxed)
},
[state.mapProperties.bounds]
)
[state.mapProperties.bounds],
),
)
},
},
@ -1191,7 +1196,7 @@ export default class SpecialVisualizations {
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[]
args: string[],
): BaseUIElement {
let [text, href, classnames, download, ariaLabel] = args
if (download === "") {
@ -1203,13 +1208,13 @@ export default class SpecialVisualizations {
(tags) =>
new SvelteUIElement(Link, {
text: Utils.SubstituteKeys(text, tags),
href: Utils.SubstituteKeys(href, tags).replaceAll(/ /g, '%20') /* Chromium based browsers eat the spaces */,
href: Utils.SubstituteKeys(href, tags).replaceAll(/ /g, "%20") /* Chromium based browsers eat the spaces */,
classnames,
download: Utils.SubstituteKeys(download, tags),
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
newTab,
})
)
}),
),
)
},
},
@ -1231,7 +1236,7 @@ export default class SpecialVisualizations {
},
},
null,
" "
" ",
) +
"\n```",
args: [
@ -1255,7 +1260,7 @@ export default class SpecialVisualizations {
featureTags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
) {
const [key, tr, classesRaw] = args
let classes = classesRaw ?? ""
@ -1280,7 +1285,7 @@ export default class SpecialVisualizations {
elements.push(subsTr)
}
return elements
})
}),
)
},
},
@ -1300,7 +1305,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
return new VariableUiElement(
tagSource.map((tags) => {
@ -1312,7 +1317,7 @@ export default class SpecialVisualizations {
console.error("Cannot create a translation for", v, "due to", e)
return JSON.stringify(v)
}
})
}),
)
},
},
@ -1332,7 +1337,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
const key = argument[0]
const validator = new FediverseValidator()
@ -1342,7 +1347,7 @@ export default class SpecialVisualizations {
.map((fediAccount) => {
fediAccount = validator.reformat(fediAccount)
const [_, username, host] = fediAccount.match(
FediverseValidator.usernameAtServer
FediverseValidator.usernameAtServer,
)
const normalLink = new SvelteUIElement(Link, {
@ -1354,10 +1359,10 @@ export default class SpecialVisualizations {
const loggedInContributorMastodon =
state.userRelatedState?.preferencesAsTags?.data?.[
"_mastodon_link"
]
]
console.log(
"LoggedinContributorMastodon",
loggedInContributorMastodon
loggedInContributorMastodon,
)
if (!loggedInContributorMastodon) {
return normalLink
@ -1373,7 +1378,7 @@ export default class SpecialVisualizations {
newTab: true,
}).SetClass("button"),
])
})
}),
)
},
},
@ -1393,7 +1398,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
return new FixedUiElement("{" + args[0] + "}")
},
@ -1414,7 +1419,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
const key = argument[0] ?? "value"
return new VariableUiElement(
@ -1432,12 +1437,12 @@ export default class SpecialVisualizations {
} catch (e) {
return new FixedUiElement(
"Could not parse this tag: " +
JSON.stringify(value) +
" due to " +
e
JSON.stringify(value) +
" due to " +
e,
).SetClass("alert")
}
})
}),
)
},
},
@ -1458,7 +1463,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
const giggityUrl = argument[0]
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
@ -1474,12 +1479,12 @@ export default class SpecialVisualizations {
_: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
const tags = (<ThemeViewState>(
state
)).geolocation.currentUserLocation.features.map(
(features) => features[0]?.properties
(features) => features[0]?.properties,
)
return new Combine([
new SvelteUIElement(OrientationDebugPanel, {}),
@ -1501,7 +1506,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
return new SvelteUIElement(MarkAsFavourite, {
tags: tagSource,
@ -1521,7 +1526,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
return new SvelteUIElement(MarkAsFavouriteMini, {
tags: tagSource,
@ -1541,7 +1546,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
return new SvelteUIElement(DirectionIndicator, { state, feature })
},
@ -1556,7 +1561,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
return new VariableUiElement(
tagSource
@ -1578,9 +1583,9 @@ export default class SpecialVisualizations {
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${layout}lat=${lat}&lon=${lon}&z=15` +
`#${id}`
return new Img(new Qr(url).toImageElement(75)).SetStyle(
"width: 75px"
"width: 75px",
)
})
}),
)
},
},
@ -1600,7 +1605,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
return new VariableUiElement(
@ -1611,11 +1616,11 @@ export default class SpecialVisualizations {
})
.mapD((value) => {
const dir = GeoOperations.bearingToHuman(
GeoOperations.parseBearing(value)
GeoOperations.parseBearing(value),
)
console.log("Human dir", dir)
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
})
}),
)
},
},
@ -1650,7 +1655,7 @@ export default class SpecialVisualizations {
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
layer: LayerConfig,
): BaseUIElement {
const url = args[0]
const postprocessVelopark = args[2] === "velopark"
@ -1666,6 +1671,17 @@ export default class SpecialVisualizations {
})
},
},
{
funcName: "login_button",
args: [
],
docs: "Show a login button",
needsUrls: [],
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, args: string[], feature: Feature, layer: LayerConfig): BaseUIElement {
return new Toggle(undefined,
new SvelteUIElement(LoginButton), state.osmConnection.isLoggedIn)
},
},
]
specialVisualizations.push(new AutoApplyButton(specialVisualizations))
@ -1677,7 +1693,7 @@ export default class SpecialVisualizations {
throw (
"Invalid special visualisation found: funcName is undefined for " +
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"
)
}