forked from MapComplete/MapComplete
		
	Refactoring: split 'Utils' into multiple files; fix some stray uppercase-method names
This commit is contained in:
		
							parent
							
								
									81be4db044
								
							
						
					
					
						commit
						3ec89826e4
					
				
					 97 changed files with 884 additions and 921 deletions
				
			
		|  | @ -5,6 +5,7 @@ import { LayerConfigJson } from "../../../src/Models/ThemeConfig/Json/LayerConfi | |||
| import FilterConfigJson from "../../../src/Models/ThemeConfig/Json/FilterConfigJson" | ||||
| import RewritableConfigJson from "../../../src/Models/ThemeConfig/Json/RewritableConfigJson" | ||||
| import { TagRenderingConfigJson } from "../../../src/Models/ThemeConfig/Json/TagRenderingConfigJson" | ||||
| import { Lists } from "../../../src/Utils/Lists" | ||||
| 
 | ||||
| interface ChargingStandard { | ||||
|     key: string, | ||||
|  | @ -27,36 +28,36 @@ function colonSplit(value: string): string[] { | |||
| } | ||||
| 
 | ||||
| function loadCsv(file): ChargingStandard[] { | ||||
|     const entries: string[] = Utils.NoNull(readFileSync(file, "utf8").split("\n").map(str => str.trim())) | ||||
|     const entries: string[] = Lists.noNull(readFileSync(file, "utf8").split("\n").map(str => str.trim())) | ||||
|     const header = entries.shift().split(",") | ||||
| 
 | ||||
|     return Utils.NoNull(entries.map(entry => { | ||||
|         const values = entry.split(",").map(str => str.trim()) | ||||
|         if (values[0] === undefined || values[0] === "") { | ||||
|             return undefined | ||||
|     return Lists.noNull(entries.map(entry => { | ||||
|       const values = entry.split(",").map(str => str.trim()) | ||||
|       if (values[0] === undefined || values[0] === "") { | ||||
|         return undefined | ||||
|       } | ||||
| 
 | ||||
|       const v = {} | ||||
|       const colonSeperated = ["commonVoltages", "commonOutputs", "commonCurrents", "countryWhiteList", "countryBlackList", "associatedVehicleTypes", "neverAssociatedWith"] | ||||
|       const descriptionTranslations = new Map<string, string>() | ||||
|       for (let j = 0; j < header.length; j++) { | ||||
|         const key = header[j] | ||||
|         if (key.startsWith("description")) { | ||||
|           const language = key.substring("description:".length) | ||||
|           descriptionTranslations.set(language, values[j]) | ||||
|         } | ||||
| 
 | ||||
|         const v = {} | ||||
|         const colonSeperated = ["commonVoltages", "commonOutputs", "commonCurrents", "countryWhiteList", "countryBlackList", "associatedVehicleTypes", "neverAssociatedWith"] | ||||
|         const descriptionTranslations = new Map<string, string>() | ||||
|         for (let j = 0; j < header.length; j++) { | ||||
|             const key = header[j] | ||||
|             if (key.startsWith("description")) { | ||||
|                 const language = key.substring("description:".length) | ||||
|                 descriptionTranslations.set(language, values[j]) | ||||
|             } | ||||
| 
 | ||||
|             if (colonSeperated.indexOf(key) >= 0) { | ||||
|                 v[key] = colonSplit(values[j]) | ||||
|             } else { | ||||
|                 v[key] = values[j] | ||||
|             } | ||||
|         if (colonSeperated.indexOf(key) >= 0) { | ||||
|           v[key] = colonSplit(values[j]) | ||||
|         } else { | ||||
|           v[key] = values[j] | ||||
|         } | ||||
|         v["description"] = descriptionTranslations | ||||
|         if (v["id"] === "") { | ||||
|             v["id"] = v["key"] | ||||
|         } | ||||
|         return <any>v | ||||
|       } | ||||
|       v["description"] = descriptionTranslations | ||||
|       if (v["id"] === "") { | ||||
|         v["id"] = v["key"] | ||||
|       } | ||||
|       return <any>v | ||||
|     })) | ||||
| } | ||||
| 
 | ||||
|  | @ -161,7 +162,7 @@ function run(file, protojson) { | |||
|         // We add a second time for any amount to trigger a visualisation; but this is not an answer option
 | ||||
|         const no_ask_json = { | ||||
|             if: { | ||||
|                 and: Utils.NoEmpty([`${e.key}~*`, `${e.key}!=1`, ...e.extraVisualisationCondition.split(";")]), | ||||
|                 and: Lists.noEmpty([`${e.key}~*`, `${e.key}!=1`, ...e.extraVisualisationCondition.split(";")]), | ||||
|             }, | ||||
|             then: txt, | ||||
|             hideInAnswer: true, | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -943,9 +943,9 @@ | |||
|           "ru": "Onroerend Erfgoed ID:" | ||||
|         }, | ||||
|         "special": { | ||||
|           "type": "link", | ||||
|           "href": "https://id.erfgoed.net/erfgoedobjecten/{ref:OnroerendErfgoed}", | ||||
|           "text": "{ref:OnroerendErfgoed}" | ||||
|           "text": "{ref:OnroerendErfgoed}", | ||||
|           "type": "link" | ||||
|         } | ||||
|       }, | ||||
|       "icon": "./assets/layers/tree/Onroerend_Erfgoed_logo_without_text.svg", | ||||
|  |  | |||
|  | @ -103,13 +103,13 @@ class StatsDownloader { | |||
|         let page = 1 | ||||
|         let allFeatures: ChangeSetData[] = [] | ||||
|         const endDay = new Date(year, month - 1 /* Zero-indexed: 0 = january*/, day + 1) | ||||
|         const endDate = `${endDay.getFullYear()}-${Utils.TwoDigits( | ||||
|         const endDate = `${endDay.getFullYear()}-${Utils.twoDigits( | ||||
|             endDay.getMonth() + 1 | ||||
|         )}-${Utils.TwoDigits(endDay.getDate())}` | ||||
|         )}-${Utils.twoDigits(endDay.getDate())}` | ||||
|         let url = this.urlTemplate | ||||
|             .replace( | ||||
|                 "{start_date}", | ||||
|                 year + "-" + Utils.TwoDigits(month) + "-" + Utils.TwoDigits(day) | ||||
|                 year + "-" + Utils.twoDigits(month) + "-" + Utils.twoDigits(day) | ||||
|             ) | ||||
|             .replace("{end_date}", endDate) | ||||
|             .replace("{page}", "" + page) | ||||
|  | @ -142,7 +142,7 @@ class StatsDownloader { | |||
|             } | ||||
|             url = result.next | ||||
|         } | ||||
|         allFeatures = Utils.NoNull(allFeatures) | ||||
|         allFeatures = Lists.noNull(allFeatures) | ||||
|         allFeatures.forEach((f) => { | ||||
|             f.properties = { ...f.properties, ...f.properties.metadata } | ||||
|             if (f.properties.editor.toLowerCase().indexOf("android") >= 0) { | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import { existsSync, readFileSync, writeFileSync } from "fs" | |||
| import WikidataUtils from "../src/Utils/WikidataUtils" | ||||
| import LanguageUtils from "../src/Utils/LanguageUtils" | ||||
| import Wikidata from "../src/Logic/Web/Wikidata" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| interface value<T> { | ||||
|     value: T | ||||
|  | @ -196,7 +197,7 @@ async function main(wipeCache = false) { | |||
|             }) | ||||
| 
 | ||||
|         translatedForId["_meta"] = { | ||||
|             countries: Utils.Dedup(languagesPerCountry[key]), | ||||
|             countries: Lists.dedup(languagesPerCountry[key]), | ||||
|             dir: value.directionality, | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import Validators from "../src/UI/InputElement/Validators" | |||
| import { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts" | ||||
| import { AllSharedLayers } from "../src/Customizations/AllSharedLayers" | ||||
| import Constants from "../src/Models/Constants" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| const metainfo = { | ||||
|     type: "One of the inputValidator types", | ||||
|  | @ -371,7 +372,7 @@ function extractMeta( | |||
|     const fullPath = "./src/assets/schemas/" + path + ".json" | ||||
|     writeFileSync(fullPath, JSON.stringify(paths, null, "  ")) | ||||
|     console.log("Written meta to " + fullPath) | ||||
|     return Utils.NoNull(paths.map((p) => validateMeta(p))) | ||||
|     return Lists.noNull(paths.map((p) => validateMeta(p))) | ||||
| } | ||||
| 
 | ||||
| function main() { | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ import { ImmutableStore } from "../src/Logic/UIEventSource" | |||
| import * as unitUsage from "../Docs/Schemas/UnitConfigJson.schema.json" | ||||
| import { ThemeConfigJson } from "../src/Models/ThemeConfig/Json/ThemeConfigJson" | ||||
| import { ServerSourceInfo, SourceOverview } from "../src/Models/SourceOverview" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use | ||||
|  | @ -562,7 +563,7 @@ export class GenerateDocs extends Script { | |||
|                 ) | ||||
|         ) | ||||
|         const serverInfos = Utils.DedupOnId(serverInfosDupl, (item) => item.url) | ||||
|         const titles = Utils.Dedup(Utils.NoEmpty(serverInfos.map((s) => s.category))) | ||||
|         const titles = Lists.dedup(Lists.noEmpty(serverInfos.map((s) => s.category))) | ||||
|         titles.sort() | ||||
| 
 | ||||
|         function getHost(item: ServerSourceInfo) { | ||||
|  | @ -597,7 +598,7 @@ export class GenerateDocs extends Script { | |||
|             md.push(items.length + " items") | ||||
|             md.push(categoryExplanation[title]) | ||||
| 
 | ||||
|             const hosts = Utils.Dedup(items.map(getHost)) | ||||
|             const hosts = Lists.dedup(items.map(getHost)) | ||||
|             hosts.sort() | ||||
|             if (title === "maplayer") { | ||||
|                 md.push(MarkdownUtils.list(hosts)) | ||||
|  | @ -636,7 +637,7 @@ export class GenerateDocs extends Script { | |||
|                         return [ | ||||
|                             item.url, | ||||
|                             identicalDescription ? "" : item.description, | ||||
|                             Utils.NoEmpty([ | ||||
|                             Lists.noEmpty([ | ||||
|                                 item.openData ? "OpenData" : "", | ||||
|                                 sourceAvailable, | ||||
|                                 selfHostable, | ||||
|  | @ -720,9 +721,7 @@ export class GenerateDocs extends Script { | |||
|             MarkdownUtils.list( | ||||
|                 Constants.priviliged_layers.map((id) => "[" + id + "](#" + id + ")") | ||||
|             ), | ||||
|             ...Utils.NoNull( | ||||
|                 Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id)) | ||||
|             ).map((l) => | ||||
|             ...Lists.noNull(Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id))).map((l) => | ||||
|                 l.generateDocumentation({ | ||||
|                     usedInThemes: themesPerLayer.get(l.id), | ||||
|                     layerIsNeededBy: layerIsNeededBy, | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import { TagUtils } from "../src/Logic/Tags/TagUtils" | |||
| import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" | ||||
| import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" | ||||
| import * as questions from "../assets/layers/questions/questions.json" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| export class GenerateFavouritesLayer extends Script { | ||||
|     private readonly layers: LayerConfigJson[] = [] | ||||
|  | @ -155,7 +156,7 @@ export class GenerateFavouritesLayer extends Script { | |||
|                             continue | ||||
|                         } | ||||
|                         newTr.condition = { | ||||
|                             and: Utils.NoNull([newTr.condition, layerConfig.source["osmTags"]]), | ||||
|                             and: Lists.noNull([newTr.condition, layerConfig.source["osmTags"]]), | ||||
|                         } | ||||
|                         generatedTagRenderings.push(newTr) | ||||
|                         blacklistedIds.add(newTr.id) | ||||
|  |  | |||
|  | @ -37,10 +37,8 @@ import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" | |||
| import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers" | ||||
| import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages" | ||||
| import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" | ||||
| import { | ||||
|     LayerConfigDependencyGraph, | ||||
|     LevelInfo, | ||||
| } from "../src/Models/ThemeConfig/LayerConfigDependencyGraph" | ||||
| import { LayerConfigDependencyGraph, LevelInfo } from "../src/Models/ThemeConfig/LayerConfigDependencyGraph" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
 | ||||
| // It spits out an overview of those to be used to load them
 | ||||
|  | @ -126,20 +124,18 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye | |||
|             pr.location.has("point") | ||||
|         ) | ||||
|         const defaultTags = layerConfig.baseTags | ||||
|         fixed["_layerIcon"] = Utils.NoNull( | ||||
|             (pointRendering?.marker ?? []).map((i) => { | ||||
|                 const icon = i.icon?.GetRenderValue(defaultTags)?.txt | ||||
|                 if (!icon) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 const result = { icon } | ||||
|                 const c = i.color?.GetRenderValue(defaultTags)?.txt | ||||
|                 if (c) { | ||||
|                     result["color"] = c | ||||
|                 } | ||||
|                 return result | ||||
|             }) | ||||
|         ) | ||||
|         fixed["_layerIcon"] = Lists.noNull((pointRendering?.marker ?? []).map((i) => { | ||||
|           const icon = i.icon?.GetRenderValue(defaultTags)?.txt | ||||
|           if (!icon) { | ||||
|             return undefined | ||||
|           } | ||||
|           const result = { icon } | ||||
|           const c = i.color?.GetRenderValue(defaultTags)?.txt | ||||
|           if (c) { | ||||
|             result["color"] = c | ||||
|           } | ||||
|           return result | ||||
|         })) | ||||
|         return { raw: fixed, parsed: layerConfig } | ||||
|     } | ||||
| } | ||||
|  | @ -233,7 +229,7 @@ class LayerBuilder extends Conversion<object, Map<string, LayerConfigJson>> { | |||
|         const origIds: ReadonlyArray<string> = [...ids] | ||||
| 
 | ||||
|         const deps = this._dependencies | ||||
|         const allDeps = Utils.Dedup([].concat(...ids.map((id) => deps.get(id)))) | ||||
|         const allDeps = Lists.dedup([].concat(...ids.map((id) => deps.get(id)))) | ||||
|         const depsRecord = Utils.asRecord(Array.from(deps.keys()), (k) => | ||||
|             deps.get(k).filter((dep) => ids.indexOf(dep) >= 0) | ||||
|         ) | ||||
|  | @ -864,7 +860,7 @@ class LayerOverviewUtils extends Script { | |||
|             ) | ||||
|         } | ||||
|         if(printAssets){ | ||||
|             const images = Utils.Dedup(Array.from(sharedThemes.values()).flatMap(th => th._usedImages ?? [] )) | ||||
|             const images = Lists.dedup(Array.from(sharedThemes.values()).flatMap(th => th._usedImages ?? [])) | ||||
|             writeFileSync("needed_assets.csv", images.join("\n")) | ||||
|             console.log("Written needed_assets.csv") | ||||
|         } | ||||
|  | @ -1278,11 +1274,9 @@ class LayerOverviewUtils extends Script { | |||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 const usedImages = Utils.Dedup( | ||||
|                     new ExtractImages(true, knownTagRenderings) | ||||
|                         .convertStrict(themeFile) | ||||
|                         .map((x) => x.path) | ||||
|                 ) | ||||
|                 const usedImages = Lists.dedup(new ExtractImages(true, knownTagRenderings) | ||||
|                     .convertStrict(themeFile) | ||||
|                     .map((x) => x.path)) | ||||
|                 usedImages.sort() | ||||
| 
 | ||||
|                 themeFile["_usedImages"] = usedImages | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import SmallLicense from "../src/Models/smallLicense" | |||
| import ScriptUtils from "./ScriptUtils" | ||||
| import Script from "./Script" | ||||
| import { Utils } from "../src/Utils" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| const prompt = require("prompt-sync")() | ||||
| 
 | ||||
|  | @ -297,9 +298,7 @@ export class GenerateLicenseInfo extends Script { | |||
|                 sources: license.sources, | ||||
|             } | ||||
| 
 | ||||
|             cloned.license = Utils.Dedup( | ||||
|                 cloned.license.split(";").map((l) => this.toSPDXCompliantLicense(l)) | ||||
|             ).join("; ") | ||||
|             cloned.license = Lists.dedup(cloned.license.split(";").map((l) => this.toSPDXCompliantLicense(l))).join("; ") | ||||
|             if (cloned.license === "CC0-1.0; TRIVIAL") { | ||||
|                 cloned.license = "TRIVIAL" | ||||
|             } | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import Script from "./Script" | |||
| import NameSuggestionIndex from "../src/Logic/Web/NameSuggestionIndex" | ||||
| import TagInfo from "../src/Logic/Web/TagInfo" | ||||
| import { TagsFilter } from "../src/Logic/Tags/TagsFilter" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| class GenerateNsiStats extends Script { | ||||
|     async createOptimizationFile(includeTags = true) { | ||||
|  | @ -139,9 +140,7 @@ class GenerateNsiStats extends Script { | |||
|             ) | ||||
|         } | ||||
|         const nsi = await NameSuggestionIndex.singleton() | ||||
|         const allBrandNames: string[] = Utils.Dedup( | ||||
|             nsi.allPossible(<any>type).map((item) => item.tags[type]) | ||||
|         ) | ||||
|         const allBrandNames: string[] = Lists.dedup(nsi.allPossible(<any>type).map((item) => item.tags[type])) | ||||
|         const batchSize = 50 | ||||
|         for (let i = 0; i < allBrandNames.length; i += batchSize) { | ||||
|             console.warn( | ||||
|  | @ -152,7 +151,7 @@ class GenerateNsiStats extends Script { | |||
|             ) | ||||
|             let downloaded = 0 | ||||
|             await Promise.all( | ||||
|                 Utils.TimesT(batchSize, async (j) => { | ||||
|                 Utils.timesT(batchSize, async (j) => { | ||||
|                     const brand = allBrandNames[i + j] | ||||
|                     if (!allBrands[brand]) { | ||||
|                         allBrands[brand] = {} | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { readFileSync, writeFileSync } from "fs" | |||
| import ThemeConfig from "../src/Models/ThemeConfig/ThemeConfig" | ||||
| import LayerConfig from "../src/Models/ThemeConfig/LayerConfig" | ||||
| import { Utils } from "../src/Utils" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * Generates all the files in "Docs/TagInfo". These are picked up by the taginfo project, showing a link to the mapcomplete theme if the key is used | ||||
|  | @ -269,7 +270,7 @@ function main() { | |||
|         } | ||||
|         files.push(generateTagInfoEntry(layout)) | ||||
|     } | ||||
|     generateProjectsOverview(Utils.NoNull(files)) | ||||
|     generateProjectsOverview(Lists.noNull(files)) | ||||
| } | ||||
| 
 | ||||
| main() | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { Utils } from "../src/Utils" | |||
| import ScriptUtils from "./ScriptUtils" | ||||
| import Script from "./Script" | ||||
| import Constants from "../src/Models/Constants" | ||||
| import { Lists } from "../src/Utils/Lists" | ||||
| 
 | ||||
| const knownLanguages = ["en", "nl", "de", "fr", "es", "gl", "ca"] | ||||
| const ignoreTerms = ["searchTerms"] | ||||
|  | @ -172,7 +173,7 @@ class TranslationPart { | |||
|                 languages.push(...(value as TranslationPart).knownLanguages()) | ||||
|             } | ||||
|         } | ||||
|         return Utils.Dedup(languages) | ||||
|         return Lists.dedup(languages) | ||||
|     } | ||||
| 
 | ||||
|     toJson(neededLanguage?: string): string { | ||||
|  | @ -351,7 +352,7 @@ function transformTranslation( | |||
|     } | ||||
| 
 | ||||
|     const values: string[] = [] | ||||
|     const spaces = Utils.Times((_) => "  ", path.length + 1) | ||||
|     const spaces = Utils.times((_) => "  ", path.length + 1) | ||||
| 
 | ||||
|     for (const key in obj) { | ||||
|         if (key === "#") { | ||||
|  | @ -381,7 +382,7 @@ function transformTranslation( | |||
|             let expr = `new Translation(${JSON.stringify(value)}, "core:${path.join(".")}.${key}")` | ||||
|             if (subParts !== null) { | ||||
|                 // convert '{to_substitute}' into 'to_substitute'
 | ||||
|                 const types = Utils.Dedup(subParts.map((tp) => tp.substring(1, tp.length - 1))) | ||||
|                 const types = Lists.dedup(subParts.map((tp) => tp.substring(1, tp.length - 1))) | ||||
|                 const invalid = types.filter( | ||||
|                     (part) => part.match(/^[a-z0-9A-Z_]+(\(.*\))?$/) == null | ||||
|                 ) | ||||
|  | @ -764,7 +765,7 @@ class GenerateTranslations extends Script { | |||
|                 "themes" | ||||
|             ) | ||||
| 
 | ||||
|             const usedLanguages: string[] = Utils.Dedup(l1.concat(l2)).filter((v) => v !== "*") | ||||
|             const usedLanguages: string[] = Lists.dedup(l1.concat(l2)).filter((v) => v !== "*") | ||||
|             usedLanguages.sort() | ||||
|             fs.writeFileSync( | ||||
|                 "./src/assets/used_languages.json", | ||||
|  |  | |||
|  | @ -128,7 +128,7 @@ class NsiLogos extends Script { | |||
|             } | ||||
| 
 | ||||
|             const results = await Promise.all( | ||||
|                 Utils.TimesT(stepcount, (j) => j).map(async (j) => { | ||||
|                 Utils.timesT(stepcount, (j) => j).map(async (j) => { | ||||
|                     return await this.downloadLogo(items[i + j], type, basePath, alreadyDownloaded) | ||||
|                 }) | ||||
|             ) | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import Script from "../Script" | ||||
| import { readFileSync, writeFileSync } from "fs" | ||||
| import { OsmId } from "../../src/Models/OsmFeature" | ||||
| import { Utils } from "../../src/Utils" | ||||
| import { Lists } from "../../src/Utils/Lists" | ||||
| 
 | ||||
| interface DiffItem { | ||||
|     /** | ||||
|  | @ -34,7 +34,7 @@ export class DiffToCsv extends Script { | |||
|             JSON.parse(readFileSync(file, "utf8")) | ||||
|         ) | ||||
|         const diffs = json.diffs | ||||
|         const allKeys = Utils.Dedup(diffs.flatMap((item) => item.diffs.map((d) => d.key))) | ||||
|         const allKeys = Lists.dedup(diffs.flatMap((item) => item.diffs.map((d) => d.key))) | ||||
|         allKeys.sort() | ||||
| 
 | ||||
|         const header = [ | ||||
|  |  | |||
|  | @ -74,7 +74,7 @@ class VeloParkToGeojson extends Script { | |||
|         const batchSize = 50 | ||||
|         for (let i = 0; i < allVeloparkRaw.length; i += batchSize) { | ||||
|             await Promise.all( | ||||
|                 Utils.TimesT(batchSize, (j) => j).map(async (j) => { | ||||
|                 Utils.timesT(batchSize, (j) => j).map(async (j) => { | ||||
|                     const f = allVeloparkRaw[i + j] | ||||
|                     if (!f) { | ||||
|                         return | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import Locale from "../../UI/i18n/Locale" | |||
| import { Utils } from "../../Utils" | ||||
| import { Feature } from "geojson" | ||||
| import { SpecialVisualizationState } from "../../UI/SpecialVisualization" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export default class TitleHandler { | ||||
|     constructor(selectedElement: Store<Feature>, state: SpecialVisualizationState) { | ||||
|  | @ -22,7 +23,7 @@ export default class TitleHandler { | |||
|                 if (layer.title === undefined) { | ||||
|                     return defaultTitle | ||||
|                 } | ||||
|                 const toRender = Utils.NoNull(layer?.title?.GetRenderValues(tags)) | ||||
|                 const toRender = Lists.noNull(layer?.title?.GetRenderValues(tags)) | ||||
|                 const titleUnsubbed = toRender[0]?.then?.textFor(lng) | ||||
|                 if (titleUnsubbed === undefined) { | ||||
|                     return defaultTitle | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import { ThemeConfigJson } from "../Models/ThemeConfig/Json/ThemeConfigJson" | |||
| import { ValidateThemeAndLayers } from "../Models/ThemeConfig/Conversion/ValidateThemeAndLayers" | ||||
| import * as theme_overview from "../assets/generated/theme_overview.json" | ||||
| import * as favourite_layer from "../../assets/layers/favourite/favourite.json" | ||||
| import { Lists } from "../Utils/Lists" | ||||
| export default class DetermineTheme { | ||||
|     private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path)) | ||||
|     private static readonly loadCustomThemeParam = QueryParameters.GetQueryParameter( | ||||
|  | @ -143,27 +144,25 @@ export default class DetermineTheme { | |||
|         if (json.layers === undefined && json.tagRenderings !== undefined) { | ||||
|             // We got fed a layer instead of a theme
 | ||||
|             const layerConfig = <LayerConfigJson>json | ||||
|             let icon = Utils.NoNull( | ||||
|                 layerConfig.pointRendering | ||||
|                     .flatMap((pr) => pr.marker) | ||||
|                     .map((iconSpec) => { | ||||
|                         if (!iconSpec) { | ||||
|                             return undefined | ||||
|                         } | ||||
|                         const icon = new TagRenderingConfig(<TagRenderingConfigJson>iconSpec.icon) | ||||
|                             .render.txt | ||||
|                         if ( | ||||
|                             iconSpec.color === undefined || | ||||
|                             icon.startsWith("http:") || | ||||
|                             icon.startsWith("https:") | ||||
|                         ) { | ||||
|                             return icon | ||||
|                         } | ||||
|                         const color = new TagRenderingConfig(<TagRenderingConfigJson>iconSpec.color) | ||||
|                             .render.txt | ||||
|                         return icon + ":" + color | ||||
|                     }) | ||||
|             ).join(";") | ||||
|             let icon = Lists.noNull(layerConfig.pointRendering | ||||
|               .flatMap((pr) => pr.marker) | ||||
|               .map((iconSpec) => { | ||||
|                 if (!iconSpec) { | ||||
|                   return undefined | ||||
|                 } | ||||
|                 const icon = new TagRenderingConfig(<TagRenderingConfigJson>iconSpec.icon) | ||||
|                   .render.txt | ||||
|                 if ( | ||||
|                   iconSpec.color === undefined || | ||||
|                   icon.startsWith("http:") || | ||||
|                   icon.startsWith("https:") | ||||
|                 ) { | ||||
|                   return icon | ||||
|                 } | ||||
|                 const color = new TagRenderingConfig(<TagRenderingConfigJson>iconSpec.color) | ||||
|                   .render.txt | ||||
|                 return icon + ":" + color | ||||
|               })).join(";") | ||||
| 
 | ||||
|             if (!icon) { | ||||
|                 icon = "./assets/svg/bug.svg" | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import { Store, UIEventSource } from "../../UIEventSource" | ||||
| import { FeatureSource, IndexedFeatureSource, UpdatableFeatureSource } from "../FeatureSource" | ||||
| import { Feature } from "geojson" | ||||
| import { Utils } from "../../../Utils" | ||||
| import { OsmFeature } from "../../../Models/OsmFeature" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * The featureSourceMerger receives complete geometries from various sources. | ||||
|  | @ -23,7 +23,7 @@ export default class FeatureSourceMerger<Src extends FeatureSource = FeatureSour | |||
|     constructor(...sources: Src[]) { | ||||
|         this._featuresById = new UIEventSource<Map<string, Feature>>(new Map<string, Feature>()) | ||||
|         this.featuresById = this._featuresById | ||||
|         sources = Utils.NoNull(sources) | ||||
|         sources = Lists.noNull(sources) | ||||
|         for (const source of sources) { | ||||
|             source.features.addCallback(() => { | ||||
|                 this.addDataFromSources(sources) | ||||
|  | @ -69,7 +69,7 @@ export default class FeatureSourceMerger<Src extends FeatureSource = FeatureSour | |||
|     } | ||||
| 
 | ||||
|     protected addData(sources: Feature[][]) { | ||||
|         sources = Utils.NoNull(sources) | ||||
|         sources = Lists.noNull(sources) | ||||
|         let somethingChanged = false | ||||
|         const all: Map<string, Feature> = new Map() | ||||
|         const unseen = new Set<string>() | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import BaseUIElement from "../../../UI/BaseUIElement" | |||
| import { Utils } from "../../../Utils" | ||||
| import { OsmTags } from "../../../Models/OsmFeature" | ||||
| import { FeatureSource } from "../FeatureSource" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * Highly specialized feature source. | ||||
|  | @ -48,11 +49,9 @@ export class LastClickFeatureSource implements FeatureSource { | |||
|                 allPresets.push(html) | ||||
|             } | ||||
| 
 | ||||
|         this.renderings = Utils.Dedup( | ||||
|             allPresets.map((uiElem) => | ||||
|                 Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML | ||||
|             ) | ||||
|         ) | ||||
|         this.renderings = Lists.dedup(allPresets.map((uiElem) => | ||||
|           Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML | ||||
|         )) | ||||
| 
 | ||||
|         this._features = new UIEventSource<Feature[]>([]) | ||||
|         this.features = this._features | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import { Feature } from "geojson" | |||
| import FeatureSourceMerger from "../Sources/FeatureSourceMerger" | ||||
| import OsmObjectDownloader from "../../Osm/OsmObjectDownloader" | ||||
| import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * If a tile is needed (requested via the UIEventSource in the constructor), will download the appropriate tile and pass it via 'handleTile' | ||||
|  | @ -173,7 +174,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger { | |||
|                 for (let i = 0; i < features.length; i++) { | ||||
|                     features[i] = await this.patchIncompleteRelations(features[i], <any>osmJson) | ||||
|                 } | ||||
|                 features = Utils.NoNull(features) | ||||
|                 features = Lists.noNull(features) | ||||
|                 features.forEach((f) => { | ||||
|                     f.properties["_backend"] = this._backend | ||||
|                 }) | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import { Utils } from "../../../Utils" | |||
| import { TagsFilter } from "../../Tags/TagsFilter" | ||||
| import { BBox } from "../../BBox" | ||||
| import { OsmTags } from "../../../Models/OsmFeature" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| ("use strict") | ||||
| 
 | ||||
|  | @ -199,7 +200,7 @@ export default class OverpassFeatureSource implements UpdatableFeatureSource { | |||
|      */ | ||||
|     private GetFilter(interpreterUrl: string, layersToDownload: LayerConfig[]): Overpass { | ||||
|         let filters: TagsFilter[] = layersToDownload.map((layer) => layer.source.osmTags) | ||||
|         filters = Utils.NoNull(filters) | ||||
|         filters = Lists.noNull(filters) | ||||
|         if (filters.length === 0) { | ||||
|             return undefined | ||||
|         } | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { Utils } from "../../../Utils" | |||
| import { Feature, MultiLineString, Position } from "geojson" | ||||
| import { GeoOperations } from "../../GeoOperations" | ||||
| import { UpdatableDynamicTileSource } from "./DynamicTileSource" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together. | ||||
|  | @ -32,7 +33,7 @@ export class LineSourceMerger extends UpdatableDynamicTileSource< | |||
|     } | ||||
| 
 | ||||
|     protected addDataFromSources(sources: FeatureSourceForTile[]) { | ||||
|         sources = Utils.NoNull(sources) | ||||
|         sources = Lists.noNull(sources) | ||||
|         const all: Map<string, Feature<MultiLineString>> = new Map() | ||||
|         const currentZoom = this._zoomlevel?.data ?? 0 | ||||
|         for (const source of sources) { | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| import { FeatureSourceForTile, UpdatableFeatureSource } from "../FeatureSource" | ||||
| import { Store } from "../../UIEventSource" | ||||
| import { BBox } from "../../BBox" | ||||
| import { Utils } from "../../../Utils" | ||||
| import { Feature } from "geojson" | ||||
| import { GeoOperations } from "../../GeoOperations" | ||||
| import { UpdatableDynamicTileSource } from "./DynamicTileSource" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together. | ||||
|  | @ -29,7 +29,7 @@ export class PolygonSourceMerger extends UpdatableDynamicTileSource< | |||
|     } | ||||
| 
 | ||||
|     protected addDataFromSources(sources: FeatureSourceForTile[]) { | ||||
|         sources = Utils.NoNull(sources) | ||||
|         sources = Lists.noNull(sources) | ||||
|         const all: Map<string, Feature> = new Map() | ||||
|         const zooms: Map<string, number> = new Map() | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import { | |||
| } from "geojson" | ||||
| import { Tiles } from "../Models/TileRange" | ||||
| import { Utils } from "../Utils" | ||||
| import { Lists } from "../Utils/Lists" | ||||
| 
 | ||||
| ("use strict") | ||||
| 
 | ||||
|  | @ -597,7 +598,7 @@ export class GeoOperations { | |||
|                 newFeatures.push(intersectionPart) | ||||
|             } | ||||
|         } | ||||
|         return Utils.NoNull(newFeatures) | ||||
|         return Lists.noNull(newFeatures) | ||||
|     } | ||||
| 
 | ||||
|     public static toGpx( | ||||
|  | @ -610,7 +611,7 @@ export class GeoOperations { | |||
|         if (title === undefined || title === "") { | ||||
|             title = "Uploaded with MapComplete" | ||||
|         } | ||||
|         title = Utils.EncodeXmlValue(title) | ||||
|         title = Utils.encodeXmlValue(title) | ||||
|         const trackPoints: string[] = [] | ||||
|         let locationsWithMeta: Feature<Point>[] | ||||
|         if (Array.isArray(locations)) { | ||||
|  | @ -664,7 +665,7 @@ export class GeoOperations { | |||
|         if (title === undefined || title === "") { | ||||
|             title = "Created with MapComplete" | ||||
|         } | ||||
|         title = Utils.EncodeXmlValue(title) | ||||
|         title = Utils.encodeXmlValue(title) | ||||
|         const trackPoints: string[] = [] | ||||
|         for (const l of locations) { | ||||
|             let trkpt = `    <wpt lat="${l.geometry.coordinates[1]}" lon="${l.geometry.coordinates[0]}">` | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import { WikidataImageProvider } from "./WikidataImageProvider" | |||
| import Panoramax from "./Panoramax" | ||||
| import { Utils } from "../../Utils" | ||||
| import { ServerSourceInfo } from "../../Models/SourceOverview" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * A generic 'from the interwebz' image picker, without attribution | ||||
|  | @ -101,9 +102,7 @@ export default class AllImageProviders { | |||
|             Mapillary.singleton, | ||||
|             AllImageProviders.genericImageProvider, | ||||
|         ] | ||||
|         const allPrefixes = Utils.Dedup( | ||||
|             prefixes ?? [].concat(...sources.map((s) => s.defaultKeyPrefixes)) | ||||
|         ) | ||||
|         const allPrefixes = Lists.dedup(prefixes ?? [].concat(...sources.map((s) => s.defaultKeyPrefixes))) | ||||
|         for (const prefix of allPrefixes) { | ||||
|             for (const k in tags) { | ||||
|                 const v = tags[k] | ||||
|  | @ -149,7 +148,7 @@ export default class AllImageProviders { | |||
|             allSources.push(singleSource) | ||||
|         } | ||||
|         const source = Stores.concat(allSources).map((result) => { | ||||
|             const all = Utils.concat(result) | ||||
|             const all = result.flatMap(x => x) | ||||
|             return Utils.DedupOnId(all, (i) => [i?.id, i?.url, i?.alt_id]) | ||||
|         }) | ||||
|         this._cachedImageStores[cachekey] = source | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { Utils } from "../../Utils" | |||
| import { Feature, Point } from "geojson" | ||||
| import { ServerSourceInfo } from "../../Models/SourceOverview" | ||||
| import { ComponentType } from "svelte/types/runtime/internal/dev" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export interface ProvidedImage { | ||||
|     url: string | ||||
|  | @ -92,7 +93,7 @@ export default abstract class ImageProvider { | |||
|             ) { | ||||
|                 continue | ||||
|             } | ||||
|             const values = Utils.NoEmpty(tags[key]?.split(";")?.map((v) => v.trim()) ?? []) | ||||
|             const values = Lists.noEmpty(tags[key]?.split(";")?.map((v) => v.trim()) ?? []) | ||||
|             for (const value of values) { | ||||
|                 if (seenValues.has(value)) { | ||||
|                     continue | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ import NoteCommentElement from "../../UI/Popup/Notes/NoteCommentElement" | |||
| import OsmObjectDownloader from "../Osm/OsmObjectDownloader" | ||||
| import ExifReader from "exifreader" | ||||
| import { Utils } from "../../Utils" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * The ImageUploadManager has a | ||||
|  | @ -176,7 +177,7 @@ export class ImageUploadManager { | |||
|             const failed: Set<ImageUploadArguments> = new Set() | ||||
|             this.uploadingAll = true | ||||
|             do { | ||||
|                 queue = Utils.NoNull(this._queue.imagesInQueue.data ?? []).filter( | ||||
|                 queue = Lists.noNull(this._queue.imagesInQueue.data ?? []).filter( | ||||
|                     (item) => !failed.has(item) | ||||
|                 ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import { Feature, Point } from "geojson" | |||
| import { Store, UIEventSource } from "../UIEventSource" | ||||
| import { ServerSourceInfo } from "../../Models/SourceOverview" | ||||
| import { ComponentType } from "svelte/types/runtime/internal/dev" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export class Mapillary extends ImageProvider { | ||||
|     public static readonly singleton = new Mapillary() | ||||
|  | @ -74,11 +75,9 @@ export class Mapillary extends ImageProvider { | |||
|             pKey, | ||||
|         } | ||||
|         const baselink = `https://www.mapillary.com/app/?` | ||||
|         const paramsStr = Utils.NoNull( | ||||
|             Object.keys(params).map((k) => | ||||
|                 params[k] === undefined ? undefined : k + "=" + params[k] | ||||
|             ) | ||||
|         ) | ||||
|         const paramsStr = Lists.noNull(Object.keys(params).map((k) => | ||||
|           params[k] === undefined ? undefined : k + "=" + params[k] | ||||
|         )) | ||||
|         return baselink + paramsStr.join("&") | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import { Utils } from "../../Utils" | |||
| import { Feature, Point } from "geojson" | ||||
| import { ServerSourceInfo } from "../../Models/SourceOverview" | ||||
| import { ComponentType } from "svelte/types/runtime/internal/dev" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export class WikidataImageProvider extends ImageProvider { | ||||
|     public static readonly singleton = new WikidataImageProvider() | ||||
|  | @ -13,7 +14,7 @@ export class WikidataImageProvider extends ImageProvider { | |||
|     public readonly name = "Wikidata" | ||||
|     private static readonly keyBlacklist: ReadonlySet<string> = new Set([ | ||||
|         "mapillary", | ||||
|         ...Utils.Times((i) => "mapillary:" + i, 10), | ||||
|         ...Utils.times((i) => "mapillary:" + i, 10), | ||||
|     ]) | ||||
| 
 | ||||
|     private constructor() { | ||||
|  | @ -60,7 +61,7 @@ export class WikidataImageProvider extends ImageProvider { | |||
|             const promises = WikimediaImageProvider.singleton.ExtractUrls(undefined, commons) | ||||
|             allImages.push(promises) | ||||
|         } | ||||
|         const resolved = await Promise.all(Utils.NoNull(allImages)) | ||||
|         const resolved = await Promise.all(Lists.noNull(allImages)) | ||||
|         const flattened = resolved.flatMap((x) => x) | ||||
|         if (flattened.length === 1) { | ||||
|             flattened[0].originalAttribute = { key, value } | ||||
|  |  | |||
|  | @ -7,8 +7,8 @@ import { TagsFilter } from "../../Tags/TagsFilter" | |||
| import { And } from "../../Tags/And" | ||||
| import { Tag } from "../../Tags/Tag" | ||||
| import { OsmId } from "../../../Models/OsmFeature" | ||||
| import { Utils } from "../../../Utils" | ||||
| import OsmObjectDownloader from "../OsmObjectDownloader" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export default class DeleteAction extends OsmChangeAction { | ||||
|     private readonly _softDeletionTags: TagsFilter | ||||
|  | @ -50,12 +50,12 @@ export default class DeleteAction extends OsmChangeAction { | |||
|             this._softDeletionTags = softDeletionTags | ||||
|         } else { | ||||
|             this._softDeletionTags = new And( | ||||
|                 Utils.NoNull([ | ||||
|                     softDeletionTags, | ||||
|                     new Tag( | ||||
|                         "fixme", | ||||
|                         `A mapcomplete user marked this feature to be deleted (${meta.specialMotivation})` | ||||
|                     ), | ||||
|                 Lists.noNull([ | ||||
|                   softDeletionTags, | ||||
|                   new Tag( | ||||
|                     "fixme", | ||||
|                     `A mapcomplete user marked this feature to be deleted (${meta.specialMotivation})` | ||||
|                   ), | ||||
|                 ]) | ||||
|             ) | ||||
|         } | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import { Utils } from "../../../Utils" | |||
| import { OsmConnection } from "../OsmConnection" | ||||
| import { Feature, Geometry, LineString, Point } from "geojson" | ||||
| import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export default class ReplaceGeometryAction extends OsmChangeAction implements PreviewableAction { | ||||
|     /** | ||||
|  | @ -164,7 +165,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr | |||
|             preview.push(feature) | ||||
|         }) | ||||
| 
 | ||||
|         return StaticFeatureSource.fromGeojson(Utils.NoNull(preview)) | ||||
|         return StaticFeatureSource.fromGeojson(Lists.noNull(preview)) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -317,7 +318,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr | |||
|                 candidate = undefined | ||||
|                 moveDistance = Infinity | ||||
|                 distances.forEach((distances, nodeId) => { | ||||
|                     const minDist = Math.min(...Utils.NoNull(distances)) | ||||
|                     const minDist = Math.min(...(Lists.noNull(distances))) | ||||
|                     if (moveDistance > minDist) { | ||||
|                         // We have found a candidate to move
 | ||||
|                         candidate = nodeId | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import DeleteAction from "./Actions/DeleteAction" | |||
| import MarkdownUtils from "../../Utils/MarkdownUtils" | ||||
| import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" | ||||
| import { Feature, Point } from "geojson" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * Handles all changes made to OSM. | ||||
|  | @ -260,7 +261,7 @@ export class Changes { | |||
|     } | ||||
| 
 | ||||
|     public static GetNeededIds(changes: ChangeDescription[]) { | ||||
|         return Utils.Dedup(changes.filter((c) => c.id >= 0).map((c) => c.type + "/" + c.id)) | ||||
|         return Lists.dedup(changes.filter((c) => c.id >= 0).map((c) => c.type + "/" + c.id)) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -467,8 +468,8 @@ export class Changes { | |||
|             if (change.changes !== undefined) { | ||||
|                 switch (change.type) { | ||||
|                     case "node": { | ||||
|                         const nlat = Utils.Round7(change.changes.lat) | ||||
|                         const nlon = Utils.Round7(change.changes.lon) | ||||
|                         const nlat = Utils.round7(change.changes.lat) | ||||
|                         const nlon = Utils.round7(change.changes.lon) | ||||
|                         const n = <OsmNode>obj | ||||
|                         if (n.lat !== nlat || n.lon !== nlon) { | ||||
|                             n.lat = nlat | ||||
|  | @ -717,11 +718,9 @@ export class Changes { | |||
|          *  We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes | ||||
|          */ | ||||
|         const downloader = new OsmObjectDownloader(this.backend, undefined) | ||||
|         const osmObjects = Utils.NoNull( | ||||
|             await Promise.all<{ id: string; osmObj: OsmObject | "deleted" }>( | ||||
|                 neededIds.map((id) => this.getOsmObject(id, downloader)) | ||||
|             ) | ||||
|         ) | ||||
|         const osmObjects = Lists.noNull(await Promise.all<{ id: string; osmObj: OsmObject | "deleted" }>( | ||||
|           neededIds.map((id) => this.getOsmObject(id, downloader)) | ||||
|         )) | ||||
| 
 | ||||
|         // Drop changes to deleted items
 | ||||
|         for (const { osmObj, id } of osmObjects) { | ||||
|  | @ -801,7 +800,7 @@ export class Changes { | |||
|                 value: descr.meta.specialMotivation, | ||||
|             })) | ||||
| 
 | ||||
|         const distances = Utils.NoNull(pending.map((descr) => descr.meta.distanceToObject)) | ||||
|         const distances = Lists.noNull(pending.map((descr) => descr.meta.distanceToObject)) | ||||
|         distances.sort((a, b) => a - b) | ||||
|         const perBinCount = Constants.distanceToChangeObjectBins.map(() => 0) | ||||
| 
 | ||||
|  | @ -816,23 +815,21 @@ export class Changes { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const perBinMessage = Utils.NoNull( | ||||
|             perBinCount.map((count, i) => { | ||||
|                 if (count === 0) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 const maxD = maxDistances[i] | ||||
|                 let key = `change_within_${maxD}m` | ||||
|                 if (maxD === Number.MAX_VALUE) { | ||||
|                     key = `change_over_${maxDistances[i - 1]}m` | ||||
|                 } | ||||
|                 return { | ||||
|                     key, | ||||
|                     value: count, | ||||
|                     aggregate: true, | ||||
|                 } | ||||
|             }) | ||||
|         ) | ||||
|         const perBinMessage = Lists.noNull(perBinCount.map((count, i) => { | ||||
|           if (count === 0) { | ||||
|             return undefined | ||||
|           } | ||||
|           const maxD = maxDistances[i] | ||||
|           let key = `change_within_${maxD}m` | ||||
|           if (maxD === Number.MAX_VALUE) { | ||||
|             key = `change_over_${maxDistances[i - 1]}m` | ||||
|           } | ||||
|           return { | ||||
|             key, | ||||
|             value: count, | ||||
|             aggregate: true, | ||||
|           } | ||||
|         })) | ||||
| 
 | ||||
|         // This method is only called with changedescriptions for this theme
 | ||||
|         const theme = pending[0].meta.theme | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import { Utils } from "../../Utils" | |||
| import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" | ||||
| import { AndroidPolyfill } from "../Web/AndroidPolyfill" | ||||
| import ImageUploadQueue from "../ImageProviders/ImageUploadQueue" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export interface ChangesetTag { | ||||
|     key: string | ||||
|  | @ -393,7 +394,7 @@ export class ChangesetHandler { | |||
|     private async UpdateTags(csId: number, tags: ChangesetTag[]) { | ||||
|         tags = ChangesetHandler.removeDuplicateMetaTags(tags) | ||||
| 
 | ||||
|         tags = Utils.NoNull(tags).filter( | ||||
|         tags = Lists.noNull(tags).filter( | ||||
|             (tag) => | ||||
|                 tag.key !== undefined && | ||||
|                 tag.value !== undefined && | ||||
|  |  | |||
|  | @ -150,7 +150,7 @@ export abstract class OsmObject { | |||
|             } | ||||
|             const v = this.tags[key] | ||||
|             if (v !== "" && v !== undefined) { | ||||
|                 tags += `        <tag k="${Utils.EncodeXmlValue(key)}" v="${Utils.EncodeXmlValue( | ||||
|                 tags += `        <tag k="${Utils.encodeXmlValue(key)}" v="${Utils.encodeXmlValue( | ||||
|                     this.tags[key] | ||||
|                 )}"/> | ||||
| ` | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ import { Store, UIEventSource } from "../UIEventSource" | |||
| import { OsmConnection } from "./OsmConnection" | ||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||
| import { Utils } from "../../Utils" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| import OSMAuthInstance = OSMAuth.osmAuth | ||||
| 
 | ||||
| export class OsmPreferences { | ||||
|  | @ -270,7 +271,7 @@ export class OsmPreferences { | |||
|             return | ||||
|         } | ||||
|         // _All_ keys are deleted first, to avoid pending parts
 | ||||
|         const keysToDelete = Utils.Dedup(OsmPreferences.keysStartingWith(this.seenKeys, k)) | ||||
|         const keysToDelete = Lists.dedup(OsmPreferences.keysStartingWith(this.seenKeys, k)) | ||||
|         if (v === null || v === undefined || v === "" || v === "undefined" || v === "null") { | ||||
|             for (const k of keysToDelete) { | ||||
|                 await this.deleteKeyDirectly(k) | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ export default class CombinedSearcher implements GeocodingProvider { | |||
|      * @param providers | ||||
|      */ | ||||
|     constructor(...providers: ReadonlyArray<GeocodingProvider>) { | ||||
|         this._providers = Utils.NoNull(providers) | ||||
|         this._providers = Utils.noNull(providers) | ||||
|         this._providersWithSuggest = this._providers.filter((pr) => pr.suggest !== undefined) | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import GeocodingProvider, { GeocodeResult } from "./GeocodingProvider" | ||||
| import { Utils } from "../../Utils" | ||||
| import { ImmutableStore, Store } from "../UIEventSource" | ||||
| import CoordinateParser from "coordinate-parser" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * A simple search-class which interprets possible locations | ||||
|  | @ -71,13 +71,11 @@ export default class CoordinateSearch implements GeocodingProvider { | |||
|      * results[0] // => {lat: 51.047977, lon: 3.51184, "display_name": "lon: 3.51184, lat: 51.047977",  "category": "coordinate","osm_id": "3.51184/51.047977", "source": "coordinate:latlon"}
 | ||||
|      */ | ||||
|     private directSearch(query: string): GeocodeResult[] { | ||||
|         const matches = Utils.NoNull(CoordinateSearch.latLonRegexes.map((r) => query.match(r))).map( | ||||
|         const matches = Lists.noNull(CoordinateSearch.latLonRegexes.map((r) => query.match(r))).map( | ||||
|             (m) => CoordinateSearch.asResult(m[2], m[1], "latlon") | ||||
|         ) | ||||
| 
 | ||||
|         const matchesLonLat = Utils.NoNull( | ||||
|             CoordinateSearch.lonLatRegexes.map((r) => query.match(r)) | ||||
|         ).map((m) => CoordinateSearch.asResult(m[1], m[2], "lonlat")) | ||||
|         const matchesLonLat = Lists.noNull(CoordinateSearch.lonLatRegexes.map((r) => query.match(r))).map((m) => CoordinateSearch.asResult(m[1], m[2], "lonlat")) | ||||
|         const init = matches.concat(matchesLonLat) | ||||
|         if (init.length > 0) { | ||||
|             return init | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import FilterConfig, { FilterConfigOption } from "../../Models/ThemeConfig/Filte | |||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
| import LayerState from "../State/LayerState" | ||||
| import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export type FilterSearchResult = { | ||||
|     option: FilterConfigOption | ||||
|  | @ -64,7 +65,7 @@ export default class FilterSearch { | |||
|                     ].flatMap((term) => [term, ...(term?.split(" ") ?? [])]) | ||||
|                     terms = terms.map((t) => Utils.simplifyStringForSearch(t)) | ||||
|                     terms.push(option.emoji) | ||||
|                     Utils.NoNullInplace(terms) | ||||
|                     Lists.noNullInplace(terms) | ||||
|                     const distances = queries.flatMap((query) => | ||||
|                         terms.map((entry) => { | ||||
|                             const d = Utils.levenshteinDistance(query, entry.slice(0, query.length)) | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { Feature } from "geojson" | |||
| import { GeoOperations } from "../GeoOperations" | ||||
| import { ImmutableStore, Store, Stores } from "../UIEventSource" | ||||
| import OpenStreetMapIdSearch from "./OpenStreetMapIdSearch" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| type IntermediateResult = { | ||||
|     feature: Feature | ||||
|  | @ -41,13 +42,13 @@ export default class LocalElementSearch implements GeocodingProvider { | |||
| 
 | ||||
|         for (const feature of features) { | ||||
|             const props = feature.properties | ||||
|             const searchTerms: string[] = Utils.NoNull([ | ||||
|                 props.name, | ||||
|                 props.alt_name, | ||||
|                 props.local_name, | ||||
|                 props["addr:street"] && props["addr:number"] | ||||
|                     ? props["addr:street"] + props["addr:number"] | ||||
|                     : undefined, | ||||
|             const searchTerms: string[] = Lists.noNull([ | ||||
|               props.name, | ||||
|               props.alt_name, | ||||
|               props.local_name, | ||||
|               props["addr:street"] && props["addr:number"] | ||||
|                 ? props["addr:street"] + props["addr:number"] | ||||
|                 : undefined, | ||||
|             ]) | ||||
| 
 | ||||
|             let levehnsteinD: number | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import { Store, UIEventSource } from "../UIEventSource" | ||||
| import GeocodingProvider, { GeocodeResult, GeocodingOptions } from "./GeocodingProvider" | ||||
| import { OsmId } from "../../Models/OsmFeature" | ||||
| import { Utils } from "../../Utils" | ||||
| import OsmObjectDownloader from "../Osm/OsmObjectDownloader" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export default class OpenStreetMapIdSearch implements GeocodingProvider { | ||||
|     private static readonly regex = | ||||
|  | @ -76,7 +76,7 @@ export default class OpenStreetMapIdSearch implements GeocodingProvider { | |||
|     async search(query: string, _: GeocodingOptions): Promise<GeocodeResult[]> { | ||||
|         if (!isNaN(Number(query))) { | ||||
|             const n = Number(query) | ||||
|             return Utils.NoNullInplace( | ||||
|             return Lists.noNullInplace( | ||||
|                 await Promise.all([ | ||||
|                     this.getInfoAbout(`node/${n}`).catch(() => undefined), | ||||
|                     this.getInfoAbout(`way/${n}`).catch(() => undefined), | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import Locale from "../../UI/i18n/Locale" | ||||
| import { Utils } from "../../Utils" | ||||
| import ThemeSearch from "./ThemeSearch" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export default class SearchUtils { | ||||
|     /** Applies special search terms, such as 'studio', 'osmcha', ... | ||||
|  | @ -66,7 +67,7 @@ export default class SearchUtils { | |||
|         } else { | ||||
|             terms = (keywords[language] ?? []).concat(keywords["*"]) | ||||
|         } | ||||
|         const termsAll = Utils.NoNullInplace(terms).flatMap((t) => t.split(" ")) | ||||
|         const termsAll = Lists.noNullInplace(terms).flatMap((t) => t.split(" ")) | ||||
| 
 | ||||
|         let distanceSummed = 0 | ||||
|         for (let i = 0; i < queryParts.length; i++) { | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import Fuse, { IFuseOptions } from "fuse.js" | |||
| import Constants from "../../Models/Constants" | ||||
| import Locale from "../../UI/i18n/Locale" | ||||
| import { Utils } from "../../Utils" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export class ThemeSearchIndex { | ||||
|     private readonly themeIndex: Fuse<MinimalThemeInformation> | ||||
|  | @ -18,7 +19,7 @@ export class ThemeSearchIndex { | |||
|         themesToSearch?: MinimalThemeInformation[], | ||||
|         layersToIgnore: string[] = [] | ||||
|     ) { | ||||
|         const themes = Utils.NoNull(themesToSearch ?? ThemeSearch.officialThemes?.themes) | ||||
|         const themes = Utils.noNull(themesToSearch ?? ThemeSearch.officialThemes?.themes) | ||||
|         if (!themes) { | ||||
|             throw "No themes loaded. Did generate:layeroverview fail?" | ||||
|         } | ||||
|  | @ -100,7 +101,7 @@ export class ThemeSearchIndex { | |||
|         const knownHidden: Store<string[]> = Stores.listStabilized( | ||||
|             UserRelatedState.initDiscoveredHiddenThemes(state.osmConnection) | ||||
|                 .stabilized(1000) | ||||
|                 .map((list) => Utils.Dedup(list)) | ||||
|                 .map((list) => Lists.dedup(list)) | ||||
|         ) | ||||
|         const otherThemes: MinimalThemeInformation[] = ThemeSearch.officialThemes.themes.filter( | ||||
|             (th) => th.id !== state.theme.id | ||||
|  |  | |||
|  | @ -18,8 +18,8 @@ import { Feature } from "geojson" | |||
| import OpenLocationCodeSearch from "../Search/OpenLocationCodeSearch" | ||||
| import { BBox } from "../BBox" | ||||
| import { QueryParameters } from "../Web/QueryParameters" | ||||
| import { Utils } from "../../Utils" | ||||
| import { NominatimGeocoding } from "../Search/NominatimGeocoding" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export default class SearchState { | ||||
|     public readonly feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined) | ||||
|  | @ -83,7 +83,7 @@ export default class SearchState { | |||
|                         } | ||||
|                     }))) | ||||
|         this.runningEngines = isRunningPerEngine.bindD( | ||||
|             listOfSources => Stores.concat(listOfSources).mapD(list => Utils.NoNull(list))) | ||||
|             listOfSources => Stores.concat(listOfSources).mapD(list => Lists.noNull(list))) | ||||
| 
 | ||||
| 
 | ||||
|         this.failedEngines = suggestionsListWithSource | ||||
|  | @ -102,7 +102,7 @@ export default class SearchState { | |||
|                             return [] | ||||
|                         } | ||||
|                     }), | ||||
|                 ))).map(list => Utils.NoNull(list?.flatMap(x => x) ?? [])) | ||||
|                 ))).map(list => Lists.noNull(list?.flatMap(x => x) ?? [])) | ||||
| 
 | ||||
|         this.suggestionsSearchRunning = this.runningEngines.map(running => running?.length > 0) | ||||
|         this.suggestions = suggestionsList.bindD((suggestions) => | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import Showdown from "showdown" | |||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||
| import { GeocodeResult } from "../Search/GeocodingProvider" | ||||
| import Translations from "../../UI/i18n/Translations" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| class RoundRobinStore<T> { | ||||
|     private readonly _store: UIEventSource<T[]> | ||||
|  | @ -41,7 +42,7 @@ class RoundRobinStore<T> { | |||
|     private set() { | ||||
|         const v = this._store.data | ||||
|         const i = this._index.data | ||||
|         const newList = Utils.NoNull(v.slice(i + 1, v.length).concat(v.slice(0, i + 1))) | ||||
|         const newList = Lists.noNull(v.slice(i + 1, v.length).concat(v.slice(0, i + 1))) | ||||
|         if (newList.length === 0) { | ||||
|             this._index.set(0) | ||||
|         } | ||||
|  | @ -89,7 +90,7 @@ export class OptionallySyncedHistory<T extends object | string> { | |||
|             defaultValue: "sync", | ||||
|         }) | ||||
| 
 | ||||
|         this.syncedBackingStore = UIEventSource.concat(Utils.TimesT(maxHistory, (i) => { | ||||
|         this.syncedBackingStore = UIEventSource.concat(Utils.timesT(maxHistory, (i) => { | ||||
|             const pref = osmconnection.getPreference(key + "-hist-" + i + "-") | ||||
|             return UIEventSource.asObject<T>(pref, undefined) | ||||
|         })) | ||||
|  | @ -555,27 +556,25 @@ export default class UserRelatedState { | |||
| 
 | ||||
|                     const untranslated = missing.untranslated.get(language) ?? [] | ||||
|                     const hasMissingTheme = untranslated.some((k) => k.startsWith("themes:")) | ||||
|                     const missingLayers = Utils.Dedup( | ||||
|                         untranslated | ||||
|                             .filter((k) => k.startsWith("layers:")) | ||||
|                             .map((k) => k.slice("layers:".length).split(".")[0]) | ||||
|                     ) | ||||
|                     const missingLayers = Lists.dedup(untranslated | ||||
|                         .filter((k) => k.startsWith("layers:")) | ||||
|                         .map((k) => k.slice("layers:".length).split(".")[0])) | ||||
| 
 | ||||
|                     const zenLinks: { link: string; id: string }[] = Utils.NoNull([ | ||||
|                         hasMissingTheme | ||||
|                             ? { | ||||
|                                   id: "theme:" + layout.id, | ||||
|                                   link: Translations.hrefToWeblateZen( | ||||
|                                       language, | ||||
|                                       "themes", | ||||
|                                       layout.id | ||||
|                                   ), | ||||
|                               } | ||||
|                             : undefined, | ||||
|                         ...missingLayers.map((id) => ({ | ||||
|                             id: "layer:" + id, | ||||
|                             link: Translations.hrefToWeblateZen(language, "layers", id), | ||||
|                         })), | ||||
|                     const zenLinks: { link: string; id: string }[] = Lists.noNull([ | ||||
|                       hasMissingTheme | ||||
|                         ? { | ||||
|                           id: "theme:" + layout.id, | ||||
|                           link: Translations.hrefToWeblateZen( | ||||
|                             language, | ||||
|                             "themes", | ||||
|                             layout.id | ||||
|                           ), | ||||
|                         } | ||||
|                         : undefined, | ||||
|                       ...missingLayers.map((id) => ({ | ||||
|                         id: "layer:" + id, | ||||
|                         link: Translations.hrefToWeblateZen(language, "layers", id), | ||||
|                       })), | ||||
|                     ]) | ||||
|                     const untranslated_count = untranslated.length | ||||
|                     amendedPrefs.data["_translation_total"] = "" + total | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import key_counts from "../../assets/key_totals.json" | |||
| 
 | ||||
| import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext" | ||||
| import { FlatTag, TagsFilterClosed, UploadableTag } from "./TagTypes" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| type Tags = Record<string, string> | ||||
| 
 | ||||
|  | @ -383,7 +384,7 @@ export class TagUtils { | |||
|         const keyValues = TagUtils.SplitKeys(tagsFilters) | ||||
|         const and: UploadableTag[] = [] | ||||
|         for (const key in keyValues) { | ||||
|             const values = Utils.Dedup(keyValues[key]).filter((v) => v !== "") | ||||
|             const values = Lists.dedup(keyValues[key]).filter((v) => v !== "") | ||||
|             values.sort() | ||||
|             and.push(new Tag(key, values.join(";"))) | ||||
|         } | ||||
|  | @ -742,7 +743,7 @@ export class TagUtils { | |||
|         if (level === undefined || level === null) { | ||||
|             return [] | ||||
|         } | ||||
|         let spec = Utils.NoNull([level]) | ||||
|         let spec = Lists.noNull([level]) | ||||
|         spec = [].concat(...spec.map((s) => s?.split(";"))) | ||||
|         spec = [].concat( | ||||
|             ...spec.map((s) => { | ||||
|  | @ -764,7 +765,7 @@ export class TagUtils { | |||
|                 return values | ||||
|             }) | ||||
|         ) | ||||
|         return Utils.NoNull(spec) | ||||
|         return Lists.noNull(spec) | ||||
|     } | ||||
| 
 | ||||
|     private static ParseTagUnsafe(json: TagConfigJson, context: string = ""): TagsFilterClosed { | ||||
|  | @ -919,10 +920,10 @@ export class TagUtils { | |||
| 
 | ||||
|     public static GetPopularity(tag: TagsFilter): number | undefined { | ||||
|         if (tag instanceof And) { | ||||
|             return Math.min(...Utils.NoNull(tag.and.map((t) => TagUtils.GetPopularity(t)))) - 1 | ||||
|             return Math.min(...(Lists.noNull(tag.and.map((t) => TagUtils.GetPopularity(t))))) - 1 | ||||
|         } | ||||
|         if (tag instanceof Or) { | ||||
|             return Math.max(...Utils.NoNull(tag.or.map((t) => TagUtils.GetPopularity(t)))) + 1 | ||||
|             return Math.max(...(Lists.noNull(tag.or.map((t) => TagUtils.GetPopularity(t))))) + 1 | ||||
|         } | ||||
|         if (tag instanceof Tag) { | ||||
|             return TagUtils.GetCount(tag.key, tag.value) | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import { RegexTag } from "../Tags/RegexTag" | |||
| import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" | ||||
| import { TagUtils } from "../Tags/TagUtils" | ||||
| import Constants from "../../Models/Constants" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * Main name suggestion index file | ||||
|  | @ -103,7 +104,7 @@ export default class NameSuggestionIndex { | |||
|                 } | ||||
|             }) | ||||
|         ) | ||||
|         stats = Utils.NoNull(stats) | ||||
|         stats = Lists.noNull(stats) | ||||
|         if (stats.length === 1) { | ||||
|             return stats[0] | ||||
|         } | ||||
|  | @ -282,7 +283,7 @@ export default class NameSuggestionIndex { | |||
|         } | ||||
|         const keys = Object.keys(this.nsiFile.nsi) | ||||
|         const all = keys.map((k) => this.nsiFile.nsi[k].properties.path.split("/")[0]) | ||||
|         this._supportedTypes = Utils.Dedup(all).map((s) => { | ||||
|         this._supportedTypes = Lists.dedup(all).map((s) => { | ||||
|             if (s.endsWith("s")) { | ||||
|                 s = s.substring(0, s.length - 1) | ||||
|             } | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ import { Utils } from "../../Utils" | |||
| import { Store, UIEventSource } from "../UIEventSource" | ||||
| import { SimplifiedClaims, WBK } from "wikibase-sdk" | ||||
| import { ServerSourceInfo } from "../../Models/SourceOverview" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export class WikidataResponse { | ||||
|     public readonly id: string | ||||
|  | @ -294,7 +295,7 @@ export default class Wikidata { | |||
|                 } | ||||
|             }) | ||||
|         ) | ||||
|         return Utils.NoNull(maybeResponses.map((r) => <WikidataResponse>r["success"])) | ||||
|         return Lists.noNull(maybeResponses.map((r) => <WikidataResponse>r["success"])) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import { Utils } from "../Utils" | |||
| import { TagUtils } from "../Logic/Tags/TagUtils" | ||||
| import { And } from "../Logic/Tags/And" | ||||
| import { GlobalFilter } from "./GlobalFilter" | ||||
| import { Lists } from "../Utils/Lists" | ||||
| 
 | ||||
| export default class FilteredLayer { | ||||
|     /** | ||||
|  | @ -287,7 +288,7 @@ export default class FilteredLayer { | |||
|             } | ||||
|             needed.push(filter.options[state.data].osmTags) | ||||
|         } | ||||
|         needed = Utils.NoNull(needed) | ||||
|         needed = Lists.noNull(needed) | ||||
|         if (needed.length == 0) { | ||||
|             return undefined | ||||
|         } | ||||
|  |  | |||
|  | @ -11,9 +11,10 @@ import { ThemeConfigJson } from "../../src/Models/ThemeConfig/Json/ThemeConfigJs | |||
| import SpecialVisualizations from "../../src/UI/SpecialVisualizations" | ||||
| import ValidationUtils from "../../src/Models/ThemeConfig/Conversion/ValidationUtils" | ||||
| import { | ||||
|     QuestionableTagRenderingConfigJson | ||||
|     QuestionableTagRenderingConfigJson, | ||||
| } from "../../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||
| import { LayerConfigJson } from "../../src/Models/ThemeConfig/Json/LayerConfigJson" | ||||
| import { Lists } from "../Utils/Lists" | ||||
| 
 | ||||
| export interface ServerSourceInfo { | ||||
|     url: string | ||||
|  | @ -81,7 +82,7 @@ export class SourceOverview { | |||
| 
 | ||||
|         const geojsonSources: string[] = layout?.layers?.map((l) => l.source?.geojsonSource) ?? [] | ||||
| 
 | ||||
|         return Utils.NoNull(apiUrls.concat(...geojsonSources)).filter((item) => { | ||||
|         return Lists.noNull(apiUrls.concat(...geojsonSources)).filter((item) => { | ||||
|             if (typeof item === "string") { | ||||
|                 return true | ||||
|             } | ||||
|  | @ -117,7 +118,7 @@ export class SourceOverview { | |||
|                         "Background layer source or supporting sources for " + f.properties.id, | ||||
|                     trigger: ["specific_feature"], | ||||
|                     category: "maplayer", | ||||
|                     moreInfo: Utils.NoEmpty([ | ||||
|                     moreInfo: Lists.noEmpty([ | ||||
|                         "https://github.com/osmlab/editor-layer-index", | ||||
|                         f.properties?.attribution?.url, | ||||
|                     ]), | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import { | |||
|     QuestionableTagRenderingConfigJson, | ||||
| } from "../Json/QuestionableTagRenderingConfigJson" | ||||
| import { Utils } from "../../../Utils" | ||||
| import { ConversionContext } from "./ConversionContext" | ||||
| 
 | ||||
| export default class AddPrefixToTagRenderingConfig extends DesugaringStep<QuestionableTagRenderingConfigJson> { | ||||
|     private readonly _prefix: string | ||||
|  | @ -142,7 +143,8 @@ export default class AddPrefixToTagRenderingConfig extends DesugaringStep<Questi | |||
|     } | ||||
| 
 | ||||
|     public convert( | ||||
|         json: Readonly<QuestionableTagRenderingConfigJson> | ||||
|         json: Readonly<QuestionableTagRenderingConfigJson>, | ||||
|         context: ConversionContext | ||||
|     ): QuestionableTagRenderingConfigJson { | ||||
|         let freeform = json.freeform | ||||
|         if (freeform) { | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||
| import { Utils } from "../../../Utils" | ||||
| import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" | ||||
| import { ConversionContext } from "./ConversionContext" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export interface DesugaringContext { | ||||
|     tagRenderings: Map<string, QuestionableTagRenderingConfigJson> | ||||
|  | @ -219,7 +219,7 @@ export class Concat<X, T> extends Conversion<X[], T[]> { | |||
|             return <undefined | null>values | ||||
|         } | ||||
|         const vals: T[][] = new Each(this._step).convert(values, context.inOperation("concat")) | ||||
|         return [].concat(...vals) | ||||
|         return vals.flatMap(l => l) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -264,7 +264,6 @@ export class Cached<TIn, TOut> extends Conversion<TIn, TOut> { | |||
| } | ||||
| 
 | ||||
| export class Fuse<T> extends DesugaringStep<T> { | ||||
|     protected debug = false | ||||
|     private readonly steps: DesugaringStep<T>[] | ||||
| 
 | ||||
|     constructor(doc: string, ...steps: DesugaringStep<T>[]) { | ||||
|  | @ -274,18 +273,12 @@ export class Fuse<T> extends DesugaringStep<T> { | |||
|                 "This fused pipeline of the following steps: " + | ||||
|                 steps.map((s) => s.name).join(", ") | ||||
|         ) | ||||
|         this.steps = Utils.NoNull(steps) | ||||
|         this.steps = Lists.noNull(steps) | ||||
|     } | ||||
| 
 | ||||
|     public enableDebugging(): Fuse<T> { | ||||
|         this.debug = true | ||||
|         return this | ||||
|     } | ||||
| 
 | ||||
|     convert(json: T, context: ConversionContext): T { | ||||
|         const timings = [] | ||||
|         for (let i = 0; i < this.steps.length; i++) { | ||||
|             const start = new Date() | ||||
|             const step = this.steps[i] | ||||
|             try { | ||||
|                 const r = step.convert(json, context.inOperation(step.name)) | ||||
|  | @ -297,14 +290,6 @@ export class Fuse<T> extends DesugaringStep<T> { | |||
|                 console.error("Step " + step.name + " failed due to ", e, e.stack) | ||||
|                 throw e | ||||
|             } | ||||
|             if (this.debug) { | ||||
|                 const stop = new Date() | ||||
|                 const timeNeededMs = stop.getTime() - start.getTime() | ||||
|                 timings.push(timeNeededMs) | ||||
|             } | ||||
|         } | ||||
|         if (this.debug) { | ||||
|             console.log("Time needed,", timings.join(", ")) | ||||
|         } | ||||
|         return json | ||||
|     } | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import { DesugaringStep } from "./Conversion" | ||||
| import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" | ||||
| import { ConversionContext } from "./ConversionContext" | ||||
| import { Utils } from "../../../Utils" | ||||
| import Translations from "../../../UI/i18n/Translations" | ||||
| import { DoesImageExist } from "./Validation" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJson> { | ||||
|     private readonly _doesImageExist: DoesImageExist | ||||
|  | @ -45,7 +45,7 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ | |||
|         for (let i = 0; i < json.mappings.length; i++) { | ||||
|             const mapping = json.mappings[i] | ||||
|             const ignore = mapping["#"]?.indexOf(ignoreToken) >= 0 | ||||
|             const images = Utils.Dedup(Translations.T(mapping.then)?.ExtractImages() ?? []) | ||||
|             const images = Lists.dedup(Translations.T(mapping.then)?.ExtractImages() ?? []) | ||||
|             const ctx = context.enters("mappings", i) | ||||
|             if (images.length > 0) { | ||||
|                 if (!ignore) { | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import Translations from "../../../UI/i18n/Translations" | |||
| import { FlatTag, OptimizedTag, TagsFilterClosed } from "../../../Logic/Tags/TagTypes" | ||||
| import { TagsFilter } from "../../../Logic/Tags/TagsFilter" | ||||
| import { Translation } from "../../../UI/i18n/Translation" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export class PruneFilters extends DesugaringStep<LayerConfigJson> { | ||||
|     constructor() { | ||||
|  | @ -107,9 +108,7 @@ export class PruneFilters extends DesugaringStep<LayerConfigJson> { | |||
|         const sourceTags = TagUtils.Tag(json.source["osmTags"]) | ||||
|         return { | ||||
|             ...json, | ||||
|             filter: Utils.NoNull( | ||||
|                 json.filter?.map((obj) => this.prune(sourceTags, <FilterConfigJson>obj, context)) | ||||
|             ), | ||||
|             filter: Lists.noNull(json.filter?.map((obj) => this.prune(sourceTags, <FilterConfigJson>obj, context))), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import { Utils } from "../../../Utils" | |||
| import { AddContextToTranslations } from "./AddContextToTranslations" | ||||
| import AddPrefixToTagRenderingConfig from "./AddPrefixToTagRenderingConfig" | ||||
| import { Translatable } from "../Json/Translatable" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export class ExpandTagRendering extends Conversion< | ||||
|     | string | ||||
|  | @ -41,8 +42,7 @@ export class ExpandTagRendering extends Conversion< | |||
|     ) { | ||||
|         super( | ||||
|             "ExpandTagRendering", | ||||
|             "Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question and reusing the builtins", | ||||
|             [] | ||||
|             "Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question and reusing the builtins" | ||||
|         ) | ||||
|         this._state = state | ||||
|         this._self = self | ||||
|  | @ -387,7 +387,7 @@ export class ExpandTagRendering extends Conversion< | |||
|                         if (layer === undefined) { | ||||
|                             const candidates = Utils.sortedByLevenshteinDistance( | ||||
|                                 layerName, | ||||
|                                 Utils.NoNull(Array.from(state.sharedLayers.keys())) | ||||
|                                 Lists.noNull(Array.from(state.sharedLayers.keys())) | ||||
|                             ) | ||||
|                             if (candidates.length === 0) { | ||||
|                                 ctx.err( | ||||
|  | @ -413,7 +413,7 @@ export class ExpandTagRendering extends Conversion< | |||
|                             // We are dealing with a looping import, no error is necessary
 | ||||
|                             continue | ||||
|                         } | ||||
|                         candidates = Utils.NoNull(layer.tagRenderings.map((tr) => tr["id"])).map( | ||||
|                         candidates = Lists.noNull(layer.tagRenderings.map((tr) => tr["id"])).map( | ||||
|                             (id) => layerName + "." + id | ||||
|                         ) | ||||
|                     } | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import Translations from "../../../UI/i18n/Translations" | |||
| 
 | ||||
| import { parse as parse_html } from "node-html-parser" | ||||
| import { ConversionContext } from "./ConversionContext" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export class ExtractImages extends Conversion< | ||||
|     ThemeConfigJson, | ||||
|  | @ -243,16 +244,12 @@ export class ExtractImages extends Conversion< | |||
|             } | ||||
| 
 | ||||
|             // Split "circle:white;./assets/layers/.../something.svg" into ["circle", "./assets/layers/.../something.svg"]
 | ||||
|             const allPaths = Utils.NoNull( | ||||
|                 Utils.NoEmpty( | ||||
|                     foundImage.path?.split(";")?.map((part) => { | ||||
|                         if (part.startsWith("http")) { | ||||
|                             return part | ||||
|                         } | ||||
|                         return part.split(":")[0] | ||||
|                     }) | ||||
|                 ) | ||||
|             ) | ||||
|             const allPaths = Lists.noNull(Lists.noEmpty(foundImage.path?.split(";")?.map((part) => { | ||||
|                 if (part.startsWith("http")) { | ||||
|                     return part | ||||
|                 } | ||||
|                 return part.split(":")[0] | ||||
|             }))) | ||||
|             for (const path of allPaths) { | ||||
|                 cleanedImages.push({ | ||||
|                     path, | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| import { ThemeConfigJson } from "../Json/ThemeConfigJson" | ||||
| import { Utils } from "../../../Utils" | ||||
| import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" | ||||
| import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||
| import { DesugaringStep, Each, Fuse, On } from "./Conversion" | ||||
| import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" | ||||
| import { ConversionContext } from "./ConversionContext" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export class UpdateLegacyLayer extends DesugaringStep< | ||||
|     LayerConfigJson | string | { builtin; override } | ||||
|  | @ -190,7 +190,7 @@ export class UpdateLegacyLayer extends DesugaringStep< | |||
|                     ) { | ||||
|                         iconConfig = iconConfig.render | ||||
|                     } | ||||
|                     const icon = Utils.NoEmpty(iconConfig.split(";")) | ||||
|                     const icon = Lists.noEmpty(iconConfig.split(";")) | ||||
|                     pr.marker = icon.map((i) => { | ||||
|                         if (i.startsWith("http")) { | ||||
|                             return { icon: i } | ||||
|  | @ -281,7 +281,7 @@ class UpdateLegacyTheme extends DesugaringStep<ThemeConfigJson> { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         oldThemeConfig.layers = Utils.NoNull(oldThemeConfig.layers) | ||||
|         oldThemeConfig.layers = Lists.noNull(oldThemeConfig.layers) | ||||
|         delete oldThemeConfig["language"] | ||||
|         delete oldThemeConfig["version"] | ||||
|         delete oldThemeConfig["clustering"] | ||||
|  |  | |||
|  | @ -1,18 +1,6 @@ | |||
| import { | ||||
|     Concat, | ||||
|     DesugaringContext, | ||||
|     DesugaringStep, | ||||
|     Each, | ||||
|     FirstOf, | ||||
|     Fuse, | ||||
|     On, | ||||
|     SetDefault, | ||||
| } from "./Conversion" | ||||
| import { Concat, DesugaringContext, DesugaringStep, Each, FirstOf, Fuse, On, SetDefault } from "./Conversion" | ||||
| import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||
| import { | ||||
|     MinimalTagRenderingConfigJson, | ||||
|     TagRenderingConfigJson, | ||||
| } from "../Json/TagRenderingConfigJson" | ||||
| import { MinimalTagRenderingConfigJson, TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" | ||||
| import { Utils } from "../../../Utils" | ||||
| import RewritableConfigJson from "../Json/RewritableConfigJson" | ||||
| import SpecialVisualizations from "../../../UI/SpecialVisualizations" | ||||
|  | @ -33,6 +21,7 @@ import { TagUtils } from "../../../Logic/Tags/TagUtils" | |||
| import { ExpandFilter, PruneFilters } from "./ExpandFilter" | ||||
| import { ExpandTagRendering } from "./ExpandTagRendering" | ||||
| import layerconfig from "../../../assets/schemas/layerconfigmeta.json" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| class AddFiltersFromTagRenderings extends DesugaringStep<LayerConfigJson> { | ||||
|     constructor() { | ||||
|  | @ -360,7 +349,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> { | |||
|         const addByDefault = this.builtinQuestions.filter((tr) => tr.labels?.indexOf(key) >= 0) | ||||
|         const ids = new Set(addByDefault.map((tr) => tr.id)) | ||||
|         const idsInOrder = this._desugaring.tagRenderingOrder?.filter((id) => ids.has(id)) ?? [] | ||||
|         return Utils.NoNull(idsInOrder.map((id) => this._desugaring.tagRenderings.get(id))) | ||||
|         return Lists.noNull(idsInOrder.map((id) => this._desugaring.tagRenderings.get(id))) | ||||
|     } | ||||
| 
 | ||||
|     convert(json: LayerConfigJson, _: ConversionContext): LayerConfigJson { | ||||
|  | @ -771,11 +760,11 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> { | |||
|                     const condition = tr.condition | ||||
|                     for (const trElement of tr.mappings) { | ||||
|                         const showIf = TagUtils.optimzeJson({ | ||||
|                             and: Utils.NoNull([ | ||||
|                                 condition, | ||||
|                                 { | ||||
|                                     or: Utils.NoNull([trElement.alsoShowIf, trElement.if]), | ||||
|                                 }, | ||||
|                             and: Lists.noNull([ | ||||
|                               condition, | ||||
|                               { | ||||
|                                 or: Lists.noNull([trElement.alsoShowIf, trElement.if]), | ||||
|                               }, | ||||
|                             ]), | ||||
|                         }) | ||||
|                         if (showIf === true) { | ||||
|  | @ -984,14 +973,12 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> { | |||
| 
 | ||||
|         const allAutoIndex = json.titleIcons.indexOf(<any>"auto:*") | ||||
|         if (allAutoIndex >= 0) { | ||||
|             const generated = Utils.NoNull( | ||||
|                 json.tagRenderings.map((tr) => { | ||||
|                     if (typeof tr === "string") { | ||||
|                         return undefined | ||||
|                     } | ||||
|                     return this.createTitleIconsBasedOn(<any>tr) | ||||
|                 }) | ||||
|             ) | ||||
|             const generated = Lists.noNull(json.tagRenderings.map((tr) => { | ||||
|               if (typeof tr === "string") { | ||||
|                 return undefined | ||||
|               } | ||||
|               return this.createTitleIconsBasedOn(<any>tr) | ||||
|             })) | ||||
|             json.titleIcons.splice(allAutoIndex, 1, ...generated) | ||||
|             return json | ||||
|         } | ||||
|  | @ -1113,9 +1100,7 @@ export class OrderTagRendering extends DesugaringStep<TagRenderingConfigJson | s | |||
| } | ||||
| 
 | ||||
| export class OrderLayer extends DesugaringStep<string | LayerConfigJson> { | ||||
|     private static readonly layerAttributesOrder: ReadonlyArray<string> = Utils.Dedup( | ||||
|         (<ConfigMeta[]>layerconfig).filter((c) => c.path.length === 1).map((c) => c.path[0]) | ||||
|     ) | ||||
|     private static readonly layerAttributesOrder: ReadonlyArray<string> = Lists.dedup((<ConfigMeta[]>layerconfig).filter((c) => c.path.length === 1).map((c) => c.path[0])) | ||||
| 
 | ||||
|     constructor() { | ||||
|         super("OrderLayer", "Reorders a tagRendering to the default order") | ||||
|  | @ -1151,7 +1136,8 @@ export class PrepareLayer extends Fuse<LayerConfigJson> { | |||
|             "Fully prepares and expands a layer for the LayerConfig.", | ||||
|             new DeriveSource(), | ||||
|             new On("tagRenderings", new Each(new RewriteSpecial())), | ||||
|             new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), | ||||
|             new On("tagRenderings", new Concat(new ExpandRewrite()) | ||||
|                 .andThenF(Utils.Flatten)), | ||||
|             new On( | ||||
|                 "tagRenderings", | ||||
|                 (layer) => | ||||
|  |  | |||
|  | @ -1,14 +1,4 @@ | |||
| import { | ||||
|     Concat, | ||||
|     Conversion, | ||||
|     DesugaringContext, | ||||
|     DesugaringStep, | ||||
|     Each, | ||||
|     Fuse, | ||||
|     On, | ||||
|     Pass, | ||||
|     SetDefault, | ||||
| } from "./Conversion" | ||||
| import { Concat, Conversion, DesugaringContext, DesugaringStep, Each, Fuse, On, Pass, SetDefault } from "./Conversion" | ||||
| import { ThemeConfigJson } from "../Json/ThemeConfigJson" | ||||
| import { OrderLayer, PrepareLayer, RewriteSpecial } from "./PrepareLayer" | ||||
| import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||
|  | @ -22,6 +12,7 @@ import ValidationUtils from "./ValidationUtils" | |||
| import { ConversionContext } from "./ConversionContext" | ||||
| import { ConfigMeta } from "../../../UI/Studio/configMeta" | ||||
| import themeconfig from "../../../assets/schemas/layoutconfigmeta.json" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJson[]> { | ||||
|     private readonly _state: DesugaringContext | ||||
|  | @ -198,7 +189,7 @@ export class AddDefaultLayers extends DesugaringStep<ThemeConfigJson> { | |||
| 
 | ||||
|     convert(json: ThemeConfigJson, context: ConversionContext): ThemeConfigJson { | ||||
|         const state = this._state | ||||
|         json.layers = Utils.NoNull([...(json.layers ?? [])]) | ||||
|         json.layers = Lists.noNull([...(json.layers ?? [])]) | ||||
|         const alreadyLoaded = new Set(json.layers.map((l) => l["id"])) | ||||
| 
 | ||||
|         for (const layerName of Constants.added_by_default) { | ||||
|  | @ -612,9 +603,7 @@ class PostvalidateTheme extends DesugaringStep<ThemeConfigJson> { | |||
|     } | ||||
| } | ||||
| export class OrderTheme extends Fuse<ThemeConfigJson> { | ||||
|     private static readonly themeAttributesOrder: ReadonlyArray<string> = Utils.Dedup( | ||||
|         (<ConfigMeta[]>themeconfig).filter((c) => c.path.length === 1).map((c) => c.path[0]) | ||||
|     ) | ||||
|     private static readonly themeAttributesOrder: ReadonlyArray<string> = Lists.dedup((<ConfigMeta[]>themeconfig).filter((c) => c.path.length === 1).map((c) => c.path[0])) | ||||
| 
 | ||||
|     constructor() { | ||||
|         super("Reorders the layer to the default order", | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import { And } from "../../../Logic/Tags/And" | |||
| import { DoesImageExist, ValidateFilter, ValidatePointRendering } from "./Validation" | ||||
| import { ValidateTagRenderings } from "./ValidateTagRenderings" | ||||
| import { TagsFilterClosed } from "../../../Logic/Tags/TagTypes" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | ||||
|     private readonly _isBuiltin: boolean | ||||
|  | @ -207,10 +208,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|         } | ||||
|         { | ||||
|             // duplicate ids in tagrenderings check
 | ||||
|             const duplicates = Utils.NoNull( | ||||
|                 Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) | ||||
|             ) | ||||
|             if (duplicates.length > 0) { | ||||
|             const duplicates = Lists.noNull(Utils.Duplicates(json.tagRenderings?.map((tr) => tr?.["id"]))) | ||||
|             if (duplicates?.length > 0) { | ||||
|                 // It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list
 | ||||
|                 context | ||||
|                     .enter("tagRenderings") | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" | ||||
| import { Utils } from "../../../Utils" | ||||
| import SpecialVisualizations from "../../../UI/SpecialVisualizations" | ||||
| import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization" | ||||
| import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" | ||||
| import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| export default class ValidationUtils { | ||||
|     public static getAllSpecialVisualisations( | ||||
|  | @ -51,9 +51,9 @@ export default class ValidationUtils { | |||
|                 JSON.stringify(renderingConfig.mappings) | ||||
|             ) | ||||
|         } | ||||
|         const translations: any[] = Utils.NoNull([ | ||||
|             renderingConfig.render, | ||||
|             ...(renderingConfig.mappings ?? []).map((m) => m.then), | ||||
|         const translations: any[] = Lists.noNull([ | ||||
|           renderingConfig.render, | ||||
|           ...(renderingConfig.mappings ?? []).map((m) => m.then), | ||||
|         ]) | ||||
|         const all: RenderingSpecification[] = [] | ||||
|         for (let translation of translations) { | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import { Utils } from "../../Utils" | |||
| import { RegexTag } from "../../Logic/Tags/RegexTag" | ||||
| import MarkdownUtils from "../../Utils/MarkdownUtils" | ||||
| import Validators, { ValidatorType } from "../../UI/InputElement/Validators" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export type FilterConfigOption = { | ||||
|     question: Translation | ||||
|  | @ -225,17 +226,17 @@ export default class FilterConfig { | |||
|     public GenerateDocs(): string { | ||||
|         const hasField = this.options.some((opt) => opt.fields?.length > 0) | ||||
|         return MarkdownUtils.table( | ||||
|             Utils.NoNull(["id", "question", "osmTags", hasField ? "fields" : undefined]), | ||||
|             Lists.noNull(["id", "question", "osmTags", hasField ? "fields" : undefined]), | ||||
|             this.options.map((opt, i) => { | ||||
|                 const isDefault = this.options.length > 1 && (this.defaultSelection ?? 0) == i | ||||
|                 return <string[]>( | ||||
|                     Utils.NoNull([ | ||||
|                         this.id + "." + i, | ||||
|                         isDefault ? `*${opt.question.txt}* (default)` : opt.question, | ||||
|                         opt.osmTags?.asHumanString() ?? "", | ||||
|                         opt.fields?.length > 0 | ||||
|                             ? opt.fields.map((f) => f.name + " (" + f.type + ")").join(" ") | ||||
|                             : undefined, | ||||
|                     Lists.noNull([ | ||||
|                       this.id + "." + i, | ||||
|                       isDefault ? `*${opt.question.txt}* (default)` : opt.question, | ||||
|                       opt.osmTags?.asHumanString() ?? "", | ||||
|                       opt.fields?.length > 0 | ||||
|                         ? opt.fields.map((f) => f.name + " (" + f.type + ")").join(" ") | ||||
|                         : undefined, | ||||
|                     ]) | ||||
|                 ) | ||||
|             }) | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ import MarkdownUtils from "../../Utils/MarkdownUtils" | |||
| import { And } from "../../Logic/Tags/And" | ||||
| import OsmWiki from "../../Logic/Osm/OsmWiki" | ||||
| import { UnitUtils } from "../UnitUtils" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export default class LayerConfig extends WithContextLoader { | ||||
|     public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const | ||||
|  | @ -226,7 +227,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|         } | ||||
| 
 | ||||
|         if (json.lineRendering) { | ||||
|             this.lineRendering = Utils.NoNull(json.lineRendering).map( | ||||
|             this.lineRendering = Lists.noNull(json.lineRendering).map( | ||||
|                 (r, i) => new LineRenderingConfig(r, `${context}[${i}]`) | ||||
|             ) | ||||
|         } else { | ||||
|  | @ -234,7 +235,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|         } | ||||
| 
 | ||||
|         if (json.pointRendering) { | ||||
|             this.mapRendering = Utils.NoNull(json.pointRendering).map( | ||||
|             this.mapRendering = Lists.noNull(json.pointRendering).map( | ||||
|                 (r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`) | ||||
|             ) | ||||
|         } else { | ||||
|  | @ -283,7 +284,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|         } | ||||
| 
 | ||||
|         const missingIds = | ||||
|             Utils.NoNull(json.tagRenderings)?.filter( | ||||
|             Lists.noNull(json.tagRenderings)?.filter( | ||||
|                 (tr) => | ||||
|                     typeof tr !== "string" && | ||||
|                     tr["builtin"] === undefined && | ||||
|  | @ -298,7 +299,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|             throw msg | ||||
|         } | ||||
| 
 | ||||
|         this.tagRenderings = (Utils.NoNull(json.tagRenderings) ?? []).map( | ||||
|         this.tagRenderings = (Lists.noNull(json.tagRenderings) ?? []).map( | ||||
|             (tr, i) => | ||||
|                 new TagRenderingConfig( | ||||
|                     <QuestionableTagRenderingConfigJson>tr, | ||||
|  | @ -431,7 +432,7 @@ export default class LayerConfig extends WithContextLoader { | |||
| 
 | ||||
|                     return [ | ||||
|                         `[${tr.id}](#${tr.id}) ${origDef}`, | ||||
|                         Utils.NoNull([q, r, options]).join("<br/>"), | ||||
|                         Lists.noNull([q, r, options]).join("<br/>"), | ||||
|                         tr.labels.join(", "), | ||||
|                         key, | ||||
|                     ] | ||||
|  | @ -560,7 +561,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|             ] | ||||
|         } | ||||
| 
 | ||||
|         for (const revDep of Utils.Dedup(layerIsNeededBy?.get(this.id) ?? [])) { | ||||
|         for (const revDep of Lists.dedup(layerIsNeededBy?.get(this.id) ?? [])) { | ||||
|             extraProps.push( | ||||
|                 ["This layer is needed as dependency for layer", `[${revDep}](#${revDep})`].join( | ||||
|                     " " | ||||
|  | @ -568,32 +569,30 @@ export default class LayerConfig extends WithContextLoader { | |||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         const tableRows: string[][] = Utils.NoNull( | ||||
|             this.tagRenderings | ||||
|                 .map((tr) => tr.FreeformValues()) | ||||
|                 .filter((values) => values !== undefined) | ||||
|                 .filter((values) => values.key !== "id") | ||||
|                 .map((values) => { | ||||
|                     const embedded: string[] = values.values?.map((v) => | ||||
|                         OsmWiki.constructLinkMd(values.key, v) | ||||
|                     ) ?? ["_no preset options defined, or no values in them_"] | ||||
|                     const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent( | ||||
|                         values.key | ||||
|                     )}/` | ||||
|                     const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values` | ||||
|                     return [ | ||||
|                         [ | ||||
|                             `<a target="_blank" href='${tagInfo}'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a>`, | ||||
|                             `<a target="_blank" href='${statistics}'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a>`, | ||||
|                             OsmWiki.constructLinkMd(values.key), | ||||
|                         ].join(" "), | ||||
|                         values.type === undefined | ||||
|                             ? "Multiple choice" | ||||
|                             : `[${values.type}](../SpecialInputElements.md#${values.type})`, | ||||
|                         embedded.join(" "), | ||||
|                     ] | ||||
|                 }) | ||||
|         ) | ||||
|         const tableRows: string[][] = Lists.noNull(this.tagRenderings | ||||
|           .map((tr) => tr.FreeformValues()) | ||||
|           .filter((values) => values !== undefined) | ||||
|           .filter((values) => values.key !== "id") | ||||
|           .map((values) => { | ||||
|             const embedded: string[] = values.values?.map((v) => | ||||
|               OsmWiki.constructLinkMd(values.key, v) | ||||
|             ) ?? ["_no preset options defined, or no values in them_"] | ||||
|             const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent( | ||||
|               values.key | ||||
|             )}/` | ||||
|             const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values` | ||||
|             return [ | ||||
|               [ | ||||
|                 `<a target="_blank" href='${tagInfo}'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a>`, | ||||
|                 `<a target="_blank" href='${statistics}'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a>`, | ||||
|                 OsmWiki.constructLinkMd(values.key), | ||||
|               ].join(" "), | ||||
|               values.type === undefined | ||||
|                 ? "Multiple choice" | ||||
|                 : `[${values.type}](../SpecialInputElements.md#${values.type})`, | ||||
|               embedded.join(" "), | ||||
|             ] | ||||
|           })) | ||||
| 
 | ||||
|         let quickOverview: string[] = [] | ||||
|         if (tableRows.length > 0) { | ||||
|  | @ -693,7 +692,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|     } | ||||
| 
 | ||||
|     AllTagRenderings(): TagRenderingConfig[] { | ||||
|         return Utils.NoNull([...this.tagRenderings, ...this.titleIcons, this.title]) | ||||
|         return Lists.noNull([...this.tagRenderings, ...this.titleIcons, this.title]) | ||||
|     } | ||||
| 
 | ||||
|     public isLeftRightSensitive(): boolean { | ||||
|  |  | |||
|  | @ -5,10 +5,7 @@ import { TagUtils } from "../../Logic/Tags/TagUtils" | |||
| import { And } from "../../Logic/Tags/And" | ||||
| import { Utils } from "../../Utils" | ||||
| import { Tag } from "../../Logic/Tags/Tag" | ||||
| 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" | ||||
|  | @ -21,6 +18,7 @@ import { UploadableTag } from "../../Logic/Tags/TagTypes" | |||
| import LayerConfig from "./LayerConfig" | ||||
| import ComparingTag from "../../Logic/Tags/ComparingTag" | ||||
| import { Unit } from "../Unit" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export interface Mapping { | ||||
|     readonly if: UploadableTag | ||||
|  | @ -317,7 +315,7 @@ export default class TagRenderingConfig { | |||
|                 } | ||||
|                 keys.push(...mapping.if.usedKeys()) | ||||
|             } | ||||
|             keys = Utils.Dedup(keys) | ||||
|             keys = Lists.dedup(keys) | ||||
|             for (let i = 0; i < this.mappings.length; i++) { | ||||
|                 const mapping = this.mappings[i] | ||||
|                 if (mapping.hideInAnswer) { | ||||
|  | @ -352,7 +350,7 @@ export default class TagRenderingConfig { | |||
|                 } | ||||
|                 allKeys = allKeys.concat(mapping.if.usedKeys()) | ||||
|             } | ||||
|             allKeys = Utils.Dedup(allKeys) | ||||
|             allKeys = Lists.dedup(allKeys) | ||||
|             if (allKeys.length > 1 && !allHaveIfNot) { | ||||
|                 throw `${context}: A multi-answer is defined, which generates values over multiple keys. Please define ifnot-tags too on every mapping` | ||||
|             } | ||||
|  | @ -541,20 +539,18 @@ export default class TagRenderingConfig { | |||
|             if?: TagsFilter | ||||
|             then: TypedTranslation<Record<string, string>> | ||||
|             img?: string | ||||
|         }[] = Utils.NoNull( | ||||
|             (this.mappings ?? [])?.filter((mapping) => { | ||||
|                 if (mapping.if === undefined) { | ||||
|                     return true | ||||
|                 } | ||||
|                 if (TagUtils.MatchesMultiAnswer(mapping.if, tags)) { | ||||
|                     return true | ||||
|                 } | ||||
|                 if (mapping.alsoShowIf?.matchesProperties(tags)) { | ||||
|                     return true | ||||
|                 } | ||||
|                 return false | ||||
|             }) | ||||
|         ) | ||||
|         }[] = Lists.noNull((this.mappings ?? [])?.filter((mapping) => { | ||||
|           if (mapping.if === undefined) { | ||||
|             return true | ||||
|           } | ||||
|           if (TagUtils.MatchesMultiAnswer(mapping.if, tags)) { | ||||
|             return true | ||||
|           } | ||||
|           if (mapping.alsoShowIf?.matchesProperties(tags)) { | ||||
|             return true | ||||
|           } | ||||
|           return false | ||||
|         })) | ||||
| 
 | ||||
|         if (freeformKeyDefined && tags[this.freeform.key] !== undefined) { | ||||
|             const usedFreeformValues = new Set<string>( | ||||
|  | @ -659,9 +655,7 @@ export default class TagRenderingConfig { | |||
|             const key = this.freeform?.key | ||||
|             const answerMappings = this.mappings?.filter((m) => m.hideInAnswer !== true) | ||||
|             if (key === undefined) { | ||||
|                 const values: { k: string; v: string }[][] = Utils.NoNull( | ||||
|                     answerMappings?.map((m) => m.if.asChange({})) ?? [] | ||||
|                 ) | ||||
|                 const values: { k: string; v: string }[][] = Lists.noNull(answerMappings?.map((m) => m.if.asChange({})) ?? []) | ||||
|                 if (values.length === 0) { | ||||
|                     return | ||||
|                 } | ||||
|  | @ -677,17 +671,13 @@ export default class TagRenderingConfig { | |||
|                 } | ||||
|                 return { | ||||
|                     key: commonKey, | ||||
|                     values: Utils.NoNull( | ||||
|                         values.map((arr) => arr.filter((item) => item.k === commonKey)[0]?.v) | ||||
|                     ), | ||||
|                     values: Lists.noNull(values.map((arr) => arr.filter((item) => item.k === commonKey)[0]?.v)), | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             let values = Utils.NoNull( | ||||
|                 answerMappings?.map( | ||||
|                     (m) => m.if.asChange({}).filter((item) => item.k === key)[0]?.v | ||||
|                 ) ?? [] | ||||
|             ) | ||||
|             let values = Lists.noNull(answerMappings?.map( | ||||
|               (m) => m.if.asChange({}).filter((item) => item.k === key)[0]?.v | ||||
|             ) ?? []) | ||||
|             if (values.length === undefined) { | ||||
|                 values = undefined | ||||
|             } | ||||
|  | @ -1027,18 +1017,18 @@ export default class TagRenderingConfig { | |||
|             ].join(" ") | ||||
|         } | ||||
| 
 | ||||
|         return Utils.NoNull([ | ||||
|             "### " + this.id, | ||||
|             this.description, | ||||
|             this.question !== undefined | ||||
|                 ? "The question is `" + this.question.txt + "`" | ||||
|                 : "_This tagrendering has no question and is thus read-only_", | ||||
|             freeform, | ||||
|             mappings, | ||||
|             condition, | ||||
|             labels, | ||||
|             "", | ||||
|             reuse, | ||||
|         return Lists.noNull([ | ||||
|           "### " + this.id, | ||||
|           this.description, | ||||
|           this.question !== undefined | ||||
|             ? "The question is `" + this.question.txt + "`" | ||||
|             : "_This tagrendering has no question and is thus read-only_", | ||||
|           freeform, | ||||
|           mappings, | ||||
|           condition, | ||||
|           labels, | ||||
|           "", | ||||
|           reuse, | ||||
|         ]).join("\n") | ||||
|     } | ||||
| 
 | ||||
|  | @ -1061,7 +1051,7 @@ export default class TagRenderingConfig { | |||
|             tags.push(m.ifnot) | ||||
|         } | ||||
| 
 | ||||
|         return Utils.NoNull(tags) | ||||
|         return Lists.noNull(tags) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import { TagsFilter } from "../../Logic/Tags/TagsFilter" | |||
| import TagRenderingConfig from "./TagRenderingConfig" | ||||
| import { TagUtils } from "../../Logic/Tags/TagUtils" | ||||
| import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| /** | ||||
|  * Minimal information about a theme | ||||
|  | @ -381,7 +382,7 @@ export default class ThemeConfig implements ThemeInformation { | |||
|         const usedImages = jsonNoFavourites._usedImages | ||||
|         usedImages.sort() | ||||
| 
 | ||||
|         this.usedImages = Utils.Dedup(usedImages) | ||||
|         this.usedImages = Lists.dedup(usedImages) | ||||
|         return this.usedImages | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -13,7 +13,6 @@ | |||
|   import ThemesList from "./BigComponents/ThemesList.svelte" | ||||
|   import { MinimalThemeInformation } from "../Models/ThemeConfig/ThemeConfig" | ||||
|   import LoginButton from "./Base/LoginButton.svelte" | ||||
|   import { Utils } from "../Utils" | ||||
|   import Searchbar from "./Base/Searchbar.svelte" | ||||
|   import ThemeSearch, { ThemeSearchIndex } from "../Logic/Search/ThemeSearch" | ||||
|   import SearchUtils from "../Logic/Search/SearchUtils" | ||||
|  | @ -26,6 +25,7 @@ | |||
|   import AccordionSingle from "./Flowbite/AccordionSingle.svelte" | ||||
|   import MenuDrawerIndex from "./BigComponents/MenuDrawerIndex.svelte" | ||||
|   import InsetSpacer from "./Base/InsetSpacer.svelte" | ||||
|   import { Lists } from "../Utils/Lists" | ||||
| 
 | ||||
|   AndroidPolyfill.init().then(() => console.log("Android polyfill setup completed")) | ||||
|   const featureSwitches = new OsmConnectionFeatureSwitches() | ||||
|  | @ -80,7 +80,7 @@ | |||
| 
 | ||||
|   const customThemes: Store<MinimalThemeInformation[]> = Stores.listStabilized<string>( | ||||
|     state.installedUserThemes.stabilized(1000) | ||||
|   ).mapD((stableIds) => Utils.NoNullInplace(stableIds.map((id) => state.getUnofficialTheme(id)))) | ||||
|   ).mapD((stableIds) => Lists.noNullInplace(stableIds.map((id) => state.getUnofficialTheme(id)))) | ||||
| 
 | ||||
|   function filtered(themes: Store<MinimalThemeInformation[]>): Store<MinimalThemeInformation[]> { | ||||
|     const searchIndex = Locale.language.map( | ||||
|  |  | |||
|  | @ -3,8 +3,7 @@ import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" | |||
| import { TagUtils } from "../../Logic/Tags/TagUtils" | ||||
| import { OsmFeature } from "../../Models/OsmFeature" | ||||
| import { Utils } from "../../Utils" | ||||
| import { labels } from "wikibase-sdk/dist/src/helpers/simplify" | ||||
| import { isInteger } from "tailwind-merge/dist/lib/validators" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| class ChartJsColours { | ||||
|     public static readonly unknownColor = "rgba(128, 128, 128, 0.2)" | ||||
|  | @ -243,7 +242,7 @@ export class ChartJsUtils { | |||
|             if (options?.period === "month") { | ||||
|                 trimmedDays = trimmedDays.map((d) => d.substring(0, 7)) | ||||
|             } | ||||
|             trimmedDays = Utils.Dedup(trimmedDays) | ||||
|             trimmedDays = Lists.dedup(trimmedDays) | ||||
| 
 | ||||
|             for (let i = 0; i < labels.length; i++) { | ||||
|                 const label = labels[i] | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ export default class Combine extends BaseUIElement { | |||
| 
 | ||||
|     constructor(uiElements: (string | BaseUIElement)[]) { | ||||
|         super() | ||||
|         this.uiElements = Utils.NoNull(uiElements).map((el) => { | ||||
|         this.uiElements = Utils.noNull(uiElements).map((el) => { | ||||
|             if (typeof el === "string") { | ||||
|                 return new FixedUiElement(el) | ||||
|             } | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { Translation } from "../i18n/Translation" | |||
| import Translations from "../i18n/Translations" | ||||
| import MarkdownUtils from "../../Utils/MarkdownUtils" | ||||
| import Locale from "../i18n/Locale" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export default class Hotkeys { | ||||
|     public static readonly _docs: UIEventSource< | ||||
|  | @ -130,7 +131,7 @@ export default class Hotkeys { | |||
|                 ] | ||||
|             }) | ||||
|             .sort() | ||||
|         byKey = Utils.NoNull(byKey) | ||||
|         byKey = Lists.noNull(byKey) | ||||
|         for (let i = byKey.length - 1; i > 0; i--) { | ||||
|             if (byKey[i - 1][0] === byKey[i][0]) { | ||||
|                 byKey.splice(i, 1) | ||||
|  |  | |||
|  | @ -64,7 +64,7 @@ export default class TableOfContents { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const heading = Utils.Times(() => "#", firstTitle.depth) | ||||
|         const heading = Utils.times(() => "#", firstTitle.depth) | ||||
|         toc = heading + " Table of contents\n\n" + toc | ||||
| 
 | ||||
|         const firstTitleIndex = md.indexOf(firstTitle.title) | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
|   import type SmallLicense from "../../Models/smallLicense" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import { twJoin } from "tailwind-merge" | ||||
|   import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
|   export let iconPath: string | ||||
|   export let license: SmallLicense | ||||
|  | @ -10,7 +11,7 @@ | |||
|   } catch (e) { | ||||
|     console.warn(e) | ||||
|   } | ||||
|   let sources = Utils.NoNull(Utils.NoEmpty(license?.sources)) | ||||
|   let sources = Utils.noNull(Lists.noEmpty(license?.sources)) | ||||
| 
 | ||||
|   function sourceName(lnk: string) { | ||||
|     try { | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
|   import type { ChartConfiguration } from "chart.js" | ||||
|   import ChartJs from "../Base/ChartJs.svelte" | ||||
|   import { ChartJsUtils } from "../Base/ChartJsUtils" | ||||
|   import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
|   export let values: Store<string[]> | ||||
|   let counts: Store<Map<string, number>> = values.map( | ||||
|  | @ -11,7 +12,7 @@ | |||
|         return undefined | ||||
|       } | ||||
| 
 | ||||
|       values = Utils.NoNull(values) | ||||
|       values = Utils.noNull(values) | ||||
|       const counts = new Map<string, number>() | ||||
|       for (const value of values) { | ||||
|         const c = counts.get(value) ?? 0 | ||||
|  | @ -23,7 +24,7 @@ | |||
| 
 | ||||
|   let max: Store<number> = counts.mapD(counts => Math.max(...Array.from(counts.values()))) | ||||
|   let keys: Store<string> = counts.mapD(counts => { | ||||
|     const keys = Utils.Dedup(counts.keys()) | ||||
|     const keys = Lists.dedup(counts.keys()) | ||||
|     keys.sort(/*inplace sort*/) | ||||
|     return keys | ||||
|   }) | ||||
|  |  | |||
|  | @ -13,12 +13,12 @@ | |||
|   import SnappingFeatureSource from "../../Logic/FeatureSource/Sources/SnappingFeatureSource" | ||||
|   import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger" | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import Move_arrows from "../../assets/svg/Move_arrows.svelte" | ||||
|   import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||
|   import { Tag } from "../../Logic/Tags/Tag" | ||||
|   import { TagUtils } from "../../Logic/Tags/TagUtils" | ||||
|   import type { WayId } from "../../Models/OsmFeature" | ||||
|   import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
|   /** | ||||
|    * An advanced location input, which has support to: | ||||
|  | @ -114,7 +114,7 @@ | |||
|       }) | ||||
|     } | ||||
|     const snappedLocation = new SnappingFeatureSource( | ||||
|       new FeatureSourceMerger(...Utils.NoNull(snapSources)), | ||||
|       new FeatureSourceMerger(...(Lists.noNull(snapSources))), | ||||
|       // We snap to the (constantly updating) map location | ||||
|       mapProperties.location, | ||||
|       { | ||||
|  |  | |||
|  | @ -10,11 +10,11 @@ | |||
|   import { QueryParameters } from "../../Logic/Web/QueryParameters" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import Qr from "../../Utils/Qr" | ||||
|   import AccordionSingle from "../Flowbite/AccordionSingle.svelte" | ||||
|   import Constants from "../../Models/Constants" | ||||
|   import Copyable from "../Base/Copyable.svelte" | ||||
|   import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
|   export let state: ThemeViewState | ||||
|   const tr = Translations.t.general.sharescreen | ||||
|  | @ -45,7 +45,7 @@ | |||
|     enableGeolocation: boolean | ||||
|   ) { | ||||
|     const layout = state.theme | ||||
|     let excluded = Utils.NoNull([ | ||||
|     let excluded = Lists.noNull([ | ||||
|       showWelcomeMessage ? undefined : "fs-welcome-message", | ||||
|       enableLogin ? undefined : "fs-enable-login", | ||||
|       enableFilters ? undefined : "fs-filter", | ||||
|  | @ -79,7 +79,7 @@ | |||
|       .filter((part) => !part.startsWith("layer-")) | ||||
|       .concat(...layers) | ||||
|       .concat(excluded.map((k) => k + "=" + false)) | ||||
|     linkToShare = baseLink + Utils.Dedup(params).join("&") | ||||
|     linkToShare = baseLink + Lists.dedup(params).join("&") | ||||
| 
 | ||||
|     if (layout.definitionRaw !== undefined) { | ||||
|       linkToShare += "&userlayout=" + (layout.definedAtUrl ?? layout.id) | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ | |||
|       ? "flex flex-wrap items-center justify-center gap-x-2" | ||||
|       : "theme-list my-2 gap-4 md:grid md:grid-flow-row md:grid-cols-2 lg:grid-cols-3"} | ||||
|   > | ||||
|     {#each Utils.DedupOnId(Utils.NoNull(themes)) as theme (theme.id)} | ||||
|     {#each Utils.DedupOnId(Utils.noNull(themes)) as theme (theme.id)} | ||||
|       <ThemeButton {theme} {state} iconOnly={onlyIcons}> | ||||
|         {#if $search && hasSelection && themes?.[0] === theme} | ||||
|           <span class="thanks hidden-on-mobile" aria-hidden="true"> | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ | |||
|     } | ||||
|     return trs.find((tr) => | ||||
|       tr.mappings.some((mapping) => { | ||||
|         const ifTags = Or.construct(Utils.NoNull([mapping.if, mapping.alsoShowIf])) | ||||
|         const ifTags = Or.construct(Lists.noNull([mapping.if, mapping.alsoShowIf])) | ||||
|         const keys = ifTags.usedKeys() | ||||
|         return keys.some((k) => k == key) | ||||
|       }) | ||||
|  |  | |||
|  | @ -3,12 +3,12 @@ | |||
|  */ | ||||
| import { Stores, UIEventSource } from "../../../../Logic/UIEventSource" | ||||
| import SingleCollectionTime from "./SingleCollectionTime.svelte" | ||||
| import { Utils } from "../../../../Utils" | ||||
| import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
| import { Lists } from "../../../../Utils/Lists" | ||||
| 
 | ||||
| export let value: UIEventSource<string> | ||||
| 
 | ||||
| let initialRules: string[]  = Utils.NoEmpty(value.data?.split(";")?.map(v => v.trim())) | ||||
| let initialRules: string[]  = Lists.noEmpty(value.data?.split(";")?.map(v => v.trim())) | ||||
| let singleRules: UIEventSource<UIEventSource<string>[]> = new UIEventSource( | ||||
|   initialRules?.map(v => new UIEventSource(v)) ?? [] | ||||
| ) | ||||
|  | @ -17,7 +17,7 @@ if(singleRules.data.length === 0){ | |||
| } | ||||
| singleRules.bindD(stores => Stores.concat(stores)).addCallbackAndRunD(subrules => { | ||||
|   console.log("Setting subrules", subrules) | ||||
|   value.set(Utils.NoEmpty(subrules).join("; ")) | ||||
|   value.set(Lists.noEmpty(subrules).join("; ")) | ||||
| }) | ||||
| 
 | ||||
| function rm(rule: UIEventSource){ | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| <script lang="ts"> | ||||
| 
 | ||||
|   import PlusCircle from "@rgossiaux/svelte-heroicons/solid/PlusCircle" | ||||
|   import TimeInput from "../TimeInput.svelte" | ||||
|   import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import Checkbox from "../../../Base/Checkbox.svelte" | ||||
|  | @ -8,7 +7,8 @@ | |||
|   import { Stores, UIEventSource } from "../../../../Logic/UIEventSource" | ||||
|   import Translations from "../../../i18n/Translations" | ||||
|   import { OH } from "../../../OpeningHours/OpeningHours" | ||||
|   import { Utils } from "../../../../Utils" | ||||
|   import { Lists } from "../../../../Utils/Lists" | ||||
|   import { Translation } from "../../../i18n/Translation" | ||||
| 
 | ||||
|   export let value: UIEventSource<string> | ||||
| 
 | ||||
|  | @ -18,7 +18,7 @@ | |||
|    */ | ||||
|   let weekdays: Translation[] = [wt.monday, wt.tuesday, wt.wednesday, wt.thursday, wt.friday, wt.saturday, wt.sunday, Translations.t.general.opening_hours.ph] | ||||
| 
 | ||||
|   let initialTimes= Utils.NoEmpty(value.data?.split(" ")?.[1]?.split(",")?.map(s => s.trim())??[]) | ||||
|   let initialTimes= Lists.noEmpty(value.data?.split(" ")?.[1]?.split(",")?.map(s => s.trim()) ?? []) | ||||
|   let values = new UIEventSource(initialTimes.map(t => new UIEventSource(t))) | ||||
|   if(values.data.length === 0){ | ||||
|     values.data.push(new UIEventSource("")) | ||||
|  | @ -29,7 +29,7 @@ | |||
|   let daysOfTheWeek = [...OH.days, "PH"] | ||||
|   let selectedDays = daysOfTheWeek.map(() => new UIEventSource(false)) | ||||
| 
 | ||||
|   let initialDays = Utils.NoEmpty(value.data?.split(" ")?.[0]?.split(",") ?? []) | ||||
|   let initialDays = Lists.noEmpty(value.data?.split(" ")?.[0]?.split(",") ?? []) | ||||
|   for (const initialDay of initialDays) { | ||||
|     if (initialDay.indexOf("-") > 0) { | ||||
|       let [start, end] = initialDay.split("-") | ||||
|  | @ -48,17 +48,17 @@ | |||
|   } | ||||
| 
 | ||||
|   let selectedDaysBound = Stores.concat(selectedDays) | ||||
|     .mapD(days => Utils.NoNull(days.map((selected, i) => selected ? daysOfTheWeek[i] : undefined))) | ||||
|     .mapD(days => Lists.noNull(days.map((selected, i) => selected ? daysOfTheWeek[i] : undefined))) | ||||
|   let valuesConcat: Store<string[]> = values.bindD(values => Stores.concat(values)) | ||||
|     .mapD(values => Utils.NoEmpty(values)) | ||||
|     .mapD(values => Lists.noEmpty(values)) | ||||
|   valuesConcat.mapD(times => { | ||||
|     console.log(times) | ||||
|     times = Utils.NoNull(times) | ||||
|     times = Lists.noNull(times) | ||||
|     if (!times || times?.length === 0) { | ||||
|       return undefined | ||||
|     } | ||||
|     times?.sort(/*concatted, new array*/) | ||||
|     return (Utils.NoEmpty(selectedDaysBound.data).join(",") + " " + times).trim() | ||||
|     return (Lists.noEmpty(selectedDaysBound.data).join(",") + " " + times).trim() | ||||
|   }, [selectedDaysBound]).addCallbackAndRunD(v => value.set(v)) | ||||
| 
 | ||||
|   function selectWeekdays() { | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ | |||
|   let element: HTMLTableElement | ||||
| 
 | ||||
|   function range(n: number) { | ||||
|     return Utils.TimesT(n, (n) => n) | ||||
|     return Utils.timesT(n, (n) => n) | ||||
|   } | ||||
| 
 | ||||
|   function clearSelection() { | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
|   import { Utils } from "../../../Utils" | ||||
|   import WikidataValidator from "../Validators/WikidataValidator" | ||||
|   import Searchbar from "../../Base/Searchbar.svelte" | ||||
|   import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
|   const t = Translations.t.general.wikipedia | ||||
| 
 | ||||
|  | @ -80,7 +81,7 @@ | |||
|         seen.push(item) | ||||
|       } | ||||
|     } | ||||
|     return Utils.NoNull(seen).filter((i) => !knownIds.has(i.id)) | ||||
|     return Lists.noNull(seen).filter((i) => !knownIds.has(i.id)) | ||||
|   }) | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
|   import type { Feature } from "geojson" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import WikidataValidator from "../Validators/WikidataValidator" | ||||
|   import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
|   export let args: (string | number | boolean)[] = [] | ||||
|   export let feature: Feature | ||||
|  | @ -43,12 +44,8 @@ | |||
|     }) | ||||
|   ) | ||||
| 
 | ||||
|   let instanceOf: number[] = Utils.NoNull( | ||||
|     (options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) | ||||
|   ) | ||||
|   let notInstanceOf: number[] = Utils.NoNull( | ||||
|     (options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) | ||||
|   ) | ||||
|   let instanceOf: number[] = Lists.noNull((options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))) | ||||
|   let notInstanceOf: number[] = Lists.noNull((options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))) | ||||
| 
 | ||||
|   let allowMultipleArg = options?.["multiple"] ?? "yes" | ||||
|   let allowMultiple = allowMultipleArg === "yes" || "" + allowMultipleArg === "true" | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ | |||
|       }, | ||||
|       [Stores.chronic(5 * 60 * 1000)] | ||||
|     ) | ||||
|     .mapD((date) => Utils.TwoDigits(date.getHours()) + ":" + Utils.TwoDigits(date.getMinutes())) | ||||
|     .mapD((date) => Utils.twoDigits(date.getHours()) + ":" + Utils.twoDigits(date.getMinutes())) | ||||
| 
 | ||||
|   let size = nextChange.map((change) => | ||||
|     change === undefined ? "absolute h-7 w-7" : "absolute h-5 w-5 top-0 left-1/4" | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ export class OH { | |||
|         if (h == 24) { | ||||
|             return "00:00" | ||||
|         } | ||||
|         return Utils.TwoDigits(h) + ":" + Utils.TwoDigits(m) | ||||
|         return Utils.twoDigits(h) + ":" + Utils.twoDigits(m) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -66,9 +66,7 @@ | |||
|         const feature = state.indexedFeatures.featuresById.data.get(targetFeatureId) | ||||
|         const featureTags = state.featureProperties.getStore(targetFeatureId) | ||||
|         const rendering = tagRenderingConfig.GetRenderValue(featureTags.data).txt | ||||
|         const specialRenderings = Utils.NoNull( | ||||
|           SpecialVisualizations.constructSpecification(rendering) | ||||
|         ).filter((v) => typeof v !== "string" && v.func["supportsAutoAction"] === true) | ||||
|         const specialRenderings = Lists.noNull(SpecialVisualizations.constructSpecification(rendering)).filter((v) => typeof v !== "string" && v.func["supportsAutoAction"] === true) | ||||
| 
 | ||||
|         if (specialRenderings.length == 0) { | ||||
|           console.warn( | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| <script lang="ts"> | ||||
|   import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig" | ||||
|   import { Utils } from "../../../Utils" | ||||
|   import { Translation } from "../../i18n/Translation" | ||||
|   import TagRenderingMapping from "./TagRenderingMapping.svelte" | ||||
|   import type { SpecialVisualizationState } from "../../SpecialVisualization" | ||||
|  | @ -9,6 +8,8 @@ | |||
|   import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | ||||
|   import { twMerge } from "tailwind-merge" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
| 
 | ||||
|   export let tags: UIEventSource<Record<string, string> | undefined> | ||||
| 
 | ||||
|  | @ -27,7 +28,7 @@ | |||
|     throw "Config is undefined in tagRenderingAnswer" | ||||
|   } | ||||
|   let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD((tags) => | ||||
|     Utils.NoNull(config?.GetRenderValues(tags)),onDestroy | ||||
|     Lists.noNull(config?.GetRenderValues(tags)),onDestroy | ||||
|   ) | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -28,7 +28,6 @@ | |||
|   import { And } from "../../../Logic/Tags/And" | ||||
|   import { get } from "svelte/store" | ||||
|   import Markdown from "../../Base/Markdown.svelte" | ||||
|   import { Utils } from "../../../Utils" | ||||
|   import type { UploadableTag } from "../../../Logic/Tags/TagTypes" | ||||
|   import { TagTypes } from "../../../Logic/Tags/TagTypes" | ||||
| 
 | ||||
|  | @ -36,6 +35,7 @@ | |||
|   import If from "../../Base/If.svelte" | ||||
|   import DotMenu from "../../Base/DotMenu.svelte" | ||||
|   import SidebarUnit from "../../Base/SidebarUnit.svelte" | ||||
|   import { Lists } from "../../../Utils/Lists" | ||||
| 
 | ||||
|   export let config: TagRenderingConfig | ||||
|   export let tags: UIEventSource<Record<string, string>> | ||||
|  | @ -152,7 +152,7 @@ | |||
|     feedback.setData(undefined) | ||||
|   } | ||||
| 
 | ||||
|   let usedKeys: string[] = Utils.Dedup(config.usedTags().flatMap((t) => t.usedKeys())) | ||||
|   let usedKeys: string[] = Lists.dedup(config.usedTags().flatMap((t) => t.usedKeys())) | ||||
| 
 | ||||
|   /** | ||||
|    * The 'minimalTags' is a subset of the tags of the feature, only containing the values relevant for this object. | ||||
|  | @ -342,7 +342,7 @@ | |||
|   let menuIsOpened = new UIEventSource(false) | ||||
| 
 | ||||
|   function disableQuestion() { | ||||
|     const newList = Utils.Dedup([config.id, ...disabledInTheme.data]) | ||||
|     const newList = Lists.dedup([config.id, ...disabledInTheme.data]) | ||||
|     disabledInTheme.set(newList) | ||||
|     menuIsOpened.set(false) | ||||
|   } | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ | |||
|       signature: string, | ||||
|       madeByLoggedInUser: Store<boolean> | ||||
|     })[] | ||||
|   > = reviews.reviews.map((r) => Utils.NoNull(r)) | ||||
|   > = reviews.reviews.map((r) => Lists.noNull(r)) | ||||
|   let test = state.featureSwitches.featureSwitchIsTesting | ||||
|   let debug = state.featureSwitches.featureSwitchIsDebugging | ||||
|   let subject: Store<string> = reviews.subjectUri | ||||
|  |  | |||
|  | @ -6,22 +6,19 @@ | |||
|   import ThemeSearch from "../../Logic/Search/ThemeSearch" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
|   import ThemeResult from "./ThemeResult.svelte" | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||
|   import DotMenu from "../Base/DotMenu.svelte" | ||||
|   import { TrashIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import { CogIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import { MinimalThemeInformation } from "../../Models/ThemeConfig/ThemeConfig" | ||||
|   import ThemeViewState from "../../Models/ThemeViewState" | ||||
|   import { WithSearchState } from "../../Models/ThemeViewState/WithSearchState" | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
|   export let state: WithSearchState | ||||
|   let searchTerm = state.searchState.searchTerm | ||||
|   let visitedThemes =  state.userRelatedState.recentlyVisitedThemes.value | ||||
|   let recentThemes: Store<string[]> = visitedThemes.map((themes) => | ||||
|     Utils.Dedup(themes.filter((th) => th !== state.theme.id)).slice(0, 6)) | ||||
|     Lists.dedup(themes.filter((th) => th !== state.theme.id)).slice(0, 6)) | ||||
|   let themeResults = state.searchState.themeSuggestions | ||||
| 
 | ||||
|   const t = Translations.t.general.search | ||||
|  |  | |||
|  | @ -1,8 +1,4 @@ | |||
| import { | ||||
|     SpecialVisualization, | ||||
|     SpecialVisualizationState, | ||||
|     SpecialVisualizationSvelte, | ||||
| } from "../SpecialVisualization" | ||||
| import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" | ||||
| import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
|  | @ -16,6 +12,7 @@ import { Translation } from "../i18n/Translation" | |||
| import { MapillaryLinkVis } from "../Popup/MapillaryLinkVis" | ||||
| import SendEmail from "../Popup/SendEmail.svelte" | ||||
| import DynLink from "../Base/DynLink.svelte" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| class FediverseLinkVis extends SpecialVisualization { | ||||
|     funcName = "fediverse_link" | ||||
|  | @ -88,9 +85,7 @@ class WikidatalabelVis extends SpecialVisualization { | |||
|         const id = tagsSource | ||||
|             .map((tags) => tags[args[0]]) | ||||
|             .map((wikidata) => { | ||||
|                 const wikidataIds = Utils.NoEmpty( | ||||
|                     wikidata?.split(";")?.map((wd) => wd.trim()) ?? [] | ||||
|                 ) | ||||
|                 const wikidataIds = Lists.noEmpty(wikidata?.split(";")?.map((wd) => wd.trim()) ?? []) | ||||
|                 return wikidataIds?.[0] | ||||
|             }) | ||||
|         const entry = id.bind((id) => Wikidata.LoadWikidataEntry(id)) | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ | |||
|     .filter((tr) => tr.question !== undefined) | ||||
| 
 | ||||
|   let diffInDays = overview.mapD((overview) => { | ||||
|     const dateStrings = Utils.NoNull(overview._meta.map((cs) => cs.properties.date)) | ||||
|     const dateStrings = Lists.noNull(overview._meta.map((cs) => cs.properties.date)) | ||||
|     const dates: number[] = dateStrings.map((d) => new Date(d).getTime()) | ||||
|     const mindate = Math.min(...dates) | ||||
|     const maxdate = Math.max(...dates) | ||||
|  |  | |||
|  | @ -82,7 +82,7 @@ export class ChangesetsOverview { | |||
|     public readonly _meta: (ChangeSetData & OsmFeature)[] | ||||
| 
 | ||||
|     private constructor(meta: (ChangeSetData & OsmFeature)[]) { | ||||
|         this._meta = Utils.NoNull(meta) | ||||
|         this._meta = Lists.noNull(meta) | ||||
|     } | ||||
| 
 | ||||
|     public static fromDirtyData(meta: (ChangeSetData & OsmFeature)[]): ChangesetsOverview { | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import Checkbox from "../Base/Checkbox.svelte" | ||||
|   import PlantNet from "../../Logic/Web/PlantNet" | ||||
| 
 | ||||
|   import { Lists } from "../../Utils/Lists" | ||||
|   let services: MCService[] = [] | ||||
| 
 | ||||
|   let recheckSignal: UIEventSource<any> = new UIEventSource<any>(undefined) | ||||
|  | @ -345,7 +345,7 @@ | |||
|   let someLoading = new UIEventSource(true) | ||||
| 
 | ||||
|   function setAll() { | ||||
|     const data = Utils.NoNull(services.map((s) => s.status.data)) | ||||
|     const data = Lists.noNull(services.map((s) => s.status.data)) | ||||
|     someLoading.setData(data.length !== services.length) | ||||
|     if (data.some((d) => d === "offline")) { | ||||
|       all.setData("offline") | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ | |||
|   import DeleteButton from "./DeleteButton.svelte" | ||||
|   import StudioHashSetter from "./StudioHashSetter" | ||||
|   import TitledPanel from "../Base/TitledPanel.svelte" | ||||
|   import Popup from "../Base/Popup.svelte" | ||||
|   import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
|   const layerSchema: ConfigMeta[] = <any>layerSchemaRaw | ||||
| 
 | ||||
|  | @ -36,7 +36,7 @@ | |||
|   ) | ||||
|   const configuration = state.configuration | ||||
| 
 | ||||
|   const allNames = Utils.Dedup(layerSchema.map((meta) => meta.hints.group)) | ||||
|   const allNames = Lists.dedup(layerSchema.map((meta) => meta.hints.group)) | ||||
| 
 | ||||
|   const perRegion: Record<string, ConfigMeta[]> = {} | ||||
|   for (const region of allNames) { | ||||
|  |  | |||
|  | @ -1,12 +1,7 @@ | |||
| import { ConfigMeta } from "./configMeta" | ||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||
| import { | ||||
|     Conversion, | ||||
|     ConversionMessage, | ||||
|     DesugaringContext, | ||||
|     Pipe, | ||||
| } from "../../Models/ThemeConfig/Conversion/Conversion" | ||||
| import { Conversion, ConversionMessage, DesugaringContext, Pipe } from "../../Models/ThemeConfig/Conversion/Conversion" | ||||
| import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer" | ||||
| import { PrevalidateTheme, ValidateLayer } from "../../Models/ThemeConfig/Conversion/Validation" | ||||
| import { AllSharedLayers } from "../../Customizations/AllSharedLayers" | ||||
|  | @ -25,6 +20,7 @@ import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource" | |||
| import { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson" | ||||
| import { ValidateTheme } from "../../Models/ThemeConfig/Conversion/ValidateTheme" | ||||
| import * as questions from "../../../public/assets/generated/layers/questions.json" | ||||
| import { Lists } from "../../Utils/Lists" | ||||
| 
 | ||||
| export interface HighlightedTagRendering { | ||||
|     path: ReadonlyArray<string | number> | ||||
|  | @ -380,7 +376,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> { | |||
|             if (data[key]) { | ||||
|                 // A bit of cleanup
 | ||||
|                 const lBefore = data[key].length | ||||
|                 const cleaned = Utils.NoNull(data[key]) | ||||
|                 const cleaned = Lists.noNull(data[key]) | ||||
|                 if (cleaned.length != lBefore) { | ||||
|                     data[key] = cleaned | ||||
|                     return true | ||||
|  | @ -422,7 +418,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> { | |||
| 
 | ||||
|     private addMissingTagRenderingIds() { | ||||
|         this.configuration.addCallbackD((config) => { | ||||
|             const trs = Utils.NoNull(config.tagRenderings ?? []) | ||||
|             const trs = Lists.noNull(config.tagRenderings ?? []) | ||||
|             for (let i = 0; i < trs.length; i++) { | ||||
|                 const tr = trs[i] | ||||
|                 if (typeof tr === "string") { | ||||
|  | @ -544,7 +540,7 @@ export class EditThemeState extends EditJsonState<ThemeConfigJson> { | |||
|         const prepare = this.buildValidation(state) | ||||
|         const context = ConversionContext.construct([], ["prepare"]) | ||||
|         if (configuration.layers) { | ||||
|             Utils.NoNullInplace(configuration.layers) | ||||
|             Lists.noNullInplace(configuration.layers) | ||||
|         } | ||||
|         try { | ||||
|             prepare.convert(<ThemeConfigJson>configuration, context) | ||||
|  |  | |||
							
								
								
									
										127
									
								
								src/Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										127
									
								
								src/Utils.ts
									
										
									
									
									
								
							|  | @ -1,4 +1,6 @@ | |||
| import DOMPurify from "dompurify" | ||||
| import { Lists } from "./Utils/Lists" | ||||
| import { Strings } from "./Utils/Strings" | ||||
| 
 | ||||
| export class Utils { | ||||
|     /** | ||||
|  | @ -109,7 +111,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         return <T>parsed | ||||
|     } | ||||
| 
 | ||||
|     static EncodeXmlValue(str) { | ||||
|     static encodeXmlValue(str) { | ||||
|         if (typeof str !== "string") { | ||||
|             str = "" + str | ||||
|         } | ||||
|  | @ -122,26 +124,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|             .replace(/'/g, "'") | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gives a clean float, or undefined if parsing fails | ||||
|      * @param str | ||||
|      */ | ||||
|     static asFloat(str): number { | ||||
|         if (str) { | ||||
|             const i = parseFloat(str) | ||||
|             if (isNaN(i)) { | ||||
|                 return undefined | ||||
|             } | ||||
|             return i | ||||
|         } | ||||
|         return undefined | ||||
|     public static upper(str: string) { | ||||
|         return str.substring(0, 1).toUpperCase() + str.substring(1) | ||||
|     } | ||||
| 
 | ||||
|     public static Upper(str: string) { | ||||
|         return str.substr(0, 1).toUpperCase() + str.substr(1) | ||||
|     } | ||||
| 
 | ||||
|     public static TwoDigits(i: number) { | ||||
|     public static twoDigits(i: number) { | ||||
|         if (i < 10) { | ||||
|             return "0" + i | ||||
|         } | ||||
|  | @ -151,16 +138,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|     /** | ||||
|      * Converts a number to a number with precisely 7 decimals | ||||
|      * | ||||
|      * Utils.Round7(12.123456789) // => 12.1234568
 | ||||
|      * Utils.round7(12.123456789) // => 12.1234568
 | ||||
|      */ | ||||
|     public static Round7(i: number): number { | ||||
|     public static round7(i: number): number { | ||||
|         if (i == undefined) { | ||||
|             return undefined | ||||
|         } | ||||
|         return Math.round(i * 10000000) / 10000000 | ||||
|     } | ||||
| 
 | ||||
|     public static Times(f: (i: number) => string, count: number): string { | ||||
|     public static times(f: (i: number) => string, count: number): string { | ||||
|         let res = "" | ||||
|         for (let i = 0; i < count; i++) { | ||||
|             res += f(i) | ||||
|  | @ -168,7 +155,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         return res | ||||
|     } | ||||
| 
 | ||||
|     public static TimesT<T>(count: number, f: (i: number) => T): T[] { | ||||
|     public static timesT<T>(count: number, f: (i: number) => T): T[] { | ||||
|         const res: T[] = [] | ||||
|         for (let i = 0; i < count; i++) { | ||||
|             res.push(f(i)) | ||||
|  | @ -176,16 +163,8 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         return res | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a shallow copy of the array. All elements that are not undefined/null will be in the new list | ||||
|      * @param array | ||||
|      * @constructor | ||||
|      */ | ||||
|     public static NoNull<T>(array: ReadonlyArray<T> | undefined): T[] | undefined | ||||
|     public static NoNull(array: undefined): undefined | ||||
|     public static NoNull<T>(array: ReadonlyArray<T>): T[] | ||||
|     public static NoNull<T>(array: ReadonlyArray<T>): NonNullable<T>[] { | ||||
|         return <NonNullable<T>[]>(<unknown>array?.filter((o) => o !== undefined && o !== null)) | ||||
|     public static noNull<T>(array: ReadonlyArray<T>): NonNullable<T>[] { | ||||
|         return Lists.noNull(array) | ||||
|     } | ||||
| 
 | ||||
|     public static Hist(array: ReadonlyArray<string>): Map<string, number> { | ||||
|  | @ -195,29 +174,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         } | ||||
|         return hist | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Removes all empty strings from this list | ||||
|      * If undefined or null is given, an empty list is returned | ||||
|      * | ||||
|      * Utils.NoEmpty(undefined) // => []
 | ||||
|      * Utils.NoEmpty(["abc","","def", null]) // => ["abc","def", null]
 | ||||
|      * | ||||
|      */ | ||||
|     public static NoEmpty(array: string[]): string[] { | ||||
|         const ls: string[] = [] | ||||
|         if (!array) { | ||||
|             return ls | ||||
|         } | ||||
|         for (const t of array) { | ||||
|             if (t === "") { | ||||
|                 continue | ||||
|             } | ||||
|             ls.push(t) | ||||
|         } | ||||
|         return ls | ||||
|     } | ||||
| 
 | ||||
|     public static EllipsesAfter(str: string, l: number = 100) { | ||||
|         if (str === undefined || str === null) { | ||||
|             return undefined | ||||
|  | @ -297,28 +253,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         } | ||||
|         return str | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new array with all elements from 'arr' in such a way that every element will be kept only once | ||||
|      * Elements are returned in the same order as they appear in the lists. | ||||
|      * Null/Undefined is returned as is. If an emtpy array is given, a new empty array will be returned | ||||
|      */ | ||||
|     public static Dedup(arr: NonNullable<string[]>): NonNullable<string[]> | ||||
|     public static Dedup(arr: undefined): undefined | ||||
|     public static Dedup(arr: string[] | undefined): string[] | undefined | ||||
|     public static Dedup(arr: string[]): string[] { | ||||
|         if (arr === undefined || arr === null) { | ||||
|             return arr | ||||
|         } | ||||
|         const newArr = [] | ||||
|         for (const string of arr) { | ||||
|             if (newArr.indexOf(string) < 0) { | ||||
|                 newArr.push(string) | ||||
|             } | ||||
|         } | ||||
|         return newArr | ||||
|     } | ||||
| 
 | ||||
|     public static DedupT<T>(arr: T[]): T[] { | ||||
|         if (!arr) { | ||||
|             return arr | ||||
|  | @ -359,7 +293,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|                     uniq.push(img) | ||||
|                 } | ||||
|             } else { | ||||
|                 const ksNoNull = Utils.NoNull(ks) | ||||
|                 const ksNoNull = Lists.noNull(ks) | ||||
|                 const hasBeenSeen = ksNoNull.some((k) => seen.has(k)) | ||||
|                 if (!hasBeenSeen) { | ||||
|                     uniq.push(img) | ||||
|  | @ -1213,7 +1147,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         if (days > 0) { | ||||
|             return days + "days" + " " + hours + "h" | ||||
|         } | ||||
|         return hours + ":" + Utils.TwoDigits(minutes) + ":" + Utils.TwoDigits(seconds) | ||||
|         return hours + ":" + Utils.twoDigits(minutes) + ":" + Utils.twoDigits(seconds) | ||||
|     } | ||||
| 
 | ||||
|     public static toHumanByteSize(bytes: number): string { | ||||
|  | @ -1247,9 +1181,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         const date = | ||||
|             lastWeek.getFullYear() + | ||||
|             "-" + | ||||
|             Utils.TwoDigits(lastWeek.getMonth() + 1) + | ||||
|             Utils.twoDigits(lastWeek.getMonth() + 1) + | ||||
|             "-" + | ||||
|             Utils.TwoDigits(lastWeek.getDate()) | ||||
|             Utils.twoDigits(lastWeek.getDate()) | ||||
|         let osmcha_link = `"date__gte":[{"label":"${date}","value":"${date}"}],"editor":[{"label":"mapcomplete","value":"mapcomplete"}]` | ||||
|         if (theme !== undefined) { | ||||
|             osmcha_link = | ||||
|  | @ -1833,16 +1767,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         } | ||||
|         return 50 * Math.round(number / 50) | ||||
|     } | ||||
| 
 | ||||
|     public static NoNullInplace<T>(items: T[]): T[] { | ||||
|         for (let i = items.length - 1; i >= 0; i--) { | ||||
|             if (items[i] === null || items[i] === undefined || items[i] === "") { | ||||
|                 items.splice(i, 1) | ||||
|             } | ||||
|         } | ||||
|         return items | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Removes or rewrites some characters in links, as some blink/chromium based browsers are picky about them | ||||
|      * | ||||
|  | @ -1860,8 +1784,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         return href | ||||
|     } | ||||
| 
 | ||||
|     private static emojiRegex = /[\p{Extended_Pictographic}🛰️]/u | ||||
| 
 | ||||
|     /** | ||||
|      * Returns 'true' if the given string contains at least one and only emoji characters | ||||
|      * | ||||
|  | @ -1870,19 +1792,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|      * Utils.isEmoji("🍕") // => true
 | ||||
|      */ | ||||
|     public static isEmoji(string: string) { | ||||
|         return Utils.emojiRegex.test(string) || Utils.isEmojiFlag(string) | ||||
|         return  Strings.isEmoji(string) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Utils.isEmojiFlag("🇧🇪") // => true
 | ||||
|      */ | ||||
|     public static isEmojiFlag(string: string) { | ||||
|         return /[🇦-🇿]{2}/u.test(string) // flags, see https://stackoverflow.com/questions/53360006/detect-with-regex-if-emoji-is-country-flag | ||||
|     } | ||||
| 
 | ||||
|     public static concat<T>(param: T[][]): T[] { | ||||
|         return [].concat(...param) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|  | @ -1905,9 +1817,4 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         return <T>copy | ||||
|     } | ||||
| 
 | ||||
|     public static pass(){ | ||||
|         // Does nothing
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										89
									
								
								src/Utils/Lists.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/Utils/Lists.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,89 @@ | |||
| /** | ||||
|  * Various utils to manipulate lists | ||||
|  */ | ||||
| export class Lists { | ||||
|     /** | ||||
|      * Creates a shallow copy of the array. All elements that are not undefined/null will be in the new list | ||||
|      */ | ||||
|     public static noNull<T>(array: ReadonlyArray<T> | undefined): T[] | undefined | ||||
|     public static noNull(array: undefined): undefined | ||||
|     public static noNull<T>(array: ReadonlyArray<T>): T[] | ||||
|     public static noNull<T>(array: ReadonlyArray<T>): NonNullable<T>[] { | ||||
|         return <NonNullable<T>[]>(<unknown>array?.filter((o) => o !== undefined && o !== null)) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Removes all empty strings from this list | ||||
|      * If undefined or null is given, an empty list is returned | ||||
|      * | ||||
|      * Lists.noEmpty(undefined) // => []
 | ||||
|      * Lists.noEmpty(["abc","","def", null]) // => ["abc","def", null]
 | ||||
|      * | ||||
|      */ | ||||
|     public static noEmpty(array: undefined | null): string[] | ||||
|     public static noEmpty(array: undefined[]): undefined[] | ||||
|     public static noEmpty(array: (string | "")[]): Exclude<string, "">[] | ||||
|     public static noEmpty(array: (string | undefined | null | "")[]): (string | undefined | null)[] | ||||
|     public static noEmpty(array: string[]): string[] { | ||||
|         const ls: string[] = [] | ||||
|         if (!array) { | ||||
|             return ls | ||||
|         } | ||||
|         for (const t of array) { | ||||
|             if (t === "") { | ||||
|                 continue | ||||
|             } | ||||
|             ls.push(t) | ||||
|         } | ||||
|         return ls | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new array with all elements from 'arr' in such a way that every element will be kept only once | ||||
|      * Elements are returned in the same order as they appear in the lists. | ||||
|      * Null/Undefined is returned as is. If an empty array is given, a new empty array will be returned | ||||
|      */ | ||||
|     public static dedup(arr: NonNullable<string[]>): NonNullable<string[]> | ||||
|     public static dedup(arr: undefined): undefined | ||||
|     public static dedup(arr: string[] | undefined): string[] | undefined | ||||
|     public static dedup(arr: string[]): string[] { | ||||
|         if (arr === undefined || arr === null) { | ||||
|             return arr | ||||
|         } | ||||
|         const newArr = [] | ||||
|         for (const string of arr) { | ||||
|             if (newArr.indexOf(string) < 0) { | ||||
|                 newArr.push(string) | ||||
|             } | ||||
|         } | ||||
|         return newArr | ||||
|     } | ||||
| 
 | ||||
|     public static dedupT<T>(arr: ReadonlyArray<T>): T[] ; | ||||
|     public static dedupT<T>(arr: null): null ; | ||||
|     public static dedupT<T>(arr: undefined): undefined ; | ||||
|     public static dedupT<T>(arr: ReadonlyArray<T>): T[] { | ||||
|         if (arr === undefined) { | ||||
|             return undefined | ||||
|         } | ||||
|         if (arr === null) { | ||||
|             return null | ||||
|         } | ||||
|         const items = [] | ||||
|         for (const item of arr) { | ||||
|             if (items.indexOf(item) < 0) { | ||||
|                 items.push(item) | ||||
|             } | ||||
|         } | ||||
|         return items | ||||
|     } | ||||
| 
 | ||||
|     public static noNullInplace<T>(items: T[]): T[] { | ||||
|         for (let i = items.length - 1; i >= 0; i--) { | ||||
|             if (items[i] === null || items[i] === undefined || items[i] === "") { | ||||
|                 items.splice(i, 1) | ||||
|             } | ||||
|         } | ||||
|         return items | ||||
|     } | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/Utils/Strings.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Utils/Strings.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| /** | ||||
|  * Various string-related utils | ||||
|  */ | ||||
| export class Strings{ | ||||
|      private static emojiRegex = /[\p{Extended_Pictographic}🛰️]/u | ||||
| 
 | ||||
|     /** | ||||
|      * Returns 'true' if the given string contains at least one and only emoji characters | ||||
|      * | ||||
|      * Strings.isEmoji("⛰\uFE0F") // => true
 | ||||
|      * Strings.isEmoji("🇧🇪") // => true
 | ||||
|      * Strings.isEmoji("🍕") // => true
 | ||||
|      */ | ||||
|     public static isEmoji(string: string): boolean { | ||||
|         return Strings.emojiRegex.test(string) || Strings.isEmojiFlag(string) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Strings.isEmojiFlag("🇧🇪") // => true
 | ||||
|      */ | ||||
|     public static isEmojiFlag(string: string): boolean { | ||||
|         return /[🇦-🇿]{2}/u.test(string) // flags, see https://stackoverflow.com/questions/53360006/detect-with-regex-if-emoji-is-country-flag | ||||
|     } | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue