Refactoring of conversion, fix rewriting of maprenderings and tagrenderings

This commit is contained in:
pietervdvn 2022-04-06 03:06:50 +02:00
parent c3859d56c6
commit 54d7a3a52b
8 changed files with 215 additions and 269 deletions

View file

@ -9,27 +9,27 @@ export interface DesugaringContext {
export abstract class Conversion<TIn, TOut> {
public readonly modifiedAttributes: string[];
protected readonly doc: string;
public readonly name: string
protected readonly doc: string;
constructor(doc: string, modifiedAttributes: string[] = [], name: string) {
this.modifiedAttributes = modifiedAttributes;
this.doc = doc + "\n\nModified attributes are\n" + modifiedAttributes.join(", ");
this.name = name
this.name = name
}
public static strict<T>(fixed: { errors?: string[], warnings?: string[], information?: string[], result?: T }): T {
fixed.information?.forEach(i => console.log(" ", i))
const yellow = (s) => "\x1b[33m"+s+"\x1b[0m"
const red = s => '\x1b[31m'+s+'\x1b[0m'
fixed.warnings?.forEach(w => console.warn(red(`<!> `), yellow (w)))
if (fixed?.errors !== undefined && fixed?.errors?.length > 0) {
fixed.errors?.forEach(e => console.error(red(`ERR `+e)))
fixed.information?.forEach(i => console.log(" ", i))
const yellow = (s) => "\x1b[33m" + s + "\x1b[0m"
const red = s => '\x1b[31m' + s + '\x1b[0m'
fixed.warnings?.forEach(w => console.warn(red(`<!> `), yellow(w)))
if (fixed?.errors !== undefined && fixed?.errors?.length > 0) {
fixed.errors?.forEach(e => console.error(red(`ERR ` + e)))
throw "Detected one or more errors, stopping now"
}
return fixed.result;
}
@ -38,98 +38,118 @@ export abstract class Conversion<TIn, TOut> {
return DesugaringStep.strict(fixed)
}
public andThenF<X>(f: (tout:TOut) => X ): Conversion<TIn, X>{
return new Pipe(
this,
new Pure(f)
)
}
abstract convert(json: TIn, context: string): { result: TOut, errors?: string[], warnings?: string[], information?: string[] }
}
public convertAll(jsons: TIn[], context: string): { result: TOut[], errors: string[], warnings: string[], information?: string[] } {
if(jsons === undefined || jsons === null){
throw `Detected a bug in the preprocessor pipeline: ${this.name}.convertAll received undefined or null - don't do this (at ${context})`
}
const result = []
const errors = []
const warnings = []
const information = []
for (let i = 0; i < jsons.length; i++) {
const json = jsons[i];
const r = this.convert(json, context + "[" + i + "]")
result.push(r.result)
errors.push(...r.errors ?? [])
warnings.push(...r.warnings ?? [])
information.push(...r.information ?? [])
export abstract class DesugaringStep<T> extends Conversion<T, T> {
}
class Pipe<TIn, TInter, TOut> extends Conversion<TIn, TOut> {
private readonly _step0: Conversion<TIn, TInter>;
private readonly _step1: Conversion<TInter, TOut>;
constructor(step0: Conversion<TIn, TInter>, step1: Conversion<TInter,TOut>) {
super("Merges two steps with different types", [], `Pipe(${step0.name}, ${step1.name})`);
this._step0 = step0;
this._step1 = step1;
}
convert(json: TIn, context: string): { result: TOut; errors?: string[]; warnings?: string[]; information?: string[] } {
const r0 = this._step0.convert(json, context);
const {result, errors, information, warnings } = r0;
if(result === undefined && errors.length > 0){
return {
...r0,
result: undefined
};
}
const r = this._step1.convert(result, context);
errors.push(...r.errors)
information.push(...r.information)
warnings.push(...r.warnings)
return {
result,
result: r.result,
errors,
warnings,
information
}
}
}
export abstract class DesugaringStep<T> extends Conversion<T, T> {
class Pure<TIn, TOut> extends Conversion<TIn, TOut> {
private readonly _f: (t: TIn) => TOut;
constructor(f: ((t:TIn) => TOut)) {
super("Wrapper around a pure function",[], "Pure");
this._f = f;
}
convert(json: TIn, context: string): { result: TOut; errors?: string[]; warnings?: string[]; information?: string[] } {
return {result: this._f(json)};
}
}
export class OnEvery<X, T> extends DesugaringStep<T> {
private readonly key: string;
private readonly step: DesugaringStep<X>;
private _options: { ignoreIfUndefined: boolean };
export class Each<X, Y> extends Conversion<X[], Y[]> {
private readonly _step: Conversion<X, Y>;
constructor(key: string, step: DesugaringStep<X>, options?: {
ignoreIfUndefined: false | boolean
}) {
super("Applies " + step.name + " onto every object of the list `key`", [key], "OnEvery("+step.name+")");
this.step = step;
this.key = key;
this._options = options;
constructor(step: Conversion<X, Y>) {
super("Applies the given step on every element of the list", [], "OnEach(" + step.name + ")");
this._step = step;
}
convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[], information?: string[] } {
json = {...json}
const step = this.step
const key = this.key;
if( this._options?.ignoreIfUndefined && json[key] === undefined){
return {
result: json,
};
}else{
const r = step.convertAll((<X[]>json[key]), context + "." + key)
json[key] = r.result
return {
...r,
result: json,
};
}
}
}
export class OnEveryConcat<X, T> extends DesugaringStep<T> {
private readonly key: string;
private readonly step: Conversion<X, X[]>;
constructor(key: string, step: Conversion<X, X[]>) {
super(`Applies ${step.name} onto every object of the list \`${key}\`. The results are concatenated and used as new list`, [key],
"OnEvery("+key+").Concat("+step.name+")");
this.step = step;
this.key = key;
}
convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[], information?: string[] } {
json = {...json}
const step = this.step
const key = this.key;
const values = json[key]
convert(values: X[], context: string): { result: Y[]; errors?: string[]; warnings?: string[]; information?: string[] } {
if (values === undefined || values === null) {
// Move on - nothing to see here!
return {
result: json,
}
return {result: undefined}
}
const r = step.convertAll((<X[]>values), context + "." + key)
const vals: X[][] = r.result
json[key] = [].concat(...vals)
const information: string[] = []
const warnings: string[] = []
const errors: string[] = []
const step = this._step
const result: Y[] = []
for (let i = 0; i < values.length; i++) {
const r = step.convert(values[i], context + "[" + i + "]")
information.push(...r.information)
warnings.push(...r.warnings)
errors.push(...r.errors)
result.push(r.result)
}
return {
information, errors, warnings,
result
};
}
}
export class On<P, T> extends DesugaringStep<T> {
private readonly key: string;
private readonly step: Conversion<P, P>;
constructor(key: string, step: Conversion<P, P>) {
super("Applies " + step.name + " onto property `"+key+"`", [key], `On(${key}, ${step.name})`);
this.step = step;
this.key = key;
}
convert(json: T, context: string): { result: T; errors?: string[]; warnings?: string[], information?: string[] } {
json = {...json}
const step = this.step
const key = this.key;
const value: P = json[key]
if (value === undefined || value === null) {
return { result: json };
}
const r = step.convert(value, context + "." + key)
json[key] = r.result
return {
...r,
result: json,
@ -138,13 +158,40 @@ export class OnEveryConcat<X, T> extends DesugaringStep<T> {
}
}
export class Concat<X, T> extends Conversion<X[], T[]> {
private readonly _step: Conversion<X, T[]>;
constructor(step: Conversion<X, T[]>) {
super("Executes the given step, flattens the resulting list", [], "Concat(" + step.name + ")");
this._step = step;
}
convert(values: X[], context: string): { result: T[]; errors?: string[]; warnings?: string[]; information?: string[] } {
if (values === undefined || values === null) {
// Move on - nothing to see here!
return {
result: undefined,
}
}
const r = new Each(this._step).convert(values, context)
const vals: T[][] = r.result
const flattened: T[] = [].concat(...vals)
return {
...r,
result: flattened,
};
}
}
export class Fuse<T> extends DesugaringStep<T> {
private readonly steps: DesugaringStep<T>[];
constructor(doc: string, ...steps: DesugaringStep<T>[]) {
super((doc ?? "") + "This fused pipeline of the following steps: " + steps.map(s => s.name).join(", "),
Utils.Dedup([].concat(...steps.map(step => step.modifiedAttributes))),
"Fuse of "+steps.map(s => s.name).join(", ")
"Fuse of " + steps.map(s => s.name).join(", ")
);
this.steps = Utils.NoNull(steps);
}
@ -155,7 +202,7 @@ export class Fuse<T> extends DesugaringStep<T> {
const information = []
for (let i = 0; i < this.steps.length; i++) {
const step = this.steps[i];
let r = step.convert(json, "While running step " +step.name + ": " + context)
let r = step.convert(json, "While running step " + step.name + ": " + context)
errors.push(...r.errors ?? [])
warnings.push(...r.warnings ?? [])
information.push(...r.information ?? [])
@ -180,7 +227,7 @@ export class SetDefault<T> extends DesugaringStep<T> {
private readonly _overrideEmptyString: boolean;
constructor(key: string, value: any, overrideEmptyString = false) {
super("Sets " + key + " to a default value if undefined", [], "SetDefault of "+key);
super("Sets " + key + " to a default value if undefined", [], "SetDefault of " + key);
this.key = key;
this.value = value;
this._overrideEmptyString = overrideEmptyString;

View file

@ -2,7 +2,7 @@ import {LayoutConfigJson} from "../Json/LayoutConfigJson";
import {Utils} from "../../../Utils";
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson";
import {LayerConfigJson} from "../Json/LayerConfigJson";
import {DesugaringStep, Fuse, OnEvery} from "./Conversion";
import {DesugaringStep, Each, Fuse, On} from "./Conversion";
export class UpdateLegacyLayer extends DesugaringStep<LayerConfigJson | string | { builtin, override }> {
@ -159,7 +159,7 @@ export class FixLegacyTheme extends Fuse<LayoutConfigJson> {
super(
"Fixes a legacy theme to the modern JSON format geared to humans. Syntactic sugars are kept (i.e. no tagRenderings are expandend, no dependencies are automatically gathered)",
new UpdateLegacyTheme(),
new OnEvery("layers", new UpdateLegacyLayer())
new On("layers",new Each( new UpdateLegacyLayer()))
);
}
}

View file

@ -1,4 +1,4 @@
import {Conversion, DesugaringContext, DesugaringStep, Fuse, OnEvery, OnEveryConcat, SetDefault} from "./Conversion";
import {Concat, Conversion, DesugaringContext, DesugaringStep, Each, Fuse, On, SetDefault} from "./Conversion";
import {LayerConfigJson} from "../Json/LayerConfigJson";
import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson";
import {Utils} from "../../../Utils";
@ -12,7 +12,7 @@ class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | {
private readonly _state: DesugaringContext;
constructor(state: DesugaringContext) {
super("Converts a tagRenderingSpec into the full tagRendering", [], "ExpandTagRendering");
super("Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question", [], "ExpandTagRendering");
this._state = state;
}
@ -138,138 +138,6 @@ class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | {
}
}
class ExpandGroupRewrite extends Conversion<{
rewrite: {
sourceString: string,
into: string[]
}[],
renderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[]
} | TagRenderingConfigJson, TagRenderingConfigJson[]> {
private _expandSubTagRenderings;
constructor(state: DesugaringContext) {
super(
"Converts a rewrite config for tagRenderings into the expanded form", [],
"ExpandGroupRewrite"
);
this._expandSubTagRenderings = new ExpandTagRendering(state)
}
convert(json:
{
rewrite:
{ sourceString: string; into: string[] }[]; renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[]
} | TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings?: string[] } {
if (json["rewrite"] === undefined) {
return {result: [<TagRenderingConfigJson>json], errors: [], warnings: []}
}
let config = <{
rewrite:
{ sourceString: string[]; into: (string | any)[][] };
renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[]
}>json;
{
const errors = []
if (!Array.isArray(config.rewrite.sourceString)) {
let extra = "";
if (typeof config.rewrite.sourceString === "string") {
extra = `<br/>Try <span class='literal-code'>"sourceString": [ "${config.rewrite.sourceString}" ] </span> instead (note the [ and ])`
}
const msg = context + "<br/>Invalid format: a rewrite block is defined, but the 'sourceString' should be an array of strings, but it is a " + typeof config.rewrite.sourceString + extra
errors.push(msg)
}
const expectedLength = config.rewrite.sourceString.length
for (let i = 0; i < config.rewrite.into.length; i++) {
const targets = config.rewrite.into[i];
if (!Array.isArray(targets)) {
errors.push(`${context}.rewrite.into[${i}] should be an array of values, but it is a ` + typeof targets)
} else if (targets.length !== expectedLength) {
errors.push(`${context}.rewrite.into[${i}]:<br/>The rewrite specified ${config.rewrite.sourceString} as sourcestring, which consists of ${expectedLength} values. The target ${JSON.stringify(targets)} has ${targets.length} items`)
if (typeof targets[0] !== "string") {
errors.push(context + ".rewrite.into[" + i + "]: expected a string as first rewrite value values, but got " + targets[0])
}
}
}
if (errors.length > 0) {
return {
errors,
warnings: [],
result: undefined
}
}
}
const subRenderingsRes = <{ result: TagRenderingConfigJson[][], errors, warnings }>this._expandSubTagRenderings.convertAll(config.renderings, context);
const subRenderings: TagRenderingConfigJson[] = [].concat(...subRenderingsRes.result);
const errors = subRenderingsRes.errors;
const warnings = subRenderingsRes.warnings;
const rewrittenPerGroup = new Map<string, TagRenderingConfigJson[]>()
// The actual rewriting
const sourceStrings = config.rewrite.sourceString;
for (const targets of config.rewrite.into) {
const groupName = targets[0];
if (typeof groupName !== "string") {
throw "The first string of 'targets' should always be a string"
}
const trs: TagRenderingConfigJson[] = []
for (const tr of subRenderings) {
let rewritten = tr;
for (let i = 0; i < sourceStrings.length; i++) {
const source = sourceStrings[i]
const target = targets[i] // This is a string OR a translation
rewritten = ExpandRewrite.RewriteParts(source, target, rewritten)
}
rewritten.group = rewritten.group ?? groupName
trs.push(rewritten)
}
if (rewrittenPerGroup.has(groupName)) {
rewrittenPerGroup.get(groupName).push(...trs)
} else {
rewrittenPerGroup.set(groupName, trs)
}
}
// Add questions box for this category
rewrittenPerGroup.forEach((group, groupName) => {
group.push(<TagRenderingConfigJson>{
id: "questions",
group: groupName
})
})
rewrittenPerGroup.forEach((group, _) => {
group.forEach(tr => {
if (tr.id === undefined || tr.id === "") {
errors.push("A tagrendering has an empty ID after expanding the tag; the tagrendering is: " + JSON.stringify(tr))
}
})
})
return {
result: [].concat(...Array.from(rewrittenPerGroup.values())),
errors, warnings
};
}
}
export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[]> {
constructor() {
@ -286,10 +154,15 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
* "someKey": "somevalue {xyz}"
* }
* ExpandRewrite.RewriteParts("{xyz}", "rewritten", spec) // => {"someKey": "somevalue rewritten"}
*
* // should substitute all occurances in strings
* const spec = {
* "someKey": "The left|right side has {key:left|right}"
* }
* ExpandRewrite.RewriteParts("left|right", "left", spec) // => {"someKey": "The left side has {key:left}"}
*
*/
public static RewriteParts<T>(keyToRewrite: string, target: string | any, tr: T): T {
const targetIsTranslation = Translations.isProbablyATranslation(target)
function replaceRecursive(obj: string | any, target) {
@ -300,7 +173,10 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
if (typeof obj === "string") {
// This is a simple string - we do a simple replace
return obj.replace(keyToRewrite, target)
while(obj.indexOf(keyToRewrite) >= 0){
obj = obj.replace(keyToRewrite, target)
}
return obj
}
if (Array.isArray(obj)) {
// This is a list of items
@ -581,12 +457,12 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
constructor(state: DesugaringContext) {
super(
"Fully prepares and expands a layer for the LayerConfig.",
new OnEvery("tagRenderings", new RewriteSpecial(), {ignoreIfUndefined: true}),
new OnEveryConcat("tagRenderings", new ExpandGroupRewrite(state)),
new OnEveryConcat("tagRenderings", new ExpandTagRendering(state)),
new OnEveryConcat("mapRendering", new ExpandRewrite()),
new On("tagRenderings", new Each(new RewriteSpecial())),
new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
new On("tagRenderings", new Concat(new ExpandTagRendering(state))),
new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
new SetDefault("titleIcons", ["defaults"]),
new OnEveryConcat("titleIcons", new ExpandTagRendering(state))
new On("titleIcons", new Concat(new ExpandTagRendering(state)))
);
}
}

View file

@ -1,10 +1,9 @@
import {Conversion, DesugaringContext, DesugaringStep, Fuse, OnEvery, OnEveryConcat, SetDefault} from "./Conversion";
import {Concat, Conversion, DesugaringContext, DesugaringStep, Each, Fuse, On, SetDefault} from "./Conversion";
import {LayoutConfigJson} from "../Json/LayoutConfigJson";
import {PrepareLayer} from "./PrepareLayer";
import {LayerConfigJson} from "../Json/LayerConfigJson";
import {Utils} from "../../../Utils";
import Constants from "../../Constants";
import {AllKnownLayouts} from "../../../Customizations/AllKnownLayouts";
import CreateNoteImportLayer from "./CreateNoteImportLayer";
import LayerConfig from "../LayerConfig";
import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson";
@ -457,18 +456,18 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
"Fully prepares and expands a theme",
new PreparePersonalTheme(state),
new WarnForUnsubstitutedLayersInTheme(),
new OnEveryConcat("layers", new SubstituteLayer(state)),
new On("layers", new Concat(new SubstituteLayer(state))),
new SetDefault("socialImage", "assets/SocialImage.png", true),
// We expand all tagrenderings first...
new OnEvery("layers", new PrepareLayer(state)),
new On("layers", new Each(new PrepareLayer(state))),
// Then we apply the override all
new ApplyOverrideAll(),
// And then we prepare all the layers _again_ in case that an override all contained unexpanded tagrenderings!
new OnEvery("layers", new PrepareLayer(state)),
new On("layers", new Each(new PrepareLayer(state))),
new AddDefaultLayers(state),
new AddDependencyLayersToTheme(state),
new AddImportLayers(),
new OnEvery("layers", new AddMiniMap(state))
new On("layers", new Each(new AddMiniMap(state)))
);
}
}

View file

@ -1,4 +1,4 @@
import {DesugaringStep, Fuse, OnEvery} from "./Conversion";
import {DesugaringStep, Each, Fuse, On} from "./Conversion";
import {LayerConfigJson} from "../Json/LayerConfigJson";
import LayerConfig from "../LayerConfig";
import {Utils} from "../../../Utils";
@ -17,11 +17,12 @@ import {QuestionableTagRenderingConfigJson} from "../Json/QuestionableTagRenderi
class ValidateLanguageCompleteness extends DesugaringStep<any> {
private readonly _languages: string[];
constructor(...languages: string[]) {
super("Checks that the given object is fully translated in the specified languages", [], "ValidateLanguageCompleteness");
this._languages = languages;
this._languages = languages ?? ["en"];
}
convert(obj: any, context: string): { result: LayerConfig; errors: string[] } {
@ -29,7 +30,7 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
const translations = Translation.ExtractAllTranslationsFrom(
obj
)
for (const neededLanguage of this._languages ?? ["en"]) {
for (const neededLanguage of this._languages) {
translations
.filter(t => t.tr.translations[neededLanguage] === undefined && t.tr.translations["*"] === undefined)
.forEach(missing => {
@ -173,7 +174,7 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
super("Validates a theme and the contained layers",
new ValidateTheme(knownImagePaths, path, isBuiltin, sharedTagRenderings),
new OnEvery("layers", new ValidateLayer(undefined, false))
new On("layers", new Each(new ValidateLayer(undefined, false)))
);
}
}
@ -510,7 +511,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
}
}
if (json.tagRenderings !== undefined) {
const r = new OnEvery("tagRenderings", new ValidateTagRenderings(json)).convert(json, context)
const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json))).convert(json, context)
warnings.push(...(r.warnings??[]))
errors.push(...(r.errors??[]))
information.push(...(r.information??[]))

View file

@ -183,11 +183,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
}
return str.substr(0, l - 3) + "...";
}
public static FixedLength(str: string, l: number) {
str = Utils.EllipsesAfter(str, l)
while(str.length < l){
str = " "+str
while (str.length < l) {
str = " " + str
}
return str;
}
@ -220,6 +220,23 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return newArr;
}
/**
* In the given list, all values which are lists will be merged with the values, e.g.
*
* Utils.Flatten([ [1,2], 2, [4, [5 ,6]] ]) // => [1, 2, 3, 4, [5, 6]]
*/
public static Flatten<T>(list: (T | T[])[]): T[] {
const result = []
for (const value of list) {
if (Array.isArray(value)) {
result.push(...value)
} else {
result.push(value)
}
}
return result;
}
public static Identical<T>(t1: T[], t2: T[], eq?: (t: T, t0: T) => boolean): boolean {
if (t1.length !== t2.length) {
return false
@ -533,21 +550,21 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (jtp !== "object") {
return
}
if (isLeaf(json)) {
return collect(json, path)
}
} else if (jtp === "boolean" || jtp === "string" || jtp === "number") {
return collect(json,path)
return collect(json, path)
}
if (Array.isArray(json)) {
return json.map((sub,i) => {
return Utils.WalkObject(sub, collect, isLeaf,[...path, i]);
return json.map((sub, i) => {
return Utils.WalkObject(sub, collect, isLeaf, [...path, i]);
})
}
for (const key in json) {
Utils.WalkObject(json[key], collect, isLeaf, [...path,key])
Utils.WalkObject(json[key], collect, isLeaf, [...path, key])
}
}
@ -832,9 +849,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
}
return new Date(str)
}
public static sortedByLevenshteinDistance<T>(reference: string, ts: T[], getName: (t:T) => string): T[]{
const withDistance: [T, number][] = ts.map(t => [t, Utils.levenshteinDistance(getName(t), reference)])
public static sortedByLevenshteinDistance<T>(reference: string, ts: T[], getName: (t: T) => string): T[] {
const withDistance: [T, number][] = ts.map(t => [t, Utils.levenshteinDistance(getName(t), reference)])
withDistance.sort(([_, a], [__, b]) => a - b)
return withDistance.map(n => n[0])
}
@ -872,43 +889,41 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return o
}
private static colorDiff(c0: { r: number, g: number, b: number }, c1: { r: number, g: number, b: number }) {
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b);
}
/**
* Utils.colorAsHex({r: 255, g: 128, b: 0}) // => "#ff8000"
* Utils.colorAsHex(undefined) // => undefined
*/
public static colorAsHex(c:{ r: number, g: number, b: number } ){
if(c === undefined){
public static colorAsHex(c: { r: number, g: number, b: number }) {
if (c === undefined) {
return undefined
}
function componentToHex(n) {
let hex = n.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
return "#" + componentToHex(c.r) + componentToHex(c.g) + componentToHex(c.b);
}
/**
*
*
* Utils.color("#ff8000") // => {r: 255, g:128, b: 0}
* Utils.color(" rgba (12,34,56) ") // => {r: 12, g:34, b: 56}
* Utils.color(" rgba (12,34,56,0.5) ") // => {r: 12, g:34, b: 56}
* Utils.color(undefined) // => undefined
*/
public static color(hex: string): { r: number, g: number, b: number } {
if(hex === undefined){
if (hex === undefined) {
return undefined
}
hex = hex.replace(/[ \t]/g, "")
if (hex.startsWith("rgba(")) {
const match = hex.match(/rgba\(([0-9.]+),([0-9.]+),([0-9.]+)(,[0-9.]*)?\)/)
if(match == undefined){
const match = hex.match(/rgba\(([0-9.]+),([0-9.]+),([0-9.]+)(,[0-9.]*)?\)/)
if (match == undefined) {
return undefined
}
return {r: Number(match[1]), g: Number(match[2]), b:Number( match[3])}
return {r: Number(match[1]), g: Number(match[2]), b: Number(match[3])}
}
if (!hex.startsWith("#")) {
@ -928,9 +943,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
b: parseInt(hex.substr(5, 2), 16),
}
}
public static asDict(tags: {key: string, value: string | number}[]) : Map<string, string | number>{
const d= new Map<string, string | number>()
public static asDict(tags: { key: string, value: string | number }[]): Map<string, string | number> {
const d = new Map<string, string | number>()
for (const tag of tags) {
d.set(tag.key, tag.value)
@ -938,5 +953,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return d
}
private static colorDiff(c0: { r: number, g: number, b: number }, c1: { r: number, g: number, b: number }) {
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b);
}
}

View file

@ -231,6 +231,10 @@
"if": "theme=openwindpowermap",
"then": "./assets/themes/openwindpowermap/logo.svg"
},
{
"if": "theme=parking-lanes",
"then": "./assets/svg/bug.svg"
},
{
"if": "theme=parkings",
"then": "./assets/themes/parkings/parkings.svg"

View file

@ -13,7 +13,7 @@
"watch:css": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch",
"generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css",
"generate:doctests": "doctest-ts-improved .",
"test:run-only": "mocha --require ts-node/register --require tests/testhooks.ts \"./**/*.doctest.ts\" \"tests/*\" \"tests/**/*.ts\"",
"test:run-only": "mocha --require ts-node/register --require test/testhooks.ts \"./**/*.doctest.ts\" \"test/*\" \"test/**/*.ts\"",
"test": "(npm run generate:doctests 2>&1 | grep -v \"No doctests found in\") && npm run test:run-only && npm run clean:tests",
"init": "npm ci && npm run generate && npm run generate:editor-layer-index && npm run generate:layouts && npm run clean",
"add-weblate-upstream": "git remote add weblate-layers https://hosted.weblate.org/git/mapcomplete/layer-translations/ ; git remote add weblate-core https://hosted.weblate.org/git/mapcomplete/layer-core/; git remote add weblate-themes https://hosted.weblate.org/git/mapcomplete/layer-themes/; git remote add weblate-github git@github.com:weblate/MapComplete.git",