Merge develop

This commit is contained in:
Pieter Vander Vennet 2025-01-22 01:26:12 +01:00
parent c62705c1dd
commit c167094b65
12 changed files with 251 additions and 139 deletions

View file

@ -1465,13 +1465,51 @@
"#force-save-button": "yes" "#force-save-button": "yes"
}, },
{ {
"id": "debug-gps", "id": "debug-gps-group",
"condition": "mapcomplete-show_debug=yes", "condition": "mapcomplete-show_debug=yes",
"render": {
"special": {
"type": "group",
"header": "debug-gps-title",
"labels": "debug-gps"
}
}
},
{
"id": "debug-gps-title",
"labels": ["hidden"],
"render": {
"en": "GPS info"
}
},
{
"id": "debug-gps",
"labels": ["hidden"],
"render": "{gps_all_tags()}" "render": "{gps_all_tags()}"
}, },
{
"id": "debug-info-group",
"condition": "mapcomplete-show_debug=yes",
"render": {
"special": {
"type": "group",
"header": "debug-tags-title",
"labels": "debug"
}
}
},
{
"id": "debug-tags-title",
"labels": ["hidden"],
"render": {
"en": "Debug info"
}
},
{ {
"id": "debug", "id": "debug",
"condition": "mapcomplete-show_debug=yes", "labels": ["hidden"],
"render": "{all_tags()}" "render": "{all_tags()}"
} }
], ],

View file

@ -14,7 +14,7 @@ export interface GeoLocationPointProperties extends GeolocationCoordinates {
} }
/** /**
* An abstract representation of the current state of the geolocation. * An abstract representation of the current state of the geolocation, keeping track of permissions and if a location is known.
*/ */
export class GeoLocationState { export class GeoLocationState {
/** /**
@ -167,8 +167,16 @@ export class GeoLocationState {
if(AndroidPolyfill.inAndroid.data){ if(AndroidPolyfill.inAndroid.data){
this.permission.setData("requested") this.permission.setData("requested")
AndroidPolyfill.geolocationPermission.addCallbackAndRunD(state => this.permission.set(state)) this.permission.addCallbackAndRunD(p => {
this.startWatching() if(p === "granted"){
this.startWatching()
return true
}
})
AndroidPolyfill.requestGeoPermission().then(state => {
const granted = state.value === "true"
this.permission.set(granted ? "granted" : "denied")
})
return return
} }
@ -210,6 +218,13 @@ export class GeoLocationState {
* @private * @private
*/ */
private async startWatching() { private async startWatching() {
if(AndroidPolyfill.inAndroid.data){
AndroidPolyfill.watchLocation( this.currentGPSLocation, location => {
console.log(JSON.stringify(location))
})
}
navigator.geolocation.watchPosition( navigator.geolocation.watchPosition(
(position: GeolocationPosition) => { (position: GeolocationPosition) => {
this._gpsAvailable.set(true) this._gpsAvailable.set(true)

View file

@ -416,6 +416,10 @@ export default class UserRelatedState {
typeof window === "undefined" ? "no" : window.navigator.share ? "yes" : "no", typeof window === "undefined" ? "no" : window.navigator.share ? "yes" : "no",
_iframe: Utils.isIframe ? "yes" : "no", _iframe: Utils.isIframe ? "yes" : "no",
}) })
if(!Utils.runningFromConsole){
amendedPrefs.data["_host"] = window.location.host
amendedPrefs.data["_path"] = window.location.pathname
}
for (const key in Constants.userJourney) { for (const key in Constants.userJourney) {
amendedPrefs.data["__userjourney_" + key] = Constants.userJourney[key] amendedPrefs.data["__userjourney_" + key] = Constants.userJourney[key]

View file

@ -19,9 +19,9 @@ const DatabridgePluginSingleton = registerPlugin<DatabridgePlugin>("Databridge",
return { value: "web" } return { value: "web" }
} }
return null return null
} },
} }
} },
}) })
export class AndroidPolyfill { export class AndroidPolyfill {
@ -36,12 +36,18 @@ export class AndroidPolyfill {
* @private * @private
*/ */
private backfillGeolocation(databridgePlugin: DatabridgePlugin) { private backfillGeolocation(databridgePlugin: DatabridgePlugin) {
const src = UIEventSource.FromPromise(databridgePlugin.request({ key: "location:request-permission" })) const src = UIEventSource.FromPromise(databridgePlugin.request({ key: "location:has-permission" }))
src.addCallbackAndRunD(permission => { src.addCallbackAndRunD(permission => {
AndroidPolyfill._geolocationPermission.set(<"granted" | "denied">permission.value) console.log("> Checking geopermission gave: ", JSON.stringify(permission), permission.value)
const granted = permission.value === "true"
AndroidPolyfill._geolocationPermission.set(granted ? "granted" : "denied")
}) })
} }
public static async requestGeoPermission(): Promise<{ value: string | object }> {
return DatabridgePluginSingleton.request({ key: "location:request-permission" })
}
public async init() { public async init() {
console.log("Sniffing shell version") console.log("Sniffing shell version")
const shell = await this.databridgePlugin.request({ key: "meta" }) const shell = await this.databridgePlugin.request({ key: "meta" })
@ -55,9 +61,9 @@ export class AndroidPolyfill {
} }
public static async requestLoginCodes() { public static async requestLoginCodes() {
const result = await DatabridgePluginSingleton.request<{oauth_token: string}>({ key: "request:login" }) const result = await DatabridgePluginSingleton.request<{ oauth_token: string }>({ key: "request:login" })
const token: string = result.value.oauth_token const token: string = result.value.oauth_token
console.log("AndroidPolyfill: received oauth_token; trying to pass them to the oauth lib",token) console.log("AndroidPolyfill: received oauth_token; trying to pass them to the oauth lib", token)
return token return token
} }
@ -67,7 +73,7 @@ export class AndroidPolyfill {
console.log("Registering back button callback", callback) console.log("Registering back button callback", callback)
DatabridgePluginSingleton.request({ key: "backbutton" }).then(ev => { DatabridgePluginSingleton.request({ key: "backbutton" }).then(ev => {
console.log("AndroidPolyfill: received backbutton: ", ev) console.log("AndroidPolyfill: received backbutton: ", ev)
if(ev === null){ if (ev === null) {
// Probably in web environment // Probably in web environment
return return
} }
@ -84,5 +90,26 @@ export class AndroidPolyfill {
}) })
} }
public static watchLocation(writeInto: UIEventSource<GeolocationCoordinates>, callback: (location) => void) {
DatabridgePluginSingleton.request({
key: "location:watch",
}).then((l: {
value: { latitude: number, longitude: number, accuraccy: number, altidude: number, heading: number, speed:number }
}) => {
// example l: {"value":{"latitude":51.0618627,"longitude":3.730468566666667,"accuracy":2.0393495559692383,"altitude":46.408,"heading":168.2969970703125}}
console.log("Received location from Android:", JSON.stringify(l))
const loc = l.value
writeInto.set({
latitude: loc.latitude,
longitude: loc.longitude,
heading: loc.heading,
accuracy: loc.accuraccy,
altitude: loc.altidude,
altitudeAccuracy: undefined,
speed: loc.speed,
})
})
}
} }

View file

@ -126,6 +126,9 @@ export default class TagRenderingConfig {
this.id this.id
) )
} }
if(json.labels && !Array.isArray( json.labels)){
throw (`Invalid labels at ${context}: labels should be a list of strings, but got a ${typeof json.labels}`)
}
this.labels = json.labels ?? [] this.labels = json.labels ?? []
if (typeof json.classes === "string") { if (typeof json.classes === "string") {

View file

@ -7,6 +7,7 @@
import { ariaLabel } from "../../Utils/ariaLabel" import { ariaLabel } from "../../Utils/ariaLabel"
import { Translation } from "../i18n/Translation" import { Translation } from "../i18n/Translation"
import Backspace from "@babeard/svelte-heroicons/outline/Backspace" import Backspace from "@babeard/svelte-heroicons/outline/Backspace"
import { AndroidPolyfill } from "../../Logic/Web/AndroidPolyfill"
export let value: UIEventSource<string> export let value: UIEventSource<string>
let _value = value.data ?? "" let _value = value.data ?? ""
@ -36,6 +37,7 @@
if (autofocus) { if (autofocus) {
isFocused.set(true) isFocused.set(true)
} }
let isAndroid = AndroidPolyfill.inAndroid
</script> </script>
<form class="w-full" on:submit|preventDefault={() => dispatch("search")}> <form class="w-full" on:submit|preventDefault={() => dispatch("search")}>
@ -62,18 +64,20 @@
use:set_placeholder={placeholder} use:set_placeholder={placeholder}
use:ariaLabel={placeholder} use:ariaLabel={placeholder}
/> />
{#if !isAndroid}
{#if $value.length > 0} <!-- Show a 'clear field' icon in the searchbar. The android-build already provides this for us, hence the outer if -->
<Backspace {#if $value.length > 0}
on:click={(e) => { <Backspace
on:click={(e) => {
value.set("") value.set("")
e.preventDefault() e.preventDefault()
}} }}
color="var(--button-background)" color="var(--button-background)"
class="mr-3 h-6 w-6 cursor-pointer" class="mr-3 h-6 w-6 cursor-pointer"
/> />
{:else} {:else}
<div class="mr-3 w-6" /> <div class="mr-3 w-6" />
{/if}
{/if} {/if}
</label> </label>
</form> </form>

View file

@ -6,6 +6,7 @@
import Location_locked from "../../assets/svg/Location_locked.svelte" import Location_locked from "../../assets/svg/Location_locked.svelte"
import Location_unlocked from "../../assets/svg/Location_unlocked.svelte" import Location_unlocked from "../../assets/svg/Location_unlocked.svelte"
import Location from "../../assets/svg/Location.svelte" import Location from "../../assets/svg/Location.svelte"
import Location_empty from "../../assets/svg/Location_empty.svelte"
export let state: ThemeViewState export let state: ThemeViewState
let geolocationstate = state.geolocation.geolocationState let geolocationstate = state.geolocation.geolocationState
@ -31,10 +32,10 @@
{:else if $geopermission === "denied" || !$isAvailable} {:else if $geopermission === "denied" || !$isAvailable}
<Location_refused class={clss} /> <Location_refused class={clss} />
{:else if $geopermission === "prompt"} {:else if $geopermission === "prompt"}
<Location class={clss} /> <Location_empty class={clss} />
{:else if $geopermission === "requested"} {:else if $geopermission === "requested"}
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup --> <!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
<Location class={clss} style="animation: 3s linear 0s infinite normal none running spin;" /> <Location_empty class={clss} style="animation: 3s linear 0s infinite normal none running spin;" />
{:else} {:else}
<Location class={clss} style="animation: 3s linear 0s infinite normal none running spin;" /> <Location class={clss} style="animation: 3s linear 0s infinite normal none running spin;" />
{/if} {/if}

View file

@ -319,7 +319,7 @@
if (state?.osmConnection) { if (state?.osmConnection) {
onDestroy( onDestroy(
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => { state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
numberOfCs = ud.csCount numberOfCs = ud?.csCount
}) })
) )
} }

View file

@ -122,7 +122,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" || args[1] === "yes" const readonly = args[1] === "readonly" || args[1] === "yes"
@ -189,7 +189,7 @@ class StealViz implements SpecialVisualization {
selectedElement: otherFeature, selectedElement: otherFeature,
state, state,
layer, layer,
}) }),
) )
} }
if (elements.length === 1) { if (elements.length === 1) {
@ -197,8 +197,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],
) ),
) )
} }
@ -250,11 +250,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, {
@ -295,7 +295,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(";")
@ -327,7 +327,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,
) )
} }
} }
@ -347,15 +347,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>",
@ -364,18 +364,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([
@ -408,10 +408,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()
@ -452,7 +452,7 @@ export default class SpecialVisualizations {
(ud) => ud?.languages ?? [] (ud) => ud?.languages ?? []
), ),
}) })
}) }),
) )
}, },
}, },
@ -491,7 +491,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 })
}, },
@ -503,7 +503,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
@ -513,7 +513,7 @@ export default class SpecialVisualizations {
return new SvelteUIElement(SplitRoadWizard, { id, state }) return new SvelteUIElement(SplitRoadWizard, { id, state })
} }
return undefined return undefined
}) }),
) )
}, },
}, },
@ -527,7 +527,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
@ -550,7 +550,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
@ -576,7 +576,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,
) { ) {
if (feature.geometry.type !== "LineString") { if (feature.geometry.type !== "LineString") {
return undefined return undefined
@ -608,7 +608,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, {
@ -671,7 +671,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(
@ -681,9 +681,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(),
@ -697,7 +697,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 }),
}, },
{ {
@ -782,7 +782,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,
@ -821,7 +821,7 @@ export default class SpecialVisualizations {
nameKey: nameKey, nameKey: nameKey,
fallbackName, fallbackName,
}, },
state.featureSwitchIsTesting state.featureSwitchIsTesting,
) )
return new SvelteUIElement(ReviewForm, { return new SvelteUIElement(ReviewForm, {
reviews, reviews,
@ -859,7 +859,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 })
}, },
@ -889,7 +889,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 SpecialVisualizations.specialVisualisationsDict
@ -914,7 +914,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 })
@ -971,7 +971,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]
@ -1008,17 +1008,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)
}) }),
) )
}, },
}, },
@ -1072,7 +1072,7 @@ export default class SpecialVisualizations {
constr: (state) => { constr: (state) => {
return new SubtleButton( return new SubtleButton(
new SvelteUIElement(Trash), new SvelteUIElement(Trash),
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)
@ -1113,10 +1113,10 @@ export default class SpecialVisualizations {
new SvelteUIElement(NoteCommentElement, { new SvelteUIElement(NoteCommentElement, {
comment, comment,
state, state,
}) }),
) ),
).SetClass("flex flex-col") ).SetClass("flex flex-col")
}) }),
), ),
}, },
{ {
@ -1149,7 +1149,7 @@ export default class SpecialVisualizations {
tags: UIEventSource<Record<string, string>>, tags: UIEventSource<Record<string, string>>,
_: string[], _: string[],
feature: Feature, feature: Feature,
layer: LayerConfig layer: LayerConfig,
) => { ) => {
return new SvelteUIElement(FeatureTitle, { state, tags, feature, layer }) return new SvelteUIElement(FeatureTitle, { state, tags, feature, layer })
}, },
@ -1167,8 +1167,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(
@ -1193,7 +1193,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.",
@ -1207,15 +1207,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" +
@ -1291,7 +1291,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) => {
@ -1301,8 +1301,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],
) ),
) )
}, },
}, },
@ -1372,7 +1372,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 === "") {
@ -1410,7 +1410,7 @@ export default class SpecialVisualizations {
}, },
}, },
null, null,
" " " ",
) + ) +
"\n```", "\n```",
args: [ args: [
@ -1434,7 +1434,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 ?? ""
@ -1452,7 +1452,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
} }
@ -1469,7 +1469,7 @@ export default class SpecialVisualizations {
elements.push(subsTr) elements.push(subsTr)
} }
return elements return elements
}) }),
) )
}, },
}, },
@ -1489,7 +1489,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) => {
@ -1501,7 +1501,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)
} }
}) }),
) )
}, },
}, },
@ -1521,7 +1521,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] const key = argument[0]
return new SvelteUIElement(FediverseLink, { key, tags, state }) return new SvelteUIElement(FediverseLink, { key, tags, state })
@ -1543,7 +1543,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] + "}")
}, },
@ -1564,7 +1564,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(
@ -1582,12 +1582,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")
} }
}) }),
) )
}, },
}, },
@ -1608,7 +1608,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 })
@ -1624,15 +1624,24 @@ 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 geostate = (<ThemeViewState>(state)).geolocation.geolocationState
state const tags = geostate.currentGPSLocation.map(
)).geolocation.currentUserLocation.features.map( (geoloc) => {
(features) => features[0]?.properties const tags = { }
for (const k in geoloc ?? {}) {
tags[k] = geoloc[k]
}
tags["_permission"] = geostate.permission.data,
tags["_request_moment"] = geostate.requestMoment.data
return tags
},
[geostate.permission, geostate.requestMoment]
) )
return new Combine([ return new Combine([
new SvelteUIElement(OrientationDebugPanel, {}), new SvelteUIElement(OrientationDebugPanel, {}), // compass and gyroscope info
new SvelteUIElement(AllTagsPanel, { new SvelteUIElement(AllTagsPanel, {
state, state,
tags, tags,
@ -1651,7 +1660,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,
@ -1671,7 +1680,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,
@ -1691,7 +1700,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 })
}, },
@ -1704,7 +1713,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 })
}, },
@ -1723,7 +1732,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(
@ -1734,11 +1743,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]
}) }),
) )
}, },
}, },
@ -1768,7 +1777,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"
@ -1794,12 +1803,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,
) )
}, },
}, },
@ -1837,7 +1846,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"
@ -1845,7 +1854,7 @@ export default class SpecialVisualizations {
const isClosed = (argument[4] ?? "yes") === "yes" const isClosed = (argument[4] ?? "yes") === "yes"
const countryStore: Store<string | undefined> = tags.mapD( const countryStore: Store<string | undefined> = tags.mapD(
(tags) => tags._country (tags) => tags._country,
) )
const sourceUrl: Store<string | undefined> = tags.mapD((tags) => { const sourceUrl: Store<string | undefined> = tags.mapD((tags) => {
if (!tags[key] || tags[key] === "undefined") { if (!tags[key] || tags[key] === "undefined") {
@ -1867,24 +1876,24 @@ 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
console.log( console.log(
"Got properties from velopark:", "Got properties from velopark:",
properties properties,
) )
return properties return properties
} catch (e) { } catch (e) {
console.error(e) console.error(e)
throw e throw e
} }
})() })(),
) )
} }
if (country === undefined) { if (country === undefined) {
@ -1896,29 +1905,29 @@ 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",
) )
} }
})() })(),
) )
}, },
[countryStore] [countryStore],
) )
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(
@ -1933,7 +1942,7 @@ export default class SpecialVisualizations {
collapsed: isClosed, collapsed: isClosed,
}), }),
undefined, undefined,
sourceUrl.map((url) => !!url) sourceUrl.map((url) => !!url),
) )
}, },
}, },
@ -1953,7 +1962,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]
@ -1975,7 +1984,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)
@ -2007,7 +2016,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",
@ -2032,7 +2041,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())
@ -2055,7 +2064,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 = {
@ -2095,7 +2104,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(() => {
@ -2126,7 +2135,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"
) )
} }

View file

@ -5,6 +5,7 @@ import MetaTagging from "./src/Logic/MetaTagging";
import { FixedUiElement } from "./src/UI/Base/FixedUiElement"; import { FixedUiElement } from "./src/UI/Base/FixedUiElement";
import { Utils } from "./src/Utils" import { Utils } from "./src/Utils"
import Constants from "./src/Models/Constants" import Constants from "./src/Models/Constants"
import { AndroidPolyfill } from "./src/Logic/Web/AndroidPolyfill"
function webgl_support() { function webgl_support() {
try { try {

View file

@ -19,5 +19,5 @@
"esModuleInterop": true "esModuleInterop": true
} }
}, },
"exclude": ["node_modules", "test", "scripts"] "exclude": ["node_modules", "test", "scripts","android","dist","dist-full"]
} }

View file

@ -21,11 +21,21 @@ export default defineConfig({
build: { build: {
rollupOptions: { rollupOptions: {
input, input,
external:[
"android"
]
}, },
}, },
base: `${ASSET_URL}`, base: `${ASSET_URL}`,
plugins , plugins ,
server: { server: {
port: 1234, port: 1234,
watch:{
ignored: [
"**/android/**",
'**/.git/**',
'**/dist/**'
]
}
}, },
}) })