Refactoring: split 'Utils' into multiple files; fix some stray uppercase-method names

This commit is contained in:
Pieter Vander Vennet 2025-08-01 04:02:09 +02:00
parent 81be4db044
commit 3ec89826e4
97 changed files with 884 additions and 921 deletions

View file

@ -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

View file

@ -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",

View file

@ -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) {

View file

@ -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,
}

View file

@ -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() {

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -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"
}

View file

@ -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] = {}

View file

@ -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()

View file

@ -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",

View file

@ -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)
})
)

View file

@ -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 = [

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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>()

View file

@ -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

View file

@ -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
})

View file

@ -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
}

View file

@ -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) {

View file

@ -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()

View file

@ -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]}">`

View file

@ -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

View file

@ -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

View file

@ -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)
)

View file

@ -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("&")
}

View file

@ -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 }

View file

@ -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})`
),
])
)
}

View file

@ -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

View file

@ -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

View file

@ -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 &&

View file

@ -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]
)}"/>
`

View file

@ -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)

View file

@ -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)
}

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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),

View file

@ -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++) {

View file

@ -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

View file

@ -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) =>

View file

@ -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

View file

@ -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)

View file

@ -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)
}

View file

@ -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"]))
}
/**

View file

@ -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
}

View file

@ -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,
]),

View file

@ -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) {

View file

@ -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
}

View file

@ -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) {

View file

@ -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))),
}
}
}

View file

@ -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
)
}

View file

@ -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,

View file

@ -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"]

View file

@ -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) =>

View file

@ -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",

View file

@ -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")

View file

@ -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) {

View file

@ -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,
])
)
})

View file

@ -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 {

View file

@ -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)
}
/**

View file

@ -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
}
}

View file

@ -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(

View file

@ -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]

View file

@ -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)
}

View file

@ -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)

View file

@ -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)

View file

@ -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 {

View file

@ -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
})

View file

@ -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,
{

View file

@ -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)

View file

@ -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">

View file

@ -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)
})

View file

@ -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){

View file

@ -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() {

View file

@ -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() {

View file

@ -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>

View file

@ -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"

View file

@ -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"

View file

@ -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)
}
/**

View file

@ -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(

View file

@ -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>

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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)

View file

@ -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 {

View file

@ -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")

View file

@ -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) {

View file

@ -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)

View file

@ -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, "&apos;")
}
/**
* 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
View 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
View 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
}
}