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

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