forked from MapComplete/MapComplete
Merge branch 'develop' into Robin-patch-1
This commit is contained in:
commit
ad3a0a10cb
40 changed files with 798 additions and 262 deletions
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue