forked from MapComplete/MapComplete
		
	Add further support for special UI-elements; add documentation, fix a few bugs
This commit is contained in:
		
							parent
							
								
									3ab3cef249
								
							
						
					
					
						commit
						07e611bf10
					
				
					 12 changed files with 113 additions and 55 deletions
				
			
		| 
						 | 
					@ -145,7 +145,6 @@ export class FromJSON {
 | 
				
			||||||
                    json = "{image_carousel()}{image_upload()}";
 | 
					                    json = "{image_carousel()}{image_upload()}";
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            console.warn("Possible literal rendering:", json)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return new TagRenderingOptions({
 | 
					            return new TagRenderingOptions({
 | 
				
			||||||
                freeform: {
 | 
					                freeform: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ export class ElementStorage {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addElement(element): UIEventSource<any> {
 | 
					    addElement(element): UIEventSource<any> {
 | 
				
			||||||
        const eventSource = new UIEventSource<any>(element.properties);
 | 
					        const eventSource = new UIEventSource<any>(element.properties);
 | 
				
			||||||
 | 
					        console.log("Creating a new tag storate for ", element.properties.id)
 | 
				
			||||||
        this._elements[element.properties.id] = eventSource;
 | 
					        this._elements[element.properties.id] = eventSource;
 | 
				
			||||||
        return eventSource;
 | 
					        return eventSource;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,7 +111,10 @@ export class FilteredLayer {
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (this.filters.matches(tags)) {
 | 
					            if (this.filters.matches(tags)) {
 | 
				
			||||||
                const centerPoint = GeoOperations.centerpoint(feature);
 | 
					                const centerPoint = GeoOperations.centerpoint(feature);
 | 
				
			||||||
                feature.properties["_surface"] = "" + GeoOperations.surfaceAreaInSqMeters(feature);
 | 
					                const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature);
 | 
				
			||||||
 | 
					                feature.properties["_surface"] = "" + sqMeters;
 | 
				
			||||||
 | 
					                feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000)/10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const lat = centerPoint.geometry.coordinates[1];
 | 
					                const lat = centerPoint.geometry.coordinates[1];
 | 
				
			||||||
                const lon = centerPoint.geometry.coordinates[0]
 | 
					                const lon = centerPoint.geometry.coordinates[0]
 | 
				
			||||||
                feature.properties["_lon"] = "" + lat; // We expect a string here for lat/lon
 | 
					                feature.properties["_lon"] = "" + lat; // We expect a string here for lat/lon
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@ export class ImageSearcher extends UIEventSource<{key: string, url: string}[]> {
 | 
				
			||||||
    private readonly _commons = new UIEventSource<string>("");
 | 
					    private readonly _commons = new UIEventSource<string>("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(tags: UIEventSource<any>) {
 | 
					    constructor(tags: UIEventSource<any>, imagePrefix = "image", loadSpecial = true) {
 | 
				
			||||||
        super([]);
 | 
					        super([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this._tags = tags;
 | 
					        this._tags = tags;
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ export class ImageSearcher extends UIEventSource<{key: string, url: string}[]> {
 | 
				
			||||||
        this._commons.addCallback(() => self.LoadCommons());
 | 
					        this._commons.addCallback(() => self.LoadCommons());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this._tags.addCallbackAndRun(() => self.LoadImages());
 | 
					        this._tags.addCallbackAndRun(() => self.LoadImages(imagePrefix, loadSpecial));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,9 +48,9 @@ export class ImageSearcher extends UIEventSource<{key: string, url: string}[]> {
 | 
				
			||||||
        if (url === undefined || url === null || url === "") {
 | 
					        if (url === undefined || url === null || url === "") {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const el of this.data) {
 | 
					        for (const el of this.data) {
 | 
				
			||||||
            if (el.url === url) {
 | 
					            if (el.url === url) {
 | 
				
			||||||
 | 
					                // This url is already seen -> don't add it
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -102,17 +102,18 @@ export class ImageSearcher extends UIEventSource<{key: string, url: string}[]> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private LoadImages(imagePrefix: string = "image", loadAdditional = true): void {
 | 
					    private LoadImages(imagePrefix: string, loadAdditional: boolean): void {
 | 
				
			||||||
        const imageTag = this._tags.data.image;
 | 
					        console.log("Loading images from",this._tags)
 | 
				
			||||||
 | 
					        const imageTag = this._tags.data[imagePrefix];
 | 
				
			||||||
        if (imageTag !== undefined) {
 | 
					        if (imageTag !== undefined) {
 | 
				
			||||||
            const bareImages = imageTag.split(";");
 | 
					            const bareImages = imageTag.split(";");
 | 
				
			||||||
            for (const bareImage of bareImages) {
 | 
					            for (const bareImage of bareImages) {
 | 
				
			||||||
                this.AddImage("image", bareImage);
 | 
					                this.AddImage(imagePrefix, bareImage);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const key in this._tags.data) {
 | 
					        for (const key in this._tags.data) {
 | 
				
			||||||
            if (key.startsWith("image:")) {
 | 
					            if (key.startsWith(imagePrefix+":")) {
 | 
				
			||||||
                const url = this._tags.data[key]
 | 
					                const url = this._tags.data[key]
 | 
				
			||||||
                this.AddImage(key, url);
 | 
					                this.AddImage(key, url);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -130,7 +131,7 @@ export class ImageSearcher extends UIEventSource<{key: string, url: string}[]> {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (this._tags.data.mapillary) {
 | 
					            if (this._tags.data.mapillary) {
 | 
				
			||||||
                this.AddImage("mapillary", "https://www.mapillary.com/map/im/" + this._tags.data.mapillary)
 | 
					                this.AddImage(undefined,"https://www.mapillary.com/map/im/" + this._tags.data.mapillary)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,7 @@ export class Changes {
 | 
				
			||||||
        if(pending.length === 0){
 | 
					        if(pending.length === 0){
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        console.log("Sending ping",eventSource)
 | 
				
			||||||
        eventSource.ping();
 | 
					        eventSource.ping();
 | 
				
			||||||
        this.uploadAll([], pending);
 | 
					        this.uploadAll([], pending);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ import State from "../../State";
 | 
				
			||||||
import {VariableUiElement} from "../Base/VariableUIElement";
 | 
					import {VariableUiElement} from "../Base/VariableUIElement";
 | 
				
			||||||
import {FromJSON} from "../../Customizations/JSON/FromJSON";
 | 
					import {FromJSON} from "../../Customizations/JSON/FromJSON";
 | 
				
			||||||
import ValidatedTextField from "../Input/ValidatedTextField";
 | 
					import ValidatedTextField from "../Input/ValidatedTextField";
 | 
				
			||||||
 | 
					import SpecialVisualizations from "../SpecialVisualizations";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> {
 | 
					export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,7 +84,10 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
 | 
				
			||||||
        const settings: (string | SingleSetting<any>)[] = [
 | 
					        const settings: (string | SingleSetting<any>)[] = [
 | 
				
			||||||
            setting(
 | 
					            setting(
 | 
				
			||||||
                options?.noLanguage ? new TextField({placeholder:"Rendering"}) :
 | 
					                options?.noLanguage ? new TextField({placeholder:"Rendering"}) :
 | 
				
			||||||
                    new MultiLingualTextFields(languages), "render", "Value to show", " Renders this value. Note that <span class='literal-code'>{key}</span>-parts are substituted by the corresponding values of the element. If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value."),
 | 
					                    new MultiLingualTextFields(languages), "render", "Value to show", 
 | 
				
			||||||
 | 
					                "Renders this value. Note that <span class='literal-code'>{key}</span>-parts are substituted by the corresponding values of the element. If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value." +
 | 
				
			||||||
 | 
					                "<br/><br/>" +
 | 
				
			||||||
 | 
					                "Furhtermore, some special functions are supported:"+SpecialVisualizations.HelpMessage.Render()),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            questionsNotUnlocked ? `You need at least ${State.userJourney.themeGeneratorFullUnlock} changesets to unlock the 'question'-field and to use your theme to edit OSM data` : "",
 | 
					            questionsNotUnlocked ? `You need at least ${State.userJourney.themeGeneratorFullUnlock} changesets to unlock the 'question'-field and to use your theme to edit OSM data` : "",
 | 
				
			||||||
            ...(options?.disableQuestions ? [] : questionSettings),
 | 
					            ...(options?.disableQuestions ? [] : questionSettings),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,9 +11,9 @@ export class ImageCarousel extends TagDependantUIElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public readonly slideshow: SlideShow;
 | 
					    public readonly slideshow: SlideShow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(tags: UIEventSource<any>) {
 | 
					    constructor(tags: UIEventSource<any>, imagePrefix: string = "image", loadSpecial: boolean =true) {
 | 
				
			||||||
        super(tags);
 | 
					        super(tags);
 | 
				
			||||||
        const searcher : UIEventSource<{url:string}[]> = new ImageSearcher(tags);
 | 
					        const searcher : UIEventSource<{url:string}[]> = new ImageSearcher(tags, imagePrefix, loadSpecial);
 | 
				
			||||||
        const uiElements = searcher.map((imageURLS: {key: string, url:string}[]) => {
 | 
					        const uiElements = searcher.map((imageURLS: {key: string, url:string}[]) => {
 | 
				
			||||||
            const uiElements: UIElement[] = [];
 | 
					            const uiElements: UIElement[] = [];
 | 
				
			||||||
            for (const url of imageURLS) {
 | 
					            for (const url of imageURLS) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,10 +17,12 @@ export class ImageUploadFlow extends UIElement {
 | 
				
			||||||
    private readonly _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
					    private readonly _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
				
			||||||
    private readonly _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
					    private readonly _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
				
			||||||
    private readonly _connectButton: UIElement;
 | 
					    private readonly _connectButton: UIElement;
 | 
				
			||||||
 | 
					    private readonly _imagePrefix: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(tags: UIEventSource<any>) {
 | 
					    constructor(tags: UIEventSource<any>, imagePrefix: string = "image") {
 | 
				
			||||||
        super(State.state.osmConnection.userDetails);
 | 
					        super(State.state.osmConnection.userDetails);
 | 
				
			||||||
        this._tags = tags;
 | 
					        this._tags = tags;
 | 
				
			||||||
 | 
					        this._imagePrefix = imagePrefix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.ListenTo(this._isUploading);
 | 
					        this.ListenTo(this._isUploading);
 | 
				
			||||||
        this.ListenTo(this._didFail);
 | 
					        this.ListenTo(this._didFail);
 | 
				
			||||||
| 
						 | 
					@ -131,20 +133,21 @@ export class ImageUploadFlow extends UIElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private handleSuccessfulUpload(url) {
 | 
					    private handleSuccessfulUpload(url) {
 | 
				
			||||||
        const tags = this._tags.data;
 | 
					        const tags = this._tags.data;
 | 
				
			||||||
        let key = "image";
 | 
					        let key = this._imagePrefix;
 | 
				
			||||||
        if (tags["image"] !== undefined) {
 | 
					        if (tags[this._imagePrefix] !== undefined) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let freeIndex = 0;
 | 
					            let freeIndex = 0;
 | 
				
			||||||
            while (tags["image:" + freeIndex] !== undefined) {
 | 
					            while (tags[this._imagePrefix + ":" + freeIndex] !== undefined) {
 | 
				
			||||||
                freeIndex++;
 | 
					                freeIndex++;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            key = "image:" + freeIndex;
 | 
					            key = this._imagePrefix + ":" + freeIndex;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.log("Adding image:" + key, url);
 | 
					        console.log("Adding image:" + key, url);
 | 
				
			||||||
        State.state.changes.addTag(tags.id, new Tag(key, url));
 | 
					        State.state.changes.addTag(tags.id, new Tag(key, url));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private handleFiles(files) {
 | 
					    private handleFiles(files) {
 | 
				
			||||||
 | 
					        console.log("Received images from the user, starting upload")
 | 
				
			||||||
        this._isUploading.setData(files.length);
 | 
					        this._isUploading.setData(files.length);
 | 
				
			||||||
        this._allDone.setData(false);
 | 
					        this._allDone.setData(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -189,7 +192,6 @@ export class ImageUploadFlow extends UIElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    InnerUpdate(htmlElement: HTMLElement) {
 | 
					    InnerUpdate(htmlElement: HTMLElement) {
 | 
				
			||||||
        super.InnerUpdate(htmlElement);
 | 
					        super.InnerUpdate(htmlElement);
 | 
				
			||||||
        const user = State.state.osmConnection.userDetails.data;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this._licensePicker.Update()
 | 
					        this._licensePicker.Update()
 | 
				
			||||||
        const form = document.getElementById('fileselector-form-' + this.id) as HTMLFormElement
 | 
					        const form = document.getElementById('fileselector-form-' + this.id) as HTMLFormElement
 | 
				
			||||||
| 
						 | 
					@ -197,8 +199,7 @@ export class ImageUploadFlow extends UIElement {
 | 
				
			||||||
        const self = this
 | 
					        const self = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function submitHandler() {
 | 
					        function submitHandler() {
 | 
				
			||||||
            const files = $(selector).prop('files');
 | 
					            self.handleFiles($(selector).prop('files'))
 | 
				
			||||||
            self.handleFiles(files)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (selector != null && form != null) {
 | 
					        if (selector != null && form != null) {
 | 
				
			||||||
| 
						 | 
					@ -206,8 +207,6 @@ export class ImageUploadFlow extends UIElement {
 | 
				
			||||||
                submitHandler()
 | 
					                submitHandler()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            form.addEventListener('submit', e => {
 | 
					            form.addEventListener('submit', e => {
 | 
				
			||||||
                console.log(e)
 | 
					 | 
				
			||||||
                alert('wait')
 | 
					 | 
				
			||||||
                e.preventDefault()
 | 
					                e.preventDefault()
 | 
				
			||||||
                submitHandler()
 | 
					                submitHandler()
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const self = this;
 | 
					        const self = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.currentTags = this._source.map(tags => {
 | 
					        this.currentTags = tags.map(tags => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (options.tagsPreprocessor === undefined) {
 | 
					                if (options.tagsPreprocessor === undefined) {
 | 
				
			||||||
                    return tags;
 | 
					                    return tags;
 | 
				
			||||||
| 
						 | 
					@ -96,6 +96,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
 | 
				
			||||||
                return newTags;
 | 
					                return newTags;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        tags.addCallback(() => self.currentTags.ping());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (options.question !== undefined) {
 | 
					        if (options.question !== undefined) {
 | 
				
			||||||
            this._question = options.question;
 | 
					            this._question = options.question;
 | 
				
			||||||
| 
						 | 
					@ -516,7 +517,10 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
 | 
				
			||||||
        this._editButton.Update();
 | 
					        this._editButton.Update();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private answerCache = {}
 | 
					    private readonly answerCache = {}
 | 
				
			||||||
 | 
					    // Makes sure that the elements receive updates
 | 
				
			||||||
 | 
					    // noinspection JSMismatchedCollectionQueryUpdate
 | 
				
			||||||
 | 
					    private readonly substitutedElements : UIElement[]= [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ApplyTemplate(template: string | Translation): UIElement {
 | 
					    private ApplyTemplate(template: string | Translation): UIElement {
 | 
				
			||||||
        const tr = Translations.WT(template);
 | 
					        const tr = Translations.WT(template);
 | 
				
			||||||
| 
						 | 
					@ -526,6 +530,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
 | 
				
			||||||
        // We have to cache these elemnts, otherwise it is to slow
 | 
					        // We have to cache these elemnts, otherwise it is to slow
 | 
				
			||||||
        const el = new SubstitutedTranslation(tr, this.currentTags);
 | 
					        const el = new SubstitutedTranslation(tr, this.currentTags);
 | 
				
			||||||
        this.answerCache[tr.id] = el;
 | 
					        this.answerCache[tr.id] = el;
 | 
				
			||||||
 | 
					        this.substitutedElements.push(el);
 | 
				
			||||||
        return el;
 | 
					        return el;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ import {ImageUploadFlow} from "./Image/ImageUploadFlow";
 | 
				
			||||||
export class SubstitutedTranslation extends UIElement {
 | 
					export class SubstitutedTranslation extends UIElement {
 | 
				
			||||||
    private readonly tags: UIEventSource<any>;
 | 
					    private readonly tags: UIEventSource<any>;
 | 
				
			||||||
    private readonly translation: Translation;
 | 
					    private readonly translation: Translation;
 | 
				
			||||||
    private content: UIElement;
 | 
					    private content: UIElement[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
        translation: Translation,
 | 
					        translation: Translation,
 | 
				
			||||||
| 
						 | 
					@ -25,18 +25,19 @@ export class SubstitutedTranslation extends UIElement {
 | 
				
			||||||
        Locale.language.addCallbackAndRun(() => {
 | 
					        Locale.language.addCallbackAndRun(() => {
 | 
				
			||||||
            self.content = self.CreateContent();
 | 
					            self.content = self.CreateContent();
 | 
				
			||||||
            self.Update();
 | 
					            self.Update();
 | 
				
			||||||
        })
 | 
					        });
 | 
				
			||||||
 | 
					        this.dumbMode = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    InnerRender(): string {
 | 
					    InnerRender(): string {
 | 
				
			||||||
        return this.content.Render();
 | 
					        return new Combine(this.content).Render();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CreateContent(): UIElement {
 | 
					    private CreateContent(): UIElement[] {
 | 
				
			||||||
        let txt = this.translation?.txt;
 | 
					        let txt = this.translation?.txt;
 | 
				
			||||||
        if (txt === undefined) {
 | 
					        if (txt === undefined) {
 | 
				
			||||||
            return new FixedUiElement("")
 | 
					            return []
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const tags = this.tags.data;
 | 
					        const tags = this.tags.data;
 | 
				
			||||||
        for (const key in tags) {
 | 
					        for (const key in tags) {
 | 
				
			||||||
| 
						 | 
					@ -44,11 +45,10 @@ export class SubstitutedTranslation extends UIElement {
 | 
				
			||||||
            txt = txt.split("{" + key + "}").join(tags[key]);
 | 
					            txt = txt.split("{" + key + "}").join(tags[key]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.EvaluateSpecialComponents(txt);
 | 
				
			||||||
        return new Combine(this.EvaluateSpecialComponents(txt));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public EvaluateSpecialComponents(template: string): UIElement[] {
 | 
					    private EvaluateSpecialComponents(template: string): UIElement[] {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
 | 
					        for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,10 +58,22 @@ export class SubstitutedTranslation extends UIElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // We found a special component that should be brought to live
 | 
					                // We found a special component that should be brought to live
 | 
				
			||||||
                const partBefore = this.EvaluateSpecialComponents(matched[1]);
 | 
					                const partBefore = this.EvaluateSpecialComponents(matched[1]);
 | 
				
			||||||
                const argument = matched[2];
 | 
					                const argument = matched[2].trim();
 | 
				
			||||||
                const partAfter = this.EvaluateSpecialComponents(matched[3]);
 | 
					                const partAfter = this.EvaluateSpecialComponents(matched[3]);
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    const args = argument.trim().split(",").map(str => str.trim());
 | 
					                    const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
 | 
				
			||||||
 | 
					                    if (argument.length > 0) {
 | 
				
			||||||
 | 
					                        const realArgs = argument.split(",").map(str => str.trim());
 | 
				
			||||||
 | 
					                        for (let i = 0; i < realArgs.length; i++) {
 | 
				
			||||||
 | 
					                            if (args.length <= i) {
 | 
				
			||||||
 | 
					                                args.push(realArgs[i]);
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                args[i] = realArgs[i];
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    const element = knownSpecial.constr(this.tags, args);
 | 
					                    const element = knownSpecial.constr(this.tags, args);
 | 
				
			||||||
                    return [...partBefore, element, ...partAfter]
 | 
					                    return [...partBefore, element, ...partAfter]
 | 
				
			||||||
                } catch (e) {
 | 
					                } catch (e) {
 | 
				
			||||||
| 
						 | 
					@ -83,6 +95,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
        funcName: string,
 | 
					        funcName: string,
 | 
				
			||||||
        constr: ((tagSource: UIEventSource<any>, argument: string[]) => UIElement),
 | 
					        constr: ((tagSource: UIEventSource<any>, argument: string[]) => UIElement),
 | 
				
			||||||
        docs: string,
 | 
					        docs: string,
 | 
				
			||||||
 | 
					        example?: string,
 | 
				
			||||||
        args: { name: string, defaultValue?: string, doc: string }[]
 | 
					        args: { name: string, defaultValue?: string, doc: string }[]
 | 
				
			||||||
    }[] =
 | 
					    }[] =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -91,16 +104,17 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                funcName: "image_carousel",
 | 
					                funcName: "image_carousel",
 | 
				
			||||||
                docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)",
 | 
					                docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)",
 | 
				
			||||||
                args: [{
 | 
					                args: [{
 | 
				
			||||||
                    name: "image tag(s)",
 | 
					                    name: "image key/prefix",
 | 
				
			||||||
                    defaultValue: "image,image:*,wikidata,wikipedia,wikimedia_commons",
 | 
					                    defaultValue: "image",
 | 
				
			||||||
                    doc: "Image tag(s) where images are searched"
 | 
					                    doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... "
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        name: "smart search",
 | 
				
			||||||
 | 
					                        defaultValue: "true",
 | 
				
			||||||
 | 
					                        doc: "Also include images given via 'Wikidata', 'wikimedia_commons' and 'mapillary"
 | 
				
			||||||
                    }],
 | 
					                    }],
 | 
				
			||||||
                constr: (tags, args) => {
 | 
					                constr: (tags, args) => {
 | 
				
			||||||
                    if (args.length > 0) {
 | 
					                    return new ImageCarousel(tags, args[0], args[1].toLowerCase() === "true");
 | 
				
			||||||
                        console.error("TODO HANDLE THESE ARGS") // TODO FIXME
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return new ImageCarousel(tags);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -108,15 +122,12 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                funcName: "image_upload",
 | 
					                funcName: "image_upload",
 | 
				
			||||||
                docs: "Creates a button where a user can upload an image to IMGUR",
 | 
					                docs: "Creates a button where a user can upload an image to IMGUR",
 | 
				
			||||||
                args: [{
 | 
					                args: [{
 | 
				
			||||||
 | 
					                    name: "image-key",
 | 
				
			||||||
                    doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
 | 
					                    doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
 | 
				
			||||||
                    defaultValue: "image", name: "image-key"
 | 
					                    defaultValue: "image"
 | 
				
			||||||
                }],
 | 
					                }],
 | 
				
			||||||
                constr: (tags, args) => {
 | 
					                constr: (tags, args) => {
 | 
				
			||||||
                    if (args.length > 0) {
 | 
					                    return new ImageUploadFlow(tags, args[0])
 | 
				
			||||||
                        console.error("TODO HANDLE THESE ARGS") // TODO FIXME
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return new ImageUploadFlow(tags)
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					@ -125,7 +136,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                args: [{
 | 
					                args: [{
 | 
				
			||||||
                    name: "key",
 | 
					                    name: "key",
 | 
				
			||||||
                    defaultValue: "opening_hours",
 | 
					                    defaultValue: "opening_hours",
 | 
				
			||||||
                    doc: "The tag from which the table is constructed"
 | 
					                    doc: "The tagkey from which the table is constructed."
 | 
				
			||||||
                }],
 | 
					                }],
 | 
				
			||||||
                constr: (tagSource: UIEventSource<any>, args) => {
 | 
					                constr: (tagSource: UIEventSource<any>, args) => {
 | 
				
			||||||
                    let keyname = args[0];
 | 
					                    let keyname = args[0];
 | 
				
			||||||
| 
						 | 
					@ -139,6 +150,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                funcName: "live",
 | 
					                funcName: "live",
 | 
				
			||||||
                docs: "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}",
 | 
					                docs: "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}",
 | 
				
			||||||
 | 
					                example: "{live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)}",
 | 
				
			||||||
                args: [{
 | 
					                args: [{
 | 
				
			||||||
                    name: "Url", doc: "The URL to load"
 | 
					                    name: "Url", doc: "The URL to load"
 | 
				
			||||||
                }, {
 | 
					                }, {
 | 
				
			||||||
| 
						 | 
					@ -157,5 +169,38 @@ export default class SpecialVisualizations {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					    static HelpMessage: UIElement = SpecialVisualizations.GenHelpMessage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static GenHelpMessage() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const helpTexts =
 | 
				
			||||||
 | 
					            SpecialVisualizations.specialVisualizations.map(viz => new Combine(
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    `<h3>${viz.funcName}</h3>`,
 | 
				
			||||||
 | 
					                    viz.docs,
 | 
				
			||||||
 | 
					                    "<ol>",
 | 
				
			||||||
 | 
					                    ...viz.args.map(arg => new Combine([
 | 
				
			||||||
 | 
					                        "<li>",
 | 
				
			||||||
 | 
					                        "<b>" + arg.name + "</b>: ",
 | 
				
			||||||
 | 
					                        arg.doc,
 | 
				
			||||||
 | 
					                        arg.defaultValue === undefined ? "" : (" Default: <span class='literal-code'>" + arg.defaultValue + "</span>"),
 | 
				
			||||||
 | 
					                        "</li>"
 | 
				
			||||||
 | 
					                    ])),
 | 
				
			||||||
 | 
					                    "</ol>",
 | 
				
			||||||
 | 
					                    "<b>Example usage: </b>",
 | 
				
			||||||
 | 
					                    new FixedUiElement(
 | 
				
			||||||
 | 
					                        viz.example ?? "{" + viz.funcName + "(" + viz.args.map(arg => arg.defaultValue).join(",") + ")}"
 | 
				
			||||||
 | 
					                    ).SetClass("literal-code"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new Combine([
 | 
				
			||||||
 | 
					                "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
 | 
				
			||||||
 | 
					                ...helpTexts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								test.ts
									
										
									
									
									
								
							| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import SpecialVisualizations from "./UI/SpecialVisualizations";
 | 
					import SpecialVisualizations from "./UI/SpecialVisualizations";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SpecialVisualizations.HelpMessage.AttachTo("maindivgi")
 | 
					SpecialVisualizations.HelpMessage.AttachTo("maindiv")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*/
 | 
					/*/
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue