forked from MapComplete/MapComplete
		
	Add ifnot-tags for multianswers, in order to indicate something is _not_ possible (e.g.: playment:coins=no)
This commit is contained in:
		
							parent
							
								
									972128516b
								
							
						
					
					
						commit
						ae9d93138b
					
				
					 8 changed files with 99 additions and 131 deletions
				
			
		| 
						 | 
					@ -4,6 +4,7 @@ import Translations from "../../UI/i18n/Translations";
 | 
				
			||||||
import {FromJSON} from "./FromJSON";
 | 
					import {FromJSON} from "./FromJSON";
 | 
				
			||||||
import ValidatedTextField from "../../UI/Input/ValidatedTextField";
 | 
					import ValidatedTextField from "../../UI/Input/ValidatedTextField";
 | 
				
			||||||
import {Translation} from "../../UI/i18n/Translation";
 | 
					import {Translation} from "../../UI/i18n/Translation";
 | 
				
			||||||
 | 
					import {Utils} from "../../Utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***
 | 
					/***
 | 
				
			||||||
 * The parsed version of TagRenderingConfigJSON
 | 
					 * The parsed version of TagRenderingConfigJSON
 | 
				
			||||||
| 
						 | 
					@ -25,6 +26,7 @@ export default class TagRenderingConfig {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    readonly mappings?: {
 | 
					    readonly mappings?: {
 | 
				
			||||||
        readonly if: TagsFilter,
 | 
					        readonly if: TagsFilter,
 | 
				
			||||||
 | 
					        readonly ifnot?: TagsFilter,
 | 
				
			||||||
        readonly then: Translation
 | 
					        readonly then: Translation
 | 
				
			||||||
        readonly hideInAnswer: boolean | TagsFilter
 | 
					        readonly hideInAnswer: boolean | TagsFilter
 | 
				
			||||||
    }[]
 | 
					    }[]
 | 
				
			||||||
| 
						 | 
					@ -74,7 +76,10 @@ export default class TagRenderingConfig {
 | 
				
			||||||
            this.mappings = json.mappings.map((mapping, i) => {
 | 
					            this.mappings = json.mappings.map((mapping, i) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (mapping.then === undefined) {
 | 
					                if (mapping.then === undefined) {
 | 
				
			||||||
                    throw "Invalid mapping: if without body"
 | 
					                    throw `${context}.mapping[${i}]: Invalid mapping: if without body`
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (mapping.ifnot !== undefined && !this.multiAnswer) {
 | 
				
			||||||
 | 
					                    throw `${context}.mapping[${i}]: Invalid mapping: ifnot defined, but the tagrendering is not a multianswer`
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                let hideInAnswer: boolean | TagsFilter = false;
 | 
					                let hideInAnswer: boolean | TagsFilter = false;
 | 
				
			||||||
                if (typeof mapping.hideInAnswer === "boolean") {
 | 
					                if (typeof mapping.hideInAnswer === "boolean") {
 | 
				
			||||||
| 
						 | 
					@ -82,25 +87,53 @@ export default class TagRenderingConfig {
 | 
				
			||||||
                } else if (mapping.hideInAnswer !== undefined) {
 | 
					                } else if (mapping.hideInAnswer !== undefined) {
 | 
				
			||||||
                    hideInAnswer = FromJSON.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`);
 | 
					                    hideInAnswer = FromJSON.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return {
 | 
					                const mp = {
 | 
				
			||||||
                    if: FromJSON.Tag(mapping.if, `${context}.mapping[${i}].if`),
 | 
					                    if: FromJSON.Tag(mapping.if, `${context}.mapping[${i}].if`),
 | 
				
			||||||
 | 
					                    ifnot: (mapping.ifnot !== undefined ? FromJSON.Tag(mapping.ifnot, `${context}.mapping[${i}].ifnot`) : undefined),
 | 
				
			||||||
                    then: Translations.T(mapping.then),
 | 
					                    then: Translations.T(mapping.then),
 | 
				
			||||||
                    hideInAnswer: hideInAnswer
 | 
					                    hideInAnswer: hideInAnswer
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					                if (this.question) {
 | 
				
			||||||
 | 
					                    if (hideInAnswer !== true && !mp.if.isUsableAsAnswer()) {
 | 
				
			||||||
 | 
					                        throw `${context}.mapping[${i}].if: This value cannot be used to answer a question, probably because it contains a regex or an OR. Either change it or set 'hideInAnswer'`
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (hideInAnswer !== true && !(mp.ifnot?.isUsableAsAnswer() ?? true)) {
 | 
				
			||||||
 | 
					                        throw `${context}.mapping[${i}].ifnot: This value cannot be used to answer a question, probably because it contains a regex or an OR. Either change it or set 'hideInAnswer'`
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return mp;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.question && this.freeform?.key === undefined && this.mappings === undefined) {
 | 
					        if (this.question && this.freeform?.key === undefined && this.mappings === undefined) {
 | 
				
			||||||
            throw `A question is defined, but no mappings nor freeform (key) are. The question is ${this.question.txt} at ${context}`
 | 
					            throw `${context}: A question is defined, but no mappings nor freeform (key) are. The question is ${this.question.txt} at ${context}`
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(this.freeform && this.render === undefined){
 | 
					        if (this.freeform && this.render === undefined) {
 | 
				
			||||||
            throw `Detected a freeform key without rendering... Key: ${this.freeform.key} in ${context}`
 | 
					            throw `${context}: Detected a freeform key without rendering... Key: ${this.freeform.key} in ${context}`
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (json.multiAnswer) {
 | 
					        if (this.question !== undefined && json.multiAnswer) {
 | 
				
			||||||
            if ((this.mappings?.length ?? 0) === 0) {
 | 
					            if ((this.mappings?.length ?? 0) === 0) {
 | 
				
			||||||
                throw "MultiAnswer is set, but no mappings are defined"
 | 
					                throw `${context} MultiAnswer is set, but no mappings are defined`
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let allKeys = [];
 | 
				
			||||||
 | 
					            let allHaveIfNot = true;
 | 
				
			||||||
 | 
					            for (const mapping of this.mappings) {
 | 
				
			||||||
 | 
					                if (mapping.hideInAnswer) {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (mapping.ifnot === undefined) {
 | 
				
			||||||
 | 
					                    allHaveIfNot = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                allKeys = allKeys.concat(mapping.if.usedKeys());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            allKeys = Utils.Dedup(allKeys);
 | 
				
			||||||
 | 
					            if (allKeys.length > 1 && !allHaveIfNot) {
 | 
				
			||||||
 | 
					                throw `${context}: A multi-answer is defined, which generates values over multiple keys. Please define ifnot-tags too on every mapping`
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,6 +50,11 @@ export interface TagRenderingConfigJson {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    mappings?: {
 | 
					    mappings?: {
 | 
				
			||||||
        if: AndOrTagConfigJson | string,
 | 
					        if: AndOrTagConfigJson | string,
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Only applicable if 'multiAnswer' is set.
 | 
				
			||||||
 | 
					         * This tag is applied if the respective checkbox is unset
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        ifnot?: AndOrTagConfigJson | string,
 | 
				
			||||||
        then: string | any
 | 
					        then: string | any
 | 
				
			||||||
        hideInAnswer?: boolean
 | 
					        hideInAnswer?: boolean
 | 
				
			||||||
    }[]
 | 
					    }[]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -294,7 +294,7 @@ export default class MetaTagging {
 | 
				
			||||||
        MetaTagging.isOpen,
 | 
					        MetaTagging.isOpen,
 | 
				
			||||||
        MetaTagging.carriageWayWidth,
 | 
					        MetaTagging.carriageWayWidth,
 | 
				
			||||||
        MetaTagging.directionSimplified,
 | 
					        MetaTagging.directionSimplified,
 | 
				
			||||||
        MetaTagging.currentTime
 | 
					    //    MetaTagging.currentTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import {Utils} from "../Utils";
 | 
					import {Utils} from "../Utils";
 | 
				
			||||||
 | 
					import {type} from "os";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class TagsFilter {
 | 
					export abstract class TagsFilter {
 | 
				
			||||||
    abstract matches(tags: { k: string, v: string }[]): boolean
 | 
					    abstract matches(tags: { k: string, v: string }[]): boolean
 | 
				
			||||||
| 
						 | 
					@ -16,6 +17,8 @@ export abstract class TagsFilter {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    abstract asHumanString(linkToWiki: boolean, shorten: boolean);
 | 
					    abstract asHumanString(linkToWiki: boolean, shorten: boolean);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    abstract usedKeys(): string[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,6 +89,13 @@ export class RegexTag extends TagsFilter {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    usedKeys(): string[] {
 | 
				
			||||||
 | 
					        if (typeof this.key === "string") {
 | 
				
			||||||
 | 
					            return [this.key];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw "Key cannot be determined as it is a regex"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -163,6 +173,10 @@ export class Tag extends TagsFilter {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    usedKeys(): string[] {
 | 
				
			||||||
 | 
					        return [this.key];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -228,6 +242,10 @@ export class Or extends TagsFilter {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    usedKeys(): string[] {
 | 
				
			||||||
 | 
					        return [].concat(this.or.map(subkeys => subkeys.usedKeys()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -343,6 +361,10 @@ export class And extends TagsFilter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    usedKeys(): string[] {
 | 
				
			||||||
 | 
					        return [].concat(this.and.map(subkeys => subkeys.usedKeys()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -456,7 +478,7 @@ export class TagUtils {
 | 
				
			||||||
        const splitted = TagUtils.SplitKeys([tag]);
 | 
					        const splitted = TagUtils.SplitKeys([tag]);
 | 
				
			||||||
        for (const splitKey in splitted) {
 | 
					        for (const splitKey in splitted) {
 | 
				
			||||||
            const neededValues = splitted[splitKey];
 | 
					            const neededValues = splitted[splitKey];
 | 
				
			||||||
            if(tags[splitKey] === undefined) {
 | 
					            if (tags[splitKey] === undefined) {
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -107,14 +107,14 @@ export default class TagRenderingQuestion extends UIElement {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this._configuration.multiAnswer) {
 | 
					        if (this._configuration.multiAnswer) {
 | 
				
			||||||
            return this.GenerateMultiAnswer(mappings, ff)
 | 
					            return this.GenerateMultiAnswer(mappings, ff, this._configuration.mappings.map(mp => mp.ifnot))
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return new RadioButton(mappings, false)
 | 
					            return new RadioButton(mappings, false)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private GenerateMultiAnswer(elements: InputElement<TagsFilter>[], freeformField: InputElement<TagsFilter>): InputElement<TagsFilter> {
 | 
					    private GenerateMultiAnswer(elements: InputElement<TagsFilter>[], freeformField: InputElement<TagsFilter>, ifNotSelected: TagsFilter[]): InputElement<TagsFilter> {
 | 
				
			||||||
        const checkBoxes = new CheckBoxes(elements);
 | 
					        const checkBoxes = new CheckBoxes(elements);
 | 
				
			||||||
        const inputEl = new InputElementMap<number[], TagsFilter>(
 | 
					        const inputEl = new InputElementMap<number[], TagsFilter>(
 | 
				
			||||||
            checkBoxes,
 | 
					            checkBoxes,
 | 
				
			||||||
| 
						 | 
					@ -126,6 +126,18 @@ export default class TagRenderingQuestion extends UIElement {
 | 
				
			||||||
                    return undefined;
 | 
					                    return undefined;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                const tags: TagsFilter[] = indices.map(i => elements[i].GetValue().data);
 | 
					                const tags: TagsFilter[] = indices.map(i => elements[i].GetValue().data);
 | 
				
			||||||
 | 
					                const oppositeTags: TagsFilter[] = [];
 | 
				
			||||||
 | 
					                for (let i = 0; i < ifNotSelected.length; i++) {
 | 
				
			||||||
 | 
					                    if(indices.indexOf(i) >= 0){
 | 
				
			||||||
 | 
					                        continue;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    const notSelected = ifNotSelected[i];
 | 
				
			||||||
 | 
					                    if(notSelected === undefined){
 | 
				
			||||||
 | 
					                        continue;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    oppositeTags.push(notSelected);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                tags.push(TagUtils.FlattenMultiAnswer(oppositeTags));
 | 
				
			||||||
                return TagUtils.FlattenMultiAnswer(tags);
 | 
					                return TagUtils.FlattenMultiAnswer(tags);
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            (tags: TagsFilter) => {
 | 
					            (tags: TagsFilter) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										47
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										47
									
								
								Utils.ts
									
										
									
									
									
								
							| 
						 | 
					@ -141,21 +141,9 @@ export class Utils {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Date will be undefined on failure
 | 
					    // Date will be undefined on failure
 | 
				
			||||||
    public static changesetDate(id: number, action: ((isFound: Date) => void)): void {
 | 
					 | 
				
			||||||
        $.getJSON("https://www.openstreetmap.org/api/0.6/changeset/" + id,
 | 
					 | 
				
			||||||
            function (data) {
 | 
					 | 
				
			||||||
                console.log(data)
 | 
					 | 
				
			||||||
                action(new Date(data.elements[0].created_at));
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .fail(() => {
 | 
					 | 
				
			||||||
                action(undefined);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    public static LoadCustomCss(location: string){
 | 
					    public static LoadCustomCss(location: string){
 | 
				
			||||||
        var head = document.getElementsByTagName('head')[0];
 | 
					        const head = document.getElementsByTagName('head')[0];
 | 
				
			||||||
        var link = document.createElement('link');
 | 
					        const link = document.createElement('link');
 | 
				
			||||||
        link.id = "customCss";
 | 
					        link.id = "customCss";
 | 
				
			||||||
        link.rel = 'stylesheet';
 | 
					        link.rel = 'stylesheet';
 | 
				
			||||||
        link.type = 'text/css';
 | 
					        link.type = 'text/css';
 | 
				
			||||||
| 
						 | 
					@ -164,17 +152,6 @@ export class Utils {
 | 
				
			||||||
        head.appendChild(link);
 | 
					        head.appendChild(link);
 | 
				
			||||||
        console.log("Added custom layout ",location)
 | 
					        console.log("Added custom layout ",location)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static MatchKeys(object: any, prototype: any, context?: string){
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const objectKey in object) {
 | 
					 | 
				
			||||||
            if(prototype[objectKey] === undefined){
 | 
					 | 
				
			||||||
                console.error("Key ", objectKey, "might be not supported (in context",context,")")
 | 
					 | 
				
			||||||
            }   
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    static Merge(source: any, target: any){
 | 
					    static Merge(source: any, target: any){
 | 
				
			||||||
        target = JSON.parse(JSON.stringify(target));
 | 
					        target = JSON.parse(JSON.stringify(target));
 | 
				
			||||||
        source = JSON.parse(JSON.stringify(source));
 | 
					        source = JSON.parse(JSON.stringify(source));
 | 
				
			||||||
| 
						 | 
					@ -195,24 +172,4 @@ export class Utils {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return target;
 | 
					        return target;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    static ToMuchTags(source: any, toCheck: any, context: string){
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const key in toCheck) {
 | 
					 | 
				
			||||||
            const toCheckV = toCheck[key];
 | 
					 | 
				
			||||||
            const sourceV = source[key];
 | 
					 | 
				
			||||||
            if(sourceV === undefined){
 | 
					 | 
				
			||||||
                console.error("Probably a wrong tag in ", context, ": ", key, "might be wrong")
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if(typeof toCheckV === "object"){
 | 
					 | 
				
			||||||
                if(typeof sourceV !== "object"){
 | 
					 | 
				
			||||||
                    console.error("Probably a wrong value in ", context, ": ", key, "is a fixed value in the source")
 | 
					 | 
				
			||||||
                }else{
 | 
					 | 
				
			||||||
                    Utils.ToMuchTags(sourceV, toCheckV, context+"."+key);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,14 +104,17 @@
 | 
				
			||||||
      "mappings": [
 | 
					      "mappings": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "if": "payment:coins=yes",
 | 
					          "if": "payment:coins=yes",
 | 
				
			||||||
 | 
					          "ifnot":  "payment:coins=no",
 | 
				
			||||||
          "then": "Payment with coins is possible"
 | 
					          "then": "Payment with coins is possible"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "if": "payment:notes=yes",
 | 
					          "if": "payment:notes=yes",
 | 
				
			||||||
 | 
					          "ifnot": "payment:notes=no",
 | 
				
			||||||
          "then": "Payment with notes is possible"
 | 
					          "then": "Payment with notes is possible"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "if": "payment:cards=yes",
 | 
					          "if": "payment:cards=yes",
 | 
				
			||||||
 | 
					          "ifnot": "payment:cards=no",
 | 
				
			||||||
          "then": "Payment with cards is possible"
 | 
					          "then": "Payment with cards is possible"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
| 
						 | 
					@ -157,22 +160,27 @@
 | 
				
			||||||
      "mappings": [
 | 
					      "mappings": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "if": "vending:bicycle_light=yes",
 | 
					          "if": "vending:bicycle_light=yes",
 | 
				
			||||||
 | 
					          "ifnot": "vending:bicycle_light=no",
 | 
				
			||||||
          "then": "Bicycle lights are sold here"
 | 
					          "then": "Bicycle lights are sold here"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "if": "vending:gloves=yes",
 | 
					          "if": "vending:gloves=yes",
 | 
				
			||||||
 | 
					          "ifnot": "vending:gloves=no",
 | 
				
			||||||
          "then": "Gloves are sold here"
 | 
					          "then": "Gloves are sold here"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "if": "vending:bicycle_repair_kit=yes",
 | 
					          "if": "vending:bicycle_repair_kit=yes",
 | 
				
			||||||
 | 
					          "ifnot": "vending:bicycle_repair_kit=no",
 | 
				
			||||||
          "then": "Bicycle repair kits are sold here"
 | 
					          "then": "Bicycle repair kits are sold here"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "if": "vending:bicycle_pump=yes",
 | 
					          "if": "vending:bicycle_pump=yes",
 | 
				
			||||||
 | 
					          "ifnot": "vending:bicycle_pump=no",
 | 
				
			||||||
          "then": "Bicycle pumps are sold here"
 | 
					          "then": "Bicycle pumps are sold here"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "if": "vending:bicycle_lock=yes",
 | 
					          "if": "vending:bicycle_lock=yes",
 | 
				
			||||||
 | 
					          "ifnot": "vending:bicycle_lock=no",
 | 
				
			||||||
          "then": "Bicycle locks are sold here"
 | 
					          "then": "Bicycle locks are sold here"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										69
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										69
									
								
								test.ts
									
										
									
									
									
								
							| 
						 | 
					@ -13,72 +13,3 @@ const images = new UIEventSource<{ url: string, key: string }[]>(
 | 
				
			||||||
new ImageCarousel(images, new UIEventSource<any>({"image:1":"https://2.bp.blogspot.com/-fQiZkz9Zlzg/T_xe2X2Ia3I/AAAAAAAAA0Q/VPS8Mb8xtIQ/s1600/cat+15.jpg"}))
 | 
					new ImageCarousel(images, new UIEventSource<any>({"image:1":"https://2.bp.blogspot.com/-fQiZkz9Zlzg/T_xe2X2Ia3I/AAAAAAAAA0Q/VPS8Mb8xtIQ/s1600/cat+15.jpg"}))
 | 
				
			||||||
    .AttachTo("maindiv")    
 | 
					    .AttachTo("maindiv")    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*/
 | 
					 | 
				
			||||||
import {Utils} from "./Utils";
 | 
					 | 
				
			||||||
import {FixedUiElement} from "./UI/Base/FixedUiElement";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function generateStats(action: (stats: string) => void) {
 | 
					 | 
				
			||||||
    // Binary searches the latest changeset
 | 
					 | 
				
			||||||
    function search(lowerBound: number,
 | 
					 | 
				
			||||||
                    upperBound: number,
 | 
					 | 
				
			||||||
                    onCsFound: ((id: number, lastDate: Date) => void),
 | 
					 | 
				
			||||||
                    depth = 0) {
 | 
					 | 
				
			||||||
        if (depth > 30) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const tested = Math.floor((lowerBound + upperBound) / 2);
 | 
					 | 
				
			||||||
        console.log("Testing", tested)
 | 
					 | 
				
			||||||
        Utils.changesetDate(tested, (createdAtDate: Date) => {
 | 
					 | 
				
			||||||
            new FixedUiElement(`Searching, value between ${lowerBound} and ${upperBound}. Queries till now: ${depth}`).AttachTo('maindiv')
 | 
					 | 
				
			||||||
            if (lowerBound + 1 >= upperBound) {
 | 
					 | 
				
			||||||
                onCsFound(lowerBound, createdAtDate);
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (createdAtDate !== undefined) {
 | 
					 | 
				
			||||||
                search(tested, upperBound, onCsFound, depth + 1)
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                search(lowerBound, tested, onCsFound, depth + 1);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    search(91000000, 100000000, (last, lastDate: Date) => {
 | 
					 | 
				
			||||||
            const link = "http://osm.org/changeset/" + last;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const delta = 100000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Utils.changesetDate(last - delta, (prevDate) => {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const diff = (lastDate.getTime() - prevDate.getTime()) / 1000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Diff: seconds needed/delta changesets
 | 
					 | 
				
			||||||
                const secsPerCS = diff / delta;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const stillNeeded = 1000000 - (last % 1000000);
 | 
					 | 
				
			||||||
                const timeNeededSeconds = Math.floor(secsPerCS * stillNeeded);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const secNeeded = timeNeededSeconds % 60;
 | 
					 | 
				
			||||||
                const minNeeded = Math.floor(timeNeededSeconds / 60) % 60;
 | 
					 | 
				
			||||||
                const hourNeeded = Math.floor(timeNeededSeconds / (60 * 60)) % 24;
 | 
					 | 
				
			||||||
                const daysNeeded = Math.floor(timeNeededSeconds / (24 * 60 * 60));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const result = `Last changeset: <a href='${link}'>${link}</a><br/>We needed ${(Math.floor(diff / 60))} minutes for the last ${delta} changesets.<br/>
 | 
					 | 
				
			||||||
This is around ${secsPerCS} seconds/changeset.<br/> The next million (still ${stillNeeded} away) will be broken in around ${daysNeeded} days ${hourNeeded}:${minNeeded}:${secNeeded}`
 | 
					 | 
				
			||||||
                action(result);
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
generateStats((stats) => {
 | 
					 | 
				
			||||||
    new FixedUiElement(stats).AttachTo('maindiv')
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//*/
 | 
					 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue