Merge branch 'master' into develop

This commit is contained in:
Pieter Vander Vennet 2024-04-16 14:15:51 +02:00
commit d149a0d31d
316 changed files with 5387 additions and 2106 deletions

View file

@ -113,7 +113,7 @@ export default class GeoLocationHandler {
* - The GPS-location iss NULL-island
* @constructor
*/
public MoveMapToCurrentLocation(zoomToAtLeast: number = 14 ) {
public MoveMapToCurrentLocation(zoomToAtLeast: number = 14) {
const newLocation = this.geolocationState.currentGPSLocation.data
const mapLocation = this.mapProperties.location
// We got a new location.

View file

@ -6,7 +6,11 @@ import { Feature } from "geojson"
import { ImageUploadManager } from "../ImageProviders/ImageUploadManager"
export default class PendingChangesUploader {
constructor(changes: Changes, selectedFeature: UIEventSource<Feature>, uploader : ImageUploadManager) {
constructor(
changes: Changes,
selectedFeature: UIEventSource<Feature>,
uploader: ImageUploadManager
) {
changes.pendingChanges
.stabilized(Constants.updateTimeoutSec * 1000)
.addCallback(() => changes.flushChanges("Flushing changes due to timeout"))

View file

@ -96,7 +96,7 @@ export default class FeaturePropertiesStore {
if (newId === undefined) {
// We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap!
const element = this._elements.get(oldId)
if(!element || element.data === undefined){
if (!element || element.data === undefined) {
return
}
element.data._deleted = "yes"

View file

@ -96,7 +96,7 @@ export default class GeoJsonSource implements FeatureSource {
const url = this.url
try {
const cacheAge = (options?.maxCacheAgeSec ?? 300) * 1000
let json = <{features: Feature[]}> await Utils.downloadJsonCached(url, cacheAge)
let json = <{ features: Feature[] }>await Utils.downloadJsonCached(url, cacheAge)
if (json.features === undefined || json.features === null) {
json.features = []

View file

@ -32,8 +32,10 @@ export interface SnappingOptions {
reusePointWithin?: number
}
export default class SnappingFeatureSource implements FeatureSource<Feature<Point, { "snapped-to": string; dist: number }>> {
public readonly features: Store<[Feature<Point, { "snapped-to": string; dist: number }>]>
export default class SnappingFeatureSource
implements FeatureSource<Feature<Point, { "snapped-to": string; dist: number }>>
{
public readonly features: Store<[Feature<Point, { "snapped-to": string; dist: number }>]>
/*Contains the id of the way it snapped to*/
public readonly snappedTo: Store<string>
private readonly _snappedTo: UIEventSource<string>

View file

@ -817,7 +817,7 @@ export class GeoOperations {
}
return undefined
default:
throw "Unkown location type: " + location+" for feature "+feature.properties.id
throw "Unkown location type: " + location + " for feature " + feature.properties.id
}
}

View file

@ -42,9 +42,12 @@ export class ImageUploadManager {
const failed = this.getCounterFor(this._uploadFailed, "*")
const done = this.getCounterFor(this._uploadFinished, "*")
this.isUploading = this.getCounterFor(this._uploadStarted, "*").map(startedCount => {
return startedCount > failed.data + done.data
}, [failed, done])
this.isUploading = this.getCounterFor(this._uploadStarted, "*").map(
(startedCount) => {
return startedCount > failed.data + done.data
},
[failed, done]
)
}
/**

View file

@ -92,9 +92,9 @@ export class Imgur extends ImageProvider implements ImageUploader {
*
*
*/
public async DownloadAttribution(providedImage: {url: string}): Promise<LicenseInfo> {
public async DownloadAttribution(providedImage: { url: string }): Promise<LicenseInfo> {
const url = providedImage.url
const hash = url.substr("https://i.imgur.com/".length).split(/\.jpe?g/i)[0]
const hash = url.substr("https://i.imgur.com/".length).split(/\.jpe?g/i)[0]
const apiUrl = "https://api.imgur.com/3/image/" + hash
const response = await Utils.downloadJsonCached(apiUrl, 365 * 24 * 60 * 60, {

View file

@ -167,7 +167,7 @@ export class Mapillary extends ImageProvider {
const url_hd = <string>response["thumb_original_url"]
const date = new Date()
date.setTime(response["captured_at"])
return <ProvidedImage> {
return <ProvidedImage>{
id: "" + mapillaryId,
url,
url_hd,

View file

@ -13,7 +13,7 @@ export class WikimediaImageProvider extends ImageProvider {
public static readonly singleton = new WikimediaImageProvider()
public static readonly apiUrls = [
"https://commons.wikimedia.org/wiki/",
"https://upload.wikimedia.org"
"https://upload.wikimedia.org",
]
public static readonly commonsPrefixes = [...WikimediaImageProvider.apiUrls, "File:"]
private readonly commons_key = "wikimedia_commons"
@ -42,7 +42,6 @@ export class WikimediaImageProvider extends ImageProvider {
return baseUrl
}
return baseUrl + `?width=500&height=400`
}
private static startsWithCommonsPrefix(value: string): boolean {
@ -174,7 +173,7 @@ export class WikimediaImageProvider extends ImageProvider {
url_hd: WikimediaImageProvider.PrepareUrl(image, true),
key: undefined,
provider: this,
id: image
id: image,
}
}
}

View file

@ -60,7 +60,7 @@ export class OsmConnection {
oauth_token?: UIEventSource<string>
// Used to keep multiple changesets open and to write to the correct changeset
singlePage?: boolean
attemptLogin?: true | boolean,
attemptLogin?: true | boolean
/**
* If true: automatically check if we're still online every 5 minutes + fetch messages
*/
@ -119,7 +119,7 @@ export class OsmConnection {
this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false)
this.updateAuthObject()
if(!this.fakeUser){
if (!this.fakeUser) {
self.CheckForMessagesContinuously()
}
@ -202,9 +202,9 @@ export class OsmConnection {
this.auth.xhr(
{
method: "GET",
path: "/api/0.6/user/details"
path: "/api/0.6/user/details",
},
function(err, details: XMLDocument) {
function (err, details: XMLDocument) {
if (err != null) {
console.log("Could not login due to:", err)
self.loadingStatus.setData("error")
@ -313,12 +313,12 @@ export class OsmConnection {
<any>{
method,
options: {
header
header,
},
content,
path: `/api/0.6/${path}`
path: `/api/0.6/${path}`,
},
function(err, response) {
function (err, response) {
if (err !== null) {
error(err)
} else {
@ -398,7 +398,7 @@ export class OsmConnection {
"notes.json",
content,
{
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
true
)
@ -439,7 +439,7 @@ export class OsmConnection {
file: gpx,
description: options.description,
tags: options.labels?.join(",") ?? "",
visibility: options.visibility
visibility: options.visibility,
}
if (!contents.description) {
@ -447,9 +447,9 @@ export class OsmConnection {
}
const extras = {
file:
"; filename=\"" +
'; filename="' +
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
"\"\r\nContent-Type: application/gpx+xml"
'"\r\nContent-Type: application/gpx+xml',
}
const boundary = "987654"
@ -457,7 +457,7 @@ export class OsmConnection {
let body = ""
for (const key in contents) {
body += "--" + boundary + "\r\n"
body += "Content-Disposition: form-data; name=\"" + key + "\""
body += 'Content-Disposition: form-data; name="' + key + '"'
if (extras[key] !== undefined) {
body += extras[key]
}
@ -468,7 +468,7 @@ export class OsmConnection {
const response = await this.post("gpx/create", body, {
"Content-Type": "multipart/form-data; boundary=" + boundary,
"Content-Length": body.length
"Content-Length": body.length,
})
const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed)
@ -491,9 +491,9 @@ export class OsmConnection {
{
method: "POST",
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`,
},
function(err, _) {
function (err, _) {
if (err !== null) {
error(err)
} else {
@ -508,7 +508,7 @@ export class OsmConnection {
* To be called by land.html
*/
public finishLogin(callback: (previousURL: string) => void) {
this.auth.authenticate(function() {
this.auth.authenticate(function () {
// Fully authed at this point
console.log("Authentication successful!")
const previousLocation = LocalStorageSource.Get("location_before_login")
@ -547,7 +547,7 @@ export class OsmConnection {
? "https://mapcomplete.org/land.html"
: window.location.protocol + "//" + window.location.host + "/land.html",
singlepage: true,
auto: true
auto: true,
})
}

View file

@ -1,14 +1,42 @@
import { Utils } from "../../Utils"
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
export class ThemeMetaTagging {
public static readonly themeName = "usersettings"
public static readonly themeName = "usersettings"
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
feat.properties['__current_backgroun'] = 'initial_value'
}
}
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
feat.properties._description
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
?.at(1)
)
Utils.AddLazyProperty(
feat.properties,
"_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/g, ">") ?? ""
)
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.href.match(/mastodon|en.osm.town/) !== null
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(
feat.properties,
"_mastodon_candidate",
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
)
feat.properties["__current_backgroun"] = "initial_value"
}
}

View file

@ -243,10 +243,7 @@ export class TagUtils {
*
* TagUtils.SplitKeysRegex([new Tag("isced:level", "bachelor; master")], true) // => {"isced:level": ["bachelor","master"]}
*/
static SplitKeysRegex(
tagsFilters: UploadableTag[],
allowRegex: false
): Record<string, string[]>
static SplitKeysRegex(tagsFilters: UploadableTag[], allowRegex: false): Record<string, string[]>
static SplitKeysRegex(
tagsFilters: UploadableTag[],
allowRegex: boolean
@ -514,7 +511,7 @@ export class TagUtils {
)}`
})
return <UploadableTag> t
return <UploadableTag>t
}
/**
@ -723,7 +720,9 @@ export class TagUtils {
}
if (typeof json != "string") {
if (json["and"] !== undefined && json["or"] !== undefined) {
throw `${context}: Error while parsing a TagConfig: got an object where both 'and' and 'or' are defined. Did you override a value? Perhaps use \`"=parent": { ... }\` instead of \"parent": {...}\` to trigger a replacement and not a fuse of values. The value is ${JSON.stringify(json)}`
throw `${context}: Error while parsing a TagConfig: got an object where both 'and' and 'or' are defined. Did you override a value? Perhaps use \`"=parent": { ... }\` instead of \"parent": {...}\` to trigger a replacement and not a fuse of values. The value is ${JSON.stringify(
json
)}`
}
if (json["and"] !== undefined) {
return new And(json["and"].map((t) => TagUtils.Tag(t, context)))

View file

@ -237,10 +237,10 @@ export abstract class Store<T> implements Readable<T> {
public bindD<X>(f: (t: Exclude<T, undefined | null>) => Store<X>): Store<X> {
return this.bind((t) => {
if(t=== null){
if (t === null) {
return null
}
if (t === undefined ) {
if (t === undefined) {
return undefined
}
return f(<Exclude<T, undefined | null>>t)

View file

@ -13,7 +13,9 @@ interface JsonLdLoaderOptions {
country?: string
}
type PropertiesSpec<T extends string> = Partial<Record<T, string | string[] | Partial<Record<T, string>>>>
type PropertiesSpec<T extends string> = Partial<
Record<T, string | string[] | Partial<Record<T, string>>>
>
export default class LinkedDataLoader {
private static readonly COMPACTING_CONTEXT = {
@ -25,23 +27,23 @@ export default class LinkedDataLoader {
opening_hours: { "@id": "http://schema.org/openingHoursSpecification" },
openingHours: { "@id": "http://schema.org/openingHours", "@container": "@set" },
geo: { "@id": "http://schema.org/geo" },
"alt_name": { "@id": "http://schema.org/alternateName" }
alt_name: { "@id": "http://schema.org/alternateName" },
}
private static COMPACTING_CONTEXT_OH = {
dayOfWeek: { "@id": "http://schema.org/dayOfWeek", "@container": "@set" },
closes: {
"@id": "http://schema.org/closes",
"@type": "http://www.w3.org/2001/XMLSchema#time"
"@type": "http://www.w3.org/2001/XMLSchema#time",
},
opens: {
"@id": "http://schema.org/opens",
"@type": "http://www.w3.org/2001/XMLSchema#time"
}
"@type": "http://www.w3.org/2001/XMLSchema#time",
},
}
private static formatters: Record<"phone" | "email" | "website", Validator> = {
phone: new PhoneValidator(),
email: new EmailValidator(),
website: new UrlValidator(undefined, undefined, true)
website: new UrlValidator(undefined, undefined, true),
}
private static ignoreKeys = [
"http://schema.org/logo",
@ -54,53 +56,61 @@ export default class LinkedDataLoader {
"http://schema.org/description",
"http://schema.org/hasMap",
"http://schema.org/priceRange",
"http://schema.org/contactPoint"
"http://schema.org/contactPoint",
]
private static shapeToPolygon(str: string): Polygon {
const polygon = str.substring("POLYGON ((".length, str.length - 2)
return <Polygon>{
type: "Polygon",
coordinates: [polygon.split(",").map(coors => coors.trim().split(" ").map(n => Number(n)))]
coordinates: [
polygon.split(",").map((coors) =>
coors
.trim()
.split(" ")
.map((n) => Number(n))
),
],
}
}
private static async geoToGeometry(geo): Promise<Geometry> {
if (Array.isArray(geo)) {
const features = await Promise.all(geo.map(g => LinkedDataLoader.geoToGeometry(g)))
const polygon = features.find(f => f.type === "Polygon")
const features = await Promise.all(geo.map((g) => LinkedDataLoader.geoToGeometry(g)))
const polygon = features.find((f) => f.type === "Polygon")
if (polygon) {
return polygon
}
const ls = features.find(f => f.type === "LineString")
const ls = features.find((f) => f.type === "LineString")
if (ls) {
return ls
}
return features[0]
}
if (geo["@type"] === "http://schema.org/GeoCoordinates") {
const context = {
lat: {
"@id": "http://schema.org/latitude",
"@type": "http://www.w3.org/2001/XMLSchema#double"
"@type": "http://www.w3.org/2001/XMLSchema#double",
},
lon: {
"@id": "http://schema.org/longitude",
"@type": "http://www.w3.org/2001/XMLSchema#double"
}
"@type": "http://www.w3.org/2001/XMLSchema#double",
},
}
const flattened = await jsonld.compact(geo, context)
return {
type: "Point",
coordinates: [Number(flattened.lon), Number(flattened.lat)]
coordinates: [Number(flattened.lon), Number(flattened.lat)],
}
}
if (geo["@type"] === "http://schema.org/GeoShape" && geo["http://schema.org/polygon"] !== undefined) {
if (
geo["@type"] === "http://schema.org/GeoShape" &&
geo["http://schema.org/polygon"] !== undefined
) {
const str = geo["http://schema.org/polygon"]["@value"]
LinkedDataLoader.shapeToPolygon(str)
}
@ -167,9 +177,8 @@ export default class LinkedDataLoader {
}
static async compact(data: object, options?: JsonLdLoaderOptions): Promise<object> {
if (Array.isArray(data)) {
return await Promise.all(data.map(point => LinkedDataLoader.compact(point, options)))
return await Promise.all(data.map((point) => LinkedDataLoader.compact(point, options)))
}
const country = options?.country
@ -214,10 +223,13 @@ export default class LinkedDataLoader {
}
}
return compacted
}
static async fetchJsonLd(url: string, options?: JsonLdLoaderOptions, useProxy: boolean = false): Promise<object> {
static async fetchJsonLd(
url: string,
options?: JsonLdLoaderOptions,
useProxy: boolean = false
): Promise<object> {
if (useProxy) {
url = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
}
@ -230,7 +242,10 @@ export default class LinkedDataLoader {
* @param externalData
* @param currentData
*/
static removeDuplicateData(externalData: Record<string, string>, currentData: Record<string, string>): Record<string, string> {
static removeDuplicateData(
externalData: Record<string, string>,
currentData: Record<string, string>
): Record<string, string> {
const d = { ...externalData }
delete d["@context"]
for (const k in d) {
@ -239,7 +254,7 @@ export default class LinkedDataLoader {
continue
}
if (k === "opening_hours") {
const oh = [].concat(...v.split(";").map(r => OH.ParseRule(r) ?? []))
const oh = [].concat(...v.split(";").map((r) => OH.ParseRule(r) ?? []))
const merged = OH.ToString(OH.MergeTimes(oh ?? []))
if (merged === d[k]) {
delete d[k]
@ -259,7 +274,9 @@ export default class LinkedDataLoader {
const properties: Record<string, string> = {}
for (const k in linkedData) {
if (linkedData[k].length > 1) {
throw "Found multiple values in properties for " + k + ": " + linkedData[k].join("; ")
throw (
"Found multiple values in properties for " + k + ": " + linkedData[k].join("; ")
)
}
properties[k] = linkedData[k].join("; ")
}
@ -268,7 +285,7 @@ export default class LinkedDataLoader {
if (properties["latitude"] && properties["longitude"]) {
geometry = {
type: "Point",
coordinates: [Number(properties["longitude"]), Number(properties["latitude"])]
coordinates: [Number(properties["longitude"]), Number(properties["latitude"])],
}
delete properties["latitude"]
delete properties["longitude"]
@ -277,11 +294,10 @@ export default class LinkedDataLoader {
geometry = LinkedDataLoader.shapeToPolygon(properties["shape"])
}
const geo: GeoJSON = {
type: "Feature",
properties,
geometry
geometry,
}
delete linkedData.geo
delete properties.shape
@ -293,7 +309,9 @@ export default class LinkedDataLoader {
return geo
}
private static patchVeloparkProperties(input: Record<string, Set<string>>): Record<string, string[]> {
private static patchVeloparkProperties(
input: Record<string, Set<string>>
): Record<string, string[]> {
const output: Record<string, string[]> = {}
console.log("Input for patchVelopark:", input)
for (const k in input) {
@ -309,12 +327,12 @@ export default class LinkedDataLoader {
if (!output[key]) {
return
}
output[key] = output[key].map(v => applyF(v))
output[key] = output[key].map((v) => applyF(v))
}
function asBoolean(key: string, invert: boolean = false) {
on(key, str => {
const isTrue = ("" + str) === "true" || str === "True" || str === "yes"
on(key, (str) => {
const isTrue = "" + str === "true" || str === "True" || str === "yes"
if (isTrue != invert) {
return "yes"
}
@ -322,7 +340,7 @@ export default class LinkedDataLoader {
})
}
on("maxstay", (maxstay => {
on("maxstay", (maxstay) => {
const match = maxstay.match(/P([0-9]+)D/)
if (match) {
const days = Number(match[1])
@ -332,7 +350,7 @@ export default class LinkedDataLoader {
return days + " days"
}
return maxstay
}))
})
function rename(source: string, target: string) {
if (output[source] === undefined || output[source] === null) {
@ -342,41 +360,40 @@ export default class LinkedDataLoader {
delete output[source]
}
on("phone", (p => this.formatters["phone"].reformat(p, () => "be")))
on("phone", (p) => this.formatters["phone"].reformat(p, () => "be"))
for (const attribute in LinkedDataLoader.formatters) {
on(attribute, p => LinkedDataLoader.formatters[attribute].reformat(p))
on(attribute, (p) => LinkedDataLoader.formatters[attribute].reformat(p))
}
rename("phone", "operator:phone")
rename("email", "operator:email")
rename("website", "operator:website")
on("charge", (p => {
on("charge", (p) => {
if (Number(p) === 0) {
output["fee"] = ["no"]
return undefined
}
return "€" + Number(p)
}))
})
if (output["charge"] && output["timeUnit"]) {
const duration = Number(output["chargeEnd"] ?? "1") - Number(output["chargeStart"] ?? "0")
const duration =
Number(output["chargeEnd"] ?? "1") - Number(output["chargeStart"] ?? "0")
const unit = output["timeUnit"][0]
let durationStr = ""
if (duration !== 1) {
durationStr = duration + ""
}
output["charge"] = output["charge"].map(c => c + "/" + (durationStr + unit))
output["charge"] = output["charge"].map((c) => c + "/" + (durationStr + unit))
}
delete output["chargeEnd"]
delete output["chargeStart"]
delete output["timeUnit"]
asBoolean("covered")
asBoolean("fee", true)
asBoolean("publicAccess")
output["images"]?.forEach((p, i) => {
if (i === 0) {
output["image"] = [p]
@ -386,9 +403,15 @@ export default class LinkedDataLoader {
})
delete output["images"]
on("access", audience => {
if (["brede publiek", "iedereen", "bezoekers", "iedereen - vooral bezoekers gemeentehuis of bibliotheek."].indexOf(audience.toLowerCase()) >= 0) {
on("access", (audience) => {
if (
[
"brede publiek",
"iedereen",
"bezoekers",
"iedereen - vooral bezoekers gemeentehuis of bibliotheek.",
].indexOf(audience.toLowerCase()) >= 0
) {
return "yes"
}
if (audience.toLowerCase().startsWith("bezoekers")) {
@ -404,15 +427,22 @@ export default class LinkedDataLoader {
return "permissive"
// return "members"
}
if (audience.toLowerCase().startsWith("klanten") ||
if (
audience.toLowerCase().startsWith("klanten") ||
audience.toLowerCase().startsWith("werknemers") ||
audience.toLowerCase().startsWith("personeel")) {
audience.toLowerCase().startsWith("personeel")
) {
return "customers"
}
console.warn("Suspicious 'access'-tag:", audience, "for", input["ref:velopark"], " assuming yes")
console.warn(
"Suspicious 'access'-tag:",
audience,
"for",
input["ref:velopark"],
" assuming yes"
)
return "yes"
})
if (output["publicAccess"]?.[0] == "no") {
@ -420,7 +450,10 @@ export default class LinkedDataLoader {
}
delete output["publicAccess"]
if (output["restrictions"]?.[0] === "Geen bromfietsen, noch andere gemotoriseerde voertuigen") {
if (
output["restrictions"]?.[0] ===
"Geen bromfietsen, noch andere gemotoriseerde voertuigen"
) {
output["motor_vehicle"] = ["no"]
delete output["restrictions"]
}
@ -437,7 +470,6 @@ export default class LinkedDataLoader {
}
rename("capacityTandem", "capacity:tandem")
if (output["electricBikeType"]) {
output["electric_bicycle"] = ["yes"]
delete output["electricBikeType"]
@ -450,14 +482,18 @@ export default class LinkedDataLoader {
return output
}
private static async fetchVeloparkProperty<T extends string, G extends T>(url: string, property: string, variable?: string): Promise<SparqlResult<T, G>> {
private static async fetchVeloparkProperty<T extends string, G extends T>(
url: string,
property: string,
variable?: string
): Promise<SparqlResult<T, G>> {
const results = await new TypedSparql().typedSparql<T, G>(
{
schema: "http://schema.org/",
mv: "http://schema.mobivoc.org/",
gr: "http://purl.org/goodrelations/v1#",
vp: "https://data.velopark.be/openvelopark/vocabulary#",
vpt: "https://data.velopark.be/openvelopark/terms#"
vpt: "https://data.velopark.be/openvelopark/terms#",
},
[url],
undefined,
@ -467,24 +503,24 @@ export default class LinkedDataLoader {
return results
}
private static async fetchVeloparkGraphProperty<T extends string>(url: string, property: string, subExpr?: string):
Promise<SparqlResult<T, "g">> {
private static async fetchVeloparkGraphProperty<T extends string>(
url: string,
property: string,
subExpr?: string
): Promise<SparqlResult<T, "g">> {
return await new TypedSparql().typedSparql<T, "g">(
{
schema: "http://schema.org/",
mv: "http://schema.mobivoc.org/",
gr: "http://purl.org/goodrelations/v1#",
vp: "https://data.velopark.be/openvelopark/vocabulary#",
vpt: "https://data.velopark.be/openvelopark/terms#"
vpt: "https://data.velopark.be/openvelopark/terms#",
},
[url],
"g",
" ?parking a <http://schema.mobivoc.org/BicycleParkingStation>",
S.graph("g",
"?section " + property + " " + (subExpr ?? ""),
"?section a ?type"
)
S.graph("g", "?section " + property + " " + (subExpr ?? ""), "?section a ?type")
)
}
@ -493,8 +529,10 @@ export default class LinkedDataLoader {
* THis is a workaround for 'optional' not working decently
* @param r0
*/
public static mergeResults(...r0: SparqlResult<string, string>[]): SparqlResult<string, string> {
const r: SparqlResult<string> = { "default": {} }
public static mergeResults(
...r0: SparqlResult<string, string>[]
): SparqlResult<string, string> {
const r: SparqlResult<string> = { default: {} }
for (const subResult of r0) {
if (Object.keys(subResult).length === 0) {
continue
@ -524,22 +562,31 @@ export default class LinkedDataLoader {
return r
}
public static async fetchEntry<T extends string>(directUrl: string,
propertiesWithoutGraph: PropertiesSpec<T>,
propertiesInGraph: PropertiesSpec<T>,
extra?: string[]): Promise<SparqlResult<T, string>> {
public static async fetchEntry<T extends string>(
directUrl: string,
propertiesWithoutGraph: PropertiesSpec<T>,
propertiesInGraph: PropertiesSpec<T>,
extra?: string[]
): Promise<SparqlResult<T, string>> {
const allPartialResults: SparqlResult<T, string>[] = []
for (const propertyName in propertiesWithoutGraph) {
const e = propertiesWithoutGraph[propertyName]
if (typeof e === "string") {
const variableName = e
const result = await this.fetchVeloparkProperty(directUrl, propertyName, "?" + variableName)
const result = await this.fetchVeloparkProperty(
directUrl,
propertyName,
"?" + variableName
)
allPartialResults.push(result)
} else {
for (const subProperty in e) {
const variableName = e[subProperty]
const result = await this.fetchVeloparkProperty(directUrl,
propertyName, `[${subProperty} ?${variableName}] `)
const result = await this.fetchVeloparkProperty(
directUrl,
propertyName,
`[${subProperty} ?${variableName}] `
)
allPartialResults.push(result)
}
}
@ -553,7 +600,11 @@ export default class LinkedDataLoader {
if (variableName.match(/[a-zA-Z_]+/)) {
variableName = "?" + subquery
}
const result = await this.fetchVeloparkGraphProperty(directUrl, propertyName, variableName)
const result = await this.fetchVeloparkGraphProperty(
directUrl,
propertyName,
variableName
)
allPartialResults.push(result)
}
} else if (typeof e === "string") {
@ -561,13 +612,20 @@ export default class LinkedDataLoader {
if (variableName.match(/[a-zA-Z_]+/)) {
variableName = "?" + e
}
const result = await this.fetchVeloparkGraphProperty(directUrl, propertyName, variableName)
const result = await this.fetchVeloparkGraphProperty(
directUrl,
propertyName,
variableName
)
allPartialResults.push(result)
} else {
for (const subProperty in e) {
const variableName = e[subProperty]
const result = await this.fetchVeloparkGraphProperty(directUrl,
propertyName, `[${subProperty} ?${variableName}] `)
const result = await this.fetchVeloparkGraphProperty(
directUrl,
propertyName,
`[${subProperty} ?${variableName}] `
)
allPartialResults.push(result)
}
}
@ -581,7 +639,6 @@ export default class LinkedDataLoader {
const results = this.mergeResults(...allPartialResults)
return results
}
/**
@ -593,22 +650,21 @@ export default class LinkedDataLoader {
const withProxyUrl = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
const optionalPaths: Record<string, string | Record<string, string>> = {
"schema:interactionService": {
"schema:url": "website"
"schema:url": "website",
},
"schema:name": "name",
"mv:operatedBy": {
"gr:legalName": "operator"
"gr:legalName": "operator",
},
"schema:contactPoint": {
"schema:email": "email",
"schema:telephone": "phone"
"schema:telephone": "phone",
},
"schema:dateModified":"_last_edit_timestamp"
"schema:dateModified": "_last_edit_timestamp",
}
const graphOptionalPaths = {
"a": "type",
a: "type",
"vp:covered": "covered",
"vp:maximumParkingDuration": "maxstay",
"mv:totalCapacity": "capacity",
@ -619,22 +675,27 @@ export default class LinkedDataLoader {
"schema:geo": {
"schema:latitude": "latitude",
"schema:longitude": "longitude",
"schema:polygon": "shape"
"schema:polygon": "shape",
},
"schema:priceSpecification": {
"mv:freeOfCharge": "fee",
"schema:price": "charge"
}
"schema:price": "charge",
},
}
const extra = [
"schema:priceSpecification [ mv:dueForTime [ mv:timeStartValue ?chargeStart; mv:timeEndValue ?chargeEnd; mv:timeUnit ?timeUnit ] ]",
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#CargoBicycle>; vp:bicyclesAmount ?capacityCargobike; vp:bicycleType ?cargoBikeType]",
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#ElectricBicycle>; vp:bicyclesAmount ?capacityElectric; vp:bicycleType ?electricBikeType]",
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#TandemBicycle>; vp:bicyclesAmount ?capacityTandem; vp:bicycleType ?tandemBikeType]"
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#TandemBicycle>; vp:bicyclesAmount ?capacityTandem; vp:bicycleType ?tandemBikeType]",
]
const unpatched = await this.fetchEntry(withProxyUrl, optionalPaths, graphOptionalPaths, extra)
const unpatched = await this.fetchEntry(
withProxyUrl,
optionalPaths,
graphOptionalPaths,
extra
)
const patched: Feature[] = []
for (const section in unpatched) {
const p = LinkedDataLoader.patchVeloparkProperties(unpatched[section])

View file

@ -6,7 +6,9 @@ import { GeoOperations } from "../GeoOperations"
import ScriptUtils from "../../../scripts/ScriptUtils"
export class MangroveIdentity {
private readonly keypair: UIEventSource<CryptoKeyPair> = new UIEventSource<CryptoKeyPair>(undefined)
private readonly keypair: UIEventSource<CryptoKeyPair> = new UIEventSource<CryptoKeyPair>(
undefined
)
/**
* Same as the one in the user settings
*/
@ -14,16 +16,19 @@ export class MangroveIdentity {
private readonly key_id: UIEventSource<string> = new UIEventSource<string>(undefined)
private readonly _mangroveIdentityCreationDate: UIEventSource<string>
constructor(mangroveIdentity: UIEventSource<string>, mangroveIdentityCreationDate: UIEventSource<string>) {
constructor(
mangroveIdentity: UIEventSource<string>,
mangroveIdentityCreationDate: UIEventSource<string>
) {
this.mangroveIdentity = mangroveIdentity
this._mangroveIdentityCreationDate = mangroveIdentityCreationDate
mangroveIdentity.addCallbackAndRunD(async (data) => {
await this.setKeypair(data)
await this.setKeypair(data)
})
}
private async setKeypair(data: string){
console.log("Setting keypair from",data)
private async setKeypair(data: string) {
console.log("Setting keypair from", data)
const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data))
this.keypair.setData(keypair)
const pem = await MangroveReviews.publicToPem(keypair.publicKey)
@ -71,22 +76,21 @@ export class MangroveIdentity {
return this.key_id
}
private geoReviewsById: Store<(Review & { kid: string; signature: string })[]> =
undefined
private geoReviewsById: Store<(Review & { kid: string; signature: string })[]> = undefined
public getGeoReviews(): Store<(Review & { kid: string, signature: string })[] | undefined> {
public getGeoReviews(): Store<(Review & { kid: string; signature: string })[] | undefined> {
if (!this.geoReviewsById) {
const all = this.getAllReviews()
this.geoReviewsById = this.getAllReviews().mapD(reviews => reviews.filter(
review => {
this.geoReviewsById = this.getAllReviews().mapD((reviews) =>
reviews.filter((review) => {
try {
const subjectUrl = new URL(review.sub)
return subjectUrl.protocol === "geo:"
} catch (e) {
return false
}
}
))
})
)
}
return this.geoReviewsById
}
@ -108,12 +112,12 @@ export class MangroveIdentity {
return []
}
const allReviews = await MangroveReviews.getReviews({
kid: pem
kid: pem,
})
this.allReviewsById.setData(
allReviews.reviews.map((r) => ({
...r,
...r.payload
...r.payload,
}))
)
})
@ -247,7 +251,13 @@ export default class FeatureReviews {
if (cached !== undefined) {
return cached
}
const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options,testmode )
const featureReviews = new FeatureReviews(
feature,
tagsSource,
mangroveIdentity,
options,
testmode
)
FeatureReviews._featureReviewsCache[key] = featureReviews
return featureReviews
}
@ -268,7 +278,7 @@ export default class FeatureReviews {
}
const r: Review = {
sub: this.subjectUri.data,
...review
...review,
}
const keypair: CryptoKeyPair = await this._identity.getKeypair()
const jwt = await MangroveReviews.signReview(keypair, r)
@ -283,7 +293,7 @@ export default class FeatureReviews {
...r,
kid,
signature: jwt,
madeByLoggedInUser: new ImmutableStore(true)
madeByLoggedInUser: new ImmutableStore(true),
}
this._reviews.data.push(reviewWithKid)
this._reviews.ping()
@ -331,7 +341,7 @@ export default class FeatureReviews {
signature: reviewData.signature,
madeByLoggedInUser: this._identity.getKeyId().map((user_key_id) => {
return reviewData.kid === user_key_id
})
}),
})
hasNew = true
}
@ -352,7 +362,7 @@ export default class FeatureReviews {
// https://www.rfc-editor.org/rfc/rfc5870#section-3.4.2
// `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3
const self = this
return this._name.map(function(name) {
return this._name.map(function (name) {
let uri = `geo:${self._lat},${self._lon}?u=${Math.round(self._uncertainty)}`
if (name) {
uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name))

View file

@ -3,11 +3,16 @@ import { QueryEngine } from "@comunica/query-sparql"
export type SparqlVar<T extends string> = `?${T}`
export type SparqlExpr = string
export type SparqlStmt<T extends string> = `${SparqlVar<T> | SparqlExpr} ${SparqlVar<T> | SparqlExpr} ${SparqlVar<T> | SparqlExpr}`
export type SparqlStmt<T extends string> = `${SparqlVar<T> | SparqlExpr} ${
| SparqlVar<T>
| SparqlExpr} ${SparqlVar<T> | SparqlExpr}`
export type TypedExpression<T extends string> = SparqlStmt<T> | string
export type SparqlResult<T extends string, G extends string = "default"> = Record<G, Record<T, Set<string>>>
export type SparqlResult<T extends string, G extends string = "default"> = Record<
G,
Record<T, Set<string>>
>
export default class TypedSparql {
private readonly comunica: QueryEngine
@ -16,15 +21,23 @@ export default class TypedSparql {
this.comunica = new QueryEngine()
}
public static optional<Vars extends string>(...statements: (TypedExpression<Vars> | string)[]): TypedExpression<Vars> {
public static optional<Vars extends string>(
...statements: (TypedExpression<Vars> | string)[]
): TypedExpression<Vars> {
return ` OPTIONAL { ${statements.join(". \n\t")} }`
}
public static graph<Vars extends string>(varname: Vars, ...statements: (string | TypedExpression<Vars>)[]): TypedExpression<Vars> {
public static graph<Vars extends string>(
varname: Vars,
...statements: (string | TypedExpression<Vars>)[]
): TypedExpression<Vars> {
return `GRAPH ?${varname} { ${statements.join(".\n")} }`
}
public static about<Vars extends string>(varname: Vars, ...statements: `${SparqlVar<Vars> | SparqlExpr} ${SparqlVar<Vars> | SparqlExpr}`[]): TypedExpression<Vars> {
public static about<Vars extends string>(
varname: Vars,
...statements: `${SparqlVar<Vars> | SparqlExpr} ${SparqlVar<Vars> | SparqlExpr}`[]
): TypedExpression<Vars> {
return `?${varname} ${statements.join(";")}`
}
@ -43,23 +56,22 @@ export default class TypedSparql {
): Promise<SparqlResult<VARS, G>> {
const q: string = this.buildQuery(query, prefixes)
try {
const bindingsStream = await this.comunica.queryBindings(
q, { sources: [...sources], lenient: true }
)
const bindingsStream = await this.comunica.queryBindings(q, {
sources: [...sources],
lenient: true,
})
const bindings = await bindingsStream.toArray()
const resultAllGraphs: SparqlResult<VARS, G> = <SparqlResult<VARS, G>>{}
bindings.forEach(item => {
bindings.forEach((item) => {
const result = <Record<VARS | G, Set<string>>>{}
item.forEach(
(value, key) => {
if (!result[key.value]) {
result[key.value] = new Set()
}
result[key.value].add(value.value)
item.forEach((value, key) => {
if (!result[key.value]) {
result[key.value] = new Set()
}
)
result[key.value].add(value.value)
})
if (graphVariable && result[graphVariable]?.size > 0) {
const id = Array.from(result[graphVariable])?.[0] ?? "default"
resultAllGraphs[id] = result
@ -73,14 +85,13 @@ export default class TypedSparql {
console.log("Running query failed. The query is", q)
throw e
}
}
private buildQuery(
query: readonly string[],
prefixes: Record<string, string>): string {
private buildQuery(query: readonly string[], prefixes: Record<string, string>): string {
return `
${Object.keys(prefixes).map(prefix => `PREFIX ${prefix}: <${prefixes[prefix]}>`).join("\n")}
${Object.keys(prefixes)
.map((prefix) => `PREFIX ${prefix}: <${prefixes[prefix]}>`)
.join("\n")}
SELECT *
WHERE {
${query.join(". \n")} .

View file

@ -24,7 +24,7 @@ export interface RasterLayerProperties {
readonly url: string
readonly category?: string | EliCategory
readonly type?: "vector" | "raster" | string
readonly style?: string,
readonly style?: string
readonly attribution?: {
readonly url?: string

View file

@ -51,9 +51,10 @@ export class AvailableRasterLayers {
/**
* The default background layer that any theme uses which does not explicitly define a background
*/
public static readonly defaultBackgroundLayer: RasterLayerPolygon = AvailableRasterLayers.globalLayers.find(l => {
return l.properties.id === "protomaps.sunny"
})
public static readonly defaultBackgroundLayer: RasterLayerPolygon =
AvailableRasterLayers.globalLayers.find((l) => {
return l.properties.id === "protomaps.sunny"
})
public static layersAvailableAt(
location: Store<{ lon: number; lat: number }>,

View file

@ -65,31 +65,37 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
const newFilters: FilterConfigJson[] = []
const filters = <(FilterConfigJson | string)[]>json.filter
for (let i = 0; i < filters.length; i++){
for (let i = 0; i < filters.length; i++) {
const filter = filters[i]
if (typeof filter !== "string") {
newFilters.push(filter)
continue
}
const matchingTr =<TagRenderingConfigJson> json.tagRenderings.find(tr => !!tr && tr["id"] === filter)
if(matchingTr){
if(!(matchingTr.mappings?.length >= 1)){
context.enters("filter",i ).err("Found a matching tagRendering to base a filter on, but this tagRendering does not contain any mappings")
const matchingTr = <TagRenderingConfigJson>(
json.tagRenderings.find((tr) => !!tr && tr["id"] === filter)
)
if (matchingTr) {
if (!(matchingTr.mappings?.length >= 1)) {
context
.enters("filter", i)
.err(
"Found a matching tagRendering to base a filter on, but this tagRendering does not contain any mappings"
)
}
const options = matchingTr.mappings.map(mapping => ({
const options = matchingTr.mappings.map((mapping) => ({
question: mapping.then,
osmTags: mapping.if
osmTags: mapping.if,
}))
options.unshift({
question: {
en:"All types"
en: "All types",
},
osmTags: undefined
osmTags: undefined,
})
newFilters.push({
id: filter,
options
options,
})
continue
}
@ -521,9 +527,9 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
json = { ...json }
json.tagRenderings = [...json.tagRenderings]
const allSpecials: Exclude<RenderingSpecification, string>[] = <any>(
ValidationUtils.getAllSpecialVisualisations(<QuestionableTagRenderingConfigJson[]> json.tagRenderings).filter(
(spec) => typeof spec !== "string"
)
ValidationUtils.getAllSpecialVisualisations(
<QuestionableTagRenderingConfigJson[]>json.tagRenderings
).filter((spec) => typeof spec !== "string")
)
const questionSpecials = allSpecials.filter((sp) => sp.func.funcName === "questions")

View file

@ -289,7 +289,10 @@ class AddContextToTranslationsInLayout extends DesugaringStep<LayoutConfigJson>
convert(json: LayoutConfigJson): LayoutConfigJson {
const conversion = new AddContextToTranslations<LayoutConfigJson>("themes:")
// The context is used to generate the 'context' in the translation .It _must_ be `json.id` to correctly link into weblate
return conversion.convert(json, ConversionContext.construct([json.id],["AddContextToTranslation"]))
return conversion.convert(
json,
ConversionContext.construct([json.id], ["AddContextToTranslation"])
)
}
}
@ -602,19 +605,32 @@ class PostvalidateTheme extends DesugaringStep<LayoutConfigJson> {
}
for (const layer of json.layers) {
if(typeof layer === "string"){
if (typeof layer === "string") {
continue
}
const config = <LayerConfigJson> layer;
const config = <LayerConfigJson>layer
const sameAs = config.filter?.["sameAs"]
if(!sameAs){
if (!sameAs) {
continue
}
const matchingLayer = json.layers.find(l => l["id"] === sameAs)
if(!matchingLayer){
const closeLayers = Utils.sortedByLevenshteinDistance(sameAs, json.layers, l => l["id"]).map(l => l["id"])
context.enters("layers", config.id, "filter","sameAs").err("The layer "+config.id+" follows the filter state of layer "+sameAs+", but no layer with this name was found.\n\tDid you perhaps mean one of: "+closeLayers.slice(0, 3).join(", "))
const matchingLayer = json.layers.find((l) => l["id"] === sameAs)
if (!matchingLayer) {
const closeLayers = Utils.sortedByLevenshteinDistance(
sameAs,
json.layers,
(l) => l["id"]
).map((l) => l["id"])
context
.enters("layers", config.id, "filter", "sameAs")
.err(
"The layer " +
config.id +
" follows the filter state of layer " +
sameAs +
", but no layer with this name was found.\n\tDid you perhaps mean one of: " +
closeLayers.slice(0, 3).join(", ")
)
}
}

View file

@ -278,10 +278,14 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
if (!isCategory && !ValidateTheme._availableLayers.has(backgroundId)) {
const options = Array.from(ValidateTheme._availableLayers)
const nearby = Utils.sortedByLevenshteinDistance(backgroundId, options, t => t)
const nearby = Utils.sortedByLevenshteinDistance(backgroundId, options, (t) => t)
context
.enter("defaultBackgroundId")
.err(`This layer ID is not known: ${backgroundId}. Perhaps you meant one of ${nearby.slice(0,5).join(", ")}`)
.err(
`This layer ID is not known: ${backgroundId}. Perhaps you meant one of ${nearby
.slice(0, 5)
.join(", ")}`
)
}
}
@ -1014,7 +1018,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
) {
continue
}
if(json.freeform.key.indexOf("wikidata")>=0){
if (json.freeform.key.indexOf("wikidata") >= 0) {
context
.enter("render")
.err(
@ -1273,7 +1277,14 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
// It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list
context
.enter("tagRenderings")
.err("Some tagrenderings have a duplicate id: " + duplicates.join(", ")+"\n"+JSON.stringify(json.tagRenderings.filter(tr=> duplicates.indexOf(tr["id"])>=0)))
.err(
"Some tagrenderings have a duplicate id: " +
duplicates.join(", ") +
"\n" +
JSON.stringify(
json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0)
)
)
}
}
@ -1865,11 +1876,14 @@ export class ValidateThemeEnsemble extends Conversion<
string,
{
tags: TagsFilter
foundInTheme: string[],
foundInTheme: string[]
isCounted: boolean
}
> {
const idToSource = new Map<string, { tags: TagsFilter; foundInTheme: string[], isCounted: boolean }>()
const idToSource = new Map<
string,
{ tags: TagsFilter; foundInTheme: string[]; isCounted: boolean }
>()
for (const theme of json) {
for (const layer of theme.layers) {

View file

@ -62,8 +62,6 @@ export default class ValidationUtils {
}
for (const key in translation) {
const template = translation[key]
const parts = SpecialVisualizations.constructSpecification(template)
const specials = parts.filter((p) => typeof p !== "string")

View file

@ -91,7 +91,7 @@ export default class LayerConfig extends WithContextLoader {
mercatorCrs: json.source["mercatorCrs"],
idKey: json.source["idKey"],
},
json.id,
json.id
)
}
@ -106,8 +106,8 @@ export default class LayerConfig extends WithContextLoader {
}
this.units = [].concat(
...(json.units ?? []).map((unitJson, i) =>
Unit.fromJson(unitJson, `${context}.unit[${i}]`),
),
Unit.fromJson(unitJson, `${context}.unit[${i}]`)
)
)
if (json.description !== undefined) {
@ -122,7 +122,7 @@ export default class LayerConfig extends WithContextLoader {
if (json.calculatedTags !== undefined) {
if (!official) {
console.warn(
`Unofficial theme ${this.id} with custom javascript! This is a security risk`,
`Unofficial theme ${this.id} with custom javascript! This is a security risk`
)
}
this.calculatedTags = []
@ -191,7 +191,7 @@ export default class LayerConfig extends WithContextLoader {
tags: pr.tags.map((t) => TagUtils.SimpleTag(t)),
description: Translations.T(
pr.description,
`${translationContext}.presets.${i}.description`,
`${translationContext}.presets.${i}.description`
),
preciseInput: preciseInput,
exampleImages: pr.exampleImages,
@ -205,7 +205,7 @@ export default class LayerConfig extends WithContextLoader {
if (json.lineRendering) {
this.lineRendering = Utils.NoNull(json.lineRendering).map(
(r, i) => new LineRenderingConfig(r, `${context}[${i}]`),
(r, i) => new LineRenderingConfig(r, `${context}[${i}]`)
)
} else {
this.lineRendering = []
@ -213,7 +213,7 @@ export default class LayerConfig extends WithContextLoader {
if (json.pointRendering) {
this.mapRendering = Utils.NoNull(json.pointRendering).map(
(r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`),
(r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`)
)
} else {
this.mapRendering = []
@ -225,7 +225,7 @@ export default class LayerConfig extends WithContextLoader {
r.location.has("centroid") ||
r.location.has("projected_centerpoint") ||
r.location.has("start") ||
r.location.has("end"),
r.location.has("end")
)
if (
@ -247,7 +247,7 @@ export default class LayerConfig extends WithContextLoader {
Constants.priviliged_layers.indexOf(<any>this.id) < 0 &&
this.source !== null /*library layer*/ &&
!this.source?.geojsonSource?.startsWith(
"https://api.openstreetmap.org/api/0.6/notes.json",
"https://api.openstreetmap.org/api/0.6/notes.json"
)
) {
throw (
@ -266,7 +266,7 @@ export default class LayerConfig extends WithContextLoader {
typeof tr !== "string" &&
tr["builtin"] === undefined &&
tr["id"] === undefined &&
tr["rewrite"] === undefined,
tr["rewrite"] === undefined
) ?? []
if (missingIds?.length > 0 && official) {
console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds)
@ -277,8 +277,8 @@ export default class LayerConfig extends WithContextLoader {
(tr, i) =>
new TagRenderingConfig(
<QuestionableTagRenderingConfigJson>tr,
this.id + ".tagRenderings[" + i + "]",
),
this.id + ".tagRenderings[" + i + "]"
)
)
if (
@ -352,7 +352,7 @@ export default class LayerConfig extends WithContextLoader {
public GetBaseTags(): Record<string, string> {
return TagUtils.changeAsProperties(
this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }],
this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }]
)
}
@ -365,7 +365,7 @@ export default class LayerConfig extends WithContextLoader {
neededLayer: string
}[] = [],
addedByDefault = false,
canBeIncluded = true,
canBeIncluded = true
): BaseUIElement {
const extraProps: (string | BaseUIElement)[] = []
@ -374,32 +374,32 @@ export default class LayerConfig extends WithContextLoader {
if (canBeIncluded) {
if (addedByDefault) {
extraProps.push(
"**This layer is included automatically in every theme. This layer might contain no points**",
"**This layer is included automatically in every theme. This layer might contain no points**"
)
}
if (this.shownByDefault === false) {
extraProps.push(
"This layer is not visible by default and must be enabled in the filter by the user. ",
"This layer is not visible by default and must be enabled in the filter by the user. "
)
}
if (this.title === undefined) {
extraProps.push(
"Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable.",
"Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable."
)
}
if (this.name === undefined && this.shownByDefault === false) {
extraProps.push(
"This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true",
"This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true"
)
}
if (this.name === undefined) {
extraProps.push(
"Not visible in the layer selection by default. If you want to make this layer toggable, override `name`",
"Not visible in the layer selection by default. If you want to make this layer toggable, override `name`"
)
}
if (this.mapRendering.length === 0) {
extraProps.push(
"Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`",
"Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`"
)
}
@ -411,26 +411,27 @@ export default class LayerConfig extends WithContextLoader {
: undefined,
"This layer is loaded from an external source, namely ",
new FixedUiElement(this.source.geojsonSource).SetClass("code"),
]),
])
)
}
} else {
extraProps.push(
"This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.",
"This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data."
)
}
let usingLayer: BaseUIElement[] = []
if (!addedByDefault) {
if (usedInThemes?.length > 0) {
usingLayer = [
new Title("Themes using this layer", 4),
new List(
(usedInThemes ?? []).map((id) => new Link(id, "https://mapcomplete.org/" + id)),
(usedInThemes ?? []).map(
(id) => new Link(id, "https://mapcomplete.org/" + id)
)
),
]
} else if(this.source !== null) {
} else if (this.source !== null) {
usingLayer = [new FixedUiElement("No themes use this layer")]
}
}
@ -443,7 +444,7 @@ export default class LayerConfig extends WithContextLoader {
" into the layout as it depends on it: ",
dep.reason,
"(" + dep.context + ")",
]),
])
)
}
@ -452,7 +453,7 @@ export default class LayerConfig extends WithContextLoader {
new Combine([
"This layer is needed as dependency for layer",
new Link(revDep, "#" + revDep),
]),
])
)
}
@ -464,14 +465,14 @@ export default class LayerConfig extends WithContextLoader {
return undefined
}
const embedded: (Link | string)[] = values.values?.map((v) =>
Link.OsmWiki(values.key, v, true).SetClass("mr-2"),
Link.OsmWiki(values.key, v, true).SetClass("mr-2")
) ?? ["_no preset options defined, or no values in them_"]
return [
new Combine([
new Link(
"<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>",
"https://taginfo.openstreetmap.org/keys/" + values.key + "#values",
true,
true
),
Link.OsmWiki(values.key),
]).SetClass("flex"),
@ -480,7 +481,7 @@ export default class LayerConfig extends WithContextLoader {
: new Link(values.type, "../SpecialInputElements.md#" + values.type),
new Combine(embedded).SetClass("flex"),
]
}),
})
)
let quickOverview: BaseUIElement = undefined
@ -490,7 +491,7 @@ export default class LayerConfig extends WithContextLoader {
"this quick overview is incomplete",
new Table(
["attribute", "type", "values which are supported by this layer"],
tableRows,
tableRows
).SetClass("zebra-table"),
]).SetClass("flex-col flex")
}
@ -504,7 +505,7 @@ export default class LayerConfig extends WithContextLoader {
(mr) =>
mr.RenderIcon(new ImmutableStore<OsmTags>({ id: "node/-1" }), {
includeBadges: false,
}).html,
}).html
)
.find((i) => i !== undefined)
}
@ -516,7 +517,7 @@ export default class LayerConfig extends WithContextLoader {
"Execute on overpass",
Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize())
.replaceAll("(", "%28")
.replaceAll(")", "%29"),
.replaceAll(")", "%29")
)
} catch (e) {
console.error("Could not generate overpasslink for " + this.id)
@ -538,19 +539,19 @@ export default class LayerConfig extends WithContextLoader {
const parts = neededTags["and"]
tagsDescription.push(
"Elements must match **all** of the following expressions:",
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"),
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n")
)
} else if (neededTags["or"]) {
const parts = neededTags["or"]
tagsDescription.push(
"Elements must match **any** of the following expressions:",
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"),
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n")
)
} else {
tagsDescription.push(
"Elements must match the expression **" +
neededTags.asHumanString(true, false, {}) +
"**",
neededTags.asHumanString(true, false, {}) +
"**"
)
}
@ -561,7 +562,7 @@ export default class LayerConfig extends WithContextLoader {
return new Combine([
new Combine([new Title(this.id, 1), iconImg, this.description, "\n"]).SetClass(
"flex flex-col",
"flex flex-col"
),
new List(extraProps),
...usingLayer,

View file

@ -313,7 +313,7 @@ export default class LayoutConfig implements LayoutInformation {
if (tags === undefined) {
return undefined
}
if(tags.id.startsWith("current_view")){
if (tags.id.startsWith("current_view")) {
return this.getLayer("current_view")
}
for (const layer of this.layers) {

View file

@ -232,11 +232,10 @@ export default class TagRenderingConfig {
throw "Tagrendering has a 'mappings'-object, but expected a list (" + context + ")"
}
const firstMappingSize: string = json.mappings.map((m) => (m.icon?.["class"])).find(c => !!c)
const commonIconSize =
firstMappingSize ??
json["#iconsize"] ??
"small"
const firstMappingSize: string = json.mappings
.map((m) => m.icon?.["class"])
.find((c) => !!c)
const commonIconSize = firstMappingSize ?? json["#iconsize"] ?? "small"
this.mappings = json.mappings.map((m, i) =>
TagRenderingConfig.ExtractMapping(
m,

View file

@ -6,10 +6,10 @@
*/
export let selected: UIEventSource<boolean>
let _c: boolean = selected.data ?? true
let id = `checkbox-input-${Math.round(Math.random()*100000000)}`
let id = `checkbox-input-${Math.round(Math.random() * 100000000)}`
$: selected.set(_c)
selected.addCallbackD(s => {
_c = s
selected.addCallbackD((s) => {
_c = s
})
</script>

View file

@ -10,7 +10,7 @@
*/
const dispatch = createEventDispatcher()
export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1"
export let enabled : Store<boolean> = new ImmutableStore(true)
export let enabled: Store<boolean> = new ImmutableStore(true)
export let arialabel: Translation = undefined
</script>
@ -18,7 +18,11 @@
on:click={(e) => dispatch("click", e)}
on:keydown
use:ariaLabel={arialabel}
class={twJoin("pointer-events-auto relative h-fit w-fit rounded-full", cls, $enabled ? "" : "disabled")}
class={twJoin(
"pointer-events-auto relative h-fit w-fit rounded-full",
cls,
$enabled ? "" : "disabled"
)}
>
<slot />
</button>

View file

@ -5,13 +5,9 @@
imgSize?: string
extraClasses?: string
} = {}
</script>
<button
class={twMerge(options.extraClasses, "secondary no-image-background")}
on:click
>
<button class={twMerge(options.extraClasses, "secondary no-image-background")} on:click>
<slot name="image" />
<slot name="message" />
</button>

View file

@ -22,7 +22,7 @@ export default class SvelteUIElement<
private readonly _props: Props
private readonly _events: Events
private readonly _slots: Slots
private tag : "div" | "span" = "div"
private tag: "div" | "span" = "div"
constructor(svelteElement, props?: Props, events?: Events, slots?: Slots) {
super()
@ -32,7 +32,7 @@ export default class SvelteUIElement<
this._slots = slots
}
public setSpan(){
public setSpan() {
this.tag = "span"
return this
}

View file

@ -1,49 +1,48 @@
<script lang="ts">
/**
* THe panel containing all filter- and layerselection options
*/
/**
* THe panel containing all filter- and layerselection options
*/
import OverlayToggle from "./OverlayToggle.svelte"
import Filterview from "./Filterview.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import Filter from "../../assets/svg/Filter.svelte"
import OverlayToggle from "./OverlayToggle.svelte"
import Filterview from "./Filterview.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import Filter from "../../assets/svg/Filter.svelte"
export let state: ThemeViewState
let layout = state.layout
let allEnabled : boolean
let allDisabled: boolean
function updateEnableState(){
allEnabled = true
allDisabled = true
state.layerState.filteredLayers.forEach((v) => {
if(!v.layerDef.name){
return
}
allEnabled &&= v.isDisplayed.data
allDisabled &&= !v.isDisplayed.data
})
}
updateEnableState()
export let state: ThemeViewState
let layout = state.layout
let allEnabled: boolean
let allDisabled: boolean
function updateEnableState() {
allEnabled = true
allDisabled = true
state.layerState.filteredLayers.forEach((v) => {
if(!v.layerDef.name){
return
}
v.isDisplayed.addCallbackD(_ => updateEnableState())
if (!v.layerDef.name) {
return
}
allEnabled &&= v.isDisplayed.data
allDisabled &&= !v.isDisplayed.data
})
function enableAll(doEnable: boolean){
state.layerState.filteredLayers.forEach((v) => {
if(!v.layerDef.name){
return
}
v.isDisplayed.setData(doEnable)
})
}
updateEnableState()
state.layerState.filteredLayers.forEach((v) => {
if (!v.layerDef.name) {
return
}
v.isDisplayed.addCallbackD((_) => updateEnableState())
})
function enableAll(doEnable: boolean) {
state.layerState.filteredLayers.forEach((v) => {
if (!v.layerDef.name) {
return
}
v.isDisplayed.setData(doEnable)
})
}
</script>
<div class="m-2 flex flex-col">
@ -59,12 +58,12 @@
highlightedLayer={state.guistate.highlightedLayerInFilters}
/>
{/each}
<div class="flex self-end mt-1">
<div class="mt-1 flex self-end">
<button class="small" class:disabled={allEnabled} on:click={() => enableAll(true)}>
<Tr t={Translations.t.general.filterPanel.enableAll}/>
<Tr t={Translations.t.general.filterPanel.enableAll} />
</button>
<button class="small" class:disabled={allDisabled} on:click={() => enableAll(false)}>
<Tr t={Translations.t.general.filterPanel.disableAll}/>
<button class="small" class:disabled={allDisabled} on:click={() => enableAll(false)}>
<Tr t={Translations.t.general.filterPanel.disableAll} />
</button>
</div>

View file

@ -66,7 +66,7 @@
/>
</If>
<Tr t={filteredLayer.layerDef.name}/>
<Tr t={filteredLayer.layerDef.name} />
{#if $zoomlevel < layer.minzoom}
<span class="alert">
@ -82,7 +82,7 @@
<!-- There are three (and a half) modes of filters: a single checkbox, a radio button/dropdown or with searchable fields -->
{#if filter.options.length === 1 && filter.options[0].fields.length === 0}
<Checkbox selected={getBooleanStateFor(filter)}>
<Tr t={filter.options[0].question}/>
<Tr t={filter.options[0].question} />
</Checkbox>
{/if}
@ -94,7 +94,7 @@
<Dropdown value={getStateFor(filter)}>
{#each filter.options as option, i}
<option value={i}>
<Tr t={option.question}/>
<Tr t={option.question} />
</option>
{/each}
</Dropdown>

View file

@ -41,7 +41,7 @@
let compass = Orientation.singleton.alpha
let relativeBearing: Store<{ distance: string; bearing: Translation }> = compass.mapD(
(compass) => {
if(!distanceToCurrentLocation.data){
if (!distanceToCurrentLocation.data) {
return undefined
}
const bearing: Translation =

View file

@ -8,7 +8,7 @@
import ShowDataLayer from "../Map/ShowDataLayer"
import type {
FeatureSource,
FeatureSourceForLayer
FeatureSourceForLayer,
} from "../../Logic/FeatureSource/FeatureSource"
import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource"
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
@ -72,7 +72,7 @@
allowMoving: new UIEventSource<boolean>(true),
allowZooming: new UIEventSource<boolean>(true),
minzoom: new UIEventSource<number>(18),
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer)
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
}
state?.showCurrentLocationOn(map)
@ -82,7 +82,7 @@
if (featuresForLayer) {
new ShowDataLayer(map, {
layer: targetLayer,
features: featuresForLayer
features: featuresForLayer,
})
}
}
@ -99,7 +99,7 @@
new ShowDataLayer(map, {
layer: layer.layer.layerDef,
zoomToFeatures: false,
features: layer
features: layer,
})
}
const snappedLocation = new SnappingFeatureSource(
@ -110,28 +110,30 @@
maxDistance: maxSnapDistance ?? 15,
allowUnsnapped: true,
snappedTo,
snapLocation: value
snapLocation: value,
}
)
const withCorrectedAttributes = new StaticFeatureSource(
snappedLocation.features.mapD(feats => feats.map(f => {
const properties = {
...f.properties,
...presetPropertiesUnpacked
}
properties["_referencing_ways"] = f.properties["snapped-to"]
return ({
...f,
properties
snappedLocation.features.mapD((feats) =>
feats.map((f) => {
const properties = {
...f.properties,
...presetPropertiesUnpacked,
}
properties["_referencing_ways"] = f.properties["snapped-to"]
return {
...f,
properties,
}
})
}))
)
)
// The actual point to be created, snapped at the new location
new ShowDataLayer(map, {
layer: targetLayer,
features: withCorrectedAttributes
features: withCorrectedAttributes,
})
withCorrectedAttributes.features.addCallbackAndRunD(f => console.log("Snapped point is", f))
withCorrectedAttributes.features.addCallbackAndRunD((f) => console.log("Snapped point is", f))
}
</script>

View file

@ -22,8 +22,7 @@
arialabel={Translations.t.general.labels.background}
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}
>
<StyleLoadingIndicator map={map ?? state.map} rasterLayer={state.mapProperties.rasterLayer} >
<StyleLoadingIndicator map={map ?? state.map} rasterLayer={state.mapProperties.rasterLayer}>
<Square3Stack3dIcon class="h-6 w-6" />
</StyleLoadingIndicator>
{#if !hideTooltip}

View file

@ -13,7 +13,7 @@
export let layer: LayerConfig
export let selectedElement: Feature
let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(
selectedElement.properties.id,
selectedElement.properties.id
)
$: {
tags = state.featureProperties.getStore(selectedElement.properties.id)
@ -43,7 +43,7 @@
class="no-weblate title-icons links-as-button mr-2 flex flex-row flex-wrap items-center gap-x-0.5 pt-0.5 sm:pt-1"
>
{#each layer.titleIcons as titleIconConfig}
{#if (titleIconConfig.condition?.matchesProperties($tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({ ...$metatags, ...$tags }) ?? true) && titleIconConfig.IsKnown($tags)}
{#if (titleIconConfig.condition?.matchesProperties($tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ...$metatags, ...$tags } ) ?? true) && titleIconConfig.IsKnown($tags)}
<div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}>
<TagRenderingAnswer
config={titleIconConfig}
@ -58,28 +58,33 @@
{/each}
{#if $isTesting || $isDebugging}
<a class="subtle" href="https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Layers/{layer.id}.md"
target="_blank" rel="noreferrer noopener ">{layer.id}</a>
<a
class="subtle"
href="https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Layers/{layer.id}.md"
target="_blank"
rel="noreferrer noopener "
>
{layer.id}
</a>
{/if}
</div>
</div>
{/if}
</div>
<slot name="close-button">
<button
class="mt-2 h-fit shrink-0 rounded-full border-none p-0"
on:click={() => state.selectedElement.setData(undefined)}
style="border: 0 !important; padding: 0 !important;"
use:ariaLabel={Translations.t.general.backToMap}
>
<XCircleIcon aria-hidden={true} class="h-8 w-8" />
</button>
<button
class="mt-2 h-fit shrink-0 rounded-full border-none p-0"
on:click={() => state.selectedElement.setData(undefined)}
style="border: 0 !important; padding: 0 !important;"
use:ariaLabel={Translations.t.general.backToMap}
>
<XCircleIcon aria-hidden={true} class="h-8 w-8" />
</button>
</slot>
</div>
<style>
:global(.title-icons a) {
display: block !important;
}
:global(.title-icons a) {
display: block !important;
}
</style>

View file

@ -20,19 +20,22 @@
selectedElement.properties.id
)
let layer: LayerConfig = selectedElement.properties.id === "settings" ? UserRelatedState.usersettingsConfig : state.layout.getMatchingLayer(tags.data)
let layer: LayerConfig =
selectedElement.properties.id === "settings"
? UserRelatedState.usersettingsConfig
: state.layout.getMatchingLayer(tags.data)
let stillMatches = tags.map(tags => !layer?.source?.osmTags || layer.source.osmTags?.matchesProperties(tags))
let stillMatches = tags.map(
(tags) => !layer?.source?.osmTags || layer.source.osmTags?.matchesProperties(tags)
)
let _metatags: Record<string, string>
if(state?.userRelatedState?.preferencesAsTags){
onDestroy(
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
_metatags = tags
}),
)
if (state?.userRelatedState?.preferencesAsTags) {
onDestroy(
state.userRelatedState.preferencesAsTags.addCallbackAndRun((tags) => {
_metatags = tags
})
)
}
let knownTagRenderings: Store<TagRenderingConfig[]> = tags.mapD((tgs) =>
@ -40,8 +43,8 @@
(config) =>
(config.condition?.matchesProperties(tgs) ?? true) &&
(config.metacondition?.matchesProperties({ ...tgs, ..._metatags }) ?? true) &&
config.IsKnown(tgs),
),
config.IsKnown(tgs)
)
)
</script>
@ -52,13 +55,12 @@
{:else if $tags._deleted === "yes"}
<div class="flex w-full flex-col p-2">
<div aria-live="assertive" class="alert flex items-center justify-center self-stretch">
<Delete_icon class="w-8 h-8 m-2" />
<Delete_icon class="m-2 h-8 w-8" />
<Tr t={Translations.t.delete.isDeleted} />
</div>
<BackButton clss="self-stretch mt-4" on:click={() => state.selectedElement.setData(undefined)}>
<Tr t={Translations.t.general.returnToTheMap} />
</BackButton>
</div>
{:else}
<div

View file

@ -71,10 +71,8 @@
</script>
<div class="flex flex-col">
<div class="flex justify-between items-start">
<div class="flex items-start justify-between">
<div class="flex flex-col">
<Tr t={tr.intro} />
<div class="flex">
{#if typeof navigator?.share === "function"}
@ -92,10 +90,10 @@
</div>
</div>
</div>
<ToSvelte construct={() => new Img(new Qr(linkToShare).toImageElement(125)).SetStyle(
"width: 125px"
)} />
<ToSvelte
construct={() => new Img(new Qr(linkToShare).toImageElement(125)).SetStyle("width: 125px")}
/>
</div>
<div class="flex justify-center">
@ -106,8 +104,7 @@
<Tr t={tr.embedIntro} />
<div class="flex flex-col interactive p-1">
<div class="interactive flex flex-col p-1">
<div class="literal-code m-1">
&lt;span class="literal-code iframe-code-block"&gt; <br />
&lt;iframe src="{linkToShare}"
@ -127,7 +124,7 @@
</label>
<label>
<input bind:checked={enableLogin} type="checkbox" id="share_enable_login"/>
<input bind:checked={enableLogin} type="checkbox" id="share_enable_login" />
<Tr t={tr.fsUserbadge} />
</label>
</div>

View file

@ -17,10 +17,9 @@ export default class StatisticsForLayerPanel extends VariableUiElement {
return new Loading("Loading data")
}
if (features.length === 0) {
return new Combine([
"No elements in view for layer ",
layer.id
]).SetClass("block")
return new Combine(["No elements in view for layer ", layer.id]).SetClass(
"block"
)
}
const els: BaseUIElement[] = []
const featuresForLayer = features

View file

@ -8,8 +8,8 @@ import { OsmFeature } from "../../Models/OsmFeature"
export interface TagRenderingChartOptions {
groupToOtherCutoff?: 3 | number
sort?: boolean,
hideUnkown?: boolean,
sort?: boolean
hideUnkown?: boolean
hideNotApplicable?: boolean
}
@ -21,8 +21,8 @@ export class StackedRenderingChart extends ChartJs {
period: "day" | "month"
groupToOtherCutoff?: 3 | number
// If given, take the sum of these fields to get the feature weight
sumFields?: string[],
hideUnknown?: boolean,
sumFields?: string[]
hideUnknown?: boolean
hideNotApplicable?: boolean
}
) {
@ -30,7 +30,7 @@ export class StackedRenderingChart extends ChartJs {
sort: true,
groupToOtherCutoff: options?.groupToOtherCutoff,
hideNotApplicable: options?.hideNotApplicable,
hideUnkown: options?.hideUnknown
hideUnkown: options?.hideUnknown,
})
if (labels === undefined || data === undefined) {
console.error(
@ -121,13 +121,13 @@ export class StackedRenderingChart extends ChartJs {
datasets.push({
data: countsPerDay,
backgroundColor,
label
label,
})
}
const perDayData = {
labels: trimmedDays,
datasets
datasets,
}
const config = <ChartConfiguration>{
@ -136,17 +136,17 @@ export class StackedRenderingChart extends ChartJs {
options: {
responsive: true,
legend: {
display: false
display: false,
},
scales: {
x: {
stacked: true
stacked: true,
},
y: {
stacked: true
}
}
}
stacked: true,
},
},
},
}
super(config)
}
@ -199,7 +199,7 @@ export default class TagRenderingChart extends Combine {
"rgba(255, 206, 86, 0.2)",
"rgba(75, 192, 192, 0.2)",
"rgba(153, 102, 255, 0.2)",
"rgba(255, 159, 64, 0.2)"
"rgba(255, 159, 64, 0.2)",
]
public static readonly borderColors = [
@ -208,7 +208,7 @@ export default class TagRenderingChart extends Combine {
"rgba(255, 206, 86, 1)",
"rgba(75, 192, 192, 1)",
"rgba(153, 102, 255, 1)",
"rgba(255, 159, 64, 1)"
"rgba(255, 159, 64, 1)",
]
/**
@ -244,12 +244,12 @@ export default class TagRenderingChart extends Combine {
const borderColor = [
TagRenderingChart.unkownBorderColor,
TagRenderingChart.otherBorderColor,
TagRenderingChart.notApplicableBorderColor
TagRenderingChart.notApplicableBorderColor,
]
const backgroundColor = [
TagRenderingChart.unkownColor,
TagRenderingChart.otherColor,
TagRenderingChart.notApplicableColor
TagRenderingChart.notApplicableColor,
]
while (borderColor.length < data.length) {
@ -281,17 +281,17 @@ export default class TagRenderingChart extends Combine {
backgroundColor,
borderColor,
borderWidth: 1,
label: undefined
}
]
label: undefined,
},
],
},
options: {
plugins: {
legend: {
display: !barchartMode
}
}
}
display: !barchartMode,
},
},
},
}
const chart = new ChartJs(config).SetClass(options?.chartclasses ?? "w-32 h-32")
@ -302,7 +302,7 @@ export default class TagRenderingChart extends Combine {
super([
options?.includeTitle ? tagRendering.question.Clone() ?? tagRendering.id : undefined,
chart
chart,
])
this.SetClass("block")
@ -402,15 +402,10 @@ export default class TagRenderingChart extends Combine {
labels.push("Other")
if (!options.hideNotApplicable) {
data.push(notApplicable)
labels.push(
"Not applicable"
)
labels.push("Not applicable")
}
data.push(...categoryCounts,
...otherData)
labels.push(
...(mappings?.map((m) => m.then.txt) ?? []),
...otherLabels)
data.push(...categoryCounts, ...otherData)
labels.push(...(mappings?.map((m) => m.then.txt) ?? []), ...otherLabels)
return { labels, data }
}

View file

@ -85,10 +85,8 @@
</script>
{#if theme.id !== personal.id || $unlockedPersonal}
<a
class={"w-full button text-ellipsis"}
href={$href}
> <img src={theme.icon} class="m-1 mr-2 block h-11 w-11 sm:m-2 sm:mr-4" alt="" />
<a class={"button w-full text-ellipsis"} href={$href}>
<img src={theme.icon} class="m-1 mr-2 block h-11 w-11 sm:m-2 sm:mr-4" alt="" />
<span class="flex flex-col overflow-hidden text-ellipsis">
<Tr t={title} />
@ -97,5 +95,6 @@
<Tr t={Translations.t.general.morescreen.enterToOpen} />
</span>
{/if}
</span></a>
</span>
</a>
{/if}

View file

@ -29,13 +29,13 @@
const splitpoint_style = new LayerConfig(
<LayerConfigJson>split_point,
"(BUILTIN) SplitRoadWizard.ts",
true,
true
)
const splitroad_style = new LayerConfig(
<LayerConfigJson>split_road,
"(BUILTIN) SplitRoadWizard.ts",
true,
true
)
/**
@ -105,7 +105,7 @@
})
</script>
<div class="h-full w-full relative">
<div class="relative h-full w-full">
<MaplibreMap {map} mapProperties={adaptor} />
<SmallZoomButtons {adaptor} />
</div>

View file

@ -38,7 +38,7 @@
tags.data,
{
theme: state.layout.id,
changeType: "import"
changeType: "import",
}
)
await state.changes.applyChanges(await change.CreateChangeDescriptions())
@ -47,43 +47,51 @@
let _country = $tags["_country"]
let mockPropertiesOsm = { id: feature.properties.id, [key]: $tags[key], _country }
let mockPropertiesExternal = { id: feature.properties.id, [key]: externalProperties[key], _country }
let trsWithKeys = layer.tagRenderings.filter(tr => {
const keys: string[] = [].concat(...tr.usedTags().map(t => t.usedKeys()))
let mockPropertiesExternal = {
id: feature.properties.id,
[key]: externalProperties[key],
_country,
}
let trsWithKeys = layer.tagRenderings.filter((tr) => {
const keys: string[] = [].concat(...tr.usedTags().map((t) => t.usedKeys()))
return keys.indexOf(key) >= 0
})
let renderingBoth = trsWithKeys.find(tr => tr.IsKnown(mockPropertiesOsm) && tr.IsKnown(mockPropertiesExternal))
let renderingExternal = renderingBoth ?? trsWithKeys.find(tr => tr.IsKnown(mockPropertiesExternal))
let renderingBoth = trsWithKeys.find(
(tr) => tr.IsKnown(mockPropertiesOsm) && tr.IsKnown(mockPropertiesExternal)
)
let renderingExternal =
renderingBoth ?? trsWithKeys.find((tr) => tr.IsKnown(mockPropertiesExternal))
let onOverwrite = false
const t = Translations.t.external
</script>
<div>
<div class="py-1 px-2 interactive flex w-full justify-between">
<div class="interactive flex w-full justify-between py-1 px-2">
<div class="flex flex-col">
<div>
{#if renderingExternal}
<TagRenderingAnswer tags={new UIEventSource(mockPropertiesExternal)} selectedElement={feature}
config={renderingExternal}
{layer} {state} />
<TagRenderingAnswer
tags={new UIEventSource(mockPropertiesExternal)}
selectedElement={feature}
config={renderingExternal}
{layer}
{state}
/>
{:else}
<div class="flex gap-x-1 items-center">
<b>{key}</b>{externalProperties[key]}
<div class="flex items-center gap-x-1">
<b>{key}</b>
{externalProperties[key]}
</div>
{/if}
</div>
{#if !readonly && ( $isTesting || $isDebug || $showTags === "yes" || $showTags === "always" || $showTags === "full")}
{#if !readonly && ($isTesting || $isDebug || $showTags === "yes" || $showTags === "always" || $showTags === "full")}
<div class="subtle text-sm">
{#if $tags[key] !== undefined}
<span>
OSM:
{key}={$tags[key]}
</span>
<span>
OSM:
{key}={$tags[key]}
</span>
{/if}
<span>
{key}= {externalProperties[key]}
@ -92,21 +100,20 @@
{/if}
</div>
{#if !readonly}
{#if currentStep === "init"}
<button class="small" on:click={() => apply(key)}
on:mouseover={() => onOverwrite = true}
on:focus={() => onOverwrite = true}
on:blur={() => onOverwrite = false}
on:mouseout={() => onOverwrite = false }
<button
class="small"
on:click={() => apply(key)}
on:mouseover={() => (onOverwrite = true)}
on:focus={() => (onOverwrite = true)}
on:blur={() => (onOverwrite = false)}
on:mouseout={() => (onOverwrite = false)}
>
{#if $tags[key]}
<Tr t={t.overwrite} />
{:else}
<Tr t={t.apply} />
{/if}
</button>
{:else if currentStep === "applying"}
@ -114,7 +121,6 @@
{:else if currentStep === "done"}
<div class="thanks">
<Tr t={t.done} />
</div>
{:else}
<div class="alert">
@ -122,22 +128,26 @@
</div>
{/if}
{/if}
</div>
{#if $tags[key] && $tags[key] !== externalProperties[key]}
<div class:glowing-shadow={onOverwrite}>
<span class="subtle">
<Tr t={t.currentInOsmIs} />
</span>
<span class="subtle">
<Tr t={t.currentInOsmIs} />
</span>
{#if renderingBoth}
<TagRenderingAnswer tags={new UIEventSource(mockPropertiesOsm)} selectedElement={feature} config={renderingBoth}
{layer} {state} />
<TagRenderingAnswer
tags={new UIEventSource(mockPropertiesOsm)}
selectedElement={feature}
config={renderingBoth}
{layer}
{state}
/>
{:else}
<div class="flex gap-x-2 items-center">
<b>{key}</b> {$tags[key]}
<div class="flex items-center gap-x-2">
<b>{key}</b>
{$tags[key]}
</div>
{/if}
</div>
{/if}
</div>

View file

@ -49,32 +49,30 @@
return osmProperties[k] === undefined && typeof externalProperties[k] === "string"
})
// let same = propertyKeysExternal.filter((key) => osmProperties[key] === externalProperties[key])
let different = propertyKeysExternal.filter(
(key) => {
if (key.startsWith("_")) {
return false
}
if (osmProperties[key] === undefined) {
return false
}
if (typeof externalProperties[key] !== "string") {
return false
}
if (osmProperties[key] === externalProperties[key]) {
return false
}
if (key === "website") {
const osmCanon = new URL(osmProperties[key]).toString()
const externalCanon = new URL(externalProperties[key]).toString()
if (osmCanon === externalCanon) {
return false
}
}
return true
let different = propertyKeysExternal.filter((key) => {
if (key.startsWith("_")) {
return false
}
)
if (osmProperties[key] === undefined) {
return false
}
if (typeof externalProperties[key] !== "string") {
return false
}
if (osmProperties[key] === externalProperties[key]) {
return false
}
if (key === "website") {
const osmCanon = new URL(osmProperties[key]).toString()
const externalCanon = new URL(externalProperties[key]).toString()
if (osmCanon === externalCanon) {
return false
}
}
return true
})
let currentStep: "init" | "applying_all" | "all_applied" = "init"
let applyAllHovered = false
@ -84,12 +82,13 @@
const tagsToApply = missing.map((k) => new Tag(k, externalProperties[k]))
const change = new ChangeTagAction(tags.data.id, new And(tagsToApply), tags.data, {
theme: state.layout.id,
changeType: "import"
changeType: "import",
})
await state.changes.applyChanges(await change.CreateChangeDescriptions())
currentStep = "all_applied"
}
</script>
{#if propertyKeysExternal.length === 0 && knownImages.size + unknownImages.length === 0}
<Tr cls="subtle" t={t.noDataLoaded} />
{:else if unknownImages.length === 0 && missing.length === 0 && different.length === 0}
@ -98,9 +97,9 @@
<Tr t={t.allIncluded} />
</div>
{:else}
<div class="low-interaction p-1 border-interactive">
<div class="low-interaction border-interactive p-1">
{#if !readonly}
<Tr t={t.loadedFrom.Subs({url: sourceUrl, source: sourceUrl})} />
<Tr t={t.loadedFrom.Subs({ url: sourceUrl, source: sourceUrl })} />
<h3>
<Tr t={t.conflicting.title} />
</h3>
@ -113,25 +112,41 @@
{#if different.length > 0}
{#each different as key}
<div class="mx-2 rounded-2xl">
<ComparisonAction {key} {state} {tags} {externalProperties} {layer} {feature} {readonly} />
<ComparisonAction
{key}
{state}
{tags}
{externalProperties}
{layer}
{feature}
{readonly}
/>
</div>
{/each}
{/if}
{#if missing.length > 0}
{#if currentStep === "init"}
{#each missing as key}
<div class:glowing-shadow={applyAllHovered} class="mx-2 rounded-2xl">
<ComparisonAction {key} {state} {tags} {externalProperties} {layer} {feature} {readonly} />
<ComparisonAction
{key}
{state}
{tags}
{externalProperties}
{layer}
{feature}
{readonly}
/>
</div>
{/each}
{#if !readonly && missing.length > 1}
<button on:click={() => applyAllMissing()}
on:mouseover={() => applyAllHovered = true}
on:focus={() => applyAllHovered = true}
on:blur={() => applyAllHovered = false}
on:mouseout={() => applyAllHovered = false }
<button
on:click={() => applyAllMissing()}
on:mouseover={() => (applyAllHovered = true)}
on:focus={() => (applyAllHovered = true)}
on:blur={() => (applyAllHovered = false)}
on:mouseout={() => (applyAllHovered = false)}
>
<Tr t={t.applyAll} />
</button>
@ -144,7 +159,6 @@
</div>
{/if}
{/if}
</div>
{#if unknownImages.length > 0}
@ -166,13 +180,13 @@
{tags}
{state}
image={{
pictureUrl: image,
provider: "Velopark",
thumbUrl: image,
details: undefined,
coordinates: undefined,
osmTags: { image },
}}
pictureUrl: image,
provider: "Velopark",
thumbUrl: image,
details: undefined,
coordinates: undefined,
osmTags: { image },
}}
{feature}
{layer}
/>
@ -181,10 +195,8 @@
{/if}
{#if externalProperties["_last_edit_timestamp"] !== undefined}
<span class="subtle text-sm">
External data has been last modified on {externalProperties["_last_edit_timestamp"]}
External data has been last modified on {externalProperties["_last_edit_timestamp"]}
</span>
{/if}
</div>
{/if}

View file

@ -1,34 +1,39 @@
<script lang="ts">
/**
* The comparison tool loads json-data from a speficied URL, eventually post-processes it
* and compares it with the current object
*/
import Loading from "../Base/Loading.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import ComparisonTable from "./ComparisonTable.svelte"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { Feature } from "geojson"
import type { OsmTags } from "../../Models/OsmFeature"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
/**
* The comparison tool loads json-data from a speficied URL, eventually post-processes it
* and compares it with the current object
*/
import Loading from "../Base/Loading.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import ComparisonTable from "./ComparisonTable.svelte"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import type { Feature } from "geojson"
import type { OsmTags } from "../../Models/OsmFeature"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
export let externalData: Store<{ success: {content: Record<string, string> } } | { error: string } | undefined | null /* null if no URL is found, undefined if loading*/>
export let state: SpecialVisualizationState
export let tags: UIEventSource<OsmTags>
export let layer: LayerConfig
export let feature: Feature
export let readonly = false
export let sourceUrl: Store<string>
export let externalData: Store<
| { success: { content: Record<string, string> } }
| { error: string }
| undefined
| null /* null if no URL is found, undefined if loading*/
>
export let state: SpecialVisualizationState
export let tags: UIEventSource<OsmTags>
export let layer: LayerConfig
export let feature: Feature
export let readonly = false
export let sourceUrl: Store<string>
</script>
{#if !$sourceUrl}
<!-- empty block -->
{:else if $externalData === undefined}
<Loading/>
<Loading />
{:else if $externalData["error"] !== undefined}
<div class="alert flex">
<Tr t={Translations.t.general.error}/>
<Tr t={Translations.t.general.error} />
{$externalData["error"]}
</div>
{:else if $externalData["success"] !== undefined}

View file

@ -13,10 +13,7 @@
export let extension: string
export let mimetype: string
export let construct: (
title: string,
status?: UIEventSource<string>
) => Promise<Blob | string>
export let construct: (title: string, status?: UIEventSource<string>) => Promise<Blob | string>
export let mainText: Translation
export let helperText: Translation
@ -28,7 +25,7 @@
let status: UIEventSource<string> = new UIEventSource<string>(undefined)
async function clicked() {
isExporting = true
const gpsLayer = state.layerState.filteredLayers.get(<PriviligedLayerType>"gps_location")
state.userRelatedState.preferencesAsTags.data["__showTimeSensitiveIcons"] = "no"
state.userRelatedState.preferencesAsTags.ping()

View file

@ -38,7 +38,7 @@
mapExtent: state.mapProperties.bounds.data,
width: maindiv.offsetWidth,
height: maindiv.offsetHeight,
noSelfIntersectingLines
noSelfIntersectingLines,
})
}
@ -46,13 +46,17 @@
let customHeight = LocalStorageSource.Get("custom-png-height", "20")
async function offerCustomPng(): Promise<Blob> {
console.log("Creating a custom size png with dimensions", customWidth.data + "mm *", customHeight.data + "mm")
console.log(
"Creating a custom size png with dimensions",
customWidth.data + "mm *",
customHeight.data + "mm"
)
const creator = new PngMapCreator(state, {
height: Number(customHeight.data), width: Number(customWidth.data)
height: Number(customHeight.data),
width: Number(customWidth.data),
})
return await creator.CreatePng("belowmap")
}
</script>
{#if $isLoading}
@ -123,25 +127,26 @@
{/each}
</div>
<div class="low-interaction p-2 mt-4">
<div class="low-interaction mt-4 p-2">
<h3 class="m-0 mb-2">
<Tr t={t.custom.title}/></h3>
<div class="flex">
<Tr t={t.custom.width} />
<ValidatedInput {state} type="pnat" value={customWidth} />
</div>
<div class="flex">
<Tr t={t.custom.height} />
<ValidatedInput {state} type="pnat" value={customHeight} />
</div>
<DownloadButton
mainText={t.custom.download.Subs({width: $customWidth, height: $customHeight})}
helperText={t.custom.downloadHelper}
extension="png"
construct={() => offerCustomPng()}
{state}
mimetype="image/png"
/>
<Tr t={t.custom.title} />
</h3>
<div class="flex">
<Tr t={t.custom.width} />
<ValidatedInput {state} type="pnat" value={customWidth} />
</div>
<div class="flex">
<Tr t={t.custom.height} />
<ValidatedInput {state} type="pnat" value={customHeight} />
</div>
<DownloadButton
mainText={t.custom.download.Subs({ width: $customWidth, height: $customHeight })}
helperText={t.custom.downloadHelper}
extension="png"
construct={() => offerCustomPng()}
{state}
mimetype="image/png"
/>
</div>
<Tr cls="link-underline" t={t.licenseInfo} />

View file

@ -38,7 +38,7 @@
</div>
{/if}
<div class="flex justify-between w-full gap-x-1">
<div class="flex w-full justify-between gap-x-1">
{#if $license.license !== undefined || $license.licenseShortName !== undefined}
<div>
{$license?.license ?? $license?.licenseShortName}
@ -58,8 +58,6 @@
{$license.date.toLocaleDateString()}
</div>
{/if}
</div>
</div>
{/if}

View file

@ -1,15 +1,13 @@
<script lang="ts">
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
export let failed: number
const t = Translations.t.image
</script>
<div class="alert flex">
<div class="alert flex">
<div class="flex flex-col items-start">
{#if failed === 1}
<Tr t={t.upload.one.failed} />
@ -20,7 +18,7 @@
<Tr cls="text-xs" t={t.upload.failReasonsAdvanced} />
</div>
<button
class="mt-2 h-fit shrink-0 rounded-full border-none p-0 pointer-events-auto"
class="pointer-events-auto mt-2 h-fit shrink-0 rounded-full border-none p-0"
on:click
style="border: 0 !important; padding: 0 !important;"
>

View file

@ -29,7 +29,9 @@
</script>
{#if $debugging}
<div class="low-interaction">Started {$uploadStarted} Done {$uploadFinished} Retry {$retried} Err {$failed}</div>
<div class="low-interaction">
Started {$uploadStarted} Done {$uploadFinished} Retry {$retried} Err {$failed}
</div>
{/if}
{#if dismissed === $uploadStarted}
<!-- We don't show anything as we ignore this number of failed items-->
@ -39,18 +41,18 @@
<Tr cls="thanks" t={t.upload.one.done} />
{/if}
{:else if $failed === 1}
<UploadFailedMessage failed={$failed} on:click={() => dismissed = $failed}/>
<UploadFailedMessage failed={$failed} on:click={() => (dismissed = $failed)} />
{:else if $retried === 1}
<div class="alert">
<Loading>
<Tr t={t.upload.one.retrying} />
</Loading>
<Loading>
<Tr t={t.upload.one.retrying} />
</Loading>
</div>
{:else}
<div class="alert">
<Loading>
<Tr t={t.upload.one.uploading} />
</Loading>
<Loading>
<Tr t={t.upload.one.uploading} />
</Loading>
</div>
{/if}
{:else if $uploadStarted > 1}
@ -75,7 +77,6 @@
</Loading>
{/if}
{#if $failed > 0}
<UploadFailedMessage failed={$failed} on:click={() => dismissed = $failed}/>
<UploadFailedMessage failed={$failed} on:click={() => (dismissed = $failed)} />
{/if}
{/if}

View file

@ -67,7 +67,7 @@
if (!rangeIsShown) {
new ShowDataLayer(map, {
layer: new LayerConfig(<any> boundsdisplay),
layer: new LayerConfig(<any>boundsdisplay),
features: new StaticFeatureSource([
turf.circle(c, maxDistanceInMeters, {
units: "meters",
@ -84,7 +84,11 @@
<div class="min-h-32 relative h-full cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 h-full w-full cursor-pointer">
<MaplibreMap center={{ lng: initialCoordinate.lon, lat: initialCoordinate.lat }} {map} mapProperties={mla}/>
<MaplibreMap
center={{ lng: initialCoordinate.lon, lat: initialCoordinate.lat }}
{map}
mapProperties={mla}
/>
</div>
<div

View file

@ -64,13 +64,13 @@
update()
})
)
</script>
<div class="flex flex-col gap-y-1">
<div class="interactive m-1 mt-2 flex space-x-1 font-bold">
<span>
{prefix}
</span>
<span>
{prefix}
</span>
<select bind:value={$currentLang}>
{#each allLanguages as language}
<option value={language}>
@ -89,14 +89,18 @@
on:submit={() => dispatch("submit")}
/>
<span>
{postfix}
</span>
{postfix}
</span>
</div>
You have currently set translations for
<ul>
{#each Object.keys($translations) as l}
<li><button class="small" on:click={() => currentLang.setData(l)}><b>{l}:</b> {$translations[l]}</button></li>
<li>
<button class="small" on:click={() => currentLang.setData(l)}>
<b>{l}:</b>
{$translations[l]}
</button>
</li>
{/each}
</ul>
</div>

View file

@ -61,7 +61,7 @@ export default class InputHelpers {
mapProperties = { ...mapProperties, zoom: new UIEventSource<number>(zoom) }
}
if (!mapProperties.rasterLayer) {
/* mapProperties = {
/* mapProperties = {
...mapProperties, rasterLayer: properties?.mapProperties?.rasterLayer
}*/
}
@ -76,8 +76,11 @@ export default class InputHelpers {
const args = inputHelperOptions.args ?? []
const searchKey: string = <string>args[0] ?? "name"
const searchFor: string = searchKey.split(";").map(k => inputHelperOptions.feature?.properties[k]?.toLowerCase())
.find(foundValue => !!foundValue) ?? ""
const searchFor: string =
searchKey
.split(";")
.map((k) => inputHelperOptions.feature?.properties[k]?.toLowerCase())
.find((foundValue) => !!foundValue) ?? ""
let searchForValue: UIEventSource<string> = new UIEventSource(searchFor)
const options: any = args[1]
@ -125,7 +128,7 @@ export default class InputHelpers {
value,
searchText: searchForValue,
instanceOf,
notInstanceOf
notInstanceOf,
})
}
}

View file

@ -47,31 +47,31 @@
<LanguageIcon class="mr-1 h-4 w-4 shrink-0" aria-hidden="true" />
</label>
<Dropdown cls="max-w-full" value={assignTo} id="pick-language">
{#if preferredFiltered}
{#each preferredFiltered as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
({language_translations[language]?.[$current] ?? language})
{/if}
</option>
{/each}
<option disabled />
{/if}
{#each availableLanguages.filter((l) => l !== "_context") as language}
<Dropdown cls="max-w-full" value={assignTo} id="pick-language">
{#if preferredFiltered}
{#each preferredFiltered as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
{#if language_translations[language]?.[$current] !== undefined}
({language_translations[language]?.[$current] + " - " + language ?? language})
{:else}
({language})
{/if}
({language_translations[language]?.[$current] ?? language})
{/if}
</option>
{/each}
</Dropdown>
<option disabled />
{/if}
{#each availableLanguages.filter((l) => l !== "_context") as language}
<option value={language} class="font-bold">
{native[language] ?? ""}
{#if language !== $current}
{#if language_translations[language]?.[$current] !== undefined}
({language_translations[language]?.[$current] + " - " + language ?? language})
{:else}
({language})
{/if}
{/if}
</option>
{/each}
</Dropdown>
</form>
{/if}

View file

@ -51,5 +51,4 @@ export default class OpeningHoursValidator extends Validator {
])
)
}
}

View file

@ -45,7 +45,7 @@ export default class UrlValidator extends Validator {
"pk_medium",
"pk_campaign",
"pk_content",
"pk_kwd"
"pk_kwd",
]
for (const dontLike of blacklistedTrackingParams) {
url.searchParams.delete(dontLike.toLowerCase())

View file

@ -26,13 +26,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
"dragRotate",
"dragPan",
"keyboard",
"touchZoomRotate"
"touchZoomRotate",
]
private static maplibre_zoom_handlers = [
"scrollZoom",
"boxZoom",
"doubleClickZoom",
"touchZoomRotate"
"touchZoomRotate",
]
readonly location: UIEventSource<{ lon: number; lat: number }>
readonly zoom: UIEventSource<number>
@ -62,8 +62,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
if (!MapLibreAdaptor.pmtilesInited) {
maplibregl.addProtocol("pmtiles", new Protocol().tile)
MapLibreAdaptor.pmtilesInited = true
console.log("PM-tiles protocol added" +
"")
console.log("PM-tiles protocol added" + "")
}
this._maplibreMap = maplibreMap
@ -113,7 +112,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
}
maplibreMap.addCallbackAndRunD((map) => {
map.on("load", () => {
self.MoveMapToCurrentLoc(self.location.data)
self.SetZoom(self.zoom.data)
@ -216,9 +214,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
return {
map: mlmap,
ui: new SvelteUIElement(MaplibreMap, {
map: mlmap
map: mlmap,
}),
mapproperties: new MapLibreAdaptor(mlmap)
mapproperties: new MapLibreAdaptor(mlmap),
}
}
@ -286,7 +284,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
) {
const event = {
date: new Date(),
key: key
key: key,
}
for (let i = 0; i < this._onKeyNavigation.length; i++) {
@ -330,13 +328,19 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
rescaleIcons: number,
pixelRatio: number
) {
{
const allimages = element.getElementsByTagName("img")
for (const img of Array.from(allimages)) {
let isLoaded: boolean = false
while (!isLoaded) {
console.log("Waiting for image", img.src, "to load", img.complete, img.naturalWidth, img)
console.log(
"Waiting for image",
img.src,
"to load",
img.complete,
img.naturalWidth,
img
)
await Utils.waitFor(250)
isLoaded = img.complete && img.width > 0
}
@ -349,22 +353,31 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
element.style.transform = ""
const offset = style.match(/translate\(([-0-9]+)%, ?([-0-9]+)%\)/)
let labels =<HTMLElement[]> Array.from(element.getElementsByClassName("marker-label"))
const origLabelTransforms = labels.map(l => l.style.transform)
let labels = <HTMLElement[]>Array.from(element.getElementsByClassName("marker-label"))
const origLabelTransforms = labels.map((l) => l.style.transform)
// We save the original width (`w`) and height (`h`) in order to restore them later on
const w = element.style.width
const h = Number(element.style.height)
const targetW = Math.max(element.getBoundingClientRect().width * 4,
...labels.map(l => l.getBoundingClientRect().width))
const targetH = element.getBoundingClientRect().height +
Math.max(...labels.map(l => l.getBoundingClientRect().height * 2 /* A bit of buffer to catch eventual 'margin-top'*/))
const targetW = Math.max(
element.getBoundingClientRect().width * 4,
...labels.map((l) => l.getBoundingClientRect().width)
)
const targetH =
element.getBoundingClientRect().height +
Math.max(
...labels.map(
(l) =>
l.getBoundingClientRect().height *
2 /* A bit of buffer to catch eventual 'margin-top'*/
)
)
// Force a wider view for icon badges
element.style.width = targetW + "px"
// Force more height to include labels
element.style.height = targetH + "px"
element.classList.add("w-full", "flex", "flex-col", "items-center")
labels.forEach(l => {
labels.forEach((l) => {
l.style.transform = ""
})
await Utils.awaitAnimationFrame()
@ -386,13 +399,12 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
y *= pixelRatio
try {
const xdiff = img.width * rescaleIcons / 2
const xdiff = (img.width * rescaleIcons) / 2
drawOn.drawImage(img, x - xdiff, y, img.width * rescaleIcons, img.height * rescaleIcons)
} catch (e) {
console.log("Could not draw image because of", e)
}
element.classList.remove("w-full", "flex", "flex-col", "items-center")
}
/**
@ -461,7 +473,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
const bounds = map.getBounds()
const bbox = new BBox([
[bounds.getEast(), bounds.getNorth()],
[bounds.getWest(), bounds.getSouth()]
[bounds.getWest(), bounds.getSouth()],
])
if (this.bounds.data === undefined || !isSetup) {
this.bounds.setData(bbox)
@ -639,14 +651,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
type: "raster-dem",
url:
"https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=" +
Constants.maptilerApiKey
Constants.maptilerApiKey,
})
try {
while (!map?.isStyleLoaded()) {
await Utils.waitFor(250)
}
map.setTerrain({
source: id
source: id,
})
} catch (e) {
console.error(e)

View file

@ -25,7 +25,6 @@
let container: HTMLElement
let _map: Map
onMount(() => {
const { lon, lat } = mapProperties?.location?.data ?? { lon: 0, lat: 0 }
@ -33,7 +32,10 @@
const rasterLayer: RasterLayerProperties = mapProperties?.rasterLayer?.data?.properties
let styleUrl: string
if (rasterLayer?.type === "vector") {
styleUrl = rasterLayer?.style ?? rasterLayer?.url ?? AvailableRasterLayers.defaultBackgroundLayer.properties.url
styleUrl =
rasterLayer?.style ??
rasterLayer?.url ??
AvailableRasterLayers.defaultBackgroundLayer.properties.url
} else {
const defaultLayer = AvailableRasterLayers.defaultBackgroundLayer.properties
styleUrl = defaultLayer.style ?? defaultLayer.url
@ -48,13 +50,13 @@
center: { lng: lon, lat },
maxZoom: 24,
interactive: true,
attributionControl: false
attributionControl: false,
}
_map = new maplibre.Map(options)
window.requestAnimationFrame(() => {
_map.resize()
})
_map.on("load", function() {
_map.on("load", function () {
_map.resize()
const canvas = _map.getCanvas()
if (interactive) {

View file

@ -65,13 +65,12 @@
updateLocation()
window.setTimeout(updateLocation, 150)
window.setTimeout(updateLocation, 500)
}),
})
)
}
</script>
<div class="absolute w-full h-full flex items-center justify-center"
style="z-index: 100">
<div class="absolute flex h-full w-full items-center justify-center" style="z-index: 100">
<StyleLoadingIndicator map={altmap} />
</div>
<MaplibreMap {interactive} map={altmap} mapProperties={altproperties} />

View file

@ -109,8 +109,12 @@ class SingleBackgroundHandler {
const background = this._targetLayer.properties
console.debug("Enabling", background.id)
let addLayerBeforeId = "transit_pier" // this is the first non-landuse item in the stylesheet, we add the raster layer before the roads but above the landuse
if(!map.getLayer(addLayerBeforeId)){
console.warn("Layer", addLayerBeforeId,"not foundhttp://127.0.0.1:1234/theme.html?layout=cyclofix&z=14.8&lat=51.05282501324558&lon=3.720591622281745&layer-range=true")
if (!map.getLayer(addLayerBeforeId)) {
console.warn(
"Layer",
addLayerBeforeId,
"not foundhttp://127.0.0.1:1234/theme.html?layout=cyclofix&z=14.8&lat=51.05282501324558&lon=3.720591622281745&layer-range=true"
)
addLayerBeforeId = undefined
}
if (background.category === "osmbasedmap" || background.category === "map") {
@ -140,15 +144,15 @@ class SingleBackgroundHandler {
type: "raster",
source: background.id,
paint: {
"raster-opacity": 0
}
"raster-opacity": 0,
},
},
addLayerBeforeId
)
this.opacity.addCallbackAndRun((o) => {
try{
try {
map.setPaintProperty(background.id, "raster-opacity", o)
}catch (e) {
} catch (e) {
console.debug("Could not set raster-opacity of", background.id)
return true // This layer probably doesn't exist anymore, so we unregister
}
@ -185,11 +189,13 @@ export default class RasterLayerHandler {
})
}
public static prepareSource(layer: RasterLayerProperties): RasterSourceSpecification | VectorSourceSpecification {
public static prepareSource(
layer: RasterLayerProperties
): RasterSourceSpecification | VectorSourceSpecification {
if (layer.type === "vector") {
const vs: VectorSourceSpecification = {
type: "vector",
url: layer.url
url: layer.url,
}
return vs
}
@ -202,7 +208,7 @@ export default class RasterLayerHandler {
minzoom: layer["min_zoom"] ?? 1,
maxzoom: layer["max_zoom"] ?? 25,
// Bit of a hack, but seems to work
scheme: layer.url.includes("{-y}") ? "tms" : "xyz"
scheme: layer.url.includes("{-y}") ? "tms" : "xyz",
}
}
@ -216,7 +222,7 @@ export default class RasterLayerHandler {
"{width}": "" + size,
"{height}": "" + size,
"{zoom}": "{z}",
"{-y}": "{y}"
"{-y}": "{y}",
}
for (const key in toReplace) {

View file

@ -78,7 +78,7 @@
{#if hasLayers}
<form class="flex h-full w-full flex-col" on:submit|preventDefault={() => {}}>
<button tabindex="-1" on:click={() => apply()} class="m-0 h-full w-full cursor-pointer p-1">
<span class="pointer-events-none h-full w-full relative">
<span class="pointer-events-none relative h-full w-full">
<OverlayMap
interactive={false}
rasterLayer={rasterLayerOnMap}

View file

@ -154,7 +154,7 @@ class PointRenderingLayer {
if (this._onClick) {
const self = this
el.addEventListener("click", function(ev) {
el.addEventListener("click", function (ev) {
ev.preventDefault()
self._onClick(feature)
// Workaround to signal the MapLibreAdaptor to ignore this click
@ -200,7 +200,7 @@ class LineRenderingLayer {
"lineCap",
"offset",
"fill",
"fillColor"
"fillColor",
] as const
private static readonly lineConfigKeysColor = ["color", "fillColor"] as const
@ -264,8 +264,8 @@ class LineRenderingLayer {
"icon-rotation-alignment": "map",
"icon-pitch-alignment": "map",
"icon-image": imgId,
"icon-size": 0.055
}
"icon-size": 0.055,
},
}
const filter = img.if?.asMapboxExpression()
if (filter) {
@ -338,9 +338,9 @@ class LineRenderingLayer {
type: "geojson",
data: {
type: "FeatureCollection",
features
features,
},
promoteId: "id"
promoteId: "id",
})
const linelayer = this._layername + "_line"
const layer: AddLayerObject = {
@ -351,15 +351,15 @@ class LineRenderingLayer {
"line-color": ["feature-state", "color"],
"line-opacity": ["feature-state", "color-opacity"],
"line-width": ["feature-state", "width"],
"line-offset": ["feature-state", "offset"]
"line-offset": ["feature-state", "offset"],
},
layout: {
"line-cap": "round"
}
"line-cap": "round",
},
}
if (this._config.dashArray) {
layer.paint["line-dasharray"] = this._config.dashArray?.split(" ")?.map(s => Number(s)) ?? null
layer.paint["line-dasharray"] =
this._config.dashArray?.split(" ")?.map((s) => Number(s)) ?? null
}
map.addLayer(layer)
@ -393,8 +393,8 @@ class LineRenderingLayer {
layout: {},
paint: {
"fill-color": ["feature-state", "fillColor"],
"fill-opacity": ["feature-state", "fillColor-opacity"]
}
"fill-opacity": ["feature-state", "fillColor-opacity"],
},
})
if (this._onClick) {
map.on("click", polylayer, (e) => {
@ -425,7 +425,7 @@ class LineRenderingLayer {
this.currentSourceData = features
src.setData({
type: "FeatureCollection",
features: this.currentSourceData
features: this.currentSourceData,
})
}
}
@ -509,14 +509,14 @@ export default class ShowDataLayer {
layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)),
features,
{
constructStore: (features, layer) => new SimpleFeatureSource(layer, features)
constructStore: (features, layer) => new SimpleFeatureSource(layer, features),
}
)
perLayer.forEach((fs) => {
new ShowDataLayer(mlmap, {
layer: fs.layer.layerDef,
features: fs,
...(options ?? {})
...(options ?? {}),
})
})
}
@ -529,12 +529,11 @@ export default class ShowDataLayer {
return new ShowDataLayer(map, {
layer: ShowDataLayer.rangeLayer,
features,
doShowLayer
doShowLayer,
})
}
public destruct() {
}
public destruct() {}
private zoomToCurrentFeatures(map: MlMap) {
if (this._options.zoomToFeatures) {
@ -543,7 +542,7 @@ export default class ShowDataLayer {
map.resize()
map.fitBounds(bbox.toLngLat(), {
padding: { top: 10, bottom: 10, left: 10, right: 10 },
animate: false
animate: false,
})
}
}
@ -552,11 +551,12 @@ export default class ShowDataLayer {
let { features, doShowLayer, fetchStore, selectedElement } = this._options
let onClick = this._options.onClick
if (!onClick && selectedElement) {
onClick = (this._options.layer.title === undefined
? undefined
: (feature: Feature) => {
selectedElement?.setData(feature)
})
onClick =
this._options.layer.title === undefined
? undefined
: (feature: Feature) => {
selectedElement?.setData(feature)
}
}
if (this._options.drawLines !== false) {
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {

View file

@ -1,15 +1,16 @@
<script lang="ts">
import Translations from "../i18n/Translations.js";
import Min from "../../assets/svg/Min.svelte";
import MapControlButton from "../Base/MapControlButton.svelte";
import Plus from "../../assets/svg/Plus.svelte";
import type { MapProperties } from "../../Models/MapProperties"
import Translations from "../i18n/Translations.js"
import Min from "../../assets/svg/Min.svelte"
import MapControlButton from "../Base/MapControlButton.svelte"
import Plus from "../../assets/svg/Plus.svelte"
import type { MapProperties } from "../../Models/MapProperties"
export let adaptor: MapProperties
let canZoomIn = adaptor.maxzoom.map(mz => adaptor.zoom.data < mz, [adaptor.zoom] )
let canZoomOut = adaptor.minzoom.map(mz => adaptor.zoom.data > mz, [adaptor.zoom] )
export let adaptor: MapProperties
let canZoomIn = adaptor.maxzoom.map((mz) => adaptor.zoom.data < mz, [adaptor.zoom])
let canZoomOut = adaptor.minzoom.map((mz) => adaptor.zoom.data > mz, [adaptor.zoom])
</script>
<div class="absolute bottom-0 right-0 pointer-events-none flex flex-col">
<div class="pointer-events-none absolute bottom-0 right-0 flex flex-col">
<MapControlButton
enabled={canZoomIn}
cls="m-0.5 p-1"

View file

@ -10,24 +10,25 @@
* Optional. Only used for the 'global' change indicator so that it won't spin on pan/zoom but only when a change _actually_ occured
*/
export let rasterLayer: UIEventSource<any> = undefined
let didChange = undefined
onDestroy(rasterLayer?.addCallback(() => {
didChange = true
}) ??( () => {}))
onDestroy(Stores.Chronic(250).addCallback(
() => {
onDestroy(
rasterLayer?.addCallback(() => {
didChange = true
}) ?? (() => {})
)
onDestroy(
Stores.Chronic(250).addCallback(() => {
const mapIsLoading = !map.data?.isStyleLoaded()
isLoading = mapIsLoading && (didChange || rasterLayer === undefined)
if(didChange && !mapIsLoading){
if (didChange && !mapIsLoading) {
didChange = false
}
},
))
})
)
</script>
{#if isLoading}
<Loading cls="h-6 w-6" />
{:else}

View file

@ -32,7 +32,7 @@ export class OH {
th: 3,
fr: 4,
sa: 5,
su: 6
su: 6,
}
public static hhmm(h: number, m: number): string {
@ -143,7 +143,7 @@ export class OH {
const queue = ohs.map((oh) => {
if (oh.endHour === 0 && oh.endMinutes === 0) {
const newOh = {
...oh
...oh,
}
newOh.endHour = 24
return newOh
@ -211,7 +211,7 @@ export class OH {
startMinutes: startMinutes,
endHour: endHour,
endMinutes: endMinutes,
weekday: guard.weekday
weekday: guard.weekday,
})
doAddEntry = false
@ -279,7 +279,7 @@ export class OH {
startHour: start.hours,
startMinutes: start.minutes,
endHour: end.hours,
endMinutes: end.minutes
endMinutes: end.minutes,
}
}
@ -337,8 +337,8 @@ export class OH {
startHour: 0,
startMinutes: 0,
endHour: 24,
endMinutes: 0
}
endMinutes: 0,
},
]
)
}
@ -388,13 +388,13 @@ export class OH {
str = str.trim()
if (str.toLowerCase() === "ph off") {
return {
mode: "off"
mode: "off",
}
}
if (str.toLowerCase() === "ph open") {
return {
mode: "open"
mode: "open",
}
}
@ -410,7 +410,7 @@ export class OH {
return {
mode: " ",
start: OH.hhmm(timerange.startHour, timerange.startMinutes),
end: OH.hhmm(timerange.endHour, timerange.endMinutes)
end: OH.hhmm(timerange.endHour, timerange.endMinutes),
}
} catch (e) {
return null
@ -576,8 +576,8 @@ This list will be sorted
lon: tags._lon,
address: {
country_code: country.toLowerCase(),
state: undefined
}
state: undefined,
},
},
<any>{ tag_key: "opening_hours" }
)
@ -753,7 +753,7 @@ This list will be sorted
isOpen: iterator.getState(),
comment: iterator.getComment(),
startDate: iterator.getDate() as Date,
endDate: endDate // Should be overwritten by the next iteration
endDate: endDate, // Should be overwritten by the next iteration
}
prevValue = value
@ -891,7 +891,7 @@ This list will be sorted
startHour: timerange.startHour,
startMinutes: timerange.startMinutes,
endHour: timerange.endHour,
endMinutes: timerange.endMinutes
endMinutes: timerange.endMinutes,
})
} else {
ohs.push({
@ -899,14 +899,14 @@ This list will be sorted
startHour: timerange.startHour,
startMinutes: timerange.startMinutes,
endHour: 0,
endMinutes: 0
endMinutes: 0,
})
ohs.push({
weekday: (weekday + 1) % 7,
startHour: 0,
startMinutes: 0,
endHour: timerange.endHour,
endMinutes: timerange.endMinutes
endMinutes: timerange.endMinutes,
})
}
}
@ -967,7 +967,7 @@ export class ToTextualDescription {
"thursday",
"friday",
"saturday",
"sunday"
"sunday",
]
function addRange(start: number, end: number) {
@ -1025,7 +1025,7 @@ export class ToTextualDescription {
private static createRangeFor(range: OpeningRange): Translation {
return Translations.t.general.opening_hours.ranges.Subs({
starttime: ToTextualDescription.timeString(range.startDate),
endtime: ToTextualDescription.timeString(range.endDate)
endtime: ToTextualDescription.timeString(range.endDate),
})
}
@ -1037,7 +1037,7 @@ export class ToTextualDescription {
for (let i = 1; i < ranges.length; i++) {
tr = Translations.t.general.opening_hours.rangescombined.Subs({
range0: tr,
range1: ToTextualDescription.createRangeFor(ranges[i])
range1: ToTextualDescription.createRangeFor(ranges[i]),
})
}
return tr

View file

@ -365,7 +365,7 @@
</div>
</div>
{:else}
<Loading><Tr t={Translations.t.general.add.creating}/> </Loading>
<Loading><Tr t={Translations.t.general.add.creating} /></Loading>
{/if}
</div>
</LoginToggle>

View file

@ -4,7 +4,7 @@
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
export let tags: UIEventSource<Record<string, any>>
export let tagKeys = tags.map(tgs => tgs === undefined ? [] : Object.keys(tgs))
export let tagKeys = tags.map((tgs) => (tgs === undefined ? [] : Object.keys(tgs)))
export let layer: LayerConfig | undefined = undefined

View file

@ -110,7 +110,10 @@ class ApplyButton extends UIElement {
mla.allowZooming.setData(false)
mla.allowMoving.setData(false)
const previewMap = new SvelteUIElement(MaplibreMap, { mapProperties: mla, map: mlmap }).SetClass("h-48")
const previewMap = new SvelteUIElement(MaplibreMap, {
mapProperties: mla,
map: mlmap,
}).SetClass("h-48")
const features = this.target_feature_ids.map((id) =>
this.state.indexedFeatures.featuresById.data.get(id)

View file

@ -109,7 +109,11 @@ export class MinimapViz implements SpecialVisualization {
state.layout.layers
)
return new SvelteUIElement(MaplibreMap, { interactive: false, map: mlmap, mapProperties: mla })
return new SvelteUIElement(MaplibreMap, {
interactive: false,
map: mlmap,
mapProperties: mla,
})
.SetClass("h-40 rounded")
.SetStyle("overflow: hidden; pointer-events: none;")
}

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature, Point } from "geojson"
import type { SpecialVisualizationState } from "../SpecialVisualization"
@ -19,18 +18,26 @@
export let state: SpecialVisualizationState
export let id: WayId
const t = Translations.t.split
let step: "initial" | "loading_way" | "splitting" | "applying_split" | "has_been_split" | "deleted" = "initial"
let step:
| "initial"
| "loading_way"
| "splitting"
| "applying_split"
| "has_been_split"
| "deleted" = "initial"
// Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
let splitPoints = new UIEventSource<Feature<
Point,
{
id: number
index: number
dist: number
location: number
}
>[]>([])
let splitpointsNotEmpty = splitPoints.map(sp => sp.length > 0)
let splitPoints = new UIEventSource<
Feature<
Point,
{
id: number
index: number
dist: number
location: number
}
>[]
>([])
let splitpointsNotEmpty = splitPoints.map((sp) => sp.length > 0)
let osmWay: OsmWay
@ -54,7 +61,7 @@
{
theme: state?.layout?.id,
},
5,
5
)
await state.changes?.applyAction(splitAction)
// We throw away the old map and splitpoints, and create a new map from scratch
@ -64,10 +71,8 @@
state.selectedElement?.setData(undefined)
step = "has_been_split"
}
</script>
<LoginToggle ignoreLoading={true} {state}>
<Tr slot="not-logged-in" t={t.loginToSplit} />
@ -75,38 +80,39 @@
<!-- Empty -->
{:else if step === "initial"}
<button on:click={() => downloadWay()}>
<Scissors class="w-6 h-6 shrink-0" />
<Scissors class="h-6 w-6 shrink-0" />
<Tr t={t.inviteToSplit} />
</button>
{:else if step === "loading_way"}
<Loading />
{:else if step === "splitting"}
<div class="flex flex-col interactive border-interactive p-2">
<div class="w-full h-80">
<div class="interactive border-interactive flex flex-col p-2">
<div class="h-80 w-full">
<WaySplitMap {state} {splitPoints} {osmWay} />
</div>
<div class="flex flex-wrap-reverse md:flex-nowrap w-full">
<BackButton clss="w-full" on:click={() => {
splitPoints.set([])
step = "initial"
}}>
<div class="flex w-full flex-wrap-reverse md:flex-nowrap">
<BackButton
clss="w-full"
on:click={() => {
splitPoints.set([])
step = "initial"
}}
>
<Tr t={Translations.t.general.cancel} />
</BackButton>
<NextButton clss={ ($splitpointsNotEmpty ? "": "disabled ") + "w-full primary"} on:click={() => doSplit()}>
<NextButton
clss={($splitpointsNotEmpty ? "" : "disabled ") + "w-full primary"}
on:click={() => doSplit()}
>
<Tr t={t.split} />
</NextButton>
</div>
</div>
{:else if step === "has_been_split"}
<Tr cls="thanks" t={ t.hasBeenSplit.Clone().SetClass("font-bold thanks block w-full")} />
<Tr cls="thanks" t={t.hasBeenSplit.Clone().SetClass("font-bold thanks block w-full")} />
<button on:click={() => downloadWay()}>
<Scissors class="w-6 h-6" />
<Scissors class="h-6 w-6" />
<Tr t={t.splitAgain} />
</button>
{/if}
</LoginToggle>

View file

@ -171,7 +171,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
feature: Feature
): BaseUIElement {
const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags)
const msg = args[1]

View file

@ -68,10 +68,14 @@
},
[skippedQuestions]
)
let firstQuestion: UIEventSource<TagRenderingConfig> = new UIEventSource<TagRenderingConfig>(undefined)
let allQuestionsToAsk : UIEventSource<TagRenderingConfig[]> = new UIEventSource<TagRenderingConfig[]>([])
let firstQuestion: UIEventSource<TagRenderingConfig> = new UIEventSource<TagRenderingConfig>(
undefined
)
let allQuestionsToAsk: UIEventSource<TagRenderingConfig[]> = new UIEventSource<
TagRenderingConfig[]
>([])
async function calculateQuestions(){
async function calculateQuestions() {
console.log("Applying questions to ask")
const qta = questionsToAsk.data
firstQuestion.setData(undefined)
@ -81,12 +85,10 @@
allQuestionsToAsk.setData(qta)
}
onDestroy(questionsToAsk.addCallback(() =>calculateQuestions()))
onDestroy(questionsToAsk.addCallback(() => calculateQuestions()))
onDestroy(showAllQuestionsAtOnce.addCallback(() => calculateQuestions()))
calculateQuestions()
let answered: number = 0
let skipped: number = 0

View file

@ -135,24 +135,25 @@
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
initialize($tags, config)
}
onDestroy(
freeformInput.subscribe((freeformValue) => {
if (!mappings || mappings?.length == 0 || config.freeform?.key === undefined) {
return
}
// If a freeform value is given, mark the 'mapping' as marked
if (config.multiAnswer) {
if (checkedMappings === undefined) {
// Initialization didn't yet run
onDestroy(
freeformInput.subscribe((freeformValue) => {
if (!mappings || mappings?.length == 0 || config.freeform?.key === undefined) {
return
}
checkedMappings[mappings.length] = freeformValue?.length > 0
return
}
if (freeformValue?.length > 0) {
selectedMapping = mappings.length
}
}))
// If a freeform value is given, mark the 'mapping' as marked
if (config.multiAnswer) {
if (checkedMappings === undefined) {
// Initialization didn't yet run
return
}
checkedMappings[mappings.length] = freeformValue?.length > 0
return
}
if (freeformValue?.length > 0) {
selectedMapping = mappings.length
}
})
)
$: {
if (
@ -243,7 +244,9 @@ onDestroy(
<form
class="interactive border-interactive relative flex flex-col overflow-y-auto px-2"
style="max-height: 75vh"
on:submit|preventDefault={() =>{ /*onSave(); This submit is not needed and triggers to early, causing bugs: see #1808*/}}
on:submit|preventDefault={() => {
/*onSave(); This submit is not needed and triggers to early, causing bugs: see #1808*/
}}
>
<fieldset>
<legend>
@ -399,7 +402,10 @@ onDestroy(
<slot name="cancel" />
<slot name="save-button" {selectedTags}>
{#if allowDeleteOfFreeform && (mappings?.length ?? 0) === 0 && $freeformInput === undefined && $freeformInputUnvalidated === ""}
<button class="primary flex" on:click|stopPropagation|preventDefault={() => onSave()}>
<button
class="primary flex"
on:click|stopPropagation|preventDefault={() => onSave()}
>
<TrashIcon class="h-6 w-6 text-red-500" />
<Tr t={Translations.t.general.eraseValue} />
</button>

View file

@ -1,90 +1,93 @@
<script lang="ts">
/**
* Allows to import a 'mangrove' private key from backup
*/
import LoginToggle from "../Base/LoginToggle.svelte"
import FileSelector from "../Base/FileSelector.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import { UIEventSource } from "../../Logic/UIEventSource"
/**
* Allows to import a 'mangrove' private key from backup
*/
import LoginToggle from "../Base/LoginToggle.svelte"
import FileSelector from "../Base/FileSelector.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import { UIEventSource } from "../../Logic/UIEventSource"
export let state: SpecialVisualizationState
export let text: string
export let state: SpecialVisualizationState
export let text: string
let error: string = undefined
let success: string = undefined
let error: string = undefined
let success: string = undefined
function importContent(str: string) {
const parsed = JSON.parse(str)
function importContent(str: string) {
const parsed = JSON.parse(str)
const format = {
"crv": "P-256",
"d": undefined,
"ext": true,
"key_ops": ["sign"],
"kty": "EC",
"x": undefined,
"y": undefined,
"metadata": "Mangrove private key",
}
const neededKeys = Object.keys(format)
for (const neededKey of neededKeys) {
const expected = format[neededKey]
const actual = parsed[neededKey]
if (actual === undefined) {
error = "Not a valid key. The value for " + neededKey + " is missing"
return
}
if (typeof expected === "string" && expected !== actual) {
error = "Not a valid key. The value for " + neededKey + " should be " + expected + " but is " + actual
return
}
}
const current: UIEventSource<string> = state.userRelatedState.mangroveIdentity.mangroveIdentity
const flattened = JSON.stringify(parsed, null, "")
if (flattened === current.data) {
success = "The imported key is identical to the existing key"
return
}
console.log("Got", flattened, current)
current.setData(flattened)
success = "Applied private key"
const format = {
crv: "P-256",
d: undefined,
ext: true,
key_ops: ["sign"],
kty: "EC",
x: undefined,
y: undefined,
metadata: "Mangrove private key",
}
async function onImport(files: FileList) {
error = undefined
success = undefined
try {
const reader = new FileReader()
reader.readAsText(files[0], "UTF-8")
// here we tell the reader what to do when it's done reading...
const content = await new Promise<string>((resolve, reject) => {
reader.onload = readerEvent => {
resolve(<string>readerEvent.target.result)
}
})
importContent(content)
} catch (e) {
error = e
}
const neededKeys = Object.keys(format)
for (const neededKey of neededKeys) {
const expected = format[neededKey]
const actual = parsed[neededKey]
if (actual === undefined) {
error = "Not a valid key. The value for " + neededKey + " is missing"
return
}
if (typeof expected === "string" && expected !== actual) {
error =
"Not a valid key. The value for " +
neededKey +
" should be " +
expected +
" but is " +
actual
return
}
}
const current: UIEventSource<string> = state.userRelatedState.mangroveIdentity.mangroveIdentity
const flattened = JSON.stringify(parsed, null, "")
if (flattened === current.data) {
success = "The imported key is identical to the existing key"
return
}
console.log("Got", flattened, current)
current.setData(flattened)
success = "Applied private key"
}
async function onImport(files: FileList) {
error = undefined
success = undefined
try {
const reader = new FileReader()
reader.readAsText(files[0], "UTF-8")
// here we tell the reader what to do when it's done reading...
const content = await new Promise<string>((resolve, reject) => {
reader.onload = (readerEvent) => {
resolve(<string>readerEvent.target.result)
}
})
importContent(content)
} catch (e) {
error = e
}
}
</script>
<LoginToggle {state}>
<div class="flex flex-col m-1">
<FileSelector accept="application/json" multiple={false} on:submit={e => onImport(e.detail)}>
<div class="m-1 flex flex-col">
<FileSelector accept="application/json" multiple={false} on:submit={(e) => onImport(e.detail)}>
{text}
</FileSelector>
{#if error}
<div class="alert">
<Tr t={Translations.t.general.error} /> {error}</div>
<Tr t={Translations.t.general.error} />
{error}
</div>
{/if}
{#if success}
<div class="thanks">

View file

@ -61,12 +61,12 @@
opinion: opinion.data,
metadata: { nickname, is_affiliated: isAffiliated.data },
}
try {
await reviews.createReview(review)
} catch (e) {
console.error("Could not create review due to", e)
uploadFailed = "" + e
}
try {
await reviews.createReview(review)
} catch (e) {
console.error("Could not create review due to", e)
uploadFailed = "" + e
}
_state = "done"
}
</script>

View file

@ -42,12 +42,13 @@
{#if $allReviews?.length - $reviews?.length === 1}
<Tr t={t.non_place_review} />
{:else}
<Tr t={t.non_place_reviews.Subs({n:$allReviews?.length - $reviews?.length })} />
<Tr t={t.non_place_reviews.Subs({ n: $allReviews?.length - $reviews?.length })} />
{/if}
<a target="_blank"
class="link-underline"
rel="noopener nofollow"
href={`https://mangrove.reviews/list?kid=${encodeURIComponent($kid)}`}
<a
target="_blank"
class="link-underline"
rel="noopener nofollow"
href={`https://mangrove.reviews/list?kid=${encodeURIComponent($kid)}`}
>
<Tr t={t.see_all} />
</a>

File diff suppressed because it is too large Load diff

View file

@ -42,7 +42,7 @@
)
let osmConnection = new OsmConnection({
oauth_token,
checkOnlineRegularly: true
checkOnlineRegularly: true,
})
const expertMode = UIEventSource.asBoolean(
osmConnection.GetPreference("studio-expert-mode", "false", {

View file

@ -1,5 +1,2 @@
<script lang="ts">
</script>

View file

@ -18,7 +18,7 @@
EyeIcon,
HeartIcon,
MenuIcon,
XCircleIcon
XCircleIcon,
} from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
@ -99,26 +99,31 @@
})
let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) => {
if (element.properties.id.startsWith("current_view")) {
return currentViewLayer
}
if (element.properties.id === "new_point_dialog") {
return layout.layers.find(l => l.id === "last_click")
}
if (element.properties.id === "location_track") {
return layout.layers.find(l => l.id === "gps_track")
}
return state.layout.getMatchingLayer(element.properties)
if (element.properties.id.startsWith("current_view")) {
return currentViewLayer
}
)
if (element.properties.id === "new_point_dialog") {
return layout.layers.find((l) => l.id === "last_click")
}
if (element.properties.id === "location_track") {
return layout.layers.find((l) => l.id === "gps_track")
}
return state.layout.getMatchingLayer(element.properties)
})
let currentZoom = state.mapProperties.zoom
let showCrosshair = state.userRelatedState.showCrosshair
let visualFeedback = state.visualFeedback
let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined)
let mapproperties: MapProperties = state.mapProperties
state.mapProperties.installCustomKeyboardHandler(viewport)
let canZoomIn = mapproperties.maxzoom.map(mz => mapproperties.zoom.data < mz, [mapproperties.zoom])
let canZoomOut = mapproperties.minzoom.map(mz => mapproperties.zoom.data > mz, [mapproperties.zoom])
let canZoomIn = mapproperties.maxzoom.map(
(mz) => mapproperties.zoom.data < mz,
[mapproperties.zoom]
)
let canZoomOut = mapproperties.minzoom.map(
(mz) => mapproperties.zoom.data > mz,
[mapproperties.zoom]
)
function updateViewport() {
const rect = viewport.data?.getBoundingClientRect()
@ -133,7 +138,7 @@
const bottomRight = mlmap.unproject([rect.right, rect.bottom])
const bbox = new BBox([
[topLeft.lng, topLeft.lat],
[bottomRight.lng, bottomRight.lat]
[bottomRight.lng, bottomRight.lat],
])
state.visualFeedbackViewportBounds.setData(bbox)
}
@ -149,7 +154,8 @@
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
let rasterLayerName =
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.defaultBackgroundLayer.properties.name
rasterLayer.data?.properties?.name ??
AvailableRasterLayers.defaultBackgroundLayer.properties.name
onDestroy(
rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name
@ -224,7 +230,12 @@
on:keydown={forwardEventToMap}
>
<div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2">
<img role="presentation" alt="" class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8" src={layout.icon} />
<img
role="presentation"
alt=""
class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8"
src={layout.icon}
/>
<b class="mr-1">
<Tr t={layout.title} />
</b>
@ -385,8 +396,6 @@
<svelte:fragment slot="error" />
</LoginToggle>
{#if $selectedElement !== undefined && $selectedLayer !== undefined && !$selectedLayer.popupInFloatover}
<!-- right modal with the selected element view -->
<ModalRight
@ -409,10 +418,10 @@
state.selectedElement.setData(undefined)
}}
>
<div class="flex flex-col h-full w-full">
<div class="flex h-full w-full flex-col">
{#if $selectedLayer.popupInFloatover === "title"}
<SelectedElementTitle {state} layer={$selectedLayer} selectedElement={$selectedElement} >
<span slot="close-button"/>
<SelectedElementTitle {state} layer={$selectedLayer} selectedElement={$selectedElement}>
<span slot="close-button" />
</SelectedElementTitle>
{/if}
<SelectedElementView {state} layer={$selectedLayer} selectedElement={$selectedElement} />
@ -423,7 +432,7 @@
<If condition={state.previewedImage.map((i) => i !== undefined)}>
<FloatOver extraClasses="p-1" on:close={() => state.previewedImage.setData(undefined)}>
<button
class="absolute p-0 right-4 top-4 h-8 w-8 rounded-full"
class="absolute right-4 top-4 h-8 w-8 rounded-full p-0"
on:click={() => previewedImage.setData(undefined)}
slot="close-button"
>
@ -595,7 +604,7 @@
highlightedRendering={state.guistate.highlightedUserSetting}
selectedElement={{
type: "Feature",
properties: {id:"settings"},
properties: { id: "settings" },
geometry: { type: "Point", coordinates: [0, 0] },
}}
{state}

View file

@ -20,7 +20,10 @@ export default class WikidataSearchBox extends InputElement<string> {
new Table(
["name", "doc"],
[
["key", "the value of this tag will initialize search (default: name). This can be a ';'-separated list in which case every key will be inspected. The non-null value will be used as search"],
[
"key",
"the value of this tag will initialize search (default: name). This can be a ';'-separated list in which case every key will be inspected. The non-null value will be used as search",
],
[
"options",
new Combine([

View file

@ -25,7 +25,9 @@
{/if}
{#if $wikipediaDetails.wikidata}
<ToSvelte construct={() => WikidataPreviewBox.WikidataResponsePreview($wikipediaDetails.wikidata)} />
<ToSvelte
construct={() => WikidataPreviewBox.WikidataResponsePreview($wikipediaDetails.wikidata)}
/>
{/if}
{#if $wikipediaDetails.articleUrl}

View file

@ -922,7 +922,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
Utils.injectedDownloads[url] = data
}
public static async download(url: string, headers?: Record<string, string>): Promise<string | undefined> {
public static async download(
url: string,
headers?: Record<string, string>
): Promise<string | undefined> {
const result = await Utils.downloadAdvanced(url, headers)
if (result["error"] !== undefined) {
throw result["error"]
@ -975,7 +978,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
})
}
public static upload(url: string, data: string | Blob, headers?: Record<string, string>): Promise<string> {
public static upload(
url: string,
data: string | Blob,
headers?: Record<string, string>
): Promise<string> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onload = () => {
@ -1031,7 +1038,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return await promise
}
public static async downloadJson(url: string, headers?: Record<string, string>): Promise<object | []> {
public static async downloadJson(
url: string,
headers?: Record<string, string>
): Promise<object | []> {
const result = await Utils.downloadJsonAdvanced(url, headers)
if (result["content"]) {
return result["content"]
@ -1039,7 +1049,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
throw result["error"]
}
public static awaitAnimationFrame(): Promise<void>{
public static awaitAnimationFrame(): Promise<void> {
return new Promise<void>((resolve) => {
window.requestAnimationFrame(() => {
resolve()
@ -1050,7 +1060,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
public static async downloadJsonAdvanced(
url: string,
headers?: Record<string, string>
): Promise<{ content: object | object[] } | { error: string; url: string; statuscode?: number }> {
): Promise<
{ content: object | object[] } | { error: string; url: string; statuscode?: number }
> {
const injected = Utils.injectedDownloads[url]
if (injected !== undefined) {
console.log("Using injected resource for test for URL", url)
@ -1066,8 +1078,8 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
const data = result["content"]
try {
if (typeof data === "string") {
if(data === ""){
return {content: {}}
if (data === "") {
return { content: {} }
}
return { content: JSON.parse(data) }
}

View file

@ -65,8 +65,10 @@ export class PngMapCreator {
document.getElementById(freeComponentId).appendChild(div)
const newZoom = settings.zoom.data + Math.log2(pixelRatio) - 1
const rasterLayerProperties = settings.rasterLayer.data?.properties ?? AvailableRasterLayers.defaultBackgroundLayer.properties
const style = rasterLayerProperties?.style ?? rasterLayerProperties?.url
const rasterLayerProperties =
settings.rasterLayer.data?.properties ??
AvailableRasterLayers.defaultBackgroundLayer.properties
const style = rasterLayerProperties?.style ?? rasterLayerProperties?.url
const mapElem = new MlMap({
container: div.id,
style,

View file

@ -1,11 +1,11 @@
{
"contributors": [
{
"commits": 6880,
"commits": 7248,
"contributor": "Pieter Vander Vennet"
},
{
"commits": 431,
"commits": 451,
"contributor": "Robin van der Linde"
},
{
@ -16,6 +16,10 @@
"commits": 38,
"contributor": "Win Olario"
},
{
"commits": 34,
"contributor": "dependabot[bot]"
},
{
"commits": 33,
"contributor": "Hosted Weblate"
@ -40,10 +44,6 @@
"commits": 29,
"contributor": "riQQ"
},
{
"commits": 27,
"contributor": "dependabot[bot]"
},
{
"commits": 26,
"contributor": "Joost"
@ -116,6 +116,10 @@
"commits": 10,
"contributor": "LiamSimons"
},
{
"commits": 9,
"contributor": "Flo Edelmann"
},
{
"commits": 9,
"contributor": "Codain"
@ -134,11 +138,11 @@
},
{
"commits": 7,
"contributor": "OliNau"
"contributor": "danieldegroot2"
},
{
"commits": 7,
"contributor": "Flo Edelmann"
"contributor": "OliNau"
},
{
"commits": 7,
@ -148,10 +152,6 @@
"commits": 6,
"contributor": "David Haberthür"
},
{
"commits": 6,
"contributor": "danieldegroot2"
},
{
"commits": 4,
"contributor": "Daniele Santini"

View file

@ -50,9 +50,6 @@
"de",
"nl"
],
"BF": [
"fr"
],
"BG": [
"bg"
],
@ -149,6 +146,7 @@
"el"
],
"CZ": [
"cs",
"cs"
],
"DE": [
@ -215,7 +213,6 @@
"fr"
],
"GB": [
"en",
"en",
"en"
],
@ -271,8 +268,7 @@
"hu"
],
"ID": [
"id",
"jv"
"id"
],
"IE": [
"en",
@ -367,8 +363,8 @@
"en"
],
"LS": [
"en",
"st"
"st",
"en"
],
"LT": [
"lt",
@ -434,8 +430,8 @@
"dv"
],
"MW": [
"ny",
"en"
"en",
"ny"
],
"MX": [
"es",

View file

@ -3664,7 +3664,6 @@
"_meta": {
"countries": [
"BE",
"BF",
"BI",
"BJ",
"CA",
@ -5229,9 +5228,6 @@
"zh_Hans": "爪哇语",
"zh_Hant": "爪哇語",
"_meta": {
"countries": [
"ID"
],
"dir": [
"left-to-right",
"right-to-left"

View file

@ -1,11 +1,11 @@
{
"contributors": [
{
"commits": 335,
"commits": 337,
"contributor": "kjon"
},
{
"commits": 309,
"commits": 336,
"contributor": "Pieter Vander Vennet"
},
{
@ -24,34 +24,34 @@
"commits": 61,
"contributor": "danieldegroot2"
},
{
"commits": 49,
"contributor": "mcliquid"
},
{
"commits": 47,
"contributor": "Anonymous"
},
{
"commits": 42,
"contributor": "Supaplex"
},
{
"commits": 36,
"contributor": "Iago"
},
{
"commits": 33,
"contributor": "mcliquid"
"commits": 34,
"contributor": "Lucas"
},
{
"commits": 33,
"contributor": "Jiří Podhorecký"
},
{
"commits": 32,
"contributor": "Lucas"
},
{
"commits": 32,
"contributor": "Babos Gábor"
},
{
"commits": 31,
"contributor": "Supaplex"
},
{
"commits": 29,
"contributor": "Artem"
@ -72,6 +72,14 @@
"commits": 18,
"contributor": "el_libre como el chaval"
},
{
"commits": 17,
"contributor": "Štefan Baebler"
},
{
"commits": 16,
"contributor": "gallegonovato"
},
{
"commits": 15,
"contributor": "macpac"
@ -80,10 +88,6 @@
"commits": 15,
"contributor": "WaldiS"
},
{
"commits": 14,
"contributor": "gallegonovato"
},
{
"commits": 14,
"contributor": "LeJun"
@ -144,6 +148,10 @@
"commits": 10,
"contributor": "Irina"
},
{
"commits": 9,
"contributor": "Lasse Liehu"
},
{
"commits": 9,
"contributor": "快乐的老鼠宝宝"
@ -168,10 +176,6 @@
"commits": 8,
"contributor": "Vinicius"
},
{
"commits": 7,
"contributor": "Lasse Liehu"
},
{
"commits": 7,
"contributor": "NetworkedPoncho"
@ -188,6 +192,10 @@
"commits": 7,
"contributor": "Niels Elgaard Larsen"
},
{
"commits": 6,
"contributor": "Jeff Huang"
},
{
"commits": 6,
"contributor": "Juele juele"
@ -212,10 +220,6 @@
"commits": 6,
"contributor": "ⵣⵓⵀⵉⵔ ⴰⵎⴰⵣⵉⵖ زهير أمازيغ"
},
{
"commits": 6,
"contributor": "Štefan Baebler"
},
{
"commits": 6,
"contributor": "seppesantens"
@ -232,6 +236,10 @@
"commits": 6,
"contributor": "lvgx"
},
{
"commits": 5,
"contributor": "Emory Shaw"
},
{
"commits": 5,
"contributor": "nilocram"
@ -272,10 +280,6 @@
"commits": 4,
"contributor": "Krzysztof Chorzempa"
},
{
"commits": 4,
"contributor": "Emory Shaw"
},
{
"commits": 4,
"contributor": "André Marcelo Alvarenga"
@ -288,10 +292,6 @@
"commits": 4,
"contributor": "Hiroshi Miura"
},
{
"commits": 4,
"contributor": "Jeff Huang"
},
{
"commits": 4,
"contributor": "Adolfo Jayme Barrientos"
@ -380,6 +380,10 @@
"commits": 3,
"contributor": "SiegbjornSitumeang"
},
{
"commits": 2,
"contributor": "CaldeiraG"
},
{
"commits": 2,
"contributor": "Henry00572"
@ -490,7 +494,11 @@
},
{
"commits": 1,
"contributor": "CaldeiraG"
"contributor": "Roosa Huovinen"
},
{
"commits": 1,
"contributor": "Berete Baba"
},
{
"commits": 1,

View file

@ -31,7 +31,7 @@ async function getAvailableLayers(): Promise<Set<string>> {
const host = new URL(Constants.VectorTileServer).host
const status: { layers: string[] } = await Promise.any([
// Utils.downloadJson("https://" + host + "/summary/status.json"),
timeout(0)
timeout(0),
])
return new Set<string>(status.layers)
} catch (e) {
@ -48,13 +48,13 @@ async function main() {
}
const [layout, availableLayers] = await Promise.all([
DetermineLayout.GetLayout(),
await getAvailableLayers()
await getAvailableLayers(),
])
console.log("The available layers on server are", Array.from(availableLayers))
const state = new ThemeViewState(layout, availableLayers)
const main = new SvelteUIElement(ThemeViewGUI, { state })
main.AttachTo("maindiv")
Array.from(document.getElementsByClassName("delete-on-load")).forEach(el => {
Array.from(document.getElementsByClassName("delete-on-load")).forEach((el) => {
el.parentElement.removeChild(el)
})
} catch (err) {
@ -65,17 +65,16 @@ async function main() {
customDefinition?.length > 0
? new SubtleButton(new SvelteUIElement(Download), "Download the raw file").onClick(
() =>
Utils.offerContentsAsDownloadableFile(
DetermineLayout.getCustomDefinition(),
"mapcomplete-theme.json",
{ mimetype: "application/json" }
)
)
: undefined
() =>
Utils.offerContentsAsDownloadableFile(
DetermineLayout.getCustomDefinition(),
"mapcomplete-theme.json",
{ mimetype: "application/json" }
)
)
: undefined,
]).AttachTo("maindiv")
}
}
main().then((_) => {
})
main().then((_) => {})

View file

@ -2,4 +2,3 @@ import SvelteUIElement from "./UI/Base/SvelteUIElement"
import Test from "./UI/Test.svelte"
new SvelteUIElement(Test).AttachTo("maindiv")