diff --git a/assets/layers/favourite/favourite.json b/assets/layers/favourite/favourite.proto.json similarity index 100% rename from assets/layers/favourite/favourite.json rename to assets/layers/favourite/favourite.proto.json diff --git a/scripts/generateFavouritesLayer.ts b/scripts/generateFavouritesLayer.ts new file mode 100644 index 000000000..334742b45 --- /dev/null +++ b/scripts/generateFavouritesLayer.ts @@ -0,0 +1,25 @@ +import Script from "./Script" +import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson" +import { readFileSync, writeFileSync } from "fs" +import { AllSharedLayers } from "../src/Customizations/AllSharedLayers" + +class PrepareFavouritesLayerJson extends Script { + constructor() { + super("Prepares the 'favourites'-layer") + } + + async main(args: string[]): Promise { + const allConfigs = AllSharedLayers.getSharedLayersConfigs() + const proto = this.readLayer("favourite/favourite.proto.json") + const questions = allConfigs.get("questions") + proto.tagRenderings.push(...questions.tagRenderings) + + writeFileSync("./assets/layers/favourite/favourite.json", JSON.stringify(proto, null, " ")) + } + + private readLayer(path: string): LayerConfigJson { + return JSON.parse(readFileSync("./assets/layers/" + path, "utf8")) + } +} + +new PrepareFavouritesLayerJson().run() diff --git a/src/Logic/Tags/And.ts b/src/Logic/Tags/And.ts index 378518dd1..98da75ae0 100644 --- a/src/Logic/Tags/And.ts +++ b/src/Logic/Tags/And.ts @@ -3,6 +3,7 @@ import { Or } from "./Or" import { TagUtils } from "./TagUtils" import { Tag } from "./Tag" import { RegexTag } from "./RegexTag" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" export class And extends TagsFilter { public and: TagsFilter[] @@ -72,6 +73,10 @@ export class And extends TagsFilter { return allChoices } + asJson(): TagConfigJson { + return { and: this.and.map((a) => a.asJson()) } + } + asHumanString(linkToWiki: boolean, shorten: boolean, properties: Record) { return this.and .map((t) => { diff --git a/src/Logic/Tags/ComparingTag.ts b/src/Logic/Tags/ComparingTag.ts index 685cd2550..8009662f4 100644 --- a/src/Logic/Tags/ComparingTag.ts +++ b/src/Logic/Tags/ComparingTag.ts @@ -1,18 +1,23 @@ import { TagsFilter } from "./TagsFilter" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" +import { Tag } from "./Tag" export default class ComparingTag implements TagsFilter { private readonly _key: string private readonly _predicate: (value: string) => boolean - private readonly _representation: string + private readonly _representation: "<" | ">" | "<=" | ">=" + private readonly _boundary: string constructor( key: string, predicate: (value: string | undefined) => boolean, - representation: string = "" + representation: "<" | ">" | "<=" | ">=", + boundary: string ) { this._key = key this._predicate = predicate this._representation = representation + this._boundary = boundary } asChange(properties: Record): { k: string; v: string }[] { @@ -20,15 +25,64 @@ export default class ComparingTag implements TagsFilter { } asHumanString(linkToWiki: boolean, shorten: boolean, properties: Record) { - return this._key + this._representation + return this._key + this._representation + this._boundary } asOverpass(): string[] { throw "A comparable tag can not be used as overpass filter" } + /** + * const tg = new ComparingTag("key", value => (Number(value) < 42), "<", "42") + * const tg0 = new ComparingTag("key", value => (Number(value) < 42), "<", "42") + * const tg1 = new ComparingTag("key", value => (Number(value) <= 42), "<=", "42") + * const against = new ComparingTag("key", value => (Number(value) > 0), ">", "0") + * tg.shadows(new Tag("key", "41")) // => true + * tg.shadows(new Tag("key", "0")) // => true + * tg.shadows(new Tag("key", "43")) // => false + * tg.shadows(new Tag("key", "0")) // => true + * tg.shadows(tg) // => true + * tg.shadows(tg0) // => true + * tg.shadows(against) // => false + * tg1.shadows(tg0) // => true + * tg0.shadows(tg1) // => false + * + */ shadows(other: TagsFilter): boolean { - return other === this + if (other === this) { + return true + } + if (other instanceof ComparingTag) { + if (other._key !== this._key) { + return false + } + const selfDesc = this._representation === "<" || this._representation === "<=" + const otherDesc = other._representation === "<" || other._representation === "<=" + if (selfDesc !== otherDesc) { + return false + } + if ( + this._boundary === other._boundary && + this._representation === other._representation + ) { + return true + } + if (this._predicate(other._boundary)) { + return true + } + return false + } + + if (other instanceof Tag) { + if (other.key !== this._key) { + return false + } + if (this.matchesProperties({ [other.key]: other.value })) { + return true + } + } + + return false } isUsableAsAnswer(): boolean { @@ -38,7 +92,7 @@ export default class ComparingTag implements TagsFilter { /** * Checks if the properties match * - * const t = new ComparingTag("key", (x => Number(x) < 42)) + * const t = new ComparingTag("key", (x => Number(x) < 42), "<", "42") * t.matchesProperties({key: 42}) // => false * t.matchesProperties({key: 41}) // => true * t.matchesProperties({key: 0}) // => true @@ -56,6 +110,10 @@ export default class ComparingTag implements TagsFilter { return [] } + asJson(): TagConfigJson { + return this._key + this._representation + } + optimize(): TagsFilter | boolean { return this } diff --git a/src/Logic/Tags/Or.ts b/src/Logic/Tags/Or.ts index f7018c40b..da932049a 100644 --- a/src/Logic/Tags/Or.ts +++ b/src/Logic/Tags/Or.ts @@ -1,6 +1,7 @@ import { TagsFilter } from "./TagsFilter" import { TagUtils } from "./TagUtils" import { And } from "./And" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" export class Or extends TagsFilter { public or: TagsFilter[] @@ -27,6 +28,10 @@ export class Or extends TagsFilter { return false } + asJson(): TagConfigJson { + return { or: this.or.map((o) => o.asJson()) } + } + /** * * import {Tag} from "./Tag"; @@ -174,9 +179,9 @@ export class Or extends TagsFilter { const newOrs: TagsFilter[] = [] let containedAnds: And[] = [] for (const tf of optimized) { - if (tf instanceof Or) { + if (tf["or"]) { // expand all the nested ors... - newOrs.push(...tf.or) + newOrs.push(...tf["or"]) } else if (tf instanceof And) { // partition of all the ands containedAnds.push(tf) @@ -191,7 +196,7 @@ export class Or extends TagsFilter { const cleanedContainedANds: And[] = [] outer: for (let containedAnd of containedAnds) { for (const known of newOrs) { - // input for optimazation: (K=V | (X=Y & K=V)) + // input for optimization: (K=V | (X=Y & K=V)) // containedAnd: (X=Y & K=V) // newOrs (and thus known): (K=V) --> false const cleaned = containedAnd.removePhraseConsideredKnown(known, false) diff --git a/src/Logic/Tags/RegexTag.ts b/src/Logic/Tags/RegexTag.ts index e6486113a..d133c3663 100644 --- a/src/Logic/Tags/RegexTag.ts +++ b/src/Logic/Tags/RegexTag.ts @@ -1,5 +1,6 @@ import { Tag } from "./Tag" import { TagsFilter } from "./TagsFilter" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" export class RegexTag extends TagsFilter { public readonly key: RegExp | string @@ -82,6 +83,10 @@ export class RegexTag extends TagsFilter { } } + asJson(): TagConfigJson { + return this.asHumanString() + } + isUsableAsAnswer(): boolean { return false } diff --git a/src/Logic/Tags/SubstitutingTag.ts b/src/Logic/Tags/SubstitutingTag.ts index 7d5435a06..6e9d29311 100644 --- a/src/Logic/Tags/SubstitutingTag.ts +++ b/src/Logic/Tags/SubstitutingTag.ts @@ -1,6 +1,7 @@ import { TagsFilter } from "./TagsFilter" import { Tag } from "./Tag" import { Utils } from "../../Utils" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" /** * The substituting-tag uses the tags of a feature a variables and replaces them. @@ -45,6 +46,10 @@ export default class SubstitutingTag implements TagsFilter { ) } + asJson(): TagConfigJson { + return this._key + (this._invert ? "!" : "") + ":=" + this._value + } + asOverpass(): string[] { throw "A variable with substitution can not be used to query overpass" } diff --git a/src/Logic/Tags/Tag.ts b/src/Logic/Tags/Tag.ts index 7cb01a9d5..b532b7053 100644 --- a/src/Logic/Tags/Tag.ts +++ b/src/Logic/Tags/Tag.ts @@ -1,5 +1,6 @@ import { Utils } from "../../Utils" import { TagsFilter } from "./TagsFilter" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" export class Tag extends TagsFilter { public key: string @@ -67,6 +68,10 @@ export class Tag extends TagsFilter { return [`["${this.key}"="${this.value}"]`] } + asJson(): TagConfigJson { + return this.key + "=" + this.value + } + /** const t = new Tag("key", "value") diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index adc862922..91d3a4ca6 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -15,13 +15,14 @@ type Tags = Record export type UploadableTag = Tag | SubstitutingTag | And export class TagUtils { - public static readonly comparators: ReadonlyArray<[string, (a: number, b: number) => boolean]> = - [ - ["<=", (a, b) => a <= b], - [">=", (a, b) => a >= b], - ["<", (a, b) => a < b], - [">", (a, b) => a > b], - ] + public static readonly comparators: ReadonlyArray< + ["<" | ">" | "<=" | ">=", (a: number, b: number) => boolean] + > = [ + ["<=", (a, b) => a <= b], + [">=", (a, b) => a >= b], + ["<", (a, b) => a < b], + [">", (a, b) => a > b], + ] public static modeDocumentation: Record< string, { name: string; docs: string; uploadable?: boolean; overpassSupport: boolean } @@ -735,11 +736,10 @@ export class TagUtils { const tag = json as string for (const [operator, comparator] of TagUtils.comparators) { if (tag.indexOf(operator) >= 0) { - const split = Utils.SplitFirst(tag, operator) - - let val = Number(split[1].trim()) + const split = Utils.SplitFirst(tag, operator).map((v) => v.trim()) + let val = Number(split[1]) if (isNaN(val)) { - val = new Date(split[1].trim()).getTime() + val = new Date(split[1]).getTime() } const f = (value: string | number | undefined) => { @@ -762,7 +762,7 @@ export class TagUtils { } return comparator(b, val) } - return new ComparingTag(split[0], f, operator + val) + return new ComparingTag(split[0], f, operator, "" + val) } } diff --git a/src/Logic/Tags/TagsFilter.ts b/src/Logic/Tags/TagsFilter.ts index b06158b4f..e925a76ef 100644 --- a/src/Logic/Tags/TagsFilter.ts +++ b/src/Logic/Tags/TagsFilter.ts @@ -1,3 +1,5 @@ +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" + export abstract class TagsFilter { abstract asOverpass(): string[] @@ -17,6 +19,8 @@ export abstract class TagsFilter { properties: Record ): string + abstract asJson(): TagConfigJson + abstract usedKeys(): string[] /** diff --git a/src/UI/Favourites/FavouriteSummary.svelte b/src/UI/Favourites/FavouriteSummary.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/src/UI/Favourites/Favourites.svelte b/src/UI/Favourites/Favourites.svelte new file mode 100644 index 000000000..ff6e0331f --- /dev/null +++ b/src/UI/Favourites/Favourites.svelte @@ -0,0 +1,8 @@ +