Chore: formatting

This commit is contained in:
Pieter Vander Vennet 2024-06-16 16:06:26 +02:00
parent 35eff07c80
commit c08fe03ed0
422 changed files with 31594 additions and 43019 deletions

View file

@ -73,7 +73,11 @@ export default class InitialMapPositioning {
if (initialHash?.match(/^(node|way|relation)\/[0-9]+$/)) {
const [type, id] = initialHash.split("/")
OsmObjectDownloader.RawDownloadObjectAsync(type, Number(id), Constants.osmAuthConfig.url + "/").then(osmObject => {
OsmObjectDownloader.RawDownloadObjectAsync(
type,
Number(id),
Constants.osmAuthConfig.url + "/"
).then((osmObject) => {
if (osmObject === "deleted") {
return
}
@ -83,6 +87,5 @@ export default class InitialMapPositioning {
this.location.setData({ lon, lat })
})
}
}
}

View file

@ -17,7 +17,7 @@ import questions from "../assets/generated/layers/questions.json"
import {
DoesImageExist,
PrevalidateTheme,
ValidateThemeAndLayers
ValidateThemeAndLayers,
} from "../Models/ThemeConfig/Conversion/Validation"
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
@ -55,8 +55,10 @@ export default class DetermineLayout {
return undefined
}
private static async expandRemoteLayers(layoutConfig: LayoutConfigJson): Promise<LayoutConfigJson> {
if(!layoutConfig.layers){
private static async expandRemoteLayers(
layoutConfig: LayoutConfigJson
): Promise<LayoutConfigJson> {
if (!layoutConfig.layers) {
// This is probably a layer in 'layer-only-mode'
return layoutConfig
}
@ -73,7 +75,6 @@ export default class DetermineLayout {
} catch (_) {
continue
}
}
return layoutConfig
}
@ -115,7 +116,9 @@ export default class DetermineLayout {
return layout
}
public static async LoadLayoutFromHash(userLayoutParam: UIEventSource<string>): Promise<LayoutConfig | null> {
public static async LoadLayoutFromHash(
userLayoutParam: UIEventSource<string>
): Promise<LayoutConfig | null> {
let hash = location.hash.substr(1)
let json: any
@ -175,11 +178,11 @@ export default class DetermineLayout {
id: json.id,
description: json.description,
descriptionTail: {
en: "<div class='alert'>Layer only mode.</div> The loaded custom theme actually isn't a custom theme, but only contains a layer."
en: "<div class='alert'>Layer only mode.</div> The loaded custom theme actually isn't a custom theme, but only contains a layer.",
},
icon,
title: json.name,
layers: [json]
layers: [json],
}
}
@ -191,7 +194,7 @@ export default class DetermineLayout {
const convertState: DesugaringContext = {
tagRenderings: DetermineLayout.getSharedTagRenderings(),
sharedLayers: knownLayersDict,
publicLayers: new Set<string>()
publicLayers: new Set<string>(),
}
json = new FixLegacyTheme().convertStrict(json)
const raw = json
@ -215,7 +218,7 @@ export default class DetermineLayout {
}
return new LayoutConfig(json, false, {
definitionRaw: JSON.stringify(raw, null, " "),
definedAtUrl: sourceUrl
definedAtUrl: sourceUrl,
})
}

View file

@ -509,7 +509,7 @@ export class ExtraFunctions {
): Record<ExtraFuncType, (feature: Feature) => Function> {
const record: Record<string, (feature: Feature) => Function> = {}
for (const f of ExtraFunctions.allFuncs) {
if ((<readonly string[]> this.types).indexOf(f._name) < 0) {
if ((<readonly string[]>this.types).indexOf(f._name) < 0) {
throw "Invalid extraFunc-type: " + f._name
}
record[f._name] = (feat) => f._f(params, feat)

View file

@ -38,7 +38,7 @@ class SingleTileSaver {
if (this._registeredIds.has(id)) {
continue
}
if(id.match(/(node|way|relation)\/-.*/)){
if (id.match(/(node|way|relation)\/-.*/)) {
// We don't cache newly created points
continue
}

View file

@ -16,7 +16,7 @@ export class LastClickFeatureSource {
private i: number = 0
private readonly hasPresets: boolean
private readonly hasNoteLayer: boolean
public static readonly newPointElementId= "new_point_dialog"
public static readonly newPointElementId = "new_point_dialog"
constructor(layout: LayoutConfig) {
this.hasNoteLayer = layout.hasNoteLayer()

View file

@ -18,15 +18,13 @@ export default class AllImageProviders {
WikidataImageProvider.singleton,
WikimediaImageProvider.singleton,
// The 'genericImageProvider' is a fallback that scans various other tags for tags, unless the URL starts with one of the given prefixes
new GenericImageProvider(
[
...Imgur.defaultValuePrefix,
...WikimediaImageProvider.commonsPrefixes,
...Mapillary.valuePrefixes,
...AllImageProviders.dontLoadFromPrefixes,
"Category:"
]
),
new GenericImageProvider([
...Imgur.defaultValuePrefix,
...WikimediaImageProvider.commonsPrefixes,
...Mapillary.valuePrefixes,
...AllImageProviders.dontLoadFromPrefixes,
"Category:",
]),
]
public static apiUrls: string[] = [].concat(
...AllImageProviders.ImageAttributionSource.map((src) => src.apiUrls())

View file

@ -71,6 +71,4 @@ export default abstract class ImageProvider {
public abstract DownloadAttribution(providedImage: ProvidedImage): Promise<LicenseInfo>
public abstract apiUrls(): string[]
}

View file

@ -73,15 +73,15 @@ export class ImageUploadManager {
}
}
public canBeUploaded(file: File): true | {error: Translation} {
public canBeUploaded(file: File): true | { error: Translation } {
const sizeInBytes = file.size
const self = this
if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) {
const error = Translations.t.image.toBig.Subs({
const error = Translations.t.image.toBig.Subs({
actual_size: Math.floor(sizeInBytes / 1000000) + "MB",
max_size: self._uploader.maxFileSizeInMegabytes + "MB",
})
return {error}
return { error }
}
return true
}
@ -98,9 +98,8 @@ export class ImageUploadManager {
tagsStore: UIEventSource<OsmTags>,
targetKey?: string
): Promise<void> {
const canBeUploaded = this.canBeUploaded(file)
if(canBeUploaded !== true){
if (canBeUploaded !== true) {
throw canBeUploaded.error
}

View file

@ -1,7 +1,7 @@
import Constants from "../Models/Constants"
export interface MaprouletteTask {
name: string,
description: string,
name: string
description: string
instruction: string
}
export default class Maproulette {

View file

@ -27,16 +27,16 @@ export default class MetaTagging {
((feature: Feature, propertiesStore: UIEventSource<any>) => void)[]
>()
private state: {
readonly selectedElement: Store<Feature>;
readonly layout: LayoutConfig;
readonly osmObjectDownloader: OsmObjectDownloader;
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>;
readonly indexedFeatures: IndexedFeatureSource;
readonly selectedElement: Store<Feature>
readonly layout: LayoutConfig
readonly osmObjectDownloader: OsmObjectDownloader
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
readonly indexedFeatures: IndexedFeatureSource
readonly featureProperties: FeaturePropertiesStore
}
private params: {
getFeatureById: (id) => Feature;
getFeaturesWithin: (layerId, bbox) => (Feature[][] | [Feature[]])
getFeatureById: (id) => Feature
getFeaturesWithin: (layerId, bbox) => Feature[][] | [Feature[]]
}
constructor(state: {
@ -48,7 +48,7 @@ export default class MetaTagging {
readonly featureProperties: FeaturePropertiesStore
}) {
this.state = state
const params = this.params = MetaTagging.createExtraFuncParams(state)
const params = (this.params = MetaTagging.createExtraFuncParams(state))
for (const layer of state.layout.layers) {
if (layer.source === null) {
continue
@ -78,7 +78,7 @@ export default class MetaTagging {
}
// Force update the tags of the currently selected element
state.selectedElement.addCallbackAndRunD(feature => {
state.selectedElement.addCallbackAndRunD((feature) => {
this.updateCurrentSelectedElement()
let lastUpdateMoment = new Date()
const tags = state?.featureProperties?.getStore(feature.properties.id)
@ -86,22 +86,19 @@ export default class MetaTagging {
tags?.addCallbackD(() => {
console.log("Received an update! Re-calculating the metatags")
if(feature !== state.selectedElement.data){
if (feature !== state.selectedElement.data) {
return true // Unregister, we are not the selected element anymore
}
if(new Date().getTime() - lastUpdateMoment.getTime() < 250){
if (new Date().getTime() - lastUpdateMoment.getTime() < 250) {
return
}
lastUpdateMoment = new Date()
window.requestIdleCallback(() => {
this.updateCurrentSelectedElement()
lastUpdateMoment = new Date()
})
})
})
}
/**
@ -125,7 +122,7 @@ export default class MetaTagging {
state.featureProperties,
{
includeDates: !lightUpdate,
evaluateStrict: !lightUpdate
evaluateStrict: !lightUpdate,
}
)
}
@ -209,8 +206,13 @@ export default class MetaTagging {
// All keys are defined - lets skip!
continue
}
const shouldPing = metatag.applyMetaTagsOnFeature(feature, layer, tags, state)
if(!shouldPing){
const shouldPing = metatag.applyMetaTagsOnFeature(
feature,
layer,
tags,
state
)
if (!shouldPing) {
continue
}
somethingChanged = true
@ -291,7 +293,7 @@ export default class MetaTagging {
return []
}
return [state.perLayer.get(layerId).GetFeaturesWithin(bbox)]
}
},
}
}
@ -336,8 +338,8 @@ export default class MetaTagging {
if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) {
console.warn(
"Could not calculate a " +
(isStrict ? "strict " : "") +
"calculated tag for key",
(isStrict ? "strict " : "") +
"calculated tag for key",
key,
"for feature",
feat.properties.id,
@ -345,9 +347,9 @@ export default class MetaTagging {
code,
"(in layer",
layerId +
") due to \n" +
e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
") due to \n" +
e +
"\n. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features",
e,
e.stack,
{ feat }

View file

@ -29,7 +29,11 @@ export class Changes {
public readonly pendingChanges: UIEventSource<ChangeDescription[]> =
LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined)
public readonly state: { allElements?: IndexedFeatureSource; osmConnection: OsmConnection, featureSwitches?: FeatureSwitchState }
public readonly state: {
allElements?: IndexedFeatureSource
osmConnection: OsmConnection
featureSwitches?: FeatureSwitchState
}
public readonly extraComment: UIEventSource<string> = new UIEventSource(undefined)
public readonly backend: string
public readonly isUploading = new UIEventSource(false)
@ -46,7 +50,7 @@ export class Changes {
allElements?: IndexedFeatureSource
featurePropertiesStore?: FeaturePropertiesStore
osmConnection: OsmConnection
historicalUserLocations?: FeatureSource,
historicalUserLocations?: FeatureSource
featureSwitches?: FeatureSwitchState
},
leftRightSensitive: boolean = false
@ -433,7 +437,7 @@ export class Changes {
// Probably irrelevant, such as a new helper node
return
}
if(this.state.featureSwitches.featureSwitchMorePrivacy?.data){
if (this.state.featureSwitches.featureSwitchMorePrivacy?.data) {
return
}

View file

@ -224,7 +224,7 @@ export class ChangesetHandler {
if (newMetaTag === undefined) {
extraMetaTags.push({
key: key,
value: oldCsTags[key]
value: oldCsTags[key],
})
continue
}
@ -360,15 +360,12 @@ export class ChangesetHandler {
["created_by", `MapComplete ${Constants.vNumber}`],
["locale", Locale.language.data],
["host", `${window.location.origin}${window.location.pathname}`],
[
"source",
setSourceAsSurvey ? "survey" : undefined
],
["imagery", this.changes.state["backgroundLayer"]?.data?.id]
["source", setSourceAsSurvey ? "survey" : undefined],
["imagery", this.changes.state["backgroundLayer"]?.data?.id],
].map(([key, value]) => ({
key,
value,
aggregate: false
aggregate: false,
}))
}

View file

@ -143,7 +143,11 @@ export class OsmConnection {
options.oauth_token.setData(undefined)
}
if (!Utils.runningFromConsole && this.auth.authenticated() && options.attemptLogin !== false) {
if (
!Utils.runningFromConsole &&
this.auth.authenticated() &&
options.attemptLogin !== false
) {
this.AttemptLogin()
} else {
console.log("Not authenticated")
@ -210,7 +214,7 @@ export class OsmConnection {
this.auth.xhr(
{
method: "GET",
path: "/api/0.6/user/details"
path: "/api/0.6/user/details",
},
(err, details: XMLDocument) => {
if (err != null) {
@ -322,9 +326,9 @@ export class OsmConnection {
method,
headers: header,
content,
path: `/api/0.6/${path}`
path: `/api/0.6/${path}`,
},
function(err, response) {
function (err, response) {
if (err !== null) {
error(err)
} else {
@ -341,7 +345,7 @@ export class OsmConnection {
header?: Record<string, string>,
allowAnonymous: boolean = false
): Promise<T> {
return <T> await this.interact(path, "POST", header, content, allowAnonymous)
return <T>await this.interact(path, "POST", header, content, allowAnonymous)
}
public async put<T extends string>(
@ -349,7 +353,7 @@ export class OsmConnection {
content?: string,
header?: Record<string, string>
): Promise<T> {
return <T> await this.interact(path, "PUT", header, content)
return <T>await this.interact(path, "PUT", header, content)
}
public async get(
@ -377,7 +381,7 @@ export class OsmConnection {
public reopenNote(id: number | string, text?: string): Promise<string> {
if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text)
return new Promise(resolve => {
return new Promise((resolve) => {
resolve("")
})
}
@ -404,7 +408,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
)
@ -445,7 +449,7 @@ export class OsmConnection {
file: gpx,
description: options.description,
tags: options.labels?.join(",") ?? "",
visibility: options.visibility
visibility: options.visibility,
}
if (!contents.description) {
@ -453,9 +457,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"
@ -463,7 +467,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]
}
@ -474,7 +478,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)
@ -497,9 +501,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 {
@ -514,7 +518,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")
@ -531,7 +535,7 @@ export class OsmConnection {
? "https://mapcomplete.org/land.html"
: window.location.protocol + "//" + window.location.host + "/land.html",
singlepage: true, // We always use 'singlePage', it is the most stable - including in PWA
auto: true
auto: true,
})
}

View file

@ -62,7 +62,12 @@ export default class OsmObjectDownloader {
if (idN < 0) {
obj = this.constructObject(<"node" | "way" | "relation">type, idN)
} else {
obj = await OsmObjectDownloader.RawDownloadObjectAsync(type, idN, this.backend, maxCacheAgeInSecs)
obj = await OsmObjectDownloader.RawDownloadObjectAsync(
type,
idN,
this.backend,
maxCacheAgeInSecs
)
}
if (obj === "deleted") {
return obj

View file

@ -126,7 +126,7 @@ class CountryTagger extends SimpleMetaTagger {
applyMetaTagsOnFeature(feature: Feature, _, tagsSource) {
const runningTasks = this.runningTasks
if(runningTasks.has(feature) || !!feature.properties._country){
if (runningTasks.has(feature) || !!feature.properties._country) {
return
}
runningTasks.add(feature)
@ -432,8 +432,11 @@ export default class SimpleMetaTaggers {
() => feature.properties["_country"]
)
const canonical =
denomination?.canonicalValue(value, defaultDenom == denomination, unit.inverted) ??
undefined
denomination?.canonicalValue(
value,
defaultDenom == denomination,
unit.inverted
) ?? undefined
if (canonical === value) {
break
}
@ -511,7 +514,7 @@ export default class SimpleMetaTaggers {
state: undefined,
},
},
<any> { tag_key: "opening_hours" }
<any>{ tag_key: "opening_hours" }
)
// Recalculate!

View file

@ -165,14 +165,12 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
"If true, shows some extra debugging help such as all the available tags on every object"
)
this.featureSwitchMorePrivacy = QueryParameters.GetBooleanQueryParameter(
"moreprivacy",
layoutToUse.enableMorePrivacy,
"If true, the location distance indication will not be written to the changeset and other privacy enhancing measures might be taken."
)
this.overpassUrl = QueryParameters.GetQueryParameter(
"overpassUrl",
(layoutToUse?.overpassUrl ?? Constants.defaultOverpassUrls).join(","),

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

@ -278,11 +278,11 @@ export class And extends TagsFilter {
}
const optimized = <TagsFilter[]>optimizedRaw
for (let i = 0; i <optimized.length; i++) {
for (let i = 0; i < optimized.length; i++) {
for (let j = i + 1; j < optimized.length; j++) {
const ti = optimized[i]
const tj = optimized[j]
if(ti.shadows(tj)){
if (ti.shadows(tj)) {
// if 'ti' is true, this implies 'tj' is always true as well.
// if 'ti' is false, then 'tj' might be true or false
// (e.g. let 'ti' be 'count>0' and 'tj' be 'count>10'.
@ -290,7 +290,7 @@ export class And extends TagsFilter {
// If 'ti' is true, then 'tj' will be true too and 'tj' can be ignored
// If 'ti' is false, then the entire expression will be false and it doesn't matter what 'tj' yields
optimized.splice(j, 1)
}else if (tj.shadows(ti)){
} else if (tj.shadows(ti)) {
optimized.splice(i, 1)
i--
continue
@ -298,7 +298,6 @@ export class And extends TagsFilter {
}
}
{
// Conflicting keys do return false
const properties: Record<string, string> = {}
@ -358,9 +357,9 @@ export class And extends TagsFilter {
i--
}
}
}else if(opt instanceof ComparingTag) {
} else if (opt instanceof ComparingTag) {
const ct = opt
if(properties[ct.key] !== undefined && !ct.matchesProperties(properties)){
if (properties[ct.key] !== undefined && !ct.matchesProperties(properties)) {
return false
}
}

View file

@ -136,15 +136,15 @@ export class Tag extends TagsFilter {
* new Tag("key","value").shadows(new And([new Tag("x","y"), new RegexTag("a","b", true)]) // => false
*/
shadows(other: TagsFilter): boolean {
if ((other["key"] !== this.key)) {
if (other["key"] !== this.key) {
return false
}
if(other instanceof Tag){
if (other instanceof Tag) {
// Other.key === this.key
return other.value === this.value
}
if(other instanceof RegexTag){
return other.matchesProperties({[this.key]: this.value})
if (other instanceof RegexTag) {
return other.matchesProperties({ [this.key]: this.value })
}
return false
}

View file

@ -835,7 +835,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
}
public mapAsyncD<J>(f: (t: T) => Promise<J>): Store<J> {
return this.bindD(t => UIEventSource.FromPromise(f(t)))
return this.bindD((t) => UIEventSource.FromPromise(f(t)))
}
/**
@ -901,5 +901,4 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
update(f: Updater<T> & ((value: T) => T)): void {
this.setData(f(this.data))
}
}

View file

@ -27,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",
@ -56,7 +56,7 @@ 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 {
@ -69,8 +69,8 @@ export default class LinkedDataLoader {
.trim()
.split(" ")
.map((n) => Number(n))
)
]
),
],
}
}
@ -92,18 +92,18 @@ export default class LinkedDataLoader {
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)],
}
}
@ -288,7 +288,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"]
@ -300,7 +300,7 @@ export default class LinkedDataLoader {
const geo: GeoJSON = {
type: "Feature",
properties,
geometry
geometry,
}
delete linkedData.geo
delete properties.shape
@ -323,7 +323,7 @@ export default class LinkedDataLoader {
if (output["type"]?.[0] === "https://data.velopark.be/openvelopark/terms#BicycleLocker") {
output["bicycle_parking"] = ["lockers"]
}
if(output["type"] === undefined){
if (output["type"] === undefined) {
console.error("No type given for", output)
}
delete output["type"]
@ -333,7 +333,7 @@ export default class LinkedDataLoader {
return
}
output[key] = output[key].map((v) => applyF(v))
if (!output[key].some(v => v !== undefined)) {
if (!output[key].some((v) => v !== undefined)) {
delete output[key]
}
}
@ -418,7 +418,7 @@ export default class LinkedDataLoader {
"brede publiek",
"iedereen",
"bezoekers",
"iedereen - vooral bezoekers gemeentehuis of bibliotheek."
"iedereen - vooral bezoekers gemeentehuis of bibliotheek.",
].indexOf(audience.toLowerCase()) >= 0
) {
return "yes"
@ -501,7 +501,7 @@ export default class LinkedDataLoader {
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,
@ -522,7 +522,7 @@ export default class LinkedDataLoader {
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",
@ -654,7 +654,10 @@ export default class LinkedDataLoader {
* The id will be saved as `ref:velopark`
* @param url
*/
public static async fetchVeloparkEntry(url: string, includeExtras: boolean = false): Promise<Feature[]> {
public static async fetchVeloparkEntry(
url: string,
includeExtras: boolean = false
): Promise<Feature[]> {
const cacheKey = includeExtras + url
if (this.veloparkCache[cacheKey]) {
return this.veloparkCache[cacheKey]
@ -662,20 +665,20 @@ 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",
},
"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",
}
if (includeExtras) {
optionalPaths["schema:address"] = {
"schema:streetAddress": "addr"
"schema:streetAddress": "addr",
}
optionalPaths["schema:name"] = "name"
optionalPaths["schema:description"] = "description"
@ -693,19 +696,19 @@ 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(

View file

@ -110,12 +110,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,
}))
)
})
@ -182,10 +182,10 @@ export default class FeatureReviews {
feature.geometry.type === "Polygon"
) {
coordss = feature.geometry.coordinates
}else if(feature.geometry.type === "MultiPolygon"){
} else if (feature.geometry.type === "MultiPolygon") {
coordss = feature.geometry.coordinates[0]
}else{
throw "Invalid feature type: "+feature.geometry.type
} else {
throw "Invalid feature type: " + feature.geometry.type
}
let maxDistance = 0
for (const coords of coordss) {
@ -288,7 +288,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)
@ -303,7 +303,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()
@ -350,7 +350,7 @@ export default class FeatureReviews {
signature: reviewData.signature,
madeByLoggedInUser: this._identity.getKeyId().map((user_key_id) => {
return reviewData.kid === user_key_id
})
}),
})
hasNew = true
}
@ -369,12 +369,16 @@ export default class FeatureReviews {
private ConstructSubjectUri(dontEncodeName: boolean = false): Store<string> {
// 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
return this._name.map(name => {
return this._name.map((name) => {
let uri = `geo:${this._lat},${this._lon}?u=${Math.round(this._uncertainty)}`
if (name) {
uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name))
}else if(this._uncertainty > 1000){
console.error("Not fetching reviews. Only got a point and a very big uncertainty range ("+this._uncertainty+"), so you'd probably only get garbage. Specify a name")
} else if (this._uncertainty > 1000) {
console.error(
"Not fetching reviews. Only got a point and a very big uncertainty range (" +
this._uncertainty +
"), so you'd probably only get garbage. Specify a name"
)
return undefined
}
return uri

View file

@ -48,7 +48,7 @@ export interface NSIItem {
displayName: string
id: string
locationSet: {
include: string[],
include: string[]
exclude: string[]
}
tags: Record<string, string>
@ -56,11 +56,15 @@ export interface NSIItem {
}
export default class NameSuggestionIndex {
private static readonly nsiFile: Readonly<NSIFile> = <any>nsi
private static readonly nsiWdFile: Readonly<Record<string, {
logos: { wikidata?: string, facebook?: string }
}>> = <any>nsiWD["wikidata"]
private static readonly nsiWdFile: Readonly<
Record<
string,
{
logos: { wikidata?: string; facebook?: string }
}
>
> = <any>nsiWD["wikidata"]
private static loco = new LocationConflation(nsiFeatures) // Some additional boundaries
@ -71,9 +75,11 @@ export default class NameSuggestionIndex {
return this._supportedTypes
}
const keys = Object.keys(NameSuggestionIndex.nsiFile.nsi)
const all = keys.map(k => NameSuggestionIndex.nsiFile.nsi[k].properties.path.split("/")[0])
this._supportedTypes = Utils.Dedup(all).map(s => {
if(s.endsWith("s")){
const all = keys.map(
(k) => NameSuggestionIndex.nsiFile.nsi[k].properties.path.split("/")[0]
)
this._supportedTypes = Utils.Dedup(all).map((s) => {
if (s.endsWith("s")) {
s = s.substring(0, s.length - 1)
}
return s
@ -81,7 +87,6 @@ export default class NameSuggestionIndex {
return this._supportedTypes
}
/**
* Fetches the data files for a single country. Note that it contains _all_ entries having this brand, not for a single type of object
* @param type
@ -89,19 +94,24 @@ export default class NameSuggestionIndex {
* @private
*/
private static async fetchFrequenciesFor(type: string, countries: string[]) {
let stats = await Promise.all(countries.map(c => {
try {
return Utils.downloadJsonCached<Record<string, number>>(`./assets/data/nsi/stats/${type}.${c.toUpperCase()}.json`, 24 * 60 * 60 * 1000)
} catch (e) {
console.error("Could not fetch " + type + " statistics due to", e)
return undefined
}
}))
let stats = await Promise.all(
countries.map((c) => {
try {
return Utils.downloadJsonCached<Record<string, number>>(
`./assets/data/nsi/stats/${type}.${c.toUpperCase()}.json`,
24 * 60 * 60 * 1000
)
} catch (e) {
console.error("Could not fetch " + type + " statistics due to", e)
return undefined
}
})
)
stats = Utils.NoNull(stats)
if (stats.length === 1) {
return stats[0]
}
if(stats.length === 0){
if (stats.length === 0) {
return {}
}
const merged = stats[0]
@ -128,7 +138,12 @@ export default class NameSuggestionIndex {
return false
}
public static async generateMappings(type: string, tags: Record<string, string>, country: string[], location?: [number, number]): Promise<Mapping[]> {
public static async generateMappings(
type: string,
tags: Record<string, string>,
country: string[],
location?: [number, number]
): Promise<Mapping[]> {
const mappings: Mapping[] = []
const frequencies = await NameSuggestionIndex.fetchFrequenciesFor(type, country)
for (const key in tags) {
@ -136,8 +151,14 @@ export default class NameSuggestionIndex {
continue
}
const value = tags[key]
const actualBrands = NameSuggestionIndex.getSuggestionsForKV(type, key, value, country.join(";"), location)
if(!actualBrands){
const actualBrands = NameSuggestionIndex.getSuggestionsForKV(
type,
key,
value,
country.join(";"),
location
)
if (!actualBrands) {
continue
}
for (const nsiItem of actualBrands) {
@ -156,7 +177,9 @@ export default class NameSuggestionIndex {
}
mappings.push({
if: new Tag(type, tags[type]),
addExtraTags: Object.keys(tags).filter(k => k !== type).map(k => new Tag(k, tags[k])),
addExtraTags: Object.keys(tags)
.filter((k) => k !== type)
.map((k) => new Tag(k, tags[k])),
then: new TypedTranslation<Record<string, never>>({ "*": nsiItem.displayName }),
hideInAnswer: false,
ifnot: undefined,
@ -164,22 +187,23 @@ export default class NameSuggestionIndex {
icon,
iconClass: "medium",
priorityIf: frequency > 0 ? new RegexTag("id", /.*/) : undefined,
searchTerms: { "*": [nsiItem.displayName, nsiItem.id] }
searchTerms: { "*": [nsiItem.displayName, nsiItem.id] },
})
}
}
return mappings
}
public static supportedTags(type: "operator" | "brand" | "flag" | "transit" | string): Record<string, string[]> {
const tags: Record<string, string []> = {}
public static supportedTags(
type: "operator" | "brand" | "flag" | "transit" | string
): Record<string, string[]> {
const tags: Record<string, string[]> = {}
const keys = Object.keys(NameSuggestionIndex.nsiFile.nsi)
for (const key of keys) {
const nsiItem = NameSuggestionIndex.nsiFile.nsi[key]
const path = nsiItem.properties.path
const [osmType, osmkey, osmvalue] = path.split("/")
if (type !== osmType && (type + "s" !== osmType)) {
if (type !== osmType && type + "s" !== osmType) {
continue
}
if (!tags[osmkey]) {
@ -204,7 +228,7 @@ export default class NameSuggestionIndex {
options.push(...suggestions)
}
}
return (options)
return options
}
/**
@ -212,8 +236,15 @@ export default class NameSuggestionIndex {
* @param country: a string containing one or more country codes, separated by ";"
* @param location: center point of the feature, should be [lon, lat]
*/
public static getSuggestionsFor(type: string, tags: {key: string, value: string}[], country: string = undefined, location: [number, number] = undefined): NSIItem[] {
return tags.flatMap(tag => this.getSuggestionsForKV(type, tag.key, tag.value, country, location))
public static getSuggestionsFor(
type: string,
tags: { key: string; value: string }[],
country: string = undefined,
location: [number, number] = undefined
): NSIItem[] {
return tags.flatMap((tag) =>
this.getSuggestionsForKV(type, tag.key, tag.value, country, location)
)
}
/**
@ -221,18 +252,26 @@ export default class NameSuggestionIndex {
* @param country: a string containing one or more country codes, separated by ";"
* @param location: center point of the feature, should be [lon, lat]
*/
public static getSuggestionsForKV(type: string, key: string, value: string, country: string = undefined, location: [number, number] = undefined): NSIItem[] {
public static getSuggestionsForKV(
type: string,
key: string,
value: string,
country: string = undefined,
location: [number, number] = undefined
): NSIItem[] {
const path = `${type}s/${key}/${value}`
const entry = NameSuggestionIndex.nsiFile.nsi[path]
return entry?.items?.filter(i => {
return entry?.items?.filter((i) => {
if (i.locationSet.include.indexOf("001") >= 0) {
return true
}
if (country === undefined ||
if (
country === undefined ||
// We prefer the countries provided by lonlat2country, they are more precise
// Country might contain multiple countries, separated by ';'
i.locationSet.include.some(c => country.indexOf(c) >= 0)) {
i.locationSet.include.some((c) => country.indexOf(c) >= 0)
) {
return true
}

View file

@ -50,10 +50,8 @@ export default class NearbyImagesSearch {
constructor(options: NearbyImageOptions, features: IndexedFeatureSource) {
this.individualStores = NearbyImagesSearch.services
.filter(s => s !== "kartaview" /*DEAD*/)
.map((s) =>
NearbyImagesSearch.buildPictureFetcher(options, s)
)
.filter((s) => s !== "kartaview" /*DEAD*/)
.map((s) => NearbyImagesSearch.buildPictureFetcher(options, s))
const allDone = new UIEventSource(false)
this.allDone = allDone

View file

@ -19,10 +19,10 @@ export interface TagInfoStats {
}
interface GeofabrikCountryProperties {
id: string,
parent: string | "europe" | "asia",
urls: string[],
name: string,
id: string
parent: string | "europe" | "asia"
urls: string[]
name: string
"iso3166-1:alpha2": string[]
}
@ -38,7 +38,9 @@ export default class TagInfo {
public async getStats(key: string, value?: string): Promise<TagInfoStats> {
let url: string
if (value) {
url = `${this._backend}api/4/tag/stats?key=${encodeURIComponent(key)}&value=${encodeURIComponent(value)}`
url = `${this._backend}api/4/tag/stats?key=${encodeURIComponent(
key
)}&value=${encodeURIComponent(value)}`
} else {
url = `${this._backend}api/4/key/stats?key=${encodeURIComponent(key)}`
}
@ -65,8 +67,13 @@ export default class TagInfo {
if (TagInfo._geofabrikCountries) {
return TagInfo._geofabrikCountries
}
const countriesFC: FeatureCollection = await Utils.downloadJsonCached<FeatureCollection>("https://download.geofabrik.de/index-v1-nogeom.json", 24 * 1000 * 60 * 60)
TagInfo._geofabrikCountries = countriesFC.features.map(f => <GeofabrikCountryProperties>f.properties)
const countriesFC: FeatureCollection = await Utils.downloadJsonCached<FeatureCollection>(
"https://download.geofabrik.de/index-v1-nogeom.json",
24 * 1000 * 60 * 60
)
TagInfo._geofabrikCountries = countriesFC.features.map(
(f) => <GeofabrikCountryProperties>f.properties
)
return TagInfo._geofabrikCountries
}
@ -80,7 +87,7 @@ export default class TagInfo {
public static async getInstanceFor(countryCode: string) {
const countries = await this.geofabrikCountries()
countryCode = countryCode.toUpperCase()
const country = countries.find(c => c["iso3166-1:alpha2"]?.indexOf(countryCode) >= 0)
const country = countries.find((c) => c["iso3166-1:alpha2"]?.indexOf(countryCode) >= 0)
if (!country || !country?.parent || !country?.id) {
return undefined
}
@ -88,7 +95,11 @@ export default class TagInfo {
return new TagInfo(url)
}
private static async getDistributionsFor(countryCode: string, key: string, value?: string): Promise<TagInfoStats>{
private static async getDistributionsFor(
countryCode: string,
key: string,
value?: string
): Promise<TagInfoStats> {
if (!countryCode) {
return undefined
}
@ -99,24 +110,30 @@ export default class TagInfo {
try {
return await ti.getStats(key, value)
} catch (e) {
console.warn("Could not fetch info for", countryCode,key,value, "due to", e)
console.warn("Could not fetch info for", countryCode, key, value, "due to", e)
return undefined
}
}
private static readonly blacklist =["VI","GF","PR"]
private static readonly blacklist = ["VI", "GF", "PR"]
public static async getGlobalDistributionsFor(key: string, value?: string): Promise<Record<string, TagInfoStats>> {
public static async getGlobalDistributionsFor(
key: string,
value?: string
): Promise<Record<string, TagInfoStats>> {
const countriesAll = await this.geofabrikCountries()
const countries = countriesAll.map(c => c["iso3166-1:alpha2"]?.[0]).filter(c => !!c && TagInfo.blacklist.indexOf(c) < 0)
const countries = countriesAll
.map((c) => c["iso3166-1:alpha2"]?.[0])
.filter((c) => !!c && TagInfo.blacklist.indexOf(c) < 0)
const perCountry: Record<string, TagInfoStats> = {}
const results = await Promise.all(countries.map(country => TagInfo.getDistributionsFor(country, key, value)))
for (let i = 0; i < countries.length; i++){
const results = await Promise.all(
countries.map((country) => TagInfo.getDistributionsFor(country, key, value))
)
for (let i = 0; i < countries.length; i++) {
const countryCode = countries[i]
if(results[i]){
perCountry[countryCode] = results[i]
if (results[i]) {
perCountry[countryCode] = results[i]
}
}
return perCountry
}
}

View file

@ -149,9 +149,8 @@ export class Denomination {
if (stripped === null) {
return null
}
if(inverted){
if (inverted) {
return (stripped + "/" + this.canonical).trim()
}
if (stripped === "1" && this._canonicalSingular !== undefined) {
return ("1 " + this._canonicalSingular).trim()
@ -187,10 +186,10 @@ export class Denomination {
return value.substring(key.length).trim()
}
let trimmed = value.substring(0, value.length - key.length).trim()
if(!inverted){
if (!inverted) {
return trimmed
}
if(trimmed.endsWith("/")){
if (trimmed.endsWith("/")) {
trimmed = trimmed.substring(0, trimmed.length - 1).trim()
}
return trimmed
@ -218,13 +217,23 @@ export class Denomination {
return null
}
if(!this._validator.isValid(value.trim())){
if (!this._validator.isValid(value.trim())) {
return null
}
return this._validator.reformat(value.trim())
}
withValidator(validator: Validator) {
return new Denomination(this.canonical, this._canonicalSingular, this.useIfNoUnitGiven, this.prefix, this.addSpace, this.alternativeDenominations, this.human, this.humanSingular, validator)
return new Denomination(
this.canonical,
this._canonicalSingular,
this.useIfNoUnitGiven,
this.prefix,
this.addSpace,
this.alternativeDenominations,
this.human,
this.humanSingular,
validator
)
}
}

View file

@ -85,7 +85,12 @@ export class AvailableRasterLayers {
matching.push(AvailableRasterLayers.bing)
}
matching.push(...AvailableRasterLayers.globalLayers)
if(!matching.some(l => l.id === AvailableRasterLayers.defaultBackgroundLayer.properties.id)){
if (
!matching.some(
(l) =>
l.id === AvailableRasterLayers.defaultBackgroundLayer.properties.id
)
) {
matching.push(AvailableRasterLayers.defaultBackgroundLayer)
}
return matching

View file

@ -39,7 +39,12 @@ export abstract class Conversion<TIn, TOut> {
ConversionContext.print(msg)
}
if (context.hasErrors()) {
throw new Error(["Detected one or more errors, stopping now:", context.getAll("error").map(e => e.context.path.join(".")+": "+e.message)].join("\n\t"))
throw new Error(
[
"Detected one or more errors, stopping now:",
context.getAll("error").map((e) => e.context.path.join(".") + ": " + e.message),
].join("\n\t")
)
}
return fixed
}

View file

@ -157,10 +157,14 @@ export class ConversionContext {
* @constructor
*/
MergeObjectsForOverride<T, S>(source: Readonly<S>, target: T): T & S {
try{
return Utils.Merge(source,target)
}catch (e) {
this.err("Could not apply an override: due to "+e+"\n\tHINT: did you just pull changes from the repository or switch branches? Try 'npm run reset:layeroverview'")
try {
return Utils.Merge(source, target)
} catch (e) {
this.err(
"Could not apply an override: due to " +
e +
"\n\tHINT: did you just pull changes from the repository or switch branches? Try 'npm run reset:layeroverview'"
)
}
}
}

View file

@ -33,7 +33,7 @@ export class UpdateLegacyLayer extends DesugaringStep<
delete config["overpassTags"]
}
if(config.allowMove?.["enableImproveAccuraccy"]){
if (config.allowMove?.["enableImproveAccuraccy"]) {
// Fix common misspelling: 'accuracy' is often typo'ed as 'accuraCCy'
config.allowMove["enableImproveAccuracy"] = config.allowMove["enableImproveAccuraccy"]
delete config.allowMove["enableImproveAccuraccy"]

View file

@ -161,9 +161,8 @@ class ExpandTagRendering extends Conversion<
private readonly _options: {
/* If true, will copy the 'osmSource'-tags into the condition */
applyCondition?: true | boolean
noHardcodedStrings?: false | boolean,
noHardcodedStrings?: false | boolean
addToContext?: false | boolean
}
constructor(
@ -171,7 +170,7 @@ class ExpandTagRendering extends Conversion<
self: LayerConfigJson,
options?: {
applyCondition?: true | boolean
noHardcodedStrings?: false | boolean,
noHardcodedStrings?: false | boolean
// If set, a question will be added to the 'sharedTagRenderings'. Should only be used for 'questions.json'
addToContext?: false | boolean
}
@ -208,17 +207,16 @@ class ExpandTagRendering extends Conversion<
if (typeof tr === "string" || tr["builtin"] !== undefined) {
const stable = this.convert(tr, ctx.inOperation("recursive_resolve"))
result.push(...stable)
if(this._options?.addToContext){
if (this._options?.addToContext) {
for (const tr of stable) {
this._state.tagRenderings?.set(tr.id, tr)
}
}
} else {
result.push(tr)
if(this._options?.addToContext){
this._state.tagRenderings?.set(tr["id"], <QuestionableTagRenderingConfigJson> tr)
if (this._options?.addToContext) {
this._state.tagRenderings?.set(tr["id"], <QuestionableTagRenderingConfigJson>tr)
}
}
}
@ -1274,14 +1272,23 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
}
export class PrepareLayer extends Fuse<LayerConfigJson> {
constructor(state: DesugaringContext, options?: {addTagRenderingsToContext?: false | boolean}) {
constructor(
state: DesugaringContext,
options?: { addTagRenderingsToContext?: false | boolean }
) {
super(
"Fully prepares and expands a layer for the LayerConfig.",
new On("tagRenderings", new Each(new RewriteSpecial())),
new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
new On("tagRenderings", (layer) => new Concat(new ExpandTagRendering(state, layer, {
addToContext: options?.addTagRenderingsToContext ?? false
}))),
new On(
"tagRenderings",
(layer) =>
new Concat(
new ExpandTagRendering(state, layer, {
addToContext: options?.addTagRenderingsToContext ?? false,
})
)
),
new On("tagRenderings", new Each(new DetectInline())),
new AddQuestionBox(),
new AddEditingElements(state),

View file

@ -566,7 +566,11 @@ export class DetectMappingsShadowedByCondition extends DesugaringStep<TagRenderi
private readonly _forceError: boolean
constructor(forceError: boolean = false) {
super("Checks that, if the tagrendering has a condition, that a mapping is not contradictory to it, i.e. that there are no dead mappings", [], "DetectMappingsShadowedByCondition")
super(
"Checks that, if the tagrendering has a condition, that a mapping is not contradictory to it, i.e. that there are no dead mappings",
[],
"DetectMappingsShadowedByCondition"
)
this._forceError = forceError
}
@ -588,24 +592,28 @@ export class DetectMappingsShadowedByCondition extends DesugaringStep<TagRenderi
* ctx.hasErrors() // => true
*/
convert(json: TagRenderingConfigJson, context: ConversionContext): TagRenderingConfigJson {
if(!json.condition && !json.metacondition){
if (!json.condition && !json.metacondition) {
return json
}
if(!json.mappings || json.mappings?.length ==0){
if (!json.mappings || json.mappings?.length == 0) {
return json
}
let conditionJson = json.condition ?? json.metacondition
if(json.condition !== undefined && json.metacondition !== undefined){
conditionJson = {and: [json.condition, json.metacondition]}
if (json.condition !== undefined && json.metacondition !== undefined) {
conditionJson = { and: [json.condition, json.metacondition] }
}
const condition = TagUtils.Tag(conditionJson, context.path.join("."))
for (let i = 0; i < json.mappings.length; i++){
for (let i = 0; i < json.mappings.length; i++) {
const mapping = json.mappings[i]
const tagIf = TagUtils.Tag(mapping.if, context.path.join("."))
const optimized = new And([tagIf, condition]).optimize()
if(optimized === false){
const msg = ("Detected a conflicting mapping and condition. The mapping requires tags " + tagIf.asHumanString() + ", yet this can never happen because the set condition requires " + condition.asHumanString())
if (optimized === false) {
const msg =
"Detected a conflicting mapping and condition. The mapping requires tags " +
tagIf.asHumanString() +
", yet this can never happen because the set condition requires " +
condition.asHumanString()
const ctx = context.enters("mappings", i)
if (this._forceError) {
ctx.err(msg)
@ -615,10 +623,8 @@ export class DetectMappingsShadowedByCondition extends DesugaringStep<TagRenderi
}
}
return undefined
}
}
export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJson> {
@ -1094,14 +1100,26 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
)
}
}
if(this._layerConfig?.source?.osmTags && NameSuggestionIndex.supportedTypes().indexOf(json.freeform.key) >= 0){
const tags= TagUtils.TagD(this._layerConfig?.source?.osmTags)?.usedTags()
if (
this._layerConfig?.source?.osmTags &&
NameSuggestionIndex.supportedTypes().indexOf(json.freeform.key) >= 0
) {
const tags = TagUtils.TagD(this._layerConfig?.source?.osmTags)?.usedTags()
const suggestions = NameSuggestionIndex.getSuggestionsFor(json.freeform.key, tags)
if(suggestions === undefined){
context.enters("freeform","type").err("No entry found in the 'Name Suggestion Index'. None of the 'osmSource'-tags match an entry in the NSI.\n\tOsmSource-tags are "+tags.map(t => t.asHumanString()).join(" ; "))
if (suggestions === undefined) {
context
.enters("freeform", "type")
.err(
"No entry found in the 'Name Suggestion Index'. None of the 'osmSource'-tags match an entry in the NSI.\n\tOsmSource-tags are " +
tags.map((t) => t.asHumanString()).join(" ; ")
)
}
}else if(json.freeform.type === "nsi"){
context.enters("freeform","type").warn("No need to explicitly set type to 'NSI', autodetected based on freeform type")
} else if (json.freeform.type === "nsi") {
context
.enters("freeform", "type")
.warn(
"No need to explicitly set type to 'NSI', autodetected based on freeform type"
)
}
}
if (json.render && json["question"] && json.freeform === undefined) {
@ -1720,8 +1738,12 @@ export class ValidateLayer extends Conversion<
}
}
if(json.allowMove?.["enableAccuraccy"] !== undefined){
context.enters("allowMove", "enableAccuracy").err("`enableAccuracy` is written with two C in the first occurrence and only one in the last")
if (json.allowMove?.["enableAccuraccy"] !== undefined) {
context
.enters("allowMove", "enableAccuracy")
.err(
"`enableAccuracy` is written with two C in the first occurrence and only one in the last"
)
}
return { raw: json, parsed: layerConfig }

View file

@ -527,7 +527,16 @@ export interface LayerConfigJson {
*/
units?: (
| UnitConfigJson
| Record<string, string | { quantity: string; denominations: string[]; canonical?: string, inverted?: boolean }>
| Record<
string,
| string
| {
quantity: string
denominations: string[]
canonical?: string
inverted?: boolean
}
>
)[]
/**

View file

@ -450,5 +450,4 @@ export interface LayoutConfigJson {
* iftrue: Do not write 'change_within_x_m' and do not indicate that this was done by survey
*/
enableMorePrivacy: boolean
}

View file

@ -264,7 +264,7 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
* ifunset: The question will be considered answered if any value is set for the key
* group: expert
*/
invalidValues?: TagConfigJson,
invalidValues?: TagConfigJson
/**
* question: If this key shared and distinguished by a postfix, what is the postfix?

View file

@ -279,7 +279,7 @@ export default class LayerConfig extends WithContextLoader {
}
this.units = [].concat(
...(json.units ?? []).map((unitJson, i) =>
Unit.fromJson(unitJson, this.tagRenderings,`${context}.unit[${i}]`)
Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`)
)
)

View file

@ -20,11 +20,11 @@ export class LayoutInformation {
id: string
icon: string
title: Translatable | Translation
shortDescription: Translatable| Translation
definition?: Translatable| Translation
shortDescription: Translatable | Translation
definition?: Translatable | Translation
mustHaveLanguage?: boolean
hideFromOverview?: boolean
keywords?: (Translatable| Translation)[]
keywords?: (Translatable | Translation)[]
}
export default class LayoutConfig implements LayoutInformation {
@ -65,7 +65,6 @@ export default class LayoutConfig implements LayoutInformation {
public readonly enableTerrain: boolean
public readonly enableMorePrivacy: boolean
public readonly customCss?: string
public readonly overpassUrl: string[]
@ -206,7 +205,9 @@ export default class LayoutConfig implements LayoutInformation {
this.overpassTimeout = json.overpassTimeout ?? 30
this.overpassMaxZoom = json.overpassMaxZoom ?? 16
this.osmApiTileSize = json.osmApiTileSize ?? this.overpassMaxZoom + 1
this.enableMorePrivacy = json.enableMorePrivacy || json.layers.some(l => (<LayerConfigJson> l).enableMorePrivacy)
this.enableMorePrivacy =
json.enableMorePrivacy ||
json.layers.some((l) => (<LayerConfigJson>l).enableMorePrivacy)
this.layersDict = new Map<string, LayerConfig>()
for (const layer of this.layers) {
@ -315,7 +316,8 @@ export default class LayoutConfig implements LayoutInformation {
continue
}
if (layer.source.osmTags.matchesProperties(tags)) {
if(!layer.isShown || layer.isShown.matchesProperties(tags)){// https://github.com/pietervdvn/MapComplete/issues/1959
if (!layer.isShown || layer.isShown.matchesProperties(tags)) {
// https://github.com/pietervdvn/MapComplete/issues/1959
return layer
}
}
@ -324,16 +326,22 @@ export default class LayoutConfig implements LayoutInformation {
return undefined
}
public getUsedImages(){
if(this.usedImages){
public getUsedImages() {
if (this.usedImages) {
return this.usedImages
}
const json = this.source
// The 'favourite'-layer contains pretty much all images as it bundles all layers, so we exclude it
const jsonNoFavourites = {...json, layers: json.layers.filter(l => l["id"] !== "favourite")}
const jsonNoFavourites = {
...json,
layers: json.layers.filter((l) => l["id"] !== "favourite"),
}
this.usedImages = Array.from(
new ExtractImages(this.official, undefined)
.convertStrict(jsonNoFavourites, ConversionContext.construct([json.id], ["ExtractImages"]))
.convertStrict(
jsonNoFavourites,
ConversionContext.construct([json.id], ["ExtractImages"])
)
.map((i) => i.path)
).sort()
return this.usedImages

View file

@ -20,8 +20,13 @@ export default class LineRenderingConfig extends WithContextLoader {
this.color = this.tr("color", "#0000ff")
this.width = this.tr("width", "7")
this.dashArray = json.dashArray
if(this.dashArray !== undefined && typeof this.dashArray !== "string"){
throw "Invalid dasharray at "+context+"; this should be a string but is a "+typeof this.dashArray
if (this.dashArray !== undefined && typeof this.dashArray !== "string") {
throw (
"Invalid dasharray at " +
context +
"; this should be a string but is a " +
typeof this.dashArray
)
}
this.lineCap = this.tr("lineCap", "round")
this.fill = this.tr("fill", undefined)

View file

@ -123,12 +123,15 @@ export default class PointRenderingConfig extends WithContextLoader {
context + ".rotationAlignment"
)
}
private static FromHtmlMulti(multiSpec: string, tags: Store<Record<string, string>>): BaseUIElement {
private static FromHtmlMulti(
multiSpec: string,
tags: Store<Record<string, string>>
): BaseUIElement {
const icons: IconConfig[] = []
for (const subspec of multiSpec.split(";")) {
if(subspec.startsWith("http://") || subspec.startsWith("https://")){
icons.push(new IconConfig({icon: subspec}))
if (subspec.startsWith("http://") || subspec.startsWith("https://")) {
icons.push(new IconConfig({ icon: subspec }))
continue
}
const [icon, color] = subspec.split(":")

View file

@ -6,7 +6,10 @@ import { And } from "../../Logic/Tags/And"
import { Utils } from "../../Utils"
import { Tag } from "../../Logic/Tags/Tag"
import Link from "../../UI/Base/Link"
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
import {
MappingConfigJson,
QuestionableTagRenderingConfigJson,
} from "./Json/QuestionableTagRenderingConfigJson"
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import { RegexTag } from "../../Logic/Tags/RegexTag"
@ -76,7 +79,10 @@ export default class TagRenderingConfig {
public readonly classes: string[] | undefined
constructor(
config: string | TagRenderingConfigJson | (QuestionableTagRenderingConfigJson & { questionHintIsMd?: boolean }),
config:
| string
| TagRenderingConfigJson
| (QuestionableTagRenderingConfigJson & { questionHintIsMd?: boolean }),
context?: string
) {
let json = <string | QuestionableTagRenderingConfigJson>config
@ -196,7 +202,7 @@ export default class TagRenderingConfig {
) ?? [],
inline: json.freeform.inline ?? false,
default: json.freeform.default,
postfixDistinguished: json.freeform.postfixDistinguished?.trim()
postfixDistinguished: json.freeform.postfixDistinguished?.trim(),
}
if (json.freeform["extraTags"] !== undefined) {
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
@ -215,10 +221,18 @@ export default class TagRenderingConfig {
}
if (this.freeform.postfixDistinguished) {
if (this.multiAnswer) {
throw "At " + context + ": a postfixDistinguished-value cannot be used with a multiAnswer"
throw (
"At " +
context +
": a postfixDistinguished-value cannot be used with a multiAnswer"
)
}
if (this.freeform.postfixDistinguished.startsWith("/")) {
throw "At " + context + ": a postfixDistinguished-value should not start with `/`. This will be inserted automatically"
throw (
"At " +
context +
": a postfixDistinguished-value should not start with `/`. This will be inserted automatically"
)
}
}
@ -400,7 +414,7 @@ export default class TagRenderingConfig {
iconClass,
addExtraTags,
searchTerms: mapping.searchTerms,
priorityIf: prioritySearch
priorityIf: prioritySearch,
}
if (isQuestionable) {
if (hideInAnswer !== true && mp.if !== undefined && !mp.if.isUsableAsAnswer()) {
@ -486,13 +500,12 @@ export default class TagRenderingConfig {
})
)
if (freeformKeyDefined && tags[this.freeform.key] !== undefined) {
const usedFreeformValues = new Set<string>(
applicableMappings
?.flatMap(m => m.if?.usedTags() ?? [])
?.filter(kv => kv.key === this.freeform.key)
?.map(kv => kv.value)
?.flatMap((m) => m.if?.usedTags() ?? [])
?.filter((kv) => kv.key === this.freeform.key)
?.map((kv) => kv.value)
)
const freeformValues = tags[this.freeform.key].split(";")
@ -502,7 +515,7 @@ export default class TagRenderingConfig {
then: new TypedTranslation<object>(
this.render.replace("{" + this.freeform.key + "}", leftover).translations,
this.render.context
)
),
})
}
}
@ -544,7 +557,7 @@ export default class TagRenderingConfig {
if (this.freeform?.key === undefined || tags[this.freeform.key] !== undefined) {
const postfix = this.freeform?.postfixDistinguished
if (postfix !== undefined) {
const allFreeforms = tags[this.freeform.key].split(";").map(s => s.trim())
const allFreeforms = tags[this.freeform.key].split(";").map((s) => s.trim())
for (const allFreeform of allFreeforms) {
if (allFreeform.endsWith(postfix)) {
const [v] = allFreeform.split("/")
@ -552,7 +565,7 @@ export default class TagRenderingConfig {
return {
then: this.render.PartialSubs({ [this.freeform.key]: v.trim() }),
icon: this.renderIcon,
iconClass: this.renderIconClass
iconClass: this.renderIconClass,
}
}
}
@ -607,7 +620,7 @@ export default class TagRenderingConfig {
key: commonKey,
values: Utils.NoNull(
values.map((arr) => arr.filter((item) => item.k === commonKey)[0]?.v)
)
),
}
}
@ -622,7 +635,7 @@ export default class TagRenderingConfig {
return {
key,
type: this.freeform.type,
values
values,
}
} catch (e) {
console.error("Could not create FreeformValues for tagrendering", this.id)
@ -692,7 +705,7 @@ export default class TagRenderingConfig {
freeformValue = undefined
}
if (this.freeform?.postfixDistinguished && freeformValue !== undefined) {
const allValues = currentProperties[this.freeform.key].split(";").map(s => s.trim())
const allValues = currentProperties[this.freeform.key].split(";").map((s) => s.trim())
const perPostfix: Record<string, string> = {}
for (const value of allValues) {
const [v, postfix] = value.split("/")
@ -701,7 +714,7 @@ export default class TagRenderingConfig {
perPostfix[this.freeform.postfixDistinguished] = freeformValue
const keys = Object.keys(perPostfix)
keys.sort()
freeformValue = keys.map(k => perPostfix[k] + "/" + k).join("; ")
freeformValue = keys.map((k) => perPostfix[k] + "/" + k).join("; ")
}
if (
freeformValue === undefined &&
@ -728,7 +741,7 @@ export default class TagRenderingConfig {
// Either no mappings, or this is a radio-button selected freeform value
const tag = new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? [])
...(this.freeform.addExtraTags ?? []),
])
const newProperties = tag.applyOn(currentProperties)
if (this.invalidValues?.matchesProperties(newProperties)) {
@ -752,7 +765,7 @@ export default class TagRenderingConfig {
selectedMappings.push(
new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? [])
...(this.freeform.addExtraTags ?? []),
])
)
}
@ -778,15 +791,14 @@ export default class TagRenderingConfig {
!someMappingIsShown ||
singleSelectedMapping === undefined)
if (useFreeform) {
return new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? [])
...(this.freeform.addExtraTags ?? []),
])
} else if (singleSelectedMapping !== undefined) {
return new And([
this.mappings[singleSelectedMapping].if,
...(this.mappings[singleSelectedMapping].addExtraTags ?? [])
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
])
} else {
console.error("TagRenderingConfig.ConstructSpecification has a weird fallback for", {
@ -794,7 +806,7 @@ export default class TagRenderingConfig {
singleSelectedMapping,
multiSelectedMapping,
currentProperties,
useFreeform
useFreeform,
})
return undefined
@ -807,7 +819,7 @@ export default class TagRenderingConfig {
withRender = [
`This rendering asks information about the property `,
Link.OsmWiki(this.freeform.key).AsMarkdown(),
"This is rendered with `" + this.render.txt + "`"
"This is rendered with `" + this.render.txt + "`",
]
}
@ -815,46 +827,56 @@ export default class TagRenderingConfig {
if (this.mappings !== undefined) {
mappings = MarkdownUtils.list(
this.mappings.flatMap((m) => {
const msgs: (string)[] = [
"*" + m.then.txt + "* corresponds with " +
m.if.asHumanString(true, false, {})
]
if (m.hideInAnswer === true) {
msgs.push("_This option cannot be chosen as answer_")
}
if (m.ifnot !== undefined) {
msgs.push(
"Unselecting this answer will add " +
const msgs: string[] = [
"*" +
m.then.txt +
"* corresponds with " +
m.if.asHumanString(true, false, {}),
]
if (m.hideInAnswer === true) {
msgs.push("_This option cannot be chosen as answer_")
}
if (m.ifnot !== undefined) {
msgs.push(
"Unselecting this answer will add " +
m.ifnot.asHumanString(true, false, {})
)
}
return msgs
})
)
}
return msgs
})
)
}
let condition: string = undefined
if (this.condition !== undefined && !this.condition?.matchesProperties({})) {
const conditionAsLink = (<TagsFilter>this.condition.optimize()).asHumanString(true, false, {})
condition = "This tagrendering is only visible in the popup if the following condition is met: " + conditionAsLink
const conditionAsLink = (<TagsFilter>this.condition.optimize()).asHumanString(
true,
false,
{}
)
condition =
"This tagrendering is only visible in the popup if the following condition is met: " +
conditionAsLink
}
let labels: string = undefined
if (this.labels?.length > 0) {
labels = [
"This tagrendering has labels ",
...this.labels.map((label) => "`" + label + "`")
...this.labels.map((label) => "`" + label + "`"),
].join("\n")
}
return [
"### this.id",
this.description,
this.question !== undefined ? ("The question is `" + this.question.txt + "`") : "_This tagrendering has no question and is thus read-only_",
this.question !== undefined
? "The question is `" + this.question.txt + "`"
: "_This tagrendering has no question and is thus read-only_",
withRender.join("\n"),
mappings,
condition,
labels
labels,
].join("\n")
}
@ -879,37 +901,45 @@ export default class TagRenderingConfig {
return Utils.NoNull(tags)
}
}
export class TagRenderingConfigUtils {
public static withNameSuggestionIndex(config: TagRenderingConfig, tags: UIEventSource<Record<string, string>>, feature?: Feature): Store<TagRenderingConfig> {
public static withNameSuggestionIndex(
config: TagRenderingConfig,
tags: UIEventSource<Record<string, string>>,
feature?: Feature
): Store<TagRenderingConfig> {
const isNSI = NameSuggestionIndex.supportedTypes().indexOf(config.freeform?.key) >= 0
if (!isNSI) {
return new ImmutableStore(config)
}
const extraMappings = tags
.bindD(tags => {
const country = tags._country
if (country === undefined) {
return undefined
}
const center = GeoOperations.centerpointCoordinates(feature)
return UIEventSource.FromPromise(NameSuggestionIndex.generateMappings(config.freeform.key, tags, country.split(";"), center))
})
return extraMappings.map(extraMappings => {
const extraMappings = tags.bindD((tags) => {
const country = tags._country
if (country === undefined) {
return undefined
}
const center = GeoOperations.centerpointCoordinates(feature)
return UIEventSource.FromPromise(
NameSuggestionIndex.generateMappings(
config.freeform.key,
tags,
country.split(";"),
center
)
)
})
return extraMappings.map((extraMappings) => {
if (!extraMappings || extraMappings.length == 0) {
return config
}
const clone: TagRenderingConfig = Object.create(config)
const oldMappingsCloned = clone.mappings?.map(m => ({
...m,
priorityIf: m.priorityIf ?? TagUtils.Tag("id~*")
})) ?? []
const oldMappingsCloned =
clone.mappings?.map((m) => ({
...m,
priorityIf: m.priorityIf ?? TagUtils.Tag("id~*"),
})) ?? []
clone.mappings = [...oldMappingsCloned, ...extraMappings]
return clone
})
}
}

View file

@ -266,7 +266,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
featurePropertiesStore: this.featureProperties,
osmConnection: this.osmConnection,
historicalUserLocations: this.geolocation.historicalUserLocations,
featureSwitches: this.featureSwitches
featureSwitches: this.featureSwitches,
},
layout?.isLeftRightSensitive() ?? false
)
@ -497,8 +497,8 @@ export default class ThemeViewState implements SpecialVisualizationState {
Utils.LoadCustomCss(this.layout.customCss)
}
Hash.hash.addCallbackAndRunD(hash => {
if(hash === "current_view" || hash.match(/current_view_[0-9]+/)){
Hash.hash.addCallbackAndRunD((hash) => {
if (hash === "current_view" || hash.match(/current_view_[0-9]+/)) {
this.selectCurrentView()
}
})
@ -827,7 +827,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
)
}
public selectCurrentView(){
public selectCurrentView() {
this.guistate.closeAll()
this.selectedElement.setData(this.currentView.features?.data?.[0])
}

View file

@ -76,11 +76,13 @@ export class Unit {
static fromJson(
json:
| UnitConfigJson
| Record<string, string | { quantity: string; denominations: string[], inverted?: boolean }>,
| Record<
string,
string | { quantity: string; denominations: string[]; inverted?: boolean }
>,
tagRenderings: TagRenderingConfig[],
ctx: string
): Unit[] {
let types: Record<string, ValidatorType> = {}
for (const tagRendering of tagRenderings) {
if (tagRendering.freeform?.type) {
@ -94,7 +96,12 @@ export class Unit {
return this.parse(<UnitConfigJson>json, types, ctx)
}
private static parseDenomination(json: UnitConfigJson, validator: Validator, appliesToKey: string, ctx: string): Unit {
private static parseDenomination(
json: UnitConfigJson,
validator: Validator,
appliesToKey: string,
ctx: string
): Unit {
const applicable = json.applicableUnits.map((u, i) =>
Denomination.fromJson(u, validator, `${ctx}.units[${i}]`)
)
@ -157,7 +164,11 @@ export class Unit {
* ]
* }, "test")
*/
private static parse(json: UnitConfigJson, types: Record<string, ValidatorType>, ctx: string): Unit[] {
private static parse(
json: UnitConfigJson,
types: Record<string, ValidatorType>,
ctx: string
): Unit[] {
const appliesTo = json.appliesToKey
for (let i = 0; i < (appliesTo ?? []).length; i++) {
let key = appliesTo[i]
@ -171,7 +182,6 @@ export class Unit {
}
// Some keys do have unit handling
const units: Unit[] = []
if (appliesTo === undefined) {
units.push(this.parseDenomination(json, Validators.get("float"), undefined, ctx))
@ -213,7 +223,8 @@ export class Unit {
private static loadFromLibrary(
spec: Record<
string,
string | { quantity: string; denominations: string[]; canonical?: string, inverted?: boolean }
| string
| { quantity: string; denominations: string[]; canonical?: string; inverted?: boolean }
>,
types: Record<string, ValidatorType>,
ctx: string
@ -225,7 +236,14 @@ export class Unit {
if (typeof toLoad === "string") {
const loaded = this.getFromLibrary(toLoad, ctx)
units.push(
new Unit(loaded.quantity, [key], loaded.denominations, loaded.eraseInvalid, validator, toLoad["inverted"])
new Unit(
loaded.quantity,
[key],
loaded.denominations,
loaded.eraseInvalid,
validator,
toLoad["inverted"]
)
)
continue
}
@ -246,19 +264,32 @@ export class Unit {
return found
}
if(!Array.isArray(toLoad.denominations)){
throw "toLoad is not an array. Did you forget the [ and ] around the denominations at "+ctx+"?"
if (!Array.isArray(toLoad.denominations)) {
throw (
"toLoad is not an array. Did you forget the [ and ] around the denominations at " +
ctx +
"?"
)
}
const denoms = toLoad.denominations
.map((d) => d.toLowerCase())
.map((d) => fetchDenom(d))
.map(d => d.withValidator(validator))
.map((d) => d.withValidator(validator))
if (toLoad.canonical) {
const canonical = fetchDenom(toLoad.canonical).withValidator(validator)
denoms.unshift(canonical.withBlankCanonical())
}
units.push(new Unit(loaded.quantity, [key], denoms, loaded.eraseInvalid, validator, toLoad["inverted"]))
units.push(
new Unit(
loaded.quantity,
[key],
denoms,
loaded.eraseInvalid,
validator,
toLoad["inverted"]
)
)
}
return units
}
@ -280,7 +311,11 @@ export class Unit {
}
const defaultDenom = this.getDefaultDenomination(country)
for (const denomination of this.denominationsSorted) {
const bare = denomination.StrippedValue(valueWithDenom, defaultDenom === denomination, this.inverted)
const bare = denomination.StrippedValue(
valueWithDenom,
defaultDenom === denomination,
this.inverted
)
if (bare !== null) {
return [bare, denomination]
}
@ -294,8 +329,8 @@ export class Unit {
}
const [stripped, denom] = this.findDenomination(value, country)
const human = denom?.human
if(this.inverted ){
return human.Subs({quantity: stripped+"/"})
if (this.inverted) {
return human.Subs({ quantity: stripped + "/" })
}
if (stripped === "1") {
return denom?.humanSingular ?? stripped
@ -309,8 +344,8 @@ export class Unit {
public toOsm(value: string, denomination: string) {
const denom = this.denominations.find((d) => d.canonical === denomination)
if(this.inverted){
return value+"/"+denom._canonicalSingular
if (this.inverted) {
return value + "/" + denom._canonicalSingular
}
const space = denom.addSpace ? " " : ""

View file

@ -1,5 +1,5 @@
import StylesheetTestGui from "./UI/StylesheetTestGui.svelte"
new StylesheetTestGui({
target: document.getElementById("maindiv")
target: document.getElementById("maindiv"),
})

View file

@ -66,104 +66,104 @@
</script>
<main>
<div class="m-4 flex flex-col">
<LanguagePicker
clss="self-end max-w-full"
assignTo={state.language}
availableLanguages={t.title.SupportedLanguages()}
preferredLanguages={userLanguages}
/>
<div class="m-4 flex flex-col">
<LanguagePicker
clss="self-end max-w-full"
assignTo={state.language}
availableLanguages={t.title.SupportedLanguages()}
preferredLanguages={userLanguages}
/>
<div class="mt-4 flex">
<div class="m-3 flex-none">
<Logo alt="MapComplete Logo" class="h-12 w-12 sm:h-24 sm:w-24" />
<div class="mt-4 flex">
<div class="m-3 flex-none">
<Logo alt="MapComplete Logo" class="h-12 w-12 sm:h-24 sm:w-24" />
</div>
<div class="flex flex-col">
<h1 class="m-0 font-extrabold tracking-tight md:text-6xl">
<Tr t={t.title} />
</h1>
<Tr
cls="my-4 mr-4 text-base font-semibold sm:text-lg md:mt-5 md:text-xl lg:mx-0"
t={Translations.t.index.intro}
/>
</div>
</div>
<div class="flex flex-col">
<h1 class="m-0 font-extrabold tracking-tight md:text-6xl">
<Tr t={t.title} />
</h1>
<Tr
cls="my-4 mr-4 text-base font-semibold sm:text-lg md:mt-5 md:text-xl lg:mx-0"
t={Translations.t.index.intro}
/>
<form
class="flex justify-center"
on:submit|preventDefault={(_) => MoreScreen.applySearch(themeSearchText.data)}
>
<label
class="neutral-label my-2 flex w-full items-center rounded-full border-2 border-black sm:w-1/2"
>
<SearchIcon aria-hidden="true" class="h-8 w-8" />
<input
autofocus
bind:value={$themeSearchText}
class="mr-4 w-full outline-none"
id="theme-search"
type="search"
use:placeholder={tr.searchForATheme}
/>
</label>
</form>
<ThemesList search={themeSearchText} {state} themes={MoreScreen.officialThemes} />
<LoginToggle {state}>
<ThemesList
hideThemes={false}
isCustom={false}
search={themeSearchText}
{state}
themes={$visitedHiddenThemes}
>
<svelte:fragment slot="title">
<h3>
<Tr t={tr.previouslyHiddenTitle} />
</h3>
<p>
<Tr
t={tr.hiddenExplanation.Subs({
hidden_discovered: $visitedHiddenThemes.length.toString(),
total_hidden: hiddenThemes.length.toString(),
})}
/>
</p>
</svelte:fragment>
</ThemesList>
<UnofficialThemeList search={themeSearchText} {state} />
<div slot="not-logged-in">
<button class="m-0 my-2 w-full" on:click={() => osmConnection.AttemptLogin()}>
<Login class="mr-2 h-6 w-6 " />
<Tr t={Translations.t.index.logIn} />
</button>
</div>
<a
class="button h-fit w-full"
href={window.location.protocol + "//" + window.location.host + "/studio.html"}
>
<Pencil class="mr-2 h-6 w-6" />
<Tr t={Translations.t.general.morescreen.createYourOwnTheme} />
</a>
<a
class="button h-fit w-full"
href={window.location.protocol + "//" + window.location.host + "/privacy.html"}
>
<Eye class="mr-2 h-6 w-6" />
<Tr t={Translations.t.privacy.title} />
</a>
</LoginToggle>
<Tr cls="link-underline" t={Translations.t.general.aboutMapComplete.intro} />
<Tr t={tr.streetcomplete} />
<div class="subtle mb-16 self-end">
v{Constants.vNumber}
</div>
</div>
<form
class="flex justify-center"
on:submit|preventDefault={(_) => MoreScreen.applySearch(themeSearchText.data)}
>
<label
class="neutral-label my-2 flex w-full items-center rounded-full border-2 border-black sm:w-1/2"
>
<SearchIcon aria-hidden="true" class="h-8 w-8" />
<input
autofocus
bind:value={$themeSearchText}
class="mr-4 w-full outline-none"
id="theme-search"
type="search"
use:placeholder={tr.searchForATheme}
/>
</label>
</form>
<ThemesList search={themeSearchText} {state} themes={MoreScreen.officialThemes} />
<LoginToggle {state}>
<ThemesList
hideThemes={false}
isCustom={false}
search={themeSearchText}
{state}
themes={$visitedHiddenThemes}
>
<svelte:fragment slot="title">
<h3>
<Tr t={tr.previouslyHiddenTitle} />
</h3>
<p>
<Tr
t={tr.hiddenExplanation.Subs({
hidden_discovered: $visitedHiddenThemes.length.toString(),
total_hidden: hiddenThemes.length.toString(),
})}
/>
</p>
</svelte:fragment>
</ThemesList>
<UnofficialThemeList search={themeSearchText} {state} />
<div slot="not-logged-in">
<button class="m-0 my-2 w-full" on:click={() => osmConnection.AttemptLogin()}>
<Login class="mr-2 h-6 w-6 " />
<Tr t={Translations.t.index.logIn} />
</button>
</div>
<a
class="button h-fit w-full"
href={window.location.protocol + "//" + window.location.host + "/studio.html"}
>
<Pencil class="mr-2 h-6 w-6" />
<Tr t={Translations.t.general.morescreen.createYourOwnTheme} />
</a>
<a
class="button h-fit w-full"
href={window.location.protocol + "//" + window.location.host + "/privacy.html"}
>
<Eye class="mr-2 h-6 w-6" />
<Tr t={Translations.t.privacy.title} />
</a>
</LoginToggle>
<Tr cls="link-underline" t={Translations.t.general.aboutMapComplete.intro} />
<Tr t={tr.streetcomplete} />
<div class="subtle mb-16 self-end">
v{Constants.vNumber}
</div>
</div>
</main>

View file

@ -42,16 +42,16 @@ export default class ChartJs<
this._config = config
}
public static ConstructDoughnut(data: Record<string, number>){
public static ConstructDoughnut(data: Record<string, number>) {
const borderColor = [
// ChartJsColours.unkownBorderColor,
// ChartJsColours.otherBorderColor,
// ChartJsColours.notApplicableBorderColor,
// ChartJsColours.unkownBorderColor,
// ChartJsColours.otherBorderColor,
// ChartJsColours.notApplicableBorderColor,
]
const backgroundColor = [
// ChartJsColours.unkownColor,
// ChartJsColours.otherColor,
// ChartJsColours.notApplicableColor,
// ChartJsColours.unkownColor,
// ChartJsColours.otherColor,
// ChartJsColours.notApplicableColor,
]
let i = 0
@ -59,10 +59,10 @@ export default class ChartJs<
const bg = ChartJsColours.backgroundColors
for (const key in data) {
if(key === ""){
if (key === "") {
borderColor.push(ChartJsColours.unknownBorderColor)
backgroundColor.push(ChartJsColours.unknownColor)
}else{
} else {
borderColor.push(borders[i % borders.length])
backgroundColor.push(bg[i % bg.length])
i++

View file

@ -7,7 +7,7 @@
export let isOpened: Store<boolean>
export let moveTo: Store<HTMLElement>
export let debug : string
export let debug: string
function copySizeOf(htmlElem: HTMLElement) {
const target = htmlElem.getBoundingClientRect()
elem.style.left = target.x + "px"
@ -31,18 +31,18 @@
}
}
onDestroy(isOpened.addCallback(opened => animate(opened)))
onMount(() => requestAnimationFrame(() => animate(isOpened.data)))
onDestroy(isOpened.addCallback((opened) => animate(opened)))
onMount(() => requestAnimationFrame(() => animate(isOpened.data)))
</script>
<div class={"absolute bottom-0 right-0 h-full w-screen p-4 md:p-6 pointer-events-none invisible"}>
<div class={"pointer-events-none invisible absolute bottom-0 right-0 h-full w-screen p-4 md:p-6"}>
<div class="content h-full" bind:this={targetOuter} style="background: red" />
</div>
<div bind:this={elem} class="pointer-events-none absolute bottom-0 right-0 low-interaction rounded-2xl"
style="transition: all 0.5s ease-out, background-color 1.4s ease-out; background: var(--background-color);">
<div
bind:this={elem}
class="low-interaction pointer-events-none absolute bottom-0 right-0 rounded-2xl"
style="transition: all 0.5s ease-out, background-color 1.4s ease-out; background: var(--background-color);"
>
<!-- Classes should be the same as the 'floatoaver' -->
</div>

View file

@ -10,7 +10,6 @@
* The slotted element will be shown on top, with a lower-opacity border
*/
const dispatch = createEventDispatcher<{ close }>()
</script>
<!-- Draw the background over the total screen -->

View file

@ -7,7 +7,7 @@
export let osmConnection: OsmConnection
export let clss: string | undefined = undefined
if(osmConnection === undefined){
if (osmConnection === undefined) {
console.error("No osmConnection passed into loginButton")
}
</script>

View file

@ -19,7 +19,7 @@
/**
* Only show the 'successful' state, don't show loading or error messages
*/
export let silentFail : boolean = false
export let silentFail: boolean = false
let loadingStatus = state?.osmConnection?.loadingStatus ?? new ImmutableStore("logged-in")
let badge = state?.featureSwitches?.featureSwitchEnableLogin ?? new ImmutableStore(true)
const t = Translations.t.general

View file

@ -13,7 +13,7 @@
export let enabled: Store<boolean> = new ImmutableStore(true)
export let arialabel: Translation = undefined
export let htmlElem: UIEventSource<HTMLElement> = undefined
let _htmlElem : HTMLElement
let _htmlElem: HTMLElement
$: {
htmlElem?.setData(_htmlElem)
}

View file

@ -4,15 +4,15 @@
export let src: string = undefined
export let srcWritable: Store<string> = undefined
srcWritable?.addCallbackAndRunD(t => {
srcWritable?.addCallbackAndRunD((t) => {
src = t
})
if(src !== undefined && typeof src !== "string") {
if (src !== undefined && typeof src !== "string") {
console.trace("Got a non-string object in Markdown", src)
throw "Markdown.svelte got a non-string object"
}
</script>
{#if src?.length > 0}
{@html marked.parse(src)}
{/if}

View file

@ -6,13 +6,13 @@ import { default as turndown } from "turndown"
import { Utils } from "../../Utils"
export default class TableOfContents {
private static asLinkableId(text: string): string {
return text
?.replace(/ /g, "-")
?.replace(/[?#.;:/]/, "")
?.toLowerCase() ?? ""
return (
text
?.replace(/ /g, "-")
?.replace(/[?#.;:/]/, "")
?.toLowerCase() ?? ""
)
}
private static mergeLevel(
@ -33,7 +33,7 @@ export default class TableOfContents {
if (running.length !== undefined) {
result.push({
content: new List(running),
level: maxLevel - 1
level: maxLevel - 1,
})
running = []
}
@ -42,7 +42,7 @@ export default class TableOfContents {
if (running.length !== undefined) {
result.push({
content: new List(running),
level: maxLevel - 1
level: maxLevel - 1,
})
}
@ -56,13 +56,16 @@ export default class TableOfContents {
const firstTitle = structure[1]
let minDepth = undefined
do {
minDepth = Math.min(...structure.map(s => s.depth))
const minDepthCount = structure.filter(s => s.depth === minDepth)
minDepth = Math.min(...structure.map((s) => s.depth))
const minDepthCount = structure.filter((s) => s.depth === minDepth)
if (minDepthCount.length > 1) {
break
}
// Erase a single top level heading
structure.splice(structure.findIndex(s => s.depth === minDepth), 1)
structure.splice(
structure.findIndex((s) => s.depth === minDepth),
1
)
} while (structure.length > 0)
if (structure.length <= 1) {
@ -71,7 +74,7 @@ export default class TableOfContents {
const separators = {
1: " -",
2: " +",
3: " *"
3: " *",
}
let toc = ""
@ -96,15 +99,16 @@ export default class TableOfContents {
const splitPoint = intro.lastIndexOf("\n")
return md.substring(0, splitPoint) + toc + md.substring(splitPoint)
}
public static generateStructure(html: Element): { depth: number, title: string, el: Element }[] {
public static generateStructure(
html: Element
): { depth: number; title: string; el: Element }[] {
if (html === undefined) {
return []
}
return [].concat(...Array.from(html.childNodes ?? []).map(
child => {
return [].concat(
...Array.from(html.childNodes ?? []).map((child) => {
const tag: string = child["tagName"]?.toLowerCase()
if (!tag) {
return []
@ -114,7 +118,7 @@ export default class TableOfContents {
return [{ depth, title: child.textContent, el: child }]
}
return TableOfContents.generateStructure(<Element>child)
}
))
})
)
}
}

View file

@ -17,7 +17,6 @@
txt = t?.current
lang = t?.currentLang
}
</script>
{#if $txt}

View file

@ -40,11 +40,9 @@ export default class CopyrightPanel extends Combine {
const t = Translations.t.general.attribution
const layoutToUse = state.layout
const iconAttributions: BaseUIElement[] = (layoutToUse.getUsedImages()).map(
CopyrightPanel.IconAttribution
)
const iconAttributions: BaseUIElement[] = layoutToUse
.getUsedImages()
.map(CopyrightPanel.IconAttribution)
let maintainer: BaseUIElement = undefined
if (layoutToUse.credits !== undefined && layoutToUse.credits !== "") {

View file

@ -8,7 +8,6 @@
import Tr from "../Base/Tr.svelte"
import Icon from "../Map/Icon.svelte"
export let state: SpecialVisualizationState
let theme = state.layout?.id ?? ""
let config: ExtraLinkConfig = state.layout.extraLink
@ -23,7 +22,7 @@
...loc,
theme: theme,
basepath,
language: Locale.language.data
language: Locale.language.data,
}
return Utils.SubstituteKeys(config.href, subs)
},
@ -31,25 +30,21 @@
)
</script>
{#if config !== undefined && !(config.requirements.has("iframe") && !isIframe) && !(config.requirements.has("no-iframe") && isIframe) && !(config.requirements.has("welcome-message") && !$showWelcomeMessageSwitch) && !(config.requirements.has("no-welcome-message") && $showWelcomeMessageSwitch)}
<div class="links-as-button">
<a
href={$href}
target={config.newTab ? "_blank" : ""}
rel="noopener"
class="pointer-events-auto flex rounded-full border-black"
>
<Icon icon={config.icon} clss="w-6 h-6 m-2" />
{#if config !== undefined &&
!(config.requirements.has("iframe") && !isIframe) &&
!(config.requirements.has("no-iframe") && isIframe) &&
!(config.requirements.has("welcome-message") && !$showWelcomeMessageSwitch) &&
!(config.requirements.has("no-welcome-message") && $showWelcomeMessageSwitch)}
<div class="links-as-button">
<a href={$href} target={config.newTab ? "_blank" : ""} rel="noopener"
class="flex pointer-events-auto rounded-full border-black">
<Icon icon={config.icon} clss="w-6 h-6 m-2"/>
{#if config.text}
<Tr t={config.text} />
{:else}
<Tr t={t.screenToSmall.Subs({theme: state.layout.title})} />
{/if}
</a>
</div>
{#if config.text}
<Tr t={config.text} />
{:else}
<Tr t={t.screenToSmall.Subs({ theme: state.layout.title })} />
{/if}
</a>
</div>
{/if}

View file

@ -48,12 +48,12 @@
}
</script>
<div class="h-full flex flex-col">
<div class="flex h-full flex-col">
<h2 class="low-interaction m-0 flex items-center p-4 drop-shadow-md">
<Filter class="h-6 w-6 pr-2" />
<Tr t={Translations.t.general.menu.filter} />
</h2>
<div class="flex h-full flex-col overflow-auto p-4 border-b-2">
<div class="flex h-full flex-col overflow-auto border-b-2 p-4">
{#each layout.layers as layer}
<Filterview
zoomlevel={state.mapProperties.zoom}

View file

@ -15,7 +15,7 @@
export let state: ThemeViewState
export let map: Store<MlMap> = undefined
export let hideTooltip = false
export let htmlElem : UIEventSource<HTMLElement> = undefined
export let htmlElem: UIEventSource<HTMLElement> = undefined
</script>
<MapControlButton

View file

@ -8,7 +8,7 @@
const t = Translations.t.privacy
export let state: SpecialVisualizationState
const usersettings = UserRelatedState.usersettingsConfig
const editPrivacy = usersettings.tagRenderings.find(tr => tr.id === "more_privacy")
const editPrivacy = usersettings.tagRenderings.find((tr) => tr.id === "more_privacy")
const isLoggedIn = state.osmConnection.isLoggedIn
</script>
@ -48,16 +48,19 @@
<li>
{#if $isLoggedIn}
<TagRenderingEditable config={editPrivacy} selectedElement={{
type: "Feature",
properties: { id: "settings" },
geometry: { type: "Point", coordinates: [0, 0] },
}}
{state}
tags={state.userRelatedState.preferencesAsTags} />
{:else}
<Tr t={t.items.distanceIndicator} />
{/if}
<TagRenderingEditable
config={editPrivacy}
selectedElement={{
type: "Feature",
properties: { id: "settings" },
geometry: { type: "Point", coordinates: [0, 0] },
}}
{state}
tags={state.userRelatedState.preferencesAsTags}
/>
{:else}
<Tr t={t.items.distanceIndicator} />
{/if}
</li>
</ul>

View file

@ -185,8 +185,6 @@ export class StackedRenderingChart extends ChartJs {
}
export default class TagRenderingChart extends Combine {
/**
* Creates a chart about this tagRendering for the given data
*/
@ -223,9 +221,9 @@ export default class TagRenderingChart extends Combine {
ChartJsColours.notApplicableBorderColor,
]
const backgroundColor = [
ChartJsColours.unknownColor,
ChartJsColours.otherColor,
ChartJsColours.notApplicableColor,
ChartJsColours.unknownColor,
ChartJsColours.otherColor,
ChartJsColours.notApplicableColor,
]
while (borderColor.length < data.length) {

View file

@ -15,7 +15,6 @@
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
export let externalProperties: Record<string, string>
delete externalProperties["@context"]
console.log("External properties are", externalProperties)
@ -33,51 +32,60 @@
let externalKeys: string[] = Object.keys(externalProperties).sort()
const imageKeyRegex = /image|image:[0-9]+/
let knownImages: Store<Set<string>> = tags.map(osmProperties => new Set(
Object.keys(osmProperties)
let knownImages: Store<Set<string>> = tags.map(
(osmProperties) =>
new Set(
Object.keys(osmProperties)
.filter((k) => k.match(imageKeyRegex))
.map((k) => osmProperties[k])
)
)
let unknownImages: Store<string[]> = knownImages.map((images) =>
externalKeys
.filter((k) => k.match(imageKeyRegex))
.map((k) => osmProperties[k])
))
let unknownImages: Store<string[]> = knownImages.map(images => externalKeys
.filter((k) => k.match(imageKeyRegex))
.map((k) => externalProperties[k])
.filter((i) => !images.has(i)))
.map((k) => externalProperties[k])
.filter((i) => !images.has(i))
)
let propertyKeysExternal = externalKeys.filter((k) => k.match(imageKeyRegex) === null)
let missing: Store<string[]> = tags.map(osmProperties => propertyKeysExternal.filter((k) => {
if (k.startsWith("_")) {
return false
}
return osmProperties[k] === undefined && typeof externalProperties[k] === "string"
}))
// let same = propertyKeysExternal.filter((key) => osmProperties[key] === externalProperties[key])
let different: Store<string[]> = tags.map(osmProperties => 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) {
let missing: Store<string[]> = tags.map((osmProperties) =>
propertyKeysExternal.filter((k) => {
if (k.startsWith("_")) {
return false
}
return osmProperties[k] === undefined && typeof externalProperties[k] === "string"
})
)
// let same = propertyKeysExternal.filter((key) => osmProperties[key] === externalProperties[key])
let different: Store<string[]> = tags.map((osmProperties) =>
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
}
}
return true
}))
if (key === "website") {
const osmCanon = new URL(osmProperties[key]).toString()
const externalCanon = new URL(externalProperties[key]).toString()
if (osmCanon === externalCanon) {
return false
}
}
let hasDifferencesAtStart = (different.data.length + missing.data.length + unknownImages.data.length) > 0
return true
})
)
let hasDifferencesAtStart =
different.data.length + missing.data.length + unknownImages.data.length > 0
let currentStep: "init" | "applying_all" | "all_applied" = "init"
let applyAllHovered = false
@ -87,23 +95,23 @@
const tagsToApply = missing.data.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 !hasDifferencesAtStart}
<span class="subtle text-sm">
<Tr t={t.allIncluded.Subs({source:sourceUrl})}/>
<Tr t={t.allIncluded.Subs({ source: sourceUrl })} />
</span>
{:else if $unknownImages.length === 0 && $missing.length === 0 && $different.length === 0}
<div class="thanks m-0 flex items-center gap-x-2 px-2">
<Party class="h-8 w-8 shrink-0" />
<Tr t={t.allIncluded.Subs({source: sourceUrl})} />
<Tr t={t.allIncluded.Subs({ source: sourceUrl })} />
</div>
{:else}
<div class="low-interaction border-interactive p-1">
@ -112,7 +120,6 @@
{/if}
<div class="flex flex-col" class:gap-y-8={!readonly}>
{#if $different.length > 0}
{#if !readonly}
<h3>
@ -137,9 +144,9 @@
{#if $missing.length > 0}
{#if !readonly}
<h3 class="m-0">
<Tr t={t.missing.title} />
</h3>
<h3 class="m-0">
<Tr t={t.missing.title} />
</h3>
<Tr t={t.missing.intro} />
{/if}

View file

@ -69,12 +69,12 @@
previewedImage={state.previewedImage}
/>
</div>
<LoginToggle {state} silentFail={true} >
{#if linkable}
<label>
<input bind:checked={$isLinked} type="checkbox" />
<SpecialTranslation t={t.link} {tags} {state} {layer} {feature} />
</label>
{/if}
<LoginToggle {state} silentFail={true}>
{#if linkable}
<label>
<input bind:checked={$isLinked} type="checkbox" />
<SpecialTranslation t={t.link} {tags} {state} {layer} {feature} />
</label>
{/if}
</LoginToggle>
</div>

View file

@ -30,7 +30,7 @@
lon,
lat,
allowSpherical: new UIEventSource<boolean>(false),
blacklist: AllImageProviders.LoadImagesFor(tags)
blacklist: AllImageProviders.LoadImagesFor(tags),
},
state.indexedFeatures
)
@ -53,9 +53,9 @@
{:else}
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
{#each $images as image (image.pictureUrl)}
<span class="w-fit shrink-0" style="scroll-snap-align: start">
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
</span>
<span class="w-fit shrink-0" style="scroll-snap-align: start">
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
</span>
{/each}
</div>
{/if}

View file

@ -25,31 +25,31 @@
let expanded = false
</script>
<div class="my-4">
{#if expanded}
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer}>
<button
slot="corner"
class="no-image-background h-6 w-6 cursor-pointer border-none p-0"
use:ariaLabel={t.close}
on:click={() => {
expanded = false
}}
>
<XCircleIcon />
</button>
</NearbyImages>
{:else}
<div class="my-4">
{#if expanded}
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer}>
<button
class="flex w-full items-center"
style="margin-left: 0; margin-right: 0"
slot="corner"
class="no-image-background h-6 w-6 cursor-pointer border-none p-0"
use:ariaLabel={t.close}
on:click={() => {
expanded = true
expanded = false
}}
aria-expanded={expanded}
>
<Camera_plus class="mr-2 block h-8 w-8 p-1" />
<Tr t={t.seeNearby} />
<XCircleIcon />
</button>
{/if}
</div>
</NearbyImages>
{:else}
<button
class="flex w-full items-center"
style="margin-left: 0; margin-right: 0"
on:click={() => {
expanded = true
}}
aria-expanded={expanded}
>
<Camera_plus class="mr-2 block h-8 w-8 p-1" />
<Tr t={t.seeNearby} />
</button>
{/if}
</div>

View file

@ -63,7 +63,7 @@ export default class Validators {
"slope",
"velopark",
"nsi",
"currency"
"currency",
] as const
public static readonly AllValidators: ReadonlyArray<Validator> = [
@ -94,7 +94,7 @@ export default class Validators {
new SlopeValidator(),
new VeloparkValidator(),
new NameSuggestionIndexValidator(),
new CurrencyValidator()
new CurrencyValidator(),
]
private static _byType = Validators._byTypeConstructor()

View file

@ -13,26 +13,26 @@ export default class CurrencyValidator extends Validator {
return
}
let locale = "en-US"
if(!Utils.runningFromConsole){
locale??= navigator.language
if (!Utils.runningFromConsole) {
locale ??= navigator.language
}
this.segmenter = new Intl.Segmenter(locale, {
granularity: "word"
granularity: "word",
})
const mapping: Map<string, string> = new Map<string, string>()
const supportedCurrencies: Set<string> = new Set(Intl.supportedValuesOf("currency"))
this.supportedCurrencies = supportedCurrencies
for (const currency of supportedCurrencies) {
const symbol = (0).toLocaleString(
locale,
{
const symbol = (0)
.toLocaleString(locale, {
style: "currency",
currency: currency,
minimumFractionDigits: 0,
maximumFractionDigits: 0
}
).replace(/\d/g, "").trim()
maximumFractionDigits: 0,
})
.replace(/\d/g, "")
.trim()
mapping.set(symbol.toLowerCase(), currency)
}
@ -44,8 +44,10 @@ export default class CurrencyValidator extends Validator {
return s
}
const parts = Array.from(this.segmenter.segment(s)).map(i => i.segment).filter(part => part.trim().length > 0)
if(parts.length !== 2){
const parts = Array.from(this.segmenter.segment(s))
.map((i) => i.segment)
.filter((part) => part.trim().length > 0)
if (parts.length !== 2) {
return s
}
const mapping = this.symbolToCurrencyMapping
@ -64,10 +66,10 @@ export default class CurrencyValidator extends Validator {
}
amount = part
}
if(amount === undefined || currency === undefined){
if (amount === undefined || currency === undefined) {
return s
}
return amount+" "+currency
return amount + " " + currency
}
}

View file

@ -3,7 +3,10 @@ import UrlValidator from "./UrlValidator"
export default class VeloparkValidator extends UrlValidator {
constructor() {
super("velopark", "A special URL-validator that checks the domain name and rewrites to the correct velopark format.")
super(
"velopark",
"A special URL-validator that checks the domain name and rewrites to the correct velopark format."
)
}
getFeedback(s: string): Translation {

View file

@ -30,41 +30,42 @@
}
> = UIEventSource.FromPromise(Utils.downloadJsonCached(source))
</script>
<main>
<h1>Contributed images with MapComplete: leaderboard</h1>
<h1>Contributed images with MapComplete: leaderboard</h1>
{#if $data}
<table>
<tr>
<th>Rank</th>
<th>Contributor</th>
<th>Number of images contributed</th>
</tr>
{#each $data.leaderboard as contributor}
{#if $data}
<table>
<tr>
<td>
{contributor.rank}
</td>
<td>
{#if $loggedInContributor === contributor.name}
<a class="thanks" href={contributor.account}>{contributor.name}</a>
{:else}
<a href={contributor.account}>{contributor.name}</a>
{/if}
</td>
<td>
<b>{contributor.nrOfImages}</b>
total images
</td>
<th>Rank</th>
<th>Contributor</th>
<th>Number of images contributed</th>
</tr>
{/each}
</table>
Statistics generated on {$data.date}
{:else}
<Loading />
{/if}
{#each $data.leaderboard as contributor}
<tr>
<td>
{contributor.rank}
</td>
<td>
{#if $loggedInContributor === contributor.name}
<a class="thanks" href={contributor.account}>{contributor.name}</a>
{:else}
<a href={contributor.account}>{contributor.name}</a>
{/if}
</td>
<td>
<b>{contributor.nrOfImages}</b>
total images
</td>
</tr>
{/each}
</table>
Statistics generated on {$data.date}
{:else}
<Loading />
{/if}
<div>
Logged in as {$loggedInContributor}
</div>
<div>
Logged in as {$loggedInContributor}
</div>
</main>

View file

@ -126,7 +126,6 @@
<LinkIcon style="--svg-color: {color}" class={twMerge(clss, "apply-fill")} />
{:else if icon === "popout"}
<LinkIcon style="--svg-color: {color}" />
{:else}
<img class={clss ?? "h-full w-full"} src={icon} aria-hidden="true" alt="" />
{/if}

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,19 +351,21 @@ 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) {
try{
layer.paint["line-dasharray"] =
this._config.dashArray?.split(" ")?.map((s) => Number(s)) ?? null
}catch (e) {
console.error(`Invalid dasharray in layer ${this._layername}:`, this._config.dashArray)
try {
layer.paint["line-dasharray"] =
this._config.dashArray?.split(" ")?.map((s) => Number(s)) ?? null
} catch (e) {
console.error(
`Invalid dasharray in layer ${this._layername}:`,
this._config.dashArray
)
}
}
map.addLayer(layer)
@ -398,8 +400,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) => {
@ -430,7 +432,7 @@ class LineRenderingLayer {
this.currentSourceData = features
src.setData({
type: "FeatureCollection",
features: this.currentSourceData
features: this.currentSourceData,
})
}
}
@ -513,15 +515,15 @@ 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),
}
)
if (options?.zoomToFeatures) {
options.zoomToFeatures = false
features.features.addCallbackD(features => {
features.features.addCallbackD((features) => {
ShowDataLayer.zoomToCurrentFeatures(mlmap.data, features)
})
mlmap.addCallbackD(map => {
mlmap.addCallbackD((map) => {
ShowDataLayer.zoomToCurrentFeatures(map, features.features.data)
})
}
@ -530,7 +532,7 @@ export default class ShowDataLayer {
new ShowDataLayer(mlmap, {
layer: fs.layer.layerDef,
features: fs,
...(options ?? {})
...(options ?? {}),
})
})
}
@ -543,12 +545,11 @@ export default class ShowDataLayer {
return new ShowDataLayer(map, {
layer: ShowDataLayer.rangeLayer,
features,
doShowLayer
doShowLayer,
})
}
public destruct() {
}
public destruct() {}
private static zoomToCurrentFeatures(map: MlMap, features: Feature[]) {
if (!features || !map || features.length == 0) {
@ -560,7 +561,7 @@ export default class ShowDataLayer {
map.resize()
map.fitBounds(bbox.toLngLat(), {
padding: { top: 10, bottom: 10, left: 10, right: 10 },
animate: false
animate: false,
})
})
}
@ -573,8 +574,8 @@ export default class ShowDataLayer {
this._options.layer.title === undefined
? undefined
: (feature: Feature) => {
selectedElement?.setData(feature)
}
selectedElement?.setData(feature)
}
}
if (this._options.drawLines !== false) {
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {
@ -606,7 +607,9 @@ export default class ShowDataLayer {
}
}
if (this._options.zoomToFeatures) {
features.features.addCallbackAndRunD((features) => ShowDataLayer.zoomToCurrentFeatures(map, features))
features.features.addCallbackAndRunD((features) =>
ShowDataLayer.zoomToCurrentFeatures(map, features)
)
}
}
}

View file

@ -6,17 +6,17 @@
</script>
<main>
<div class="flex flex-col">
<Tr t={Translations.t.general["404"]} />
<BackButton
clss="m-8"
on:click={() => {
window.location = "index.html"
}}
>
<div class="flex w-full justify-center">
<Tr t={Translations.t.general.backToIndex} />
</div>
</BackButton>
</div>
<div class="flex flex-col">
<Tr t={Translations.t.general["404"]} />
<BackButton
clss="m-8"
on:click={() => {
window.location = "index.html"
}}
>
<div class="flex w-full justify-center">
<Tr t={Translations.t.general.backToIndex} />
</div>
</BackButton>
</div>
</main>

View file

@ -21,16 +21,14 @@
}
let knownValues: UIEventSource<string[]> = new UIEventSource<string[]>([])
tags.addCallbackAndRunD(tags => {
tags.addCallbackAndRunD((tags) => {
knownValues.setData(Object.keys(tags))
})
function reEvalKnownValues(){
function reEvalKnownValues() {
knownValues.setData(Object.keys(tags.data))
}
const metaKeys: string[] = [].concat(...SimpleMetaTaggers.metatags.map((k) => k.keys))
let allCalculatedTags = new Set<string>([...calculatedTags, ...metaKeys])
</script>
@ -54,7 +52,7 @@
{:else if $tags[key] === ""}
<i>Empty string</i>
{:else if typeof $tags[key] === "object"}
<div class="literal-code" >{JSON.stringify($tags[key])}</div>
<div class="literal-code">{JSON.stringify($tags[key])}</div>
{:else}
{$tags[key]}
{/if}

View file

@ -17,13 +17,13 @@ export class MinimapViz implements SpecialVisualization {
{
doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close",
name: "zoomlevel",
defaultValue: "18"
defaultValue: "18",
},
{
doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled)",
name: "idKey",
defaultValue: "id"
}
defaultValue: "id",
},
]
example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`"
@ -82,13 +82,12 @@ export class MinimapViz implements SpecialVisualization {
const mla = new MapLibreAdaptor(mlmap, {
rasterLayer: state.mapProperties.rasterLayer,
zoom: new UIEventSource<number>(17),
maxzoom: new UIEventSource<number>(17)
maxzoom: new UIEventSource<number>(17),
})
mla.allowMoving.setData(false)
mla.allowZooming.setData(false)
mla.location.setData({lon, lat})
mla.location.setData({ lon, lat })
if (args[0]) {
const parsed = Number(args[0])
@ -96,19 +95,19 @@ export class MinimapViz implements SpecialVisualization {
mla.zoom.setData(parsed)
}
}
mlmap.addCallbackAndRun(map => console.log("Map for minimap vis is now", map))
mlmap.addCallbackAndRun((map) => console.log("Map for minimap vis is now", map))
ShowDataLayer.showMultipleLayers(
mlmap,
new StaticFeatureSource(featuresToShow),
state.layout.layers,
{zoomToFeatures: true}
{ zoomToFeatures: true }
)
return new SvelteUIElement(MaplibreMap, {
interactive: false,
map: mlmap,
mapProperties: mla
mapProperties: mla,
})
.SetClass("h-40 rounded")
.SetStyle("overflow: hidden; pointer-events: none;")

View file

@ -50,7 +50,6 @@
let questionboxElem: HTMLDivElement
let questionsToAsk = tags.map(
(tags) => {
const questionsToAsk: TagRenderingConfig[] = []
for (const baseQuestion of baseQuestions) {
if (skippedQuestions.data.has(baseQuestion.id)) {
@ -164,7 +163,13 @@
{#if $showAllQuestionsAtOnce}
<div class="flex flex-col gap-y-1">
{#each $allQuestionsToAsk as question (question.id)}
<TagRenderingQuestionDynamic config={question} {tags} {selectedElement} {state} {layer} />
<TagRenderingQuestionDynamic
config={question}
{tags}
{selectedElement}
{state}
{layer}
/>
{/each}
</div>
{:else if $firstQuestion !== undefined}

View file

@ -4,7 +4,7 @@
import Locale from "../../i18n/Locale"
import type {
RenderingSpecification,
SpecialVisualizationState
SpecialVisualizationState,
} from "../../SpecialVisualization"
import { Utils } from "../../../Utils.js"
import type { Feature } from "geojson"
@ -67,7 +67,7 @@
{#each specs as specpart}
{#if typeof specpart === "string"}
<span>
{@html Utils.purify(Utils.SubstituteKeys(specpart, $tags)) }
{@html Utils.purify(Utils.SubstituteKeys(specpart, $tags))}
<WeblateLink context={t.context} />
</span>
{:else if $tags !== undefined}
@ -79,7 +79,7 @@
{#each specs as specpart}
{#if typeof specpart === "string"}
<span>
{@html Utils.purify(Utils.SubstituteKeys(specpart, $tags)) }
{@html Utils.purify(Utils.SubstituteKeys(specpart, $tags))}
<WeblateLink context={t.context} />
</span>
{:else if $tags !== undefined}

View file

@ -1,5 +1,7 @@
<script lang="ts">
import TagRenderingConfig, { TagRenderingConfigUtils } from "../../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingConfig, {
TagRenderingConfigUtils,
} from "../../../Models/ThemeConfig/TagRenderingConfig"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import type { Feature } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource"
@ -16,7 +18,14 @@
export let id: string = undefined
let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement)
</script>
<TagRenderingAnswer {selectedElement} {layer} config={$dynamicConfig} {extraClasses} {id} {tags} {state} />
<TagRenderingAnswer
{selectedElement}
{layer}
config={$dynamicConfig}
{extraClasses}
{id}
{tags}
{state}
/>

View file

@ -1,5 +1,7 @@
<script lang="ts">
import TagRenderingConfig, { TagRenderingConfigUtils } from "../../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingConfig, {
TagRenderingConfigUtils,
} from "../../../Models/ThemeConfig/TagRenderingConfig"
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
import type { Feature } from "geojson"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
@ -19,8 +21,16 @@
export let editMode = !config.IsKnown(tags.data)
let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement)
</script>
<TagRenderingEditable config={$dynamicConfig} {editMode} {clss} {highlightedRendering} {editingEnabled} {layer} {state}
{selectedElement} {tags} />
<TagRenderingEditable
config={$dynamicConfig}
{editMode}
{clss}
{highlightedRendering}
{editingEnabled}
{layer}
{state}
{selectedElement}
{tags}
/>

View file

@ -44,7 +44,7 @@
(search) => {
search = search?.trim()
if (!search) {
if(hideUnlessSearched){
if (hideUnlessSearched) {
if (mapping.priorityIf?.matchesProperties(tags.data)) {
return true
}

View file

@ -167,7 +167,11 @@
onDestroy(
freeformInput.subscribe((freeformValue) => {
if (!config?.mappings || config?.mappings?.length == 0 || config.freeform?.key === undefined) {
if (
!config?.mappings ||
config?.mappings?.length == 0 ||
config.freeform?.key === undefined
) {
return
}
// If a freeform value is given, mark the 'mapping' as marked
@ -232,7 +236,9 @@
// Add the extraTags to the existing And
selectedTags = new And([...selectedTags.and, ...extraTagsArray])
} else {
console.error("selectedTags is not of type Tag or And, it is a "+JSON.stringify(selectedTags))
console.error(
"selectedTags is not of type Tag or And, it is a " + JSON.stringify(selectedTags)
)
}
}
}
@ -289,7 +295,8 @@
let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined)
let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0
let question = config.question
let hideMappingsUnlessSearchedFor = config.mappings.length > 8 && config.mappings.some(m => m.priorityIf)
let hideMappingsUnlessSearchedFor =
config.mappings.length > 8 && config.mappings.some((m) => m.priorityIf)
$: question = config.question
if (state?.osmConnection) {
onDestroy(
@ -343,15 +350,13 @@
/>
</div>
{#if hideMappingsUnlessSearchedFor}
<div class="rounded border border-black border-dashed p-1 px-2 m-1">
<Tr t={Translations.t.general.mappingsAreHidden}/>
<div class="m-1 rounded border border-dashed border-black p-1 px-2">
<Tr t={Translations.t.general.mappingsAreHidden} />
</div>
{/if}
{/if}
{#if config.freeform?.key && !(config?.mappings?.filter(m => m.hideInAnswer != true)?.length > 0)}
{#if config.freeform?.key && !(config?.mappings?.filter((m) => m.hideInAnswer != true)?.length > 0)}
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
<FreeformInput
{config}
@ -505,7 +510,7 @@
<span class="flex flex-wrap">
{#if $featureSwitchIsTesting}
<button class="small" on:click={() => console.log("Configuration is ", config)}>
Testmode &nbsp;
Testmode &nbsp;
</button>
{/if}
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}

View file

@ -1,34 +1,36 @@
<script lang="ts">/**
* Wrapper around 'tagRenderingEditable' but might add mappings dynamically
*
* Note: does not forward the 'save-button'-slot
*/
import TagRenderingConfig, { TagRenderingConfigUtils } from "../../../Models/ThemeConfig/TagRenderingConfig"
import { UIEventSource } from "../../../Logic/UIEventSource"
import type { Feature } from "geojson"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import TagRenderingQuestion from "./TagRenderingQuestion.svelte"
import type { UploadableTag } from "../../../Logic/Tags/TagUtils"
import { writable } from "svelte/store"
import Translations from "../../i18n/Translations"
import { twJoin } from "tailwind-merge"
import Tr from "../../Base/Tr.svelte"
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
<script lang="ts">
/**
* Wrapper around 'tagRenderingEditable' but might add mappings dynamically
*
* Note: does not forward the 'save-button'-slot
*/
import TagRenderingConfig, {
TagRenderingConfigUtils,
} from "../../../Models/ThemeConfig/TagRenderingConfig"
import { UIEventSource } from "../../../Logic/UIEventSource"
import type { Feature } from "geojson"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import TagRenderingQuestion from "./TagRenderingQuestion.svelte"
import type { UploadableTag } from "../../../Logic/Tags/TagUtils"
import { writable } from "svelte/store"
import Translations from "../../i18n/Translations"
import { twJoin } from "tailwind-merge"
import Tr from "../../Base/Tr.svelte"
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
export let config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>>
export let config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>>
export let selectedElement: Feature
export let state: SpecialVisualizationState
export let layer: LayerConfig | undefined
export let selectedTags: UploadableTag = undefined
export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({})
export let selectedElement: Feature
export let state: SpecialVisualizationState
export let layer: LayerConfig | undefined
export let selectedTags: UploadableTag = undefined
export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({})
export let allowDeleteOfFreeform: boolean = false
export let allowDeleteOfFreeform: boolean = false
let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement)
let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement)
</script>
<TagRenderingQuestion

View file

@ -13,24 +13,25 @@
const osmConnection = new OsmConnection()
let state: SpecialVisualizationState = {
osmConnection,
userRelatedState: new UserRelatedState(osmConnection)
userRelatedState: new UserRelatedState(osmConnection),
}
</script>
<main>
<div class="flex h-screen flex-col overflow-hidden px-4">
<div class="flex justify-between">
<h2 class="flex items-center">
<EyeIcon class="w-6 pr-2" />
<Tr t={Translations.t.privacy.title} />
</h2>
<LanguagePicker availableLanguages={Translations.t.privacy.intro.SupportedLanguages()} />
<div class="flex h-screen flex-col overflow-hidden px-4">
<div class="flex justify-between">
<h2 class="flex items-center">
<EyeIcon class="w-6 pr-2" />
<Tr t={Translations.t.privacy.title} />
</h2>
<LanguagePicker availableLanguages={Translations.t.privacy.intro.SupportedLanguages()} />
</div>
<div class="h-full overflow-auto border border-gray-500 p-4">
<PrivacyPolicy {state} />
</div>
<a class="button flex" href={Utils.HomepageLink()}>
<Add class="h-6 w-6" />
<Tr t={Translations.t.general.backToIndex} />
</a>
</div>
<div class="h-full overflow-auto border border-gray-500 p-4">
<PrivacyPolicy {state} />
</div>
<a class="button flex" href={Utils.HomepageLink()}>
<Add class="h-6 w-6" />
<Tr t={Translations.t.general.backToIndex} />
</a>
</div>
</main>

View file

@ -1,7 +1,11 @@
import { Store, UIEventSource } from "../Logic/UIEventSource"
import BaseUIElement from "./BaseUIElement"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
import {
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { Changes } from "../Logic/Osm/Changes"
import { ExportableMap, MapProperties } from "../Models/MapProperties"
@ -64,7 +68,6 @@ export interface SpecialVisualizationState {
readonly currentView: FeatureSource<Feature<Polygon>>
readonly favourites: FavouritesFeatureSource
/**
* If data is currently being fetched from external sources
*/

File diff suppressed because it is too large Load diff

View file

@ -18,18 +18,26 @@
<div class="mt-12">
{#if deleteState === "init"}
<button on:click={() => {deleteState = "confirm"}} class="small">
<button
on:click={() => {
deleteState = "confirm"
}}
class="small"
>
<TrashIcon class="h-6 w-6" />
Delete this {objectType}
Delete this {objectType}
</button>
{:else if deleteState === "confirm"}
<div class="flex">
<BackButton on:click={() => {deleteState = "init"}}>
<BackButton
on:click={() => {
deleteState = "init"
}}
>
Don't delete
</BackButton>
<NextButton clss="primary" on:click={() => deleteLayer()}>
<div class="alert flex p-2">
<TrashIcon class="h-6 w-6" />
Do delete this {objectType}
</div>

View file

@ -81,8 +81,6 @@
})
let highlightedItem: UIEventSource<HighlightedTagRendering> = state.highlightedItem
</script>
<div class="flex h-screen flex-col">
@ -136,7 +134,7 @@
General properties
<ErrorIndicatorForRegion firstPaths={firstPathsFor("Basic")} {state} />
</div>
<div class="flex flex-col mb-8" slot="content0">
<div class="mb-8 flex flex-col" slot="content0">
<Region {state} configs={perRegion["Basic"]} />
<DeleteButton {state} {backToStudio} objectType="layer" />
</div>
@ -189,15 +187,15 @@
Below, you'll find the raw configuration file in `.json`-format. This is mostly for
debugging purposes, but you can also edit the file directly if you want.
</div>
<div class="literal-code overflow-y-auto h-full" style="min-height: 75%">
<div class="literal-code h-full overflow-y-auto" style="min-height: 75%">
<RawEditor {state} />
</div>
<ShowConversionMessages messages={$messages} />
<div class="flex w-full flex-col">
<div>
The testobject (which is used to render the questions in the 'information panel'
item has the following tags:
The testobject (which is used to render the questions in the 'information panel' item
has the following tags:
</div>
<AllTagsPanel tags={state.testTags} />

View file

@ -5,7 +5,7 @@ import {
Conversion,
ConversionMessage,
DesugaringContext,
Pipe
Pipe,
} from "../../Models/ThemeConfig/Conversion/Conversion"
import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer"
import { ValidateLayer, ValidateTheme } from "../../Models/ThemeConfig/Conversion/Validation"
@ -69,7 +69,6 @@ export abstract class EditJsonState<T> {
this.category = category
this.expertMode = options?.expertMode ?? new UIEventSource<boolean>(false)
const layerId = this.getId()
this.configuration
.mapD((config) => {
@ -89,7 +88,6 @@ export abstract class EditJsonState<T> {
await this.server.update(id, config, this.category)
})
this.messages = this.createMessagesStore()
}
public startSavingUpdates(enabled = true) {
@ -158,10 +156,10 @@ export abstract class EditJsonState<T> {
path,
type: "translation",
hints: {
typehint: "translation"
typehint: "translation",
},
required: origConfig.required ?? false,
description: origConfig.description ?? "A translatable object"
description: origConfig.description ?? "A translatable object",
}
}
@ -233,19 +231,21 @@ export abstract class EditJsonState<T> {
protected abstract getId(): Store<string>
protected abstract validate(configuration: Partial<T>): Promise<ConversionMessage[]>;
protected abstract validate(configuration: Partial<T>): Promise<ConversionMessage[]>
/**
* Creates a store that validates the configuration and which contains all relevant (error)-messages
* @private
*/
private createMessagesStore(): Store<ConversionMessage[]> {
return this.configuration.mapAsyncD(async (config) => {
if(!this.validate){
return []
}
return await this.validate(config)
}).map(messages => messages ?? [])
return this.configuration
.mapAsyncD(async (config) => {
if (!this.validate) {
return []
}
return await this.validate(config)
})
.map((messages) => messages ?? [])
}
}
@ -311,7 +311,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
public readonly imageUploadManager = {
getCountsFor() {
return 0
}
},
}
public readonly layout: { getMatchingLayer: (key: any) => LayerConfig }
public readonly featureSwitches: {
@ -327,8 +327,8 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
properties: this.testTags.data,
geometry: {
type: "Point",
coordinates: [3.21, 51.2]
}
coordinates: [3.21, 51.2],
},
}
constructor(
@ -346,10 +346,10 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
} catch (e) {
return undefined
}
}
},
}
this.featureSwitches = {
featureSwitchIsDebugging: new UIEventSource<boolean>(true)
featureSwitchIsDebugging: new UIEventSource<boolean>(true),
}
this.addMissingTagRenderingIds()
@ -426,8 +426,9 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
})
}
protected async validate(configuration: Partial<LayerConfigJson>): Promise<ConversionMessage[]> {
protected async validate(
configuration: Partial<LayerConfigJson>
): Promise<ConversionMessage[]> {
const layers = AllSharedLayers.getSharedLayersConfigs()
const questions = layers.get("questions")
@ -437,7 +438,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
}
const state: DesugaringContext = {
tagRenderings: sharedQuestions,
sharedLayers: layers
sharedLayers: layers,
}
const prepare = this.buildValidation(state)
const context = ConversionContext.construct([], ["prepare"])
@ -475,7 +476,7 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> {
/** Applies a few bandaids to get everything smoothed out in case of errors; a big bunch of hacks basically
*/
public setupFixers() {
this.configuration.addCallbackAndRunD(config => {
this.configuration.addCallbackAndRunD((config) => {
if (config.layers) {
// Remove 'null' and 'undefined' values from the layer array if any are found
for (let i = config.layers.length; i >= 0; i--) {
@ -488,17 +489,16 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> {
}
protected async validate(configuration: Partial<LayoutConfigJson>) {
const layers = AllSharedLayers.getSharedLayersConfigs()
for (const l of configuration.layers ?? []) {
if(typeof l !== "string"){
if (typeof l !== "string") {
continue
}
if (!l.startsWith("https://")) {
continue
}
const config = <LayerConfigJson> await Utils.downloadJsonCached(l, 1000*60*10)
const config = <LayerConfigJson>await Utils.downloadJsonCached(l, 1000 * 60 * 10)
layers.set(l, config)
}
@ -509,11 +509,11 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> {
}
const state: DesugaringContext = {
tagRenderings: sharedQuestions,
sharedLayers: layers
sharedLayers: layers,
}
const prepare = this.buildValidation(state)
const context = ConversionContext.construct([], ["prepare"])
if(configuration.layers){
if (configuration.layers) {
Utils.NoNullInplace(configuration.layers)
}
try {
@ -524,5 +524,4 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> {
}
return context.messages
}
}

View file

@ -22,40 +22,37 @@
export let selfLayers: { owner: number; id: string }[]
export let otherLayers: { owner: number; id: string }[]
{
/**
* We modify the schema and inject options for self-declared layers
*/
const layerSchema = schema.find(l => l.path.join(".") === "layers")
const suggestions: { if: string, then: string }[] = layerSchema.hints.suggestions
suggestions.unshift(...selfLayers.map(
l => ({
const layerSchema = schema.find((l) => l.path.join(".") === "layers")
const suggestions: { if: string; then: string }[] = layerSchema.hints.suggestions
suggestions.unshift(
...selfLayers.map((l) => ({
if: `value=https://studio.mapcomplete.org/${l.owner}/layers/${l.id}/${l.id}.json`,
then: `<b>${l.id}</b> (made by you)`
})
))
then: `<b>${l.id}</b> (made by you)`,
}))
)
for (let i = 0; i < otherLayers.length; i++) {
const l = otherLayers[i]
const mapping = {
if: `value=https://studio.mapcomplete.org/${l.owner}/layers/${l.id}/${l.id}.json`,
then: `<b>${l.id}</b> (made by ${l.owner})`
then: `<b>${l.id}</b> (made by ${l.owner})`,
}
/**
* This is a filthy hack which is time-sensitive and will break
* It downloads the username and patches the suggestion, assuming that the list with all layers will be shown a while _after_ loading the view.
* Caching in 'getInformationAboutUser' helps with this as well
*/
osmConnection.getInformationAboutUser(l.owner).then(userInfo => {
osmConnection.getInformationAboutUser(l.owner).then((userInfo) => {
mapping.then = `<b>${l.id}</b> (made by ${userInfo.display_name})`
})
suggestions.push(mapping)
}
}
let messages = state.messages
let hasErrors = messages.map(
(m: ConversionMessage[]) => m.filter((m) => m.level === "error").length
@ -102,8 +99,7 @@
<div slot="content0" class="mb-8">
<Region configs={perRegion["basic"]} path={[]} {state} title="Basic properties" />
<Region configs={perRegion["start_location"]} path={[]} {state} title="Start location" />
<DeleteButton {state} {backToStudio} objectType="theme"/>
<DeleteButton {state} {backToStudio} objectType="theme" />
</div>
<div slot="title1">Layers</div>
@ -126,10 +122,11 @@
Below, you'll find the raw configuration file in `.json`-format. This is mostly for
debugging purposes, but you can also edit the file directly if you want.
</div>
<div class="literal-code overflow-y-auto h-full" style="min-height: 75%">
<div class="literal-code h-full overflow-y-auto" style="min-height: 75%">
<RawEditor {state} />
</div>
<ShowConversionMessages messages={$messages} />
</div>
</TabbedGroup>
</div>
</div>

View file

@ -75,7 +75,7 @@
{/if}
</NextButton>
{#if description}
<Markdown src={description}/>
<Markdown src={description} />
{/if}
{#each $messages as message}
<ShowConversionMessage {message} />

View file

@ -97,7 +97,7 @@
<h3>{schema.path.at(-1)}</h3>
{#if subparts.length > 0}
<Markdown src={schema.description}/>
<Markdown src={schema.description} />
{/if}
{#if $currentValue === undefined}
No array defined

View file

@ -67,7 +67,7 @@
type = type.substring(0, type.length - 2)
}
const configJson: QuestionableTagRenderingConfigJson & {questionHintIsMd: boolean} = {
const configJson: QuestionableTagRenderingConfigJson & { questionHintIsMd: boolean } = {
id: path.join("_"),
render: rendervalue,
question: schema.hints.question,

View file

@ -40,9 +40,10 @@
if (lastIsString) {
types.splice(types.length - 1, 1)
}
const configJson: QuestionableTagRenderingConfigJson & {questionHintIsMd: boolean}= {
const configJson: QuestionableTagRenderingConfigJson & { questionHintIsMd: boolean } = {
id: "TYPE_OF:" + path.join("_"),
question: schema.hints.question ?? "Which subcategory is needed for " + schema.path.at(-1) + "?",
question:
schema.hints.question ?? "Which subcategory is needed for " + schema.path.at(-1) + "?",
questionHint: schema.description,
questionHintIsMd: true,
mappings: types

View file

@ -3,9 +3,11 @@ import Hash from "../../Logic/Web/Hash"
export default class StudioHashSetter {
constructor(mode: "layer" | "theme", tab: Store<number>, name: Store<string>) {
tab.mapD(tab => {
tab.mapD(
(tab) => {
Hash.hash.setData(mode + "/" + name.data + "/" + tab)
}
, [name])
},
[name]
)
}
}

View file

@ -11,9 +11,13 @@ import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson
export default class StudioServer {
private readonly url: string
private readonly _userId: Store<number>
private readonly overview: UIEventSource<{
success: { id: string; owner: number; category: "layers" | "themes" }[]
} | { error: any } | undefined>
private readonly overview: UIEventSource<
| {
success: { id: string; owner: number; category: "layers" | "themes" }[]
}
| { error: any }
| undefined
>
constructor(url: string, userId: Store<number>) {
this.url = url
@ -21,9 +25,13 @@ export default class StudioServer {
this.overview = UIEventSource.FromPromiseWithErr(this.fetchOverviewRaw())
}
public fetchOverview(): Store<{
success: { id: string; owner: number; category: "layers" | "themes" }[]
} | { error } | undefined> {
public fetchOverview(): Store<
| {
success: { id: string; owner: number; category: "layers" | "themes" }[]
}
| { error }
| undefined
> {
return this.overview
}
@ -80,11 +88,15 @@ export default class StudioServer {
return
}
await fetch(this.urlFor(id, category), {
method: "DELETE"
method: "DELETE",
})
const overview: { id: string; owner: number; category: "layers" | "themes" }[] = this.overview.data?.["success"]
const overview: { id: string; owner: number; category: "layers" | "themes" }[] =
this.overview.data?.["success"]
if (overview) {
const index = overview.findIndex(obj => obj.id === id && obj.category === category && obj.owner === this._userId.data)
const index = overview.findIndex(
(obj) =>
obj.id === id && obj.category === category && obj.owner === this._userId.data
)
if (index >= 0) {
overview.splice(index, 1)
this.overview.ping()
@ -99,9 +111,9 @@ export default class StudioServer {
await fetch(this.urlFor(id, category), {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8"
"Content-Type": "application/json;charset=utf-8",
},
body: config
body: config,
})
}

View file

@ -7,7 +7,7 @@
import type { ConfigMeta } from "./configMeta"
import type {
MappingConfigJson,
QuestionableTagRenderingConfigJson
QuestionableTagRenderingConfigJson,
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
@ -59,8 +59,8 @@
labelMapping = {
if: "value=" + label,
then: {
en: "Builtin collection <b>" + label + "</b>:"
}
en: "Builtin collection <b>" + label + "</b>:",
},
}
perLabel[label] = labelMapping
mappingsBuiltin.push(labelMapping)
@ -72,14 +72,14 @@
mappingsBuiltin.push({
if: "value=" + tr["id"],
then: {
en: "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>"
}
en: "Builtin <b>" + tr["id"] + "</b> <div class='subtle'>" + description + "</div>",
},
})
}
const configBuiltin = new TagRenderingConfig(<QuestionableTagRenderingConfigJson>{
question: "Which builtin element should be shown?",
mappings: mappingsBuiltin
mappings: mappingsBuiltin,
})
const tags = new UIEventSource({ value })
@ -112,7 +112,7 @@
"condition",
"metacondition",
"mappings",
"icon"
"icon",
])
const ignored = new Set(["labels", "description", "classes"])

View file

@ -44,11 +44,11 @@
)
let osmConnection = new OsmConnection({
oauth_token,
checkOnlineRegularly: true
checkOnlineRegularly: true,
})
const expertMode = UIEventSource.asBoolean(
osmConnection.GetPreference("studio-expert-mode", "false", {
documentation: "Indicates if more options are shown in mapcomplete studio"
documentation: "Indicates if more options are shown in mapcomplete studio",
})
)
expertMode.addCallbackAndRunD((expert) => console.log("Expert mode is", expert))
@ -165,18 +165,18 @@
marker: [
{
icon: "circle",
color: "white"
}
]
}
color: "white",
},
],
},
],
tagRenderings: ["images"],
lineRendering: [
{
width: 1,
color: "blue"
}
]
color: "blue",
},
],
}
editLayerState.configuration.setData(initialLayerConfig)
editLayerState.startSavingUpdates()
@ -194,10 +194,11 @@
const event = {
detail: {
id,
owner: uid.data
}
owner: uid.data,
},
}
const statePromise: Promise<EditJsonState<any>> = mode === "layer" ? editLayer(event) : editTheme(event)
const statePromise: Promise<EditJsonState<any>> =
mode === "layer" ? editLayer(event) : editTheme(event)
const state = await statePromise
state.selectedTab.setData(Number(tab))
}
@ -221,8 +222,8 @@
<li>Try again in a few minutes</li>
<li>
Contact <a href="https://app.element.io/#/room/#MapComplete:matrix.org">
the MapComplete community via the chat.
</a>
the MapComplete community via the chat.
</a>
Someone might be able to help you
</li>
<li>
@ -284,11 +285,7 @@
</div>
{:else if state === "edit_layer"}
<div class="m-4 flex flex-col">
<BackButton
clss="small p-1"
imageClass="w-8 h-8"
on:click={() => backToStudio()}
>
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => backToStudio()}>
MapComplete Studio
</BackButton>
<h2>Choose a layer to edit</h2>
@ -331,11 +328,7 @@
</div>
{:else if state === "edit_theme"}
<div class="m-4 flex flex-col">
<BackButton
clss="small p-1"
imageClass="w-8 h-8"
on:click={() => backToStudio()}
>
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => backToStudio()}>
MapComplete Studio
</BackButton>
<h2>Choose a theme to edit</h2>
@ -372,26 +365,20 @@
<Loading />
</div>
{:else if state === "editing_layer"}
<EditLayer
state={editLayerState}
{backToStudio}
>
<BackButton
clss="small p-1"
imageClass="w-8 h-8"
on:click={() => backToStudio()}
>
<EditLayer state={editLayerState} {backToStudio}>
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => backToStudio()}>
MapComplete Studio
</BackButton>
</EditLayer>
{:else if state === "editing_theme"}
<EditTheme state={editThemeState} selfLayers={$selfLayers} otherLayers={$otherLayers} {osmConnection}
{backToStudio}>
<BackButton
clss="small p-1"
imageClass="w-8 h-8"
on:click={() => backToStudio()}
>
<EditTheme
state={editThemeState}
selfLayers={$selfLayers}
otherLayers={$otherLayers}
{osmConnection}
{backToStudio}
>
<BackButton clss="small p-1" imageClass="w-8 h-8" on:click={() => backToStudio()}>
MapComplete Studio
</BackButton>
</EditTheme>

View file

@ -7,165 +7,165 @@
</script>
<main>
<div>
<h1>Stylesheet testing grounds</h1>
<div>
<h1>Stylesheet testing grounds</h1>
This document exists to explore the style hierarchy.
This document exists to explore the style hierarchy.
<div class="normal-background">
<h2>Normal background</h2>
There are a few styles, such as the
<span class="literal-code">normal-background</span>
-style which is used if there is nothing special going on. Some general information, with at most
<a href="https://example.com" target="_blank">a link to someplace</a>
<div class="subtle">Subtle</div>
<div class="normal-background">
<h2>Normal background</h2>
There are a few styles, such as the
<span class="literal-code">normal-background</span>
-style which is used if there is nothing special going on. Some general information, with at most
<a href="https://example.com" target="_blank">a link to someplace</a>
<div class="subtle">Subtle</div>
<div class="alert">Alert: something went wrong</div>
<div class="warning">Warning</div>
<div class="information">Some important information</div>
<div class="thanks">Thank you! Operation successful</div>
<div class="alert">Alert: something went wrong</div>
<div class="warning">Warning</div>
<div class="information">Some important information</div>
<div class="thanks">Thank you! Operation successful</div>
<Login class="h-12 w-12" />
<Loading>Loading...</Loading>
<Login class="h-12 w-12" />
<Loading>Loading...</Loading>
</div>
<div class="low-interaction flex flex-col">
<h2>Low interaction</h2>
<p>
There are <span class="literal-code">low-interaction</span>
areas, where some buttons might appear.
</p>
<div class="border-interactive interactive">
Highly interactive area (mostly: active question)
</div>
<div class="subtle">Subtle</div>
<div class="flex">
<button class="primary">
<Community class="h-6 w-6" />
Main action
</button>
<button class="primary disabled">
<Community class="h-6 w-6" />
Main action (disabled)
</button>
<button class="small">
<Community class="h-6 w-6" />
Small button
</button>
<button class="small primary">Small button</button>
<button class="small primary disabled">Small, disabled button</button>
</div>
<div class="flex">
<button>
<Community class="h-6 w-6" />
Secondary action
</button>
<button class="disabled">
<Community class="h-6 w-6" />
Secondary action (disabled)
</button>
</div>
<input type="text" />
<div class="flex flex-col">
<label class="checked" for="html">
<input id="html" name="fav_language" type="radio" value="HTML" />
HTML (mimicks a
<span class="literal-code">checked</span>
-element)
<Dropdown value={new UIEventSource("abc")}>
<option>abc</option>
<option>def</option>
</Dropdown>
</label>
<label for="css">
<input id="css" name="fav_language" type="radio" value="CSS" />
CSS
</label>
<label for="javascript">
<input id="javascript" name="fav_language" type="radio" value="JavaScript" />
<Community class="h-8 w-8" />
JavaScript
</label>
</div>
<div class="alert">Alert: something went wrong</div>
<div class="warning">Warning</div>
<div class="information">Some important information</div>
<div class="thanks">Thank you! Operation successful</div>
<Login class="h-12 w-12" />
<Loading>Loading...</Loading>
</div>
<div class="interactive flex flex-col">
<h2>Interactive area</h2>
<p>
There are <span class="literal-code">interactive</span>
areas, where many buttons and input elements will appear.
</p>
<div class="subtle">Subtle</div>
<div class="flex">
<button class="primary">
<Community class="h-6 w-6" />
Main action
</button>
<button class="primary disabled">
<Community class="h-6 w-6" />
Main action (disabled)
</button>
<button class="small">
<Community class="h-6 w-6" />
Small button
</button>
</div>
<div class="flex">
<button>
<Community class="h-6 w-6" />
Secondary action
</button>
<button class="disabled">
<Community class="h-6 w-6" />
Secondary action (disabled)
</button>
</div>
<div class="alert">Alert: something went wrong</div>
<div class="warning">Warning</div>
<div class="information">Some important information</div>
<div class="thanks">Thank you! Operation successful</div>
<Login class="h-12 w-12" />
<Loading>Loading...</Loading>
<div>
<label for="html0">
<input id="html0" name="fav_language" type="radio" value="HTML" />
HTML
</label>
<label for="css0">
<input id="css0" name="fav_language" type="radio" value="CSS" />
CSS
</label>
<label for="javascript0">
<input id="javascript0" name="fav_language" type="radio" value="JavaScript" />
JavaScript
</label>
</div>
<div class="border-interactive">
Area with extreme high interactivity due to `border-interactive`
</div>
<select>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
</div>
</div>
<div class="low-interaction flex flex-col">
<h2>Low interaction</h2>
<p>
There are <span class="literal-code">low-interaction</span>
areas, where some buttons might appear.
</p>
<div class="border-interactive interactive">
Highly interactive area (mostly: active question)
</div>
<div class="subtle">Subtle</div>
<div class="flex">
<button class="primary">
<Community class="h-6 w-6" />
Main action
</button>
<button class="primary disabled">
<Community class="h-6 w-6" />
Main action (disabled)
</button>
<button class="small">
<Community class="h-6 w-6" />
Small button
</button>
<button class="small primary">Small button</button>
<button class="small primary disabled">Small, disabled button</button>
</div>
<div class="flex">
<button>
<Community class="h-6 w-6" />
Secondary action
</button>
<button class="disabled">
<Community class="h-6 w-6" />
Secondary action (disabled)
</button>
</div>
<input type="text" />
<div class="flex flex-col">
<label class="checked" for="html">
<input id="html" name="fav_language" type="radio" value="HTML" />
HTML (mimicks a
<span class="literal-code">checked</span>
-element)
<Dropdown value={new UIEventSource("abc")}>
<option>abc</option>
<option>def</option>
</Dropdown>
</label>
<label for="css">
<input id="css" name="fav_language" type="radio" value="CSS" />
CSS
</label>
<label for="javascript">
<input id="javascript" name="fav_language" type="radio" value="JavaScript" />
<Community class="h-8 w-8" />
JavaScript
</label>
</div>
<div class="alert">Alert: something went wrong</div>
<div class="warning">Warning</div>
<div class="information">Some important information</div>
<div class="thanks">Thank you! Operation successful</div>
<Login class="h-12 w-12" />
<Loading>Loading...</Loading>
</div>
<div class="interactive flex flex-col">
<h2>Interactive area</h2>
<p>
There are <span class="literal-code">interactive</span>
areas, where many buttons and input elements will appear.
</p>
<div class="subtle">Subtle</div>
<div class="flex">
<button class="primary">
<Community class="h-6 w-6" />
Main action
</button>
<button class="primary disabled">
<Community class="h-6 w-6" />
Main action (disabled)
</button>
<button class="small">
<Community class="h-6 w-6" />
Small button
</button>
</div>
<div class="flex">
<button>
<Community class="h-6 w-6" />
Secondary action
</button>
<button class="disabled">
<Community class="h-6 w-6" />
Secondary action (disabled)
</button>
</div>
<div class="alert">Alert: something went wrong</div>
<div class="warning">Warning</div>
<div class="information">Some important information</div>
<div class="thanks">Thank you! Operation successful</div>
<Login class="h-12 w-12" />
<Loading>Loading...</Loading>
<div>
<label for="html0">
<input id="html0" name="fav_language" type="radio" value="HTML" />
HTML
</label>
<label for="css0">
<input id="css0" name="fav_language" type="radio" value="CSS" />
CSS
</label>
<label for="javascript0">
<input id="javascript0" name="fav_language" type="radio" value="JavaScript" />
JavaScript
</label>
</div>
<div class="border-interactive">
Area with extreme high interactivity due to `border-interactive`
</div>
<select>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
</div>
</div>
</main>

View file

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

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@
</script>
<div class="link-underline flex h-full w-full flex-col justify-between">
<div class="overflow-y-auto h-full">
<div class="h-full overflow-y-auto">
<slot />
</div>

View file

@ -12,7 +12,7 @@ import Link from "../Base/Link"
import BaseUIElement from "../BaseUIElement"
import { Utils } from "../../Utils"
import SvelteUIElement from "../Base/SvelteUIElement"
import {default as Wikidata_icon} from "../../assets/svg/Wikidata.svelte"
import { default as Wikidata_icon } from "../../assets/svg/Wikidata.svelte"
import Gender_male from "../../assets/svg/Gender_male.svelte"
import Gender_female from "../../assets/svg/Gender_female.svelte"
import Gender_inter from "../../assets/svg/Gender_inter.svelte"

View file

@ -15,7 +15,9 @@
* Shows a wikipedia-article + wikidata preview for the given item
*/
export let wikipediaDetails: Store<FullWikipediaDetails>
let titleOnly = wikipediaDetails.mapD(details => Object.keys(details).length === 1 && details.title !== undefined)
let titleOnly = wikipediaDetails.mapD(
(details) => Object.keys(details).length === 1 && details.title !== undefined
)
</script>
{#if $titleOnly}

Some files were not shown because too many files have changed in this diff Show more