forked from MapComplete/MapComplete
Fix: fix #2343, properly fix "postfixDistinguished", also when marking as unknown
This commit is contained in:
parent
0cf3d07100
commit
2286ec964f
5 changed files with 122 additions and 102 deletions
src
Logic/Tags
Models/ThemeConfig
UI/Popup
|
@ -25,6 +25,11 @@ export class TagTypes {
|
||||||
return <any>and.and
|
return <any>and.and
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uploadableAnd(and: And & UploadableTag): UploadableTag[] {
|
||||||
|
return <any>and.and
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static safeOr(or: Or & OptimizedTag): ((FlatTag | (And & OptimizedTag)) & OptimizedTag)[] {
|
static safeOr(or: Or & OptimizedTag): ((FlatTag | (And & OptimizedTag)) & OptimizedTag)[] {
|
||||||
return <any>or.or
|
return <any>or.or
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class TagUtils {
|
||||||
["<=", (a, b) => a <= b],
|
["<=", (a, b) => a <= b],
|
||||||
[">=", (a, b) => a >= b],
|
[">=", (a, b) => a >= b],
|
||||||
["<", (a, b) => a < b],
|
["<", (a, b) => a < b],
|
||||||
[">", (a, b) => a > b],
|
[">", (a, b) => a > b]
|
||||||
]
|
]
|
||||||
public static modeDocumentation: Record<
|
public static modeDocumentation: Record<
|
||||||
string,
|
string,
|
||||||
|
@ -48,7 +48,7 @@ export class TagUtils {
|
||||||
"### Removing a key\n" +
|
"### Removing a key\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"If a key should be deleted in the OpenStreetMap-database, specify `key=` as well. This can be used e.g. to remove a\n" +
|
"If a key should be deleted in the OpenStreetMap-database, specify `key=` as well. This can be used e.g. to remove a\n" +
|
||||||
"fixme or value from another mapping if another field is filled out.",
|
"fixme or value from another mapping if another field is filled out."
|
||||||
},
|
},
|
||||||
"!=": {
|
"!=": {
|
||||||
name: "strict not equals",
|
name: "strict not equals",
|
||||||
|
@ -62,7 +62,7 @@ export class TagUtils {
|
||||||
"### If key is present\n" +
|
"### If key is present\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"This implies that, to check if a key is present, `key!=` can be used. This will only match if the key is present and not\n" +
|
"This implies that, to check if a key is present, `key!=` can be used. This will only match if the key is present and not\n" +
|
||||||
"empty.",
|
"empty."
|
||||||
},
|
},
|
||||||
"~": {
|
"~": {
|
||||||
name: "Value matches regex",
|
name: "Value matches regex",
|
||||||
|
@ -73,12 +73,12 @@ export class TagUtils {
|
||||||
"The regex is put within braces as to prevent runaway values.\n" +
|
"The regex is put within braces as to prevent runaway values.\n" +
|
||||||
"\nUse `key~*` to indicate that any value is allowed. This is effectively the check that the attribute is present (defined _and_ not empty)." +
|
"\nUse `key~*` to indicate that any value is allowed. This is effectively the check that the attribute is present (defined _and_ not empty)." +
|
||||||
"\n" +
|
"\n" +
|
||||||
"Regexes will match the newline character with `.` too - the `s`-flag is enabled by default.",
|
"Regexes will match the newline character with `.` too - the `s`-flag is enabled by default."
|
||||||
},
|
},
|
||||||
"~i~": {
|
"~i~": {
|
||||||
name: "Value matches case-invariant regex",
|
name: "Value matches case-invariant regex",
|
||||||
overpassSupport: true,
|
overpassSupport: true,
|
||||||
docs: "A tag can also be tested against a regex with `key~i~regex`, where the case of the value will be ignored. The regex is still matched against the _entire_ value",
|
docs: "A tag can also be tested against a regex with `key~i~regex`, where the case of the value will be ignored. The regex is still matched against the _entire_ value"
|
||||||
},
|
},
|
||||||
"!~": {
|
"!~": {
|
||||||
name: "Value should _not_ match regex",
|
name: "Value should _not_ match regex",
|
||||||
|
@ -87,27 +87,27 @@ export class TagUtils {
|
||||||
"A tag can also be tested against a regex with `key!~regex`. This filter will match if the value does *not* match the regex. " +
|
"A tag can also be tested against a regex with `key!~regex`. This filter will match if the value does *not* match the regex. " +
|
||||||
"\n If the\n" +
|
"\n If the\n" +
|
||||||
"value is allowed to appear anywhere as substring, use `key~.*regex.*`.\n" +
|
"value is allowed to appear anywhere as substring, use `key~.*regex.*`.\n" +
|
||||||
"The regex is put within braces as to prevent runaway values.\n",
|
"The regex is put within braces as to prevent runaway values.\n"
|
||||||
},
|
},
|
||||||
"!~i~": {
|
"!~i~": {
|
||||||
name: "Value does *not* match case-invariant regex",
|
name: "Value does *not* match case-invariant regex",
|
||||||
overpassSupport: true,
|
overpassSupport: true,
|
||||||
docs: "A tag can also be tested against a regex with `key~i~regex`, where the case of the value will be ignored. The regex is still matched against the _entire_ value. This filter returns true if the value does *not* match",
|
docs: "A tag can also be tested against a regex with `key~i~regex`, where the case of the value will be ignored. The regex is still matched against the _entire_ value. This filter returns true if the value does *not* match"
|
||||||
},
|
},
|
||||||
"~~": {
|
"~~": {
|
||||||
name: "Key and value should match given regex",
|
name: "Key and value should match given regex",
|
||||||
overpassSupport: true,
|
overpassSupport: true,
|
||||||
docs: "Both the `key` and `value` part of this specification are interpreted as regexes, both the key and value musth completely match their respective regexes",
|
docs: "Both the `key` and `value` part of this specification are interpreted as regexes, both the key and value musth completely match their respective regexes"
|
||||||
},
|
},
|
||||||
"~i~~": {
|
"~i~~": {
|
||||||
name: "Key and value should match a given regex; value is case-invariant",
|
name: "Key and value should match a given regex; value is case-invariant",
|
||||||
overpassSupport: true,
|
overpassSupport: true,
|
||||||
docs: "Similar to ~~, except that the value is case-invariant",
|
docs: "Similar to ~~, except that the value is case-invariant"
|
||||||
},
|
},
|
||||||
"!~i~~": {
|
"!~i~~": {
|
||||||
name: "Key and value should match a given regex; value is case-invariant",
|
name: "Key and value should match a given regex; value is case-invariant",
|
||||||
overpassSupport: true,
|
overpassSupport: true,
|
||||||
docs: "Similar to !~~, except that the value is case-invariant",
|
docs: "Similar to !~~, except that the value is case-invariant"
|
||||||
},
|
},
|
||||||
":=": {
|
":=": {
|
||||||
name: "Substitute `... {some_key} ...` and match `key`",
|
name: "Substitute `... {some_key} ...` and match `key`",
|
||||||
|
@ -133,24 +133,24 @@ export class TagUtils {
|
||||||
"\n" +
|
"\n" +
|
||||||
"```json\n" +
|
"```json\n" +
|
||||||
"{\n" +
|
"{\n" +
|
||||||
' "mappings": [\n' +
|
" \"mappings\": [\n" +
|
||||||
" {\n" +
|
" {\n" +
|
||||||
' "if":"key:={some_other_key}",\n' +
|
" \"if\":\"key:={some_other_key}\",\n" +
|
||||||
' "then": "...",\n' +
|
" \"then\": \"...\",\n" +
|
||||||
' "hideInAnswer": "some_other_key="\n' +
|
" \"hideInAnswer\": \"some_other_key=\"\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" ]\n" +
|
" ]\n" +
|
||||||
"}\n" +
|
"}\n" +
|
||||||
"```\n" +
|
"```\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"One can use `key!:=prefix-{other_key}-postfix` as well, to match if `key` is _not_ the same\n" +
|
"One can use `key!:=prefix-{other_key}-postfix` as well, to match if `key` is _not_ the same\n" +
|
||||||
"as `prefix-{other_key}-postfix` (with `other_key` substituted by the value)",
|
"as `prefix-{other_key}-postfix` (with `other_key` substituted by the value)"
|
||||||
},
|
},
|
||||||
"!:=": {
|
"!:=": {
|
||||||
name: "Substitute `{some_key}` should not match `key`",
|
name: "Substitute `{some_key}` should not match `key`",
|
||||||
overpassSupport: false,
|
overpassSupport: false,
|
||||||
docs: "See `:=`, except that this filter is inverted",
|
docs: "See `:=`, except that this filter is inverted"
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
private static keyCounts: { keys: any; tags: any } = key_counts
|
private static keyCounts: { keys: any; tags: any } = key_counts
|
||||||
public static readonly numberAndDateComparisonDocs =
|
public static readonly numberAndDateComparisonDocs =
|
||||||
|
@ -175,10 +175,10 @@ export class TagUtils {
|
||||||
"\n" +
|
"\n" +
|
||||||
"```json\n" +
|
"```json\n" +
|
||||||
"{\n" +
|
"{\n" +
|
||||||
' "osmTags": {\n' +
|
" \"osmTags\": {\n" +
|
||||||
' "or": [\n' +
|
" \"or\": [\n" +
|
||||||
' "amenity=school",\n' +
|
" \"amenity=school\",\n" +
|
||||||
' "amenity=kindergarten"\n' +
|
" \"amenity=kindergarten\"\n" +
|
||||||
" ]\n" +
|
" ]\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
"}\n" +
|
"}\n" +
|
||||||
|
@ -194,7 +194,7 @@ export class TagUtils {
|
||||||
"If the schema-files note a type [`TagConfigJson`](https://github.com/pietervdvn/MapComplete/blob/develop/src/Models/ThemeConfig/Json/TagConfigJson.ts), you can use one of these values.\n" +
|
"If the schema-files note a type [`TagConfigJson`](https://github.com/pietervdvn/MapComplete/blob/develop/src/Models/ThemeConfig/Json/TagConfigJson.ts), you can use one of these values.\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"In some cases, not every type of tags-filter can be used. For example, _rendering_ an option with a regex is\n" +
|
"In some cases, not every type of tags-filter can be used. For example, _rendering_ an option with a regex is\n" +
|
||||||
'fine (`"if": "brand~[Bb]randname", "then":" The brand is Brandname"`); but this regex can not be used to write a value\n' +
|
"fine (`\"if\": \"brand~[Bb]randname\", \"then\":\" The brand is Brandname\"`); but this regex can not be used to write a value\n" +
|
||||||
"into the database. The theme loader will however refuse to work with such inconsistencies and notify you of this while\n" +
|
"into the database. The theme loader will however refuse to work with such inconsistencies and notify you of this while\n" +
|
||||||
"you are building your theme.\n" +
|
"you are building your theme.\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
@ -205,18 +205,18 @@ export class TagUtils {
|
||||||
"\n" +
|
"\n" +
|
||||||
"```json\n" +
|
"```json\n" +
|
||||||
"{\n" +
|
"{\n" +
|
||||||
' "and": [\n' +
|
" \"and\": [\n" +
|
||||||
' "key=value",\n' +
|
" \"key=value\",\n" +
|
||||||
" {\n" +
|
" {\n" +
|
||||||
' "or": [\n' +
|
" \"or\": [\n" +
|
||||||
' "other_key=value",\n' +
|
" \"other_key=value\",\n" +
|
||||||
' "other_key=some_other_value"\n' +
|
" \"other_key=some_other_value\"\n" +
|
||||||
" ]\n" +
|
" ]\n" +
|
||||||
" },\n" +
|
" },\n" +
|
||||||
' "key_which_should_be_missing=",\n' +
|
" \"key_which_should_be_missing=\",\n" +
|
||||||
' "key_which_should_have_a_value~*",\n' +
|
" \"key_which_should_have_a_value~*\",\n" +
|
||||||
' "key~.*some_regex_a*_b+_[a-z]?",\n' +
|
" \"key~.*some_regex_a*_b+_[a-z]?\",\n" +
|
||||||
' "height<1"\n' +
|
" \"height<1\"\n" +
|
||||||
" ]\n" +
|
" ]\n" +
|
||||||
"}\n" +
|
"}\n" +
|
||||||
"```\n" +
|
"```\n" +
|
||||||
|
@ -374,9 +374,9 @@ export class TagUtils {
|
||||||
* TagUtils.FlattenMultiAnswer(([new Tag("x","y"), new Tag("a","b")])) // => new And([new Tag("x","y"), new Tag("a","b")])
|
* TagUtils.FlattenMultiAnswer(([new Tag("x","y"), new Tag("a","b")])) // => new And([new Tag("x","y"), new Tag("a","b")])
|
||||||
* TagUtils.FlattenMultiAnswer(([new Tag("x","")])) // => new And([new Tag("x","")])
|
* TagUtils.FlattenMultiAnswer(([new Tag("x","")])) // => new And([new Tag("x","")])
|
||||||
*/
|
*/
|
||||||
static FlattenMultiAnswer(tagsFilters: UploadableTag[]): And {
|
static FlattenMultiAnswer(tagsFilters: UploadableTag[]): UploadableTag[] {
|
||||||
if (tagsFilters === undefined) {
|
if (tagsFilters === undefined) {
|
||||||
return new And([])
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyValues = TagUtils.SplitKeys(tagsFilters)
|
const keyValues = TagUtils.SplitKeys(tagsFilters)
|
||||||
|
@ -386,7 +386,7 @@ export class TagUtils {
|
||||||
values.sort()
|
values.sort()
|
||||||
and.push(new Tag(key, values.join(";")))
|
and.push(new Tag(key, values.join(";")))
|
||||||
}
|
}
|
||||||
return new And(and)
|
return and
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -985,10 +985,10 @@ export class TagUtils {
|
||||||
return ["", "## `" + mode + "` " + doc.name, "", doc.docs, "", ""].join("\n")
|
return ["", "## `" + mode + "` " + doc.name, "", doc.docs, "", ""].join("\n")
|
||||||
}),
|
}),
|
||||||
"## " +
|
"## " +
|
||||||
TagUtils.comparators.map((comparator) => "`" + comparator[0] + "`").join(" ") +
|
TagUtils.comparators.map((comparator) => "`" + comparator[0] + "`").join(" ") +
|
||||||
" Logical comparators",
|
" Logical comparators",
|
||||||
TagUtils.numberAndDateComparisonDocs,
|
TagUtils.numberAndDateComparisonDocs,
|
||||||
TagUtils.logicalOperator,
|
TagUtils.logicalOperator
|
||||||
].join("\n")
|
].join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -734,7 +734,7 @@ export default class TagRenderingConfig {
|
||||||
singleSelectedMapping: number,
|
singleSelectedMapping: number,
|
||||||
multiSelectedMapping: boolean[] | undefined,
|
multiSelectedMapping: boolean[] | undefined,
|
||||||
currentProperties: Record<string, string>
|
currentProperties: Record<string, string>
|
||||||
): UploadableTag {
|
): UploadableTag[] {
|
||||||
if (typeof freeformValue === "string") {
|
if (typeof freeformValue === "string") {
|
||||||
freeformValue = freeformValue?.trim()
|
freeformValue = freeformValue?.trim()
|
||||||
}
|
}
|
||||||
|
@ -782,14 +782,14 @@ export default class TagRenderingConfig {
|
||||||
const freeformOnly = { [this.freeform.key]: freeformValue }
|
const freeformOnly = { [this.freeform.key]: freeformValue }
|
||||||
const matchingMapping = this.mappings?.find((m) => m.if.matchesProperties(freeformOnly))
|
const matchingMapping = this.mappings?.find((m) => m.if.matchesProperties(freeformOnly))
|
||||||
if (matchingMapping) {
|
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
|
// Either no mappings, or this is a radio-button selected freeform value
|
||||||
const tag = new And([
|
const tag = [
|
||||||
new Tag(this.freeform.key, freeformValue),
|
new Tag(this.freeform.key, freeformValue),
|
||||||
...(this.freeform.addExtraTags ?? [])
|
...(this.freeform.addExtraTags ?? [])
|
||||||
])
|
]
|
||||||
const newProperties = tag.applyOn(currentProperties)
|
const newProperties = new And(tag).applyOn(currentProperties)
|
||||||
if (this.invalidValues?.matchesProperties(newProperties)) {
|
if (this.invalidValues?.matchesProperties(newProperties)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -816,14 +816,9 @@ export default class TagRenderingConfig {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const and = TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings])
|
const and = TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings])
|
||||||
if (and.and.length === 0) {
|
if (and.length === 0) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
console.log(
|
|
||||||
">>> New properties",
|
|
||||||
TagUtils.asProperties(and, currentProperties),
|
|
||||||
this.invalidValues
|
|
||||||
)
|
|
||||||
if (
|
if (
|
||||||
this.invalidValues?.matchesProperties(TagUtils.asProperties(and, currentProperties))
|
this.invalidValues?.matchesProperties(TagUtils.asProperties(and, currentProperties))
|
||||||
) {
|
) {
|
||||||
|
@ -847,15 +842,15 @@ export default class TagRenderingConfig {
|
||||||
!someMappingIsShown ||
|
!someMappingIsShown ||
|
||||||
singleSelectedMapping === undefined)
|
singleSelectedMapping === undefined)
|
||||||
if (useFreeform) {
|
if (useFreeform) {
|
||||||
return new And([
|
return [
|
||||||
new Tag(this.freeform.key, freeformValue),
|
new Tag(this.freeform.key, freeformValue),
|
||||||
...(this.freeform.addExtraTags ?? [])
|
...(this.freeform.addExtraTags ?? [])
|
||||||
])
|
]
|
||||||
} else if (singleSelectedMapping !== undefined) {
|
} else if (singleSelectedMapping !== undefined) {
|
||||||
return new And([
|
return [
|
||||||
this.mappings[singleSelectedMapping].if,
|
this.mappings[singleSelectedMapping].if,
|
||||||
...(this.mappings[singleSelectedMapping].addExtraTags ?? [])
|
...(this.mappings[singleSelectedMapping].addExtraTags ?? [])
|
||||||
])
|
]
|
||||||
} else {
|
} else {
|
||||||
console.error("TagRenderingConfig.ConstructSpecification has a weird fallback for", {
|
console.error("TagRenderingConfig.ConstructSpecification has a weird fallback for", {
|
||||||
freeformValue,
|
freeformValue,
|
||||||
|
@ -864,7 +859,6 @@ export default class TagRenderingConfig {
|
||||||
currentProperties,
|
currentProperties,
|
||||||
useFreeform
|
useFreeform
|
||||||
})
|
})
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -998,7 +992,7 @@ export default class TagRenderingConfig {
|
||||||
* The keys that should be erased if one has to revert to 'unknown'.
|
* The keys that should be erased if one has to revert to 'unknown'.
|
||||||
* Might give undefined if setting to unknown is not possible
|
* Might give undefined if setting to unknown is not possible
|
||||||
*/
|
*/
|
||||||
public removeToSetUnknown(
|
private removeToSetUnknown(
|
||||||
partOfLayer: LayerConfig,
|
partOfLayer: LayerConfig,
|
||||||
currentTags: Record<string, string>
|
currentTags: Record<string, string>
|
||||||
): string[] | undefined {
|
): string[] | undefined {
|
||||||
|
@ -1042,6 +1036,23 @@ export default class TagRenderingConfig {
|
||||||
|
|
||||||
return Array.from(toDelete)
|
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 {
|
export class TagRenderingConfigUtils {
|
||||||
|
|
|
@ -3,29 +3,30 @@
|
||||||
import FromHtml from "../Base/FromHtml.svelte"
|
import FromHtml from "../Base/FromHtml.svelte"
|
||||||
import { Translation } from "../i18n/Translation"
|
import { Translation } from "../i18n/Translation"
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A 'TagHint' will show the given tags in a human readable form.
|
* A 'TagHint' will show the given tags in a human readable form.
|
||||||
* Depending on the options, it'll link through to the wiki or might be completely hidden
|
* Depending on the options, it'll link through to the wiki or might be completely hidden
|
||||||
*/
|
*/
|
||||||
export let tags: TagsFilter
|
export let tags: TagsFilter[]
|
||||||
export let currentProperties: Record<string, string | any> = {}
|
export let currentProperties: Record<string, string> = {}
|
||||||
/**
|
|
||||||
* If given, this function will be called to embed the given tags hint into this translation
|
|
||||||
*/
|
|
||||||
export let embedIn: ((string: string) => Translation) | undefined = undefined
|
|
||||||
let tagsExplanation = ""
|
|
||||||
$: tagsExplanation = tags?.asHumanString(true, false, currentProperties)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="break-words" style="word-break: break-word">
|
{#if tags?.length > 0}
|
||||||
{#if tags === undefined}
|
{#each tags as tag}
|
||||||
<slot name="no-tags"><Tr cls="subtle" t={Translations.t.general.noTagsSelected} /></slot>
|
<div class="break-words" style="word-break: break-word">
|
||||||
{:else if embedIn === undefined}
|
{#if tag["value"] === ""}
|
||||||
<FromHtml src={tagsExplanation} />
|
<del>
|
||||||
{:else}
|
{tag["key"]}
|
||||||
<Tr t={embedIn(tagsExplanation)} />
|
</del>
|
||||||
{/if}
|
{:else}
|
||||||
</div>
|
<FromHtml src={tag.asHumanString(true, false, currentProperties)} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<slot name="no-tags">
|
||||||
|
<Tr cls="subtle" t={Translations.t.general.noTagsSelected} />
|
||||||
|
</slot>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource"
|
import { ImmutableStore, Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||||
import Tr from "../../Base/Tr.svelte"
|
import Tr from "../../Base/Tr.svelte"
|
||||||
import type { Feature } from "geojson"
|
import type { Feature } from "geojson"
|
||||||
|
@ -31,7 +31,9 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import Markdown from "../../Base/Markdown.svelte"
|
import Markdown from "../../Base/Markdown.svelte"
|
||||||
import { Utils } from "../../../Utils"
|
import { Utils } from "../../../Utils"
|
||||||
|
import { TagTypes } from "../../../Logic/Tags/TagTypes"
|
||||||
import type { UploadableTag } from "../../../Logic/Tags/TagTypes"
|
import type { UploadableTag } from "../../../Logic/Tags/TagTypes"
|
||||||
|
|
||||||
import Popup from "../../Base/Popup.svelte"
|
import Popup from "../../Base/Popup.svelte"
|
||||||
import If from "../../Base/If.svelte"
|
import If from "../../Base/If.svelte"
|
||||||
import DotMenu from "../../Base/DotMenu.svelte"
|
import DotMenu from "../../Base/DotMenu.svelte"
|
||||||
|
@ -43,7 +45,7 @@
|
||||||
export let selectedElement: Feature
|
export let selectedElement: Feature
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let layer: LayerConfig | undefined
|
export let layer: LayerConfig | undefined
|
||||||
export let selectedTags: UploadableTag = undefined
|
export let selectedTags: UploadableTag[] = undefined
|
||||||
export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({})
|
export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({})
|
||||||
|
|
||||||
export let clss = "interactive border-interactive"
|
export let clss = "interactive border-interactive"
|
||||||
|
@ -65,9 +67,9 @@
|
||||||
let checkedMappings: boolean[]
|
let checkedMappings: boolean[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IF set: we can remove the current answer by deleting all those keys
|
* The tags to apply to mark this answer as "unknown"
|
||||||
*/
|
*/
|
||||||
let settableKeys = tags.mapD((tags) => config.removeToSetUnknown(layer, tags))
|
let onMarkUnknown: Store<UploadableTag[] | undefined> = tags.mapD((tags) => config.markUnknown(layer, tags))
|
||||||
let unknownModal = new UIEventSource(false)
|
let unknownModal = new UIEventSource(false)
|
||||||
|
|
||||||
let searchTerm: UIEventSource<string> = new UIEventSource("")
|
let searchTerm: UIEventSource<string> = new UIEventSource("")
|
||||||
|
@ -118,7 +120,7 @@
|
||||||
seenFreeforms.push(newProps[confg.freeform.key])
|
seenFreeforms.push(newProps[confg.freeform.key])
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
}),
|
})
|
||||||
]
|
]
|
||||||
|
|
||||||
if (tgs !== undefined && confg.freeform) {
|
if (tgs !== undefined && confg.freeform) {
|
||||||
|
@ -211,7 +213,7 @@
|
||||||
!config.freeform.postfixDistinguished &&
|
!config.freeform.postfixDistinguished &&
|
||||||
$tags[config.freeform.key] // We need to have a current value in order to delete it
|
$tags[config.freeform.key] // We need to have a current value in order to delete it
|
||||||
) {
|
) {
|
||||||
selectedTags = new Tag(config.freeform.key, "")
|
selectedTags = [new Tag(config.freeform.key, "")]
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
selectedTags = config?.constructChangeSpecification(
|
selectedTags = config?.constructChangeSpecification(
|
||||||
|
@ -227,7 +229,7 @@
|
||||||
freeform: $freeformInput,
|
freeform: $freeformInput,
|
||||||
selectedMapping,
|
selectedMapping,
|
||||||
checkedMappings,
|
checkedMappings,
|
||||||
currentTags: tags.data,
|
currentTags: tags.data
|
||||||
},
|
},
|
||||||
" --> ",
|
" --> ",
|
||||||
selectedTags
|
selectedTags
|
||||||
|
@ -246,10 +248,10 @@
|
||||||
// Check the type of selectedTags
|
// Check the type of selectedTags
|
||||||
if (selectedTags instanceof Tag) {
|
if (selectedTags instanceof Tag) {
|
||||||
// Re-define selectedTags as an And
|
// Re-define selectedTags as an And
|
||||||
selectedTags = new And([selectedTags, ...extraTagsArray])
|
selectedTags = [selectedTags, ...extraTagsArray]
|
||||||
} else if (selectedTags instanceof And) {
|
} else if (selectedTags instanceof And) {
|
||||||
// Add the extraTags to the existing And
|
// Add the extraTags to the existing And
|
||||||
selectedTags = new And([...selectedTags.and, ...extraTagsArray])
|
selectedTags = [...TagTypes.uploadableAnd(selectedTags), ...extraTagsArray]
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
"selectedTags is not of type Tag or And, it is a " + JSON.stringify(selectedTags)
|
"selectedTags is not of type Tag or And, it is a " + JSON.stringify(selectedTags)
|
||||||
|
@ -258,17 +260,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSave(_ = undefined) {
|
function onSave() {
|
||||||
if (selectedTags === undefined) {
|
if (selectedTags === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedTagsJoined = new And(selectedTags)
|
||||||
if (layer === undefined || (layer?.source === null && layer.id !== "favourite")) {
|
if (layer === undefined || (layer?.source === null && layer.id !== "favourite")) {
|
||||||
/**
|
/**
|
||||||
* This is a special, privileged layer.
|
* This is a special, privileged layer.
|
||||||
* We simply apply the tags onto the records
|
* We simply apply the tags onto the records
|
||||||
*/
|
*/
|
||||||
const kv = selectedTags.asChange(tags.data)
|
const kv = selectedTagsJoined.asChange(tags.data)
|
||||||
for (const { k, v } of kv) {
|
for (const { k, v } of kv) {
|
||||||
if (v === undefined) {
|
if (v === undefined) {
|
||||||
// Note: we _only_ delete if it is undefined. We _leave_ the empty string and assign it, so that data consumers get correct information
|
// Note: we _only_ delete if it is undefined. We _leave_ the empty string and assign it, so that data consumers get correct information
|
||||||
|
@ -279,13 +282,13 @@
|
||||||
feedback.setData(undefined)
|
feedback.setData(undefined)
|
||||||
}
|
}
|
||||||
tags.ping()
|
tags.ping()
|
||||||
dispatch("saved", { config, applied: selectedTags })
|
dispatch("saved", { config, applied: selectedTagsJoined })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dispatch("saved", { config, applied: selectedTags })
|
dispatch("saved", { config, applied: selectedTagsJoined })
|
||||||
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
|
const change = new ChangeTagAction(tags.data.id, selectedTagsJoined, tags.data, {
|
||||||
theme: tags.data["_orig_theme"] ?? state.theme?.id,
|
theme: tags.data["_orig_theme"] ?? state.theme?.id,
|
||||||
changeType: "answer",
|
changeType: "answer"
|
||||||
})
|
})
|
||||||
freeformInput.set(undefined)
|
freeformInput.set(undefined)
|
||||||
selectedMapping = undefined
|
selectedMapping = undefined
|
||||||
|
@ -326,10 +329,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAnswer() {
|
function clearAnswer() {
|
||||||
const tagsToSet = settableKeys.data.map((k) => new Tag(k, ""))
|
const tagsToSet: UploadableTag[] = onMarkUnknown.data
|
||||||
const change = new ChangeTagAction(tags.data.id, new And(tagsToSet), tags.data, {
|
const change = new ChangeTagAction(tags.data.id, new And(tagsToSet), tags.data, {
|
||||||
theme: tags.data["_orig_theme"] ?? state.theme.id,
|
theme: tags.data["_orig_theme"] ?? state.theme.id,
|
||||||
changeType: "answer",
|
changeType: "answer"
|
||||||
})
|
})
|
||||||
freeformInput.set(undefined)
|
freeformInput.set(undefined)
|
||||||
selectedMapping = undefined
|
selectedMapping = undefined
|
||||||
|
@ -576,13 +579,7 @@
|
||||||
>
|
>
|
||||||
<div class="subtle">
|
<div class="subtle">
|
||||||
<Tr t={Translations.t.unknown.removedKeys} />
|
<Tr t={Translations.t.unknown.removedKeys} />
|
||||||
{#each $settableKeys as key}
|
<TagHint tags={$onMarkUnknown}></TagHint>
|
||||||
<code>
|
|
||||||
<del>
|
|
||||||
{key}
|
|
||||||
</del>
|
|
||||||
</code>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
</If>
|
</If>
|
||||||
<div class="flex w-full justify-end" slot="footer">
|
<div class="flex w-full justify-end" slot="footer">
|
||||||
|
@ -602,7 +599,7 @@
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
<div class="sticky bottom-0 flex flex-wrap justify-between" style="z-index: 11">
|
<div class="sticky bottom-0 flex flex-wrap justify-between" style="z-index: 11">
|
||||||
{#if $settableKeys && $isKnown && !matchesEmpty}
|
{#if $onMarkUnknown?.length > 0 && $isKnown && !matchesEmpty}
|
||||||
<button class="as-link small text-sm" on:click={() => unknownModal.set(true)}>
|
<button class="as-link small text-sm" on:click={() => unknownModal.set(true)}>
|
||||||
<Tr t={Translations.t.unknown.markUnknown} />
|
<Tr t={Translations.t.unknown.markUnknown} />
|
||||||
</button>
|
</button>
|
||||||
|
@ -614,8 +611,13 @@
|
||||||
<!-- TagRenderingQuestion-buttons -->
|
<!-- TagRenderingQuestion-buttons -->
|
||||||
<slot name="cancel" />
|
<slot name="cancel" />
|
||||||
<slot name="save-button" {selectedTags}>
|
<slot name="save-button" {selectedTags}>
|
||||||
{#if config.freeform?.key && !checkedMappings?.some((m) => m) && !$freeformInput && !$freeformInputUnvalidated && $tags[config.freeform.key]
|
|
||||||
&& (!config.freeform.postfixDistinguished)}
|
<!-- Save-button / delete button -->
|
||||||
|
{#if config.freeform?.key &&
|
||||||
|
!checkedMappings?.some((m) => m) &&
|
||||||
|
!$freeformInput && !$freeformInputUnvalidated
|
||||||
|
&& $tags[config.freeform.key]
|
||||||
|
&& $isKnown}
|
||||||
<button
|
<button
|
||||||
class="primary flex"
|
class="primary flex"
|
||||||
on:click|stopPropagation|preventDefault={() => onSave()}
|
on:click|stopPropagation|preventDefault={() => onSave()}
|
||||||
|
@ -637,9 +639,10 @@
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Taghint + debug info -->
|
||||||
{#if UserRelatedState.SHOW_TAGS_VALUES.indexOf($showTags) >= 0 || ($showTags === "" && numberOfCs >= Constants.userJourney.tagsVisibleAt) || $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
{#if UserRelatedState.SHOW_TAGS_VALUES.indexOf($showTags) >= 0 || ($showTags === "" && numberOfCs >= Constants.userJourney.tagsVisibleAt) || $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
||||||
<span class="flex flex-wrap justify-between">
|
<span class="flex flex-wrap justify-between">
|
||||||
<TagHint {state} tags={selectedTags} currentProperties={$tags} />
|
<TagHint tags={selectedTags} currentProperties={$tags} />
|
||||||
<span class="flex flex-wrap">
|
<span class="flex flex-wrap">
|
||||||
{#if $featureSwitchIsTesting}
|
{#if $featureSwitchIsTesting}
|
||||||
<div class="alert" style="padding: 0; margin: 0; margin-right: 0.5rem">
|
<div class="alert" style="padding: 0; margin: 0; margin-right: 0.5rem">
|
||||||
|
@ -647,9 +650,9 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
||||||
<a class="small" on:click={() => console.log("Configuration is ", config)}>
|
<button class="small as-link" on:click={() => console.log("Configuration is ", config)}>
|
||||||
{config.id}
|
{config.id}
|
||||||
</a>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
Loading…
Add table
Reference in a new issue