forked from MapComplete/MapComplete
Refactoring: move methods out of 'utils.ts'
This commit is contained in:
parent
d6b56f4eb3
commit
94fbf1eccf
16 changed files with 140 additions and 136 deletions
|
@ -10,6 +10,7 @@ import { Changes } from "../Osm/Changes"
|
||||||
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
|
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
|
||||||
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
|
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
|
||||||
import { WithChangesState } from "../../Models/ThemeViewState/WithChangesState"
|
import { WithChangesState } from "../../Models/ThemeViewState/WithChangesState"
|
||||||
|
import Objects from "../../Utils/Objects"
|
||||||
|
|
||||||
export default class SelectedElementTagsUpdater {
|
export default class SelectedElementTagsUpdater {
|
||||||
private static readonly metatags = new Set([
|
private static readonly metatags = new Set([
|
||||||
|
@ -160,7 +161,7 @@ export default class SelectedElementTagsUpdater {
|
||||||
const newGeometry = osmObject.asGeoJson()?.geometry
|
const newGeometry = osmObject.asGeoJson()?.geometry
|
||||||
const oldFeature = state.indexedFeatures.featuresById.data.get(id)
|
const oldFeature = state.indexedFeatures.featuresById.data.get(id)
|
||||||
const oldGeometry = oldFeature?.geometry
|
const oldGeometry = oldFeature?.geometry
|
||||||
if (oldGeometry !== undefined && !Utils.SameObject(newGeometry, oldGeometry)) {
|
if (oldGeometry !== undefined && !Objects.sameObject(newGeometry, oldGeometry)) {
|
||||||
console.log("Detected a difference in geometry for ", id)
|
console.log("Detected a difference in geometry for ", id)
|
||||||
this.invalidateCache(s)
|
this.invalidateCache(s)
|
||||||
oldFeature.geometry = newGeometry
|
oldFeature.geometry = newGeometry
|
||||||
|
|
|
@ -4,9 +4,9 @@ import TileLocalStorage from "./TileLocalStorage"
|
||||||
import { GeoOperations } from "../../GeoOperations"
|
import { GeoOperations } from "../../GeoOperations"
|
||||||
import FeaturePropertiesStore from "./FeaturePropertiesStore"
|
import FeaturePropertiesStore from "./FeaturePropertiesStore"
|
||||||
import { UIEventSource } from "../../UIEventSource"
|
import { UIEventSource } from "../../UIEventSource"
|
||||||
import { Utils } from "../../../Utils"
|
|
||||||
import { Tiles } from "../../../Models/TileRange"
|
import { Tiles } from "../../../Models/TileRange"
|
||||||
import { BBox } from "../../BBox"
|
import { BBox } from "../../BBox"
|
||||||
|
import { Lists } from "../../../Utils/Lists"
|
||||||
|
|
||||||
class SingleTileSaver {
|
class SingleTileSaver {
|
||||||
private readonly _storage: UIEventSource<Feature[]>
|
private readonly _storage: UIEventSource<Feature[]>
|
||||||
|
@ -31,7 +31,7 @@ class SingleTileSaver {
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveFeatures(features: Feature[]) {
|
public saveFeatures(features: Feature[]) {
|
||||||
if (Utils.sameList(features, this._storage.data)) {
|
if (Lists.sameList(features, this._storage.data)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (const feature of features) {
|
for (const feature of features) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Stores, UIEventSource } from "../../UIEventSource"
|
||||||
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
|
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
|
||||||
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
|
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
|
||||||
import { Feature } from "geojson"
|
import { Feature } from "geojson"
|
||||||
import { Utils } from "../../../Utils"
|
import Objects from "../../../Utils/Objects"
|
||||||
|
|
||||||
export default class ChangeGeometryApplicator implements FeatureSource {
|
export default class ChangeGeometryApplicator implements FeatureSource {
|
||||||
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
|
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
|
||||||
|
@ -69,7 +69,7 @@ export default class ChangeGeometryApplicator implements FeatureSource {
|
||||||
// We only apply the last change as that one'll have the latest geometry
|
// We only apply the last change as that one'll have the latest geometry
|
||||||
const change = changesForFeature[changesForFeature.length - 1]
|
const change = changesForFeature[changesForFeature.length - 1]
|
||||||
copy.geometry = ChangeDescriptionTools.getGeojsonGeometry(change)
|
copy.geometry = ChangeDescriptionTools.getGeojsonGeometry(change)
|
||||||
if (Utils.SameObject(copy.geometry, feature.geometry)) {
|
if (Objects.sameObject(copy.geometry, feature.geometry)) {
|
||||||
// No actual changes: pass along the original
|
// No actual changes: pass along the original
|
||||||
newFeatures.push(feature)
|
newFeatures.push(feature)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { BBox } from "../../BBox"
|
||||||
import { OsmFeature } from "../../../Models/OsmFeature"
|
import { OsmFeature } from "../../../Models/OsmFeature"
|
||||||
import { Lists } from "../../../Utils/Lists"
|
import { Lists } from "../../../Utils/Lists"
|
||||||
|
|
||||||
;("use strict")
|
("use strict")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around the 'Overpass'-object.
|
* A wrapper around the 'Overpass'-object.
|
||||||
|
@ -229,7 +229,7 @@ export default class OverpassFeatureSource<T extends OsmFeature = OsmFeature> im
|
||||||
const requestedBounds = this.state.bounds.data
|
const requestedBounds = this.state.bounds.data
|
||||||
if (
|
if (
|
||||||
this._lastQueryBBox !== undefined &&
|
this._lastQueryBBox !== undefined &&
|
||||||
Utils.sameList(this._layersToDownload.data, this._lastRequestedLayers) &&
|
Lists.sameList(this._layersToDownload.data, this._lastRequestedLayers) &&
|
||||||
requestedBounds.isContainedIn(this._lastQueryBBox)
|
requestedBounds.isContainedIn(this._lastQueryBBox)
|
||||||
) {
|
) {
|
||||||
return undefined
|
return undefined
|
||||||
|
|
|
@ -32,7 +32,7 @@ export default class FilterSearch {
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((query) => {
|
.map((query) => {
|
||||||
if (!Strings.isEmoji(query)) {
|
if (!Strings.isEmoji(query)) {
|
||||||
return Utils.simplifyStringForSearch(query)
|
return Strings.simplifyStringForSearch(query)
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
})
|
})
|
||||||
|
@ -64,7 +64,7 @@ export default class FilterSearch {
|
||||||
option.searchTerms?.["en"] ??
|
option.searchTerms?.["en"] ??
|
||||||
[]),
|
[]),
|
||||||
].flatMap((term) => [term, ...(term?.split(" ") ?? [])])
|
].flatMap((term) => [term, ...(term?.split(" ") ?? [])])
|
||||||
terms = terms.map((t) => Utils.simplifyStringForSearch(t))
|
terms = terms.map((t) => Strings.simplifyStringForSearch(t))
|
||||||
terms.push(option.emoji)
|
terms.push(option.emoji)
|
||||||
Lists.noNullInplace(terms)
|
Lists.noNullInplace(terms)
|
||||||
const distances = queries.flatMap((query) =>
|
const distances = queries.flatMap((query) =>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import SearchUtils from "./SearchUtils"
|
||||||
import ThemeSearch from "./ThemeSearch"
|
import ThemeSearch from "./ThemeSearch"
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
|
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
|
||||||
import { Utils } from "../../Utils"
|
import { Strings } from "../../Utils/Strings"
|
||||||
|
|
||||||
export default class LayerSearch {
|
export default class LayerSearch {
|
||||||
private readonly _theme: ThemeConfig
|
private readonly _theme: ThemeConfig
|
||||||
|
@ -24,7 +24,7 @@ export default class LayerSearch {
|
||||||
const queryParts = query
|
const queryParts = query
|
||||||
.trim()
|
.trim()
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((q) => Utils.simplifyStringForSearch(q))
|
.map((q) => Strings.simplifyStringForSearch(q))
|
||||||
for (const id in ThemeSearch.officialThemes.layers) {
|
for (const id in ThemeSearch.officialThemes.layers) {
|
||||||
if (options?.whitelist && !options?.whitelist.has(id)) {
|
if (options?.whitelist && !options?.whitelist.has(id)) {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { GeoOperations } from "../GeoOperations"
|
||||||
import { ImmutableStore, Store, Stores } from "../UIEventSource"
|
import { ImmutableStore, Store, Stores } from "../UIEventSource"
|
||||||
import OpenStreetMapIdSearch from "./OpenStreetMapIdSearch"
|
import OpenStreetMapIdSearch from "./OpenStreetMapIdSearch"
|
||||||
import { Lists } from "../../Utils/Lists"
|
import { Lists } from "../../Utils/Lists"
|
||||||
|
import { Strings } from "../../Utils/Strings"
|
||||||
|
|
||||||
type IntermediateResult = {
|
type IntermediateResult = {
|
||||||
feature: Feature
|
feature: Feature
|
||||||
|
@ -61,7 +62,7 @@ export default class LocalElementSearch implements GeocodingProvider {
|
||||||
...searchTerms
|
...searchTerms
|
||||||
.flatMap((entry) => entry.split(/ /))
|
.flatMap((entry) => entry.split(/ /))
|
||||||
.map((entry) => {
|
.map((entry) => {
|
||||||
let simplified = Utils.simplifyStringForSearch(entry)
|
let simplified = Strings.simplifyStringForSearch(entry)
|
||||||
if (matchStart) {
|
if (matchStart) {
|
||||||
simplified = simplified.slice(0, query.length)
|
simplified = simplified.slice(0, query.length)
|
||||||
}
|
}
|
||||||
|
@ -103,7 +104,7 @@ export default class LocalElementSearch implements GeocodingProvider {
|
||||||
const centerPoint: [number, number] = [center.lon, center.lat]
|
const centerPoint: [number, number] = [center.lon, center.lat]
|
||||||
const properties = this._state.perLayer
|
const properties = this._state.perLayer
|
||||||
const candidateId = OpenStreetMapIdSearch.extractId(query)
|
const candidateId = OpenStreetMapIdSearch.extractId(query)
|
||||||
query = Utils.simplifyStringForSearch(query)
|
query = Strings.simplifyStringForSearch(query)
|
||||||
|
|
||||||
const partials: Store<IntermediateResult[]>[] = []
|
const partials: Store<IntermediateResult[]>[] = []
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Locale from "../../UI/i18n/Locale"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import ThemeSearch from "./ThemeSearch"
|
import ThemeSearch from "./ThemeSearch"
|
||||||
import { Lists } from "../../Utils/Lists"
|
import { Lists } from "../../Utils/Lists"
|
||||||
|
import { Strings } from "../../Utils/Strings"
|
||||||
|
|
||||||
export default class SearchUtils {
|
export default class SearchUtils {
|
||||||
/** Applies special search terms, such as 'studio', 'osmcha', ...
|
/** Applies special search terms, such as 'studio', 'osmcha', ...
|
||||||
|
@ -60,7 +61,7 @@ export default class SearchUtils {
|
||||||
const queryParts = query
|
const queryParts = query
|
||||||
.trim()
|
.trim()
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((q) => Utils.simplifyStringForSearch(q))
|
.map((q) => Strings.simplifyStringForSearch(q))
|
||||||
let terms: string[]
|
let terms: string[]
|
||||||
if (Array.isArray(keywords)) {
|
if (Array.isArray(keywords)) {
|
||||||
terms = keywords
|
terms = keywords
|
||||||
|
@ -74,7 +75,7 @@ export default class SearchUtils {
|
||||||
const q = queryParts[i]
|
const q = queryParts[i]
|
||||||
let minDistance: number = 99
|
let minDistance: number = 99
|
||||||
for (const term of termsAll) {
|
for (const term of termsAll) {
|
||||||
const d = Utils.levenshteinDistance(q, Utils.simplifyStringForSearch(term))
|
const d = Utils.levenshteinDistance(q, Strings.simplifyStringForSearch(term))
|
||||||
if (d < minDistance) {
|
if (d < minDistance) {
|
||||||
minDistance = d
|
minDistance = d
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils"
|
||||||
import { Readable, Subscriber, Unsubscriber, Updater, Writable } from "svelte/store"
|
import { Readable, Subscriber, Unsubscriber, Updater, Writable } from "svelte/store"
|
||||||
|
import { Lists } from "../Utils/Lists"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Various static utils
|
* Various static utils
|
||||||
|
@ -66,7 +67,7 @@ export class Stores {
|
||||||
stable.setData(undefined)
|
stable.setData(undefined)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (Utils.sameList(stable.data, list)) {
|
if (Lists.sameList(stable.data, list)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stable.setData(list)
|
stable.setData(list)
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { eliCategory } from "../../RasterLayerProperties"
|
||||||
import licenses from "../../../assets/generated/license_info.json"
|
import licenses from "../../../assets/generated/license_info.json"
|
||||||
import { Strings } from "../../../Utils/Strings"
|
import { Strings } from "../../../Utils/Strings"
|
||||||
import { Lists } from "../../../Utils/Lists"
|
import { Lists } from "../../../Utils/Lists"
|
||||||
|
import Objects from "../../../Utils/Objects"
|
||||||
|
|
||||||
export class ValidateLanguageCompleteness extends DesugaringStep<ThemeConfig> {
|
export class ValidateLanguageCompleteness extends DesugaringStep<ThemeConfig> {
|
||||||
private readonly _languages: string[]
|
private readonly _languages: string[]
|
||||||
|
@ -1085,11 +1086,8 @@ export class DetectDuplicatePresets extends DesugaringStep<ThemeConfig> {
|
||||||
const presetBTags = optimizedTags[j]
|
const presetBTags = optimizedTags[j]
|
||||||
const presetB = presets[j]
|
const presetB = presets[j]
|
||||||
if (
|
if (
|
||||||
Utils.SameObject(presetATags, presetBTags) &&
|
Objects.sameObject(presetATags, presetBTags) &&
|
||||||
Utils.sameList(
|
Lists.sameList(presetA.preciseInput.snapToLayers, presetB.preciseInput.snapToLayers)
|
||||||
presetA.preciseInput.snapToLayers,
|
|
||||||
presetB.preciseInput.snapToLayers
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
context.err(
|
context.err(
|
||||||
`This theme has multiple presets with the same tags: ${presetATags.asHumanString(
|
`This theme has multiple presets with the same tags: ${presetATags.asHumanString(
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
import { Utils } from "../../../Utils"
|
import { Utils } from "../../../Utils"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import EditButton from "./EditButton.svelte"
|
import EditButton from "./EditButton.svelte"
|
||||||
|
import { Strings } from "../../../Utils/Strings"
|
||||||
|
|
||||||
export let config: TagRenderingConfig
|
export let config: TagRenderingConfig
|
||||||
export let tags: UIEventSource<Record<string, string>>
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
|
@ -83,7 +84,7 @@
|
||||||
onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()))
|
onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting()))
|
||||||
onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()))
|
onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting()))
|
||||||
}
|
}
|
||||||
let answerId = "answer-" + Utils.randomString(5)
|
let answerId = "answer-" + Strings.randomString(5)
|
||||||
let debug = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
let debug = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
||||||
|
|
||||||
let apiState: Store<string> = state?.osmConnection?.apiIsOnline ?? new ImmutableStore("online")
|
let apiState: Store<string> = state?.osmConnection?.apiIsOnline ?? new ImmutableStore("online")
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import { ConfigMeta } from "./configMeta"
|
import { ConfigMeta } from "./configMeta"
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||||
import {
|
import { Conversion, ConversionMessage, DesugaringContext, Pipe } from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||||
Conversion,
|
|
||||||
ConversionMessage,
|
|
||||||
DesugaringContext,
|
|
||||||
Pipe,
|
|
||||||
} from "../../Models/ThemeConfig/Conversion/Conversion"
|
|
||||||
import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer"
|
import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer"
|
||||||
import { PrevalidateTheme, ValidateLayer } from "../../Models/ThemeConfig/Conversion/Validation"
|
import { PrevalidateTheme, ValidateLayer } from "../../Models/ThemeConfig/Conversion/Validation"
|
||||||
import { AllSharedLayers } from "../../Customizations/AllSharedLayers"
|
import { AllSharedLayers } from "../../Customizations/AllSharedLayers"
|
||||||
|
@ -26,6 +21,7 @@ import { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderi
|
||||||
import { ValidateTheme } from "../../Models/ThemeConfig/Conversion/ValidateTheme"
|
import { ValidateTheme } from "../../Models/ThemeConfig/Conversion/ValidateTheme"
|
||||||
import * as questions from "../../../public/assets/generated/layers/questions.json"
|
import * as questions from "../../../public/assets/generated/layers/questions.json"
|
||||||
import { Lists } from "../../Utils/Lists"
|
import { Lists } from "../../Utils/Lists"
|
||||||
|
import { Strings } from "../../Utils/Strings"
|
||||||
|
|
||||||
export interface HighlightedTagRendering {
|
export interface HighlightedTagRendering {
|
||||||
path: ReadonlyArray<string | number>
|
path: ReadonlyArray<string | number>
|
||||||
|
@ -431,7 +427,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
||||||
}
|
}
|
||||||
if (!tr["id"] && !tr["override"]) {
|
if (!tr["id"] && !tr["override"]) {
|
||||||
const qtr = <QuestionableTagRenderingConfigJson>tr
|
const qtr = <QuestionableTagRenderingConfigJson>tr
|
||||||
let id = "" + i + "_" + Utils.randomString(5)
|
let id = "" + i + "_" + Strings.randomString(5)
|
||||||
if (qtr?.freeform?.key) {
|
if (qtr?.freeform?.key) {
|
||||||
id = qtr?.freeform?.key
|
id = qtr?.freeform?.key
|
||||||
} else if (qtr.mappings?.[0]?.if) {
|
} else if (qtr.mappings?.[0]?.if) {
|
||||||
|
|
99
src/Utils.ts
99
src/Utils.ts
|
@ -1378,66 +1378,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
element.scrollIntoView({ behavior: "smooth", block: "nearest" })
|
element.scrollIntoView({ behavior: "smooth", block: "nearest" })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the contents of `a` are the same (and in the same order) as `b`.
|
|
||||||
* Might have false negatives in some cases
|
|
||||||
* @param a
|
|
||||||
* @param b
|
|
||||||
*/
|
|
||||||
public static sameList<T>(a: ReadonlyArray<T>, b: ReadonlyArray<T>) {
|
|
||||||
if (a == b) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (a === undefined || a === null || b === undefined || b === null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (a.length !== b.length) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for (let i = 0; i < a.length; i++) {
|
|
||||||
const ai = a[i]
|
|
||||||
const bi = b[i]
|
|
||||||
if (ai == bi) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (ai === bi) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SameObject<T>(a: T, b: T, ignoreKeys?: string[]): boolean {
|
|
||||||
if (a === b) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (a === undefined || a === null || b === null || b === undefined) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (typeof a === "object" && typeof b === "object") {
|
|
||||||
for (const aKey in a) {
|
|
||||||
if (!(aKey in b)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const bKey in b) {
|
|
||||||
if (!(bKey in a)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const k in a) {
|
|
||||||
if (!Utils.SameObject(a[k], b[k])) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Utils.splitIntoSubstitutionParts("abc") // => [{message: "abc"}]
|
* Utils.splitIntoSubstitutionParts("abc") // => [{message: "abc"}]
|
||||||
|
@ -1510,45 +1450,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
filename: path.substring(path.lastIndexOf("/") + 1),
|
filename: path.substring(path.lastIndexOf("/") + 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes accents from a string
|
|
||||||
* @param str
|
|
||||||
* @constructor
|
|
||||||
*
|
|
||||||
* Utils.RemoveDiacritics("bâtiments") // => "batiments"
|
|
||||||
* Utils.RemoveDiacritics(undefined) // => undefined
|
|
||||||
*/
|
|
||||||
public static RemoveDiacritics(str?: string): string {
|
|
||||||
// See #1729
|
|
||||||
if (!str) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
return str.normalize("NFD").replace(/\p{Diacritic}/gu, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplifies a string to increase the chance of a match
|
|
||||||
* @param str
|
|
||||||
* Utils.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564"
|
|
||||||
* Utils.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564"
|
|
||||||
* Utils.simplifyStringForSearch(undefined) // => undefined
|
|
||||||
*/
|
|
||||||
public static simplifyStringForSearch(str: string): string {
|
|
||||||
return Utils.RemoveDiacritics(str)
|
|
||||||
?.toLowerCase()
|
|
||||||
?.replace(/[^a-z0-9]/g, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
public static randomString(length: number): string {
|
|
||||||
let result = ""
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
const chr = Math.random().toString(36).substr(2, 3)
|
|
||||||
result += chr
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively rewrites all keys from `+key`, `key+` and `=key` into `key
|
* Recursively rewrites all keys from `+key`, `key+` and `=key` into `key
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,12 +43,15 @@ export class Lists {
|
||||||
* Elements are returned in the same order as they appear in the lists.
|
* 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
|
* 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: NonNullable<ReadonlyArray<string>>): NonNullable<string[]>
|
||||||
public static dedup(arr: undefined): undefined
|
public static dedup(arr: undefined): undefined
|
||||||
public static dedup(arr: string[] | undefined): string[] | undefined
|
public static dedup(arr: ReadonlyArray<string> | undefined): string[] | undefined
|
||||||
public static dedup(arr: string[]): string[] {
|
public static dedup(arr: ReadonlyArray<string>): string[] {
|
||||||
if (arr === undefined || arr === null) {
|
if (arr === undefined) {
|
||||||
return arr
|
return undefined
|
||||||
|
}
|
||||||
|
if (arr === null) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
const newArr = []
|
const newArr = []
|
||||||
for (const string of arr) {
|
for (const string of arr) {
|
||||||
|
@ -60,8 +63,8 @@ export class Lists {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static dedupT<T>(arr: ReadonlyArray<T>): T[]
|
public static dedupT<T>(arr: ReadonlyArray<T>): T[]
|
||||||
public static dedupT<T>(arr: null): null
|
public static dedupT(arr: null): null
|
||||||
public static dedupT<T>(arr: undefined): undefined
|
public static dedupT(arr: undefined): undefined
|
||||||
public static dedupT<T>(arr: ReadonlyArray<T>): T[] {
|
public static dedupT<T>(arr: ReadonlyArray<T>): T[] {
|
||||||
if (arr === undefined) {
|
if (arr === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -160,7 +163,7 @@ export class Lists {
|
||||||
* Lists.duplicates(["a", "b","c","b","b"] // => ["b"]
|
* Lists.duplicates(["a", "b","c","b","b"] // => ["b"]
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static duplicates(arr: string[]): string[] {
|
public static duplicates(arr: ReadonlyArray<string>): string[] {
|
||||||
if (arr === undefined) {
|
if (arr === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -175,4 +178,34 @@ export class Lists {
|
||||||
return Array.from(duplicates)
|
return Array.from(duplicates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the contents of `a` are the same (and in the same order) as `b`.
|
||||||
|
* Might have false negatives in some cases
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
public static sameList<T>(a: ReadonlyArray<T>, b: ReadonlyArray<T>): boolean {
|
||||||
|
if (a == b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (a === undefined || a === null || b === undefined || b === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
const ai = a[i]
|
||||||
|
const bi = b[i]
|
||||||
|
if (ai == bi) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (ai === bi) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
33
src/Utils/Objects.ts
Normal file
33
src/Utils/Objects.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* Various object-related utils
|
||||||
|
*/
|
||||||
|
export default class Objects {
|
||||||
|
public static sameObject<T>(a: T, b: T, ignoreKeys?: string[]): boolean {
|
||||||
|
if (a === b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (a === undefined || a === null || b === null || b === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (typeof a === "object" && typeof b === "object") {
|
||||||
|
for (const aKey in a) {
|
||||||
|
if (!(aKey in b)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const bKey in b) {
|
||||||
|
if (!(bKey in a)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const k in a) {
|
||||||
|
if (!Objects.sameObject(a[k], b[k])) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,4 +21,42 @@ export class Strings {
|
||||||
public static isEmojiFlag(string: string): boolean {
|
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
|
return /[🇦-🇿]{2}/u.test(string) // flags, see https://stackoverflow.com/questions/53360006/detect-with-regex-if-emoji-is-country-flag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes accents from a string
|
||||||
|
* @param str
|
||||||
|
* @constructor
|
||||||
|
*
|
||||||
|
* Strings.removeDiacritics("bâtiments") // => "batiments"
|
||||||
|
* Strings.removeDiacritics(undefined) // => undefined
|
||||||
|
*/
|
||||||
|
public static removeDiacritics(str?: string): string {
|
||||||
|
// See #1729
|
||||||
|
if (!str) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return str.normalize("NFD").replace(/\p{Diacritic}/gu, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplifies a string to increase the chance of a match
|
||||||
|
* @param str
|
||||||
|
* Strings.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564"
|
||||||
|
* Strings.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564"
|
||||||
|
* Strings.simplifyStringForSearch(undefined) // => undefined
|
||||||
|
*/
|
||||||
|
public static simplifyStringForSearch(str: string): string {
|
||||||
|
return Strings.removeDiacritics(str)
|
||||||
|
?.toLowerCase()
|
||||||
|
?.replace(/[^a-z0-9]/g, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
public static randomString(length: number): string {
|
||||||
|
let result = ""
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const chr = Math.random().toString(36).substr(2, 3)
|
||||||
|
result += chr
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue