Merge branch 'develop' into Robin-patch-1

This commit is contained in:
Robin van der Linde 2025-03-13 18:53:22 +01:00
commit ad3a0a10cb
Signed by untrusted user: Robin-van-der-Linde
GPG key ID: 53956B3252478F0D
40 changed files with 798 additions and 262 deletions

View file

@ -4,6 +4,7 @@ import UserRelatedState from "../Logic/State/UserRelatedState"
import { Utils } from "../Utils"
import Zoomcontrol from "../UI/Zoomcontrol"
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
export type PageType = (typeof MenuState.pageNames)[number]
@ -27,7 +28,7 @@ export class MenuState {
"favourites",
"usersettings",
"share",
"menu",
"menu"
] as const
/**
@ -38,6 +39,9 @@ export class MenuState {
undefined
)
public static readonly nearbyImagesFeature: UIEventSource<object> = new UIEventSource<object>(
undefined
)
public readonly pageStates: Record<PageType, UIEventSource<boolean>>
public readonly highlightedLayerInFilters: UIEventSource<string> = new UIEventSource<string>(
@ -45,6 +49,7 @@ export class MenuState {
)
public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined)
private readonly _selectedElement: UIEventSource<any> | undefined
private isClosingAll = false
constructor(selectedElement: UIEventSource<any> | undefined) {
this._selectedElement = selectedElement
@ -129,29 +134,49 @@ export class MenuState {
* Returns 'true' if at least one menu was opened
*/
public closeAll(): boolean {
console.log("Closing all")
if (this.isClosingAll) {
return true
}
this.isClosingAll = true
const ps = this.pageStates
if (ps.menu.data) {
ps.menu.set(false)
return true
}
try {
if (MenuState.previewedImage.data !== undefined) {
MenuState.previewedImage.setData(undefined)
return true
}
for (const key in ps) {
const toggle = ps[key]
const wasOpen = toggle.data
toggle.setData(false)
if (wasOpen) {
if (ps.menu.data) {
ps.menu.set(false)
return true
}
}
if (this._selectedElement.data) {
this._selectedElement.setData(undefined)
return true
if (MenuState.previewedImage.data !== undefined) {
MenuState.previewedImage.setData(undefined)
return true
}
if (MenuState.nearbyImagesFeature.data !== undefined) {
MenuState.nearbyImagesFeature.setData(undefined)
return true
}
for (const key in ps) {
const toggle = ps[key]
const wasOpen = toggle.data
toggle.setData(false)
if (wasOpen) {
return true
}
}
if (this._selectedElement.data) {
this._selectedElement.setData(undefined)
return true
}
} finally {
this.isClosingAll = false
}
}
public setPreviewedImage(img?: Partial<ProvidedImage>) {
if (img === undefined && !this.isClosingAll) {
return
}
MenuState.previewedImage.setData(img)
}
}

View file

@ -116,7 +116,7 @@ export class AvailableRasterLayers {
availableLayersBboxes.map(
(eliPolygons) => {
const loc = location.data
const lonlat: [number, number] = [loc.lon, loc.lat]
const lonlat: [number, number] = [loc?.lon ?? 0, loc?.lat ?? 0]
const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => {
if (eliPolygon.geometry === null) {
return true // global ELI-layer

View file

@ -223,7 +223,7 @@ export default class TagRenderingConfig {
inline: json.freeform.inline ?? false,
default: json.freeform.default,
postfixDistinguished: json.freeform.postfixDistinguished?.trim(),
args: json.freeform.helperArgs,
args: json.freeform.helperArgs
}
if (json.freeform["extraTags"] !== undefined) {
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
@ -447,7 +447,7 @@ export default class TagRenderingConfig {
iconClass,
addExtraTags,
searchTerms: mapping.searchTerms,
priorityIf: prioritySearch,
priorityIf: prioritySearch
}
if (isQuestionable) {
if (hideInAnswer !== true && mp.if !== undefined && !mp.if.isUsableAsAnswer()) {
@ -554,7 +554,7 @@ export default class TagRenderingConfig {
then: new TypedTranslation<object>(
this.render.replace("{" + this.freeform.key + "}", leftover).translations,
this.render.context
),
)
})
}
}
@ -607,7 +607,7 @@ export default class TagRenderingConfig {
return {
then: this.render.PartialSubs({ [this.freeform.key]: v.trim() }),
icon: this.renderIcon,
iconClass: this.renderIconClass,
iconClass: this.renderIconClass
}
}
}
@ -662,7 +662,7 @@ export default class TagRenderingConfig {
key: commonKey,
values: Utils.NoNull(
values.map((arr) => arr.filter((item) => item.k === commonKey)[0]?.v)
),
)
}
}
@ -677,7 +677,7 @@ export default class TagRenderingConfig {
return {
key,
type: this.freeform.type,
values,
values
}
} catch (e) {
console.error("Could not create FreeformValues for tagrendering", this.id)
@ -734,7 +734,7 @@ export default class TagRenderingConfig {
singleSelectedMapping: number,
multiSelectedMapping: boolean[] | undefined,
currentProperties: Record<string, string>
): UploadableTag {
): UploadableTag[] {
if (typeof freeformValue === "string") {
freeformValue = freeformValue?.trim()
}
@ -746,14 +746,18 @@ export default class TagRenderingConfig {
if (freeformValue === "") {
freeformValue = undefined
}
if (this.freeform?.postfixDistinguished && freeformValue !== undefined) {
if (this.freeform?.postfixDistinguished) {
const allValues = currentProperties[this.freeform.key].split(";").map((s) => s.trim())
const perPostfix: Record<string, string> = {}
for (const value of allValues) {
const [v, postfix] = value.split("/")
perPostfix[postfix.trim()] = v.trim()
}
perPostfix[this.freeform.postfixDistinguished] = freeformValue
if (freeformValue === "" || freeformValue === undefined) {
delete perPostfix[this.freeform.postfixDistinguished]
} else {
perPostfix[this.freeform.postfixDistinguished] = freeformValue
}
const keys = Object.keys(perPostfix)
keys.sort()
freeformValue = keys.map((k) => perPostfix[k] + "/" + k).join("; ")
@ -778,14 +782,14 @@ export default class TagRenderingConfig {
const freeformOnly = { [this.freeform.key]: freeformValue }
const matchingMapping = this.mappings?.find((m) => m.if.matchesProperties(freeformOnly))
if (matchingMapping) {
return new And([matchingMapping.if, ...(matchingMapping.addExtraTags ?? [])])
return [matchingMapping.if, ...(matchingMapping.addExtraTags ?? [])]
}
// Either no mappings, or this is a radio-button selected freeform value
const tag = new And([
const tag = [
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
const newProperties = tag.applyOn(currentProperties)
...(this.freeform.addExtraTags ?? [])
]
const newProperties = new And(tag).applyOn(currentProperties)
if (this.invalidValues?.matchesProperties(newProperties)) {
return undefined
}
@ -807,19 +811,14 @@ export default class TagRenderingConfig {
selectedMappings.push(
new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
...(this.freeform.addExtraTags ?? [])
])
)
}
const and = TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings])
if (and.and.length === 0) {
if (and.length === 0) {
return undefined
}
console.log(
">>> New properties",
TagUtils.asProperties(and, currentProperties),
this.invalidValues
)
if (
this.invalidValues?.matchesProperties(TagUtils.asProperties(and, currentProperties))
) {
@ -843,24 +842,23 @@ export default class TagRenderingConfig {
!someMappingIsShown ||
singleSelectedMapping === undefined)
if (useFreeform) {
return new And([
return [
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
...(this.freeform.addExtraTags ?? [])
]
} else if (singleSelectedMapping !== undefined) {
return new And([
return [
this.mappings[singleSelectedMapping].if,
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
])
...(this.mappings[singleSelectedMapping].addExtraTags ?? [])
]
} else {
console.error("TagRenderingConfig.ConstructSpecification has a weird fallback for", {
freeformValue,
singleSelectedMapping,
multiSelectedMapping,
currentProperties,
useFreeform,
useFreeform
})
return undefined
}
}
@ -888,11 +886,11 @@ export default class TagRenderingConfig {
}
const msgs: string[] = [
icon +
" " +
"*" +
m.then.textFor(lang) +
"* is shown if with " +
m.if.asHumanString(true, false, {}),
" " +
"*" +
m.then.textFor(lang) +
"* is shown if with " +
m.if.asHumanString(true, false, {})
]
if (m.hideInAnswer === true) {
@ -901,7 +899,7 @@ export default class TagRenderingConfig {
if (m.ifnot !== undefined) {
msgs.push(
"Unselecting this answer will add " +
m.ifnot.asHumanString(true, false, {})
m.ifnot.asHumanString(true, false, {})
)
}
return msgs.join(". ")
@ -925,7 +923,7 @@ export default class TagRenderingConfig {
if (this.labels?.length > 0) {
labels = [
"This tagrendering has labels ",
...this.labels.map((label) => "`" + label + "`"),
...this.labels.map((label) => "`" + label + "`")
].join("\n")
}
@ -938,7 +936,7 @@ export default class TagRenderingConfig {
freeform,
mappings,
condition,
labels,
labels
].join("\n")
}
@ -964,11 +962,37 @@ export default class TagRenderingConfig {
return Utils.NoNull(tags)
}
/**
* Returns the freeform value that should be initially shown in the question
* @param properties
*/
public initialFreeformValue(properties: Record<string, string>): string {
const value = properties[this.freeform.key]
if (!value) {
return ""
}
const distinguish = this.freeform.postfixDistinguished
if (!distinguish) {
return value
}
const parts = value.split(";")
for (const part of parts) {
if (part.indexOf("/") < 0) {
continue
}
const [v, denom] = part.split("/").map(s => s.trim())
if (denom === distinguish) {
return v
}
}
return ""
}
/**
* The keys that should be erased if one has to revert to 'unknown'.
* Might give undefined if setting to unknown is not possible
*/
public removeToSetUnknown(
private removeToSetUnknown(
partOfLayer: LayerConfig,
currentTags: Record<string, string>
): string[] | undefined {
@ -1012,6 +1036,23 @@ export default class TagRenderingConfig {
return Array.from(toDelete)
}
/**
* Gives all the tags that should be applied to "reset" the freeform key to an "unknown" state
*/
public markUnknown(layer: LayerConfig, currentProperties: Record<string, string>): UploadableTag[] {
if (this.freeform?.postfixDistinguished) {
const allValues = currentProperties[this.freeform.key].split(";").filter(
part => part.split("/")[1]?.trim() !== this.freeform.postfixDistinguished
)
return [new Tag(this.freeform.key, allValues.join(";"))]
}
const keys = this.removeToSetUnknown(layer, currentProperties)
return keys?.map(k => new Tag(k, ""))
}
}
export class TagRenderingConfigUtils {
@ -1050,7 +1091,7 @@ export class TagRenderingConfigUtils {
clone.mappings?.map((m) => {
const mapping = {
...m,
priorityIf: m.priorityIf ?? TagUtils.Tag("id~*"),
priorityIf: m.priorityIf ?? TagUtils.Tag("id~*")
}
if (m.if.usedKeys().indexOf("nobrand") < 0) {
// Erase 'nobrand=yes', unless this option explicitly sets it

View file

@ -2,7 +2,6 @@ import BaseUIElement from "../UI/BaseUIElement"
import { Denomination } from "./Denomination"
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
import unit from "../../assets/layers/unit/unit.json"
import { QuestionableTagRenderingConfigJson } from "./ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import TagRenderingConfig from "./ThemeConfig/TagRenderingConfig"
import Validators, { ValidatorType } from "../UI/InputElement/Validators"
import { Validator } from "../UI/InputElement/Validator"
@ -14,7 +13,6 @@ export class Unit {
public readonly denominationsSorted: Denomination[]
public readonly eraseInvalid: boolean
public readonly quantity: string
private readonly _validator: Validator
public readonly inverted: boolean
constructor(
@ -26,7 +24,6 @@ export class Unit {
inverted: boolean = false
) {
this.quantity = quantity
this._validator = validator
if (
!inverted &&
["string", "text", "key", "icon", "translation", "fediverse", "id"].indexOf(
@ -97,7 +94,7 @@ export class Unit {
tagRenderings: TagRenderingConfig[],
ctx: string
): Unit[] {
let types: Record<string, ValidatorType> = {}
const types: Record<string, ValidatorType> = {}
for (const tagRendering of tagRenderings) {
if (tagRendering.freeform?.type) {
types[tagRendering.freeform.key] = tagRendering.freeform.type
@ -185,7 +182,7 @@ export class Unit {
): Unit[] {
const appliesTo = json.appliesToKey
for (let i = 0; i < (appliesTo ?? []).length; i++) {
let key = appliesTo[i]
const key = appliesTo[i]
if (key.trim() !== key) {
throw `${ctx}.appliesToKey[${i}] is invalid: it starts or ends with whitespace`
}
@ -265,7 +262,7 @@ export class Unit {
const loaded = this.getFromLibrary(toLoad.quantity, ctx)
const quantity = toLoad.quantity
function fetchDenom(d: string): Denomination {
const fetchDenom = (d: string): Denomination => {
const found = loaded.denominations.find(
(denom) => denom.canonical.toLowerCase() === d
)