diff --git a/src/Logic/Tags/TagTypes.ts b/src/Logic/Tags/TagTypes.ts index c46d13126d..17e37d96e0 100644 --- a/src/Logic/Tags/TagTypes.ts +++ b/src/Logic/Tags/TagTypes.ts @@ -25,6 +25,11 @@ export class TagTypes { return and.and } + static uploadableAnd(and: And & UploadableTag): UploadableTag[] { + return and.and + } + + static safeOr(or: Or & OptimizedTag): ((FlatTag | (And & OptimizedTag)) & OptimizedTag)[] { return or.or } diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index c3933cdca8..aa92c24bd4 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -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] ] public static modeDocumentation: Record< string, @@ -48,7 +48,7 @@ export class TagUtils { "### Removing a key\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" + - "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", @@ -62,7 +62,7 @@ export class TagUtils { "### If key is present\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" + - "empty.", + "empty." }, "~": { name: "Value matches regex", @@ -73,12 +73,12 @@ export class TagUtils { "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)." + "\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~": { name: "Value matches case-invariant regex", 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", @@ -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. " + "\n If the\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~": { name: "Value does *not* match case-invariant regex", 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", 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~~": { name: "Key and value should match a given regex; value is case-invariant", overpassSupport: true, - docs: "Similar to ~~, except that the value is case-invariant", + docs: "Similar to ~~, except that the value is case-invariant" }, "!~i~~": { name: "Key and value should match a given regex; value is case-invariant", 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`", @@ -133,24 +133,24 @@ export class TagUtils { "\n" + "```json\n" + "{\n" + - ' "mappings": [\n' + + " \"mappings\": [\n" + " {\n" + - ' "if":"key:={some_other_key}",\n' + - ' "then": "...",\n' + - ' "hideInAnswer": "some_other_key="\n' + + " \"if\":\"key:={some_other_key}\",\n" + + " \"then\": \"...\",\n" + + " \"hideInAnswer\": \"some_other_key=\"\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" + - "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`", 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 public static readonly numberAndDateComparisonDocs = @@ -175,10 +175,10 @@ export class TagUtils { "\n" + "```json\n" + "{\n" + - ' "osmTags": {\n' + - ' "or": [\n' + - ' "amenity=school",\n' + - ' "amenity=kindergarten"\n' + + " \"osmTags\": {\n" + + " \"or\": [\n" + + " \"amenity=school\",\n" + + " \"amenity=kindergarten\"\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" + "\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" + "you are building your theme.\n" + "\n" + @@ -205,18 +205,18 @@ export class TagUtils { "\n" + "```json\n" + "{\n" + - ' "and": [\n' + - ' "key=value",\n' + + " \"and\": [\n" + + " \"key=value\",\n" + " {\n" + - ' "or": [\n' + - ' "other_key=value",\n' + - ' "other_key=some_other_value"\n' + + " \"or\": [\n" + + " \"other_key=value\",\n" + + " \"other_key=some_other_value\"\n" + " ]\n" + " },\n" + - ' "key_which_should_be_missing=",\n' + - ' "key_which_should_have_a_value~*",\n' + - ' "key~.*some_regex_a*_b+_[a-z]?",\n' + - ' "height<1"\n' + + " \"key_which_should_be_missing=\",\n" + + " \"key_which_should_have_a_value~*\",\n" + + " \"key~.*some_regex_a*_b+_[a-z]?\",\n" + + " \"height<1\"\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","")])) // => new And([new Tag("x","")]) */ - static FlattenMultiAnswer(tagsFilters: UploadableTag[]): And { + static FlattenMultiAnswer(tagsFilters: UploadableTag[]): UploadableTag[] { if (tagsFilters === undefined) { - return new And([]) + return [] } const keyValues = TagUtils.SplitKeys(tagsFilters) @@ -386,7 +386,7 @@ export class TagUtils { values.sort() 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") }), "## " + - TagUtils.comparators.map((comparator) => "`" + comparator[0] + "`").join(" ") + - " Logical comparators", + TagUtils.comparators.map((comparator) => "`" + comparator[0] + "`").join(" ") + + " Logical comparators", TagUtils.numberAndDateComparisonDocs, - TagUtils.logicalOperator, + TagUtils.logicalOperator ].join("\n") } diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index c8ab20a99e..4db5a4f051 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -734,7 +734,7 @@ export default class TagRenderingConfig { singleSelectedMapping: number, multiSelectedMapping: boolean[] | undefined, currentProperties: Record - ): UploadableTag { + ): UploadableTag[] { if (typeof freeformValue === "string") { freeformValue = freeformValue?.trim() } @@ -782,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) + ] + const newProperties = new And(tag).applyOn(currentProperties) if (this.invalidValues?.matchesProperties(newProperties)) { return undefined } @@ -816,14 +816,9 @@ export default class TagRenderingConfig { ) } 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)) ) { @@ -847,15 +842,15 @@ export default class TagRenderingConfig { !someMappingIsShown || singleSelectedMapping === undefined) if (useFreeform) { - return new And([ + return [ new Tag(this.freeform.key, freeformValue), ...(this.freeform.addExtraTags ?? []) - ]) + ] } else if (singleSelectedMapping !== undefined) { - return new And([ + return [ this.mappings[singleSelectedMapping].if, ...(this.mappings[singleSelectedMapping].addExtraTags ?? []) - ]) + ] } else { console.error("TagRenderingConfig.ConstructSpecification has a weird fallback for", { freeformValue, @@ -864,7 +859,6 @@ export default class TagRenderingConfig { currentProperties, useFreeform }) - return undefined } } @@ -998,7 +992,7 @@ export default class TagRenderingConfig { * 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[] | undefined { @@ -1042,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): 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 { diff --git a/src/UI/Popup/TagHint.svelte b/src/UI/Popup/TagHint.svelte index 42e7ba7330..49549507c4 100644 --- a/src/UI/Popup/TagHint.svelte +++ b/src/UI/Popup/TagHint.svelte @@ -3,29 +3,30 @@ import FromHtml from "../Base/FromHtml.svelte" import { Translation } from "../i18n/Translation" import Tr from "../Base/Tr.svelte" - import type { SpecialVisualizationState } from "../SpecialVisualization" import Translations from "../i18n/Translations" /** * 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 */ - export let tags: TagsFilter - export let currentProperties: Record = {} - /** - * 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) + export let tags: TagsFilter[] + export let currentProperties: Record = {} -
- {#if tags === undefined} - - {:else if embedIn === undefined} - - {:else} - - {/if} -
+{#if tags?.length > 0} + {#each tags as tag} +
+ {#if tag["value"] === ""} + + {tag["key"]} + + {:else} + + {/if} +
+ {/each} +{:else} + + + +{/if} diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 3b354959e7..f78dc69162 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -1,5 +1,5 @@