forked from MapComplete/MapComplete
		
	More refactoring, still very broken
This commit is contained in:
		
							parent
							
								
									d5d90afc74
								
							
						
					
					
						commit
						62f471df1e
					
				
					 23 changed files with 428 additions and 356 deletions
				
			
		| 
						 | 
				
			
			@ -6,8 +6,11 @@ import {UIElement} from "../../UI/UIElement";
 | 
			
		|||
import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer";
 | 
			
		||||
import {ElementStorage} from "../ElementStorage";
 | 
			
		||||
import Combine from "../../UI/Base/Combine";
 | 
			
		||||
import BaseUIElement from "../../UI/BaseUIElement";
 | 
			
		||||
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
 | 
			
		||||
 | 
			
		||||
class TitleElement extends UIElement {
 | 
			
		||||
class TitleElement extends UIEventSource<string> {
 | 
			
		||||
    
 | 
			
		||||
    private readonly _layoutToUse: UIEventSource<LayoutConfig>;
 | 
			
		||||
    private readonly _selectedFeature: UIEventSource<any>;
 | 
			
		||||
    private readonly _allElementsStorage: ElementStorage;
 | 
			
		||||
| 
						 | 
				
			
			@ -15,41 +18,43 @@ class TitleElement extends UIElement {
 | 
			
		|||
    constructor(layoutToUse: UIEventSource<LayoutConfig>,
 | 
			
		||||
                selectedFeature: UIEventSource<any>,
 | 
			
		||||
                allElementsStorage: ElementStorage) {
 | 
			
		||||
        super(layoutToUse);
 | 
			
		||||
        super("MapComplete");
 | 
			
		||||
        
 | 
			
		||||
        this._layoutToUse = layoutToUse;
 | 
			
		||||
        this._selectedFeature = selectedFeature;
 | 
			
		||||
        this._allElementsStorage = allElementsStorage;
 | 
			
		||||
        this.ListenTo(Locale.language);
 | 
			
		||||
        this.ListenTo(this._selectedFeature)
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
        this.syncWith(
 | 
			
		||||
            this._selectedFeature.map(
 | 
			
		||||
                selected => {
 | 
			
		||||
                    const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ??"MapComplete"
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
                    if(selected === undefined){
 | 
			
		||||
                        return defaultTitle
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
        const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ?? "MapComplete"
 | 
			
		||||
        const feature = this._selectedFeature.data;
 | 
			
		||||
 | 
			
		||||
        if (feature === undefined) {
 | 
			
		||||
            return defaultTitle;
 | 
			
		||||
        }
 | 
			
		||||
                    const layout = layoutToUse.data;
 | 
			
		||||
                    const tags = selected.properties;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const layout = this._layoutToUse.data;
 | 
			
		||||
        const properties = this._selectedFeature.data.properties;
 | 
			
		||||
                    for (const layer of layout.layers) {
 | 
			
		||||
                        if (layer.title === undefined) {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (layer.source.osmTags.matchesProperties(tags)) {
 | 
			
		||||
                            const title = new TagRenderingAnswer(tags, layer.title)
 | 
			
		||||
                            return new Combine([defaultTitle, " | ", title]).ConstructElement().innerText;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
        for (const layer of layout.layers) {
 | 
			
		||||
            if (layer.title === undefined) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (layer.source.osmTags.matchesProperties(properties)) {
 | 
			
		||||
                const tags = this._allElementsStorage.getEventSourceById(feature.properties.id);
 | 
			
		||||
                if (tags == undefined) {
 | 
			
		||||
                    return defaultTitle;
 | 
			
		||||
                    return defaultTitle
 | 
			
		||||
                }
 | 
			
		||||
                const title = new TagRenderingAnswer(tags, layer.title)
 | 
			
		||||
                return new Combine([defaultTitle, " | ", title]).Render();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return defaultTitle;
 | 
			
		||||
                , [Locale.language, layoutToUse]
 | 
			
		||||
            )
 | 
			
		||||
            
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,14 +63,8 @@ export default class TitleHandler {
 | 
			
		|||
    constructor(layoutToUse: UIEventSource<LayoutConfig>,
 | 
			
		||||
                selectedFeature: UIEventSource<any>,
 | 
			
		||||
                allElementsStorage: ElementStorage) {
 | 
			
		||||
 | 
			
		||||
        selectedFeature.addCallbackAndRun(_ => {
 | 
			
		||||
            const title = new TitleElement(layoutToUse, selectedFeature, allElementsStorage)
 | 
			
		||||
            const d = document.createElement('div');
 | 
			
		||||
            d.innerHTML = title.InnerRenderAsString();
 | 
			
		||||
            // We pass everything into a div to strip out images etc...
 | 
			
		||||
            document.title = (d.textContent || d.innerText);
 | 
			
		||||
        new TitleElement(layoutToUse, selectedFeature, allElementsStorage).addCallbackAndRun(title => {
 | 
			
		||||
            document.title = title
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -10,11 +10,8 @@ export class Imgur {
 | 
			
		|||
        handleSuccessfullUpload: ((imageURL: string) => void),
 | 
			
		||||
        allDone: (() => void),
 | 
			
		||||
        onFail: ((reason: string) => void),
 | 
			
		||||
        offset:number) {
 | 
			
		||||
        offset:number = 0) {
 | 
			
		||||
 | 
			
		||||
        if(offset === undefined){
 | 
			
		||||
            throw "Offset undefined - not uploading to prevent to much uploads!"
 | 
			
		||||
        }
 | 
			
		||||
        if (blobs.length == offset) {
 | 
			
		||||
            allDone();
 | 
			
		||||
            return;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +33,7 @@ export class Imgur {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    static getDescriptionOfImage(url: string,
 | 
			
		||||
                       handleDescription: ((license: LicenseInfo) => void)) {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										41
									
								
								Logic/Web/ImgurUploader.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Logic/Web/ImgurUploader.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
import {UIEventSource} from "../UIEventSource";
 | 
			
		||||
import {Imgur} from "./Imgur";
 | 
			
		||||
 | 
			
		||||
export default class ImgurUploader {
 | 
			
		||||
 | 
			
		||||
    public queue: UIEventSource<string[]>;
 | 
			
		||||
    public failed: UIEventSource<string[]>;
 | 
			
		||||
    public success: UIEventSource<string[]>
 | 
			
		||||
    private readonly _handleSuccessUrl: (string) => void;
 | 
			
		||||
 | 
			
		||||
    constructor(handleSuccessUrl: (string) => void) {
 | 
			
		||||
        this._handleSuccessUrl = handleSuccessUrl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public uploadMany(title: string, description: string, files: FileList) {
 | 
			
		||||
        for (let i = 0; i < files.length; i++) {
 | 
			
		||||
            this.queue.data.push(files.item(i).name)
 | 
			
		||||
        }
 | 
			
		||||
        this.queue.ping()
 | 
			
		||||
 | 
			
		||||
        const self = this;
 | 
			
		||||
        this.queue.setData([...self.queue.data])
 | 
			
		||||
        Imgur.uploadMultiple(title,
 | 
			
		||||
            description,
 | 
			
		||||
            files,
 | 
			
		||||
            function (url) {
 | 
			
		||||
                console.log("File saved at", url);
 | 
			
		||||
                self.success.setData([...self.success.data, url]);
 | 
			
		||||
            this.    handleSuccessUrl(url);
 | 
			
		||||
            },
 | 
			
		||||
            function () {
 | 
			
		||||
                console.log("All uploads completed");
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            function (failReason) {
 | 
			
		||||
                console.log("Upload failed due to ", failReason)
 | 
			
		||||
                self.failed.setData([...self.failed.data, failReason])
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								UI/Base/FileSelectorButton.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								UI/Base/FileSelectorButton.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
import {InputElement} from "../Input/InputElement";
 | 
			
		||||
import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		||||
 | 
			
		||||
export default class FileSelectorButton extends InputElement<FileList> {
 | 
			
		||||
 | 
			
		||||
    IsSelected: UIEventSource<boolean>;
 | 
			
		||||
    private readonly _value = new UIEventSource(undefined);
 | 
			
		||||
    private readonly _label: BaseUIElement;
 | 
			
		||||
    private readonly _acceptType: string;
 | 
			
		||||
 | 
			
		||||
    constructor(label: BaseUIElement, acceptType: string = "image/*") {
 | 
			
		||||
        super();
 | 
			
		||||
        this._label = label;
 | 
			
		||||
        this._acceptType = acceptType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    GetValue(): UIEventSource<FileList> {
 | 
			
		||||
        return this._value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    IsValid(t: FileList): boolean {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected InnerConstructElement(): HTMLElement {
 | 
			
		||||
        const self = this;
 | 
			
		||||
        const el = document.createElement("form")
 | 
			
		||||
        {
 | 
			
		||||
            const label = document.createElement("label")
 | 
			
		||||
            label.appendChild(this._label.ConstructElement())
 | 
			
		||||
            el.appendChild(label)
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            const actualInputElement = document.createElement("input");
 | 
			
		||||
            actualInputElement.style.cssText = "display:none";
 | 
			
		||||
            actualInputElement.type = "file";
 | 
			
		||||
            actualInputElement.accept = this._acceptType;
 | 
			
		||||
            actualInputElement.name = "picField";
 | 
			
		||||
            actualInputElement.multiple = true;
 | 
			
		||||
 | 
			
		||||
            actualInputElement.onchange = () => {
 | 
			
		||||
                if (actualInputElement.files !== null) {
 | 
			
		||||
                    self._value.setData(actualInputElement.files)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            el.addEventListener('submit', e => {
 | 
			
		||||
                if (actualInputElement.files !== null) {
 | 
			
		||||
                    self._value.setData(actualInputElement.files)
 | 
			
		||||
                }
 | 
			
		||||
                e.preventDefault()
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            el.appendChild(actualInputElement)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,24 +5,28 @@ export class VariableUiElement extends BaseUIElement {
 | 
			
		|||
 | 
			
		||||
    private _element : HTMLElement;
 | 
			
		||||
    
 | 
			
		||||
    constructor(contents: UIEventSource<string | BaseUIElement>) {
 | 
			
		||||
    constructor(contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>) {
 | 
			
		||||
        super();
 | 
			
		||||
        
 | 
			
		||||
        this._element = document.createElement("span")
 | 
			
		||||
        const el = this._element
 | 
			
		||||
        contents.addCallbackAndRun(contents => {
 | 
			
		||||
            while(el.firstChild){
 | 
			
		||||
            while (el.firstChild) {
 | 
			
		||||
                el.removeChild(
 | 
			
		||||
                    el.lastChild
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if(contents === undefined){
 | 
			
		||||
 | 
			
		||||
            if (contents === undefined) {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            if(typeof contents === "string"){
 | 
			
		||||
            if (typeof contents === "string") {
 | 
			
		||||
                el.innerHTML = contents
 | 
			
		||||
            }else{
 | 
			
		||||
            } else if (contents instanceof Array) {
 | 
			
		||||
                for (const content of contents) {
 | 
			
		||||
                    el.appendChild(content.ConstructElement())
 | 
			
		||||
                }
 | 
			
		||||
                }else{
 | 
			
		||||
                el.appendChild(contents.ConstructElement())
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								UI/BigComponents/LicensePicker.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								UI/BigComponents/LicensePicker.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
import {DropDown} from "../Input/DropDown";
 | 
			
		||||
import Translations from "../i18n/Translations";
 | 
			
		||||
import State from "../../State";
 | 
			
		||||
 | 
			
		||||
export default class LicensePicker extends DropDown<string>{
 | 
			
		||||
    
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super(Translations.t.image.willBePublished,
 | 
			
		||||
            [
 | 
			
		||||
                {value: "CC0", shown: Translations.t.image.cco},
 | 
			
		||||
                {value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs},
 | 
			
		||||
                {value: "CC-BY 4.0", shown: Translations.t.image.ccb}
 | 
			
		||||
            ],
 | 
			
		||||
            State.state.osmConnection.GetPreference("pictures-license")
 | 
			
		||||
        )
 | 
			
		||||
            this.SetClass("flex flex-col sm:flex-row").SetStyle("float:left");
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								UI/BigComponents/UploadFlowStateUI.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								UI/BigComponents/UploadFlowStateUI.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
import {UIElement} from "../UIElement";
 | 
			
		||||
import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
import {VariableUiElement} from "../Base/VariableUIElement";
 | 
			
		||||
import Translations from "../i18n/Translations";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Shows that 'images are uploading', 'all images are uploaded' as relevant...
 | 
			
		||||
 */
 | 
			
		||||
export default class UploadFlowStateUI extends UIElement{
 | 
			
		||||
    
 | 
			
		||||
    private readonly _element: BaseUIElement
 | 
			
		||||
    
 | 
			
		||||
    constructor(queue: UIEventSource<string[]>, failed: UIEventSource<string[]>, success: UIEventSource<string[]>) {
 | 
			
		||||
        super();
 | 
			
		||||
        const t = Translations.t.image;
 | 
			
		||||
 | 
			
		||||
        this._element = new VariableUiElement(
 | 
			
		||||
            
 | 
			
		||||
          queue.map(queue => {
 | 
			
		||||
              const failedReasons = failed.data
 | 
			
		||||
              const successCount = success.data.length
 | 
			
		||||
              const pendingCount = queue.length - successCount - failedReasons.length;
 | 
			
		||||
              
 | 
			
		||||
              let stateMessages : BaseUIElement[] = []
 | 
			
		||||
              
 | 
			
		||||
              if(pendingCount == 1){
 | 
			
		||||
                  stateMessages.push(t.uploadingPicture.Clone().SetClass("alert"))
 | 
			
		||||
              }
 | 
			
		||||
              if(pendingCount > 1){
 | 
			
		||||
                  stateMessages.push(t.uploadingMultiple.Subs({count: ""+pendingCount}).SetClass("alert"))
 | 
			
		||||
              }
 | 
			
		||||
              if(failedReasons.length > 0){
 | 
			
		||||
                  stateMessages.push(t.uploadFailed.Clone().SetClass("alert"))
 | 
			
		||||
              }
 | 
			
		||||
              if(successCount > 0 && pendingCount == 0){
 | 
			
		||||
                  stateMessages.push(t.uploadDone.SetClass("thanks"))
 | 
			
		||||
              }
 | 
			
		||||
              
 | 
			
		||||
              stateMessages.forEach(msg => msg.SetStyle("display: block ruby"))
 | 
			
		||||
              
 | 
			
		||||
              return stateMessages
 | 
			
		||||
          }, [failed, success])  
 | 
			
		||||
            
 | 
			
		||||
            
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected InnerRender(): string | BaseUIElement {
 | 
			
		||||
        return this._element
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,14 +6,15 @@ import Combine from "../Base/Combine";
 | 
			
		|||
import State from "../../State";
 | 
			
		||||
import Svg from "../../Svg";
 | 
			
		||||
import {Tag} from "../../Logic/Tags/Tag";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default class DeleteImage extends UIElement {
 | 
			
		||||
    private readonly key: string;
 | 
			
		||||
    private readonly tags: UIEventSource<any>;
 | 
			
		||||
 | 
			
		||||
    private readonly isDeletedBadge: UIElement;
 | 
			
		||||
    private readonly deleteDialog: UIElement;
 | 
			
		||||
    private readonly isDeletedBadge: BaseUIElement;
 | 
			
		||||
    private readonly deleteDialog: BaseUIElement;
 | 
			
		||||
 | 
			
		||||
    constructor(key: string, tags: UIEventSource<any>) {
 | 
			
		||||
        super(tags);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,16 +6,17 @@ import DeleteImage from "./DeleteImage";
 | 
			
		|||
import {WikimediaImage} from "./WikimediaImage";
 | 
			
		||||
import {ImgurImage} from "./ImgurImage";
 | 
			
		||||
import {MapillaryImage} from "./MapillaryImage";
 | 
			
		||||
import {SimpleImageElement} from "./SimpleImageElement";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
import Img from "../Base/Img";
 | 
			
		||||
 | 
			
		||||
export class ImageCarousel extends UIElement{
 | 
			
		||||
 | 
			
		||||
    public readonly slideshow: UIElement;
 | 
			
		||||
    public readonly slideshow: BaseUIElement;
 | 
			
		||||
 | 
			
		||||
    constructor(images: UIEventSource<{key: string, url:string}[]>, tags: UIEventSource<any>) {
 | 
			
		||||
        super(images);
 | 
			
		||||
        const uiElements = images.map((imageURLS: {key: string, url:string}[]) => {
 | 
			
		||||
            const uiElements: UIElement[] = [];
 | 
			
		||||
            const uiElements: BaseUIElement[] = [];
 | 
			
		||||
            for (const url of imageURLS) {
 | 
			
		||||
                let image = ImageCarousel.CreateImageElement(url.url)
 | 
			
		||||
                if(url.key !== undefined){
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +42,7 @@ export class ImageCarousel extends UIElement{
 | 
			
		|||
     * @param url
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    private static CreateImageElement(url: string): UIElement {
 | 
			
		||||
    private static CreateImageElement(url: string): BaseUIElement {
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        if (url.startsWith("File:")) {
 | 
			
		||||
            return new WikimediaImage(url);
 | 
			
		||||
| 
						 | 
				
			
			@ -53,11 +54,11 @@ export class ImageCarousel extends UIElement{
 | 
			
		|||
        } else if (url.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) {
 | 
			
		||||
            return new MapillaryImage(url);
 | 
			
		||||
        } else {
 | 
			
		||||
            return new SimpleImageElement(new UIEventSource<string>(url));
 | 
			
		||||
            return new Img(url);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
        return this.slideshow.Render();
 | 
			
		||||
    InnerRender() {
 | 
			
		||||
        return this.slideshow;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,207 +1,119 @@
 | 
			
		|||
import $ from "jquery"
 | 
			
		||||
import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		||||
import {UIElement} from "../UIElement";
 | 
			
		||||
import State from "../../State";
 | 
			
		||||
import Combine from "../Base/Combine";
 | 
			
		||||
import {FixedUiElement} from "../Base/FixedUiElement";
 | 
			
		||||
import {Imgur} from "../../Logic/Web/Imgur";
 | 
			
		||||
import {DropDown} from "../Input/DropDown";
 | 
			
		||||
import Translations from "../i18n/Translations";
 | 
			
		||||
import Svg from "../../Svg";
 | 
			
		||||
import {Tag} from "../../Logic/Tags/Tag";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
import LicensePicker from "../BigComponents/LicensePicker";
 | 
			
		||||
import Toggle from "../Input/Toggle";
 | 
			
		||||
import FileSelectorButton from "../Base/FileSelectorButton";
 | 
			
		||||
import ImgurUploader from "../../Logic/Web/ImgurUploader";
 | 
			
		||||
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
 | 
			
		||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
 | 
			
		||||
 | 
			
		||||
export class ImageUploadFlow extends UIElement {
 | 
			
		||||
    private readonly _licensePicker: BaseUIElement;
 | 
			
		||||
 | 
			
		||||
    private readonly _element: BaseUIElement;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private readonly _tags: UIEventSource<any>;
 | 
			
		||||
    private readonly _selectedLicence: UIEventSource<string>;
 | 
			
		||||
    private readonly _isUploading: UIEventSource<number> = new UIEventSource<number>(0)
 | 
			
		||||
    private readonly _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
			
		||||
    private readonly _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
			
		||||
    private readonly _connectButton: UIElement;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private readonly _imagePrefix: string;
 | 
			
		||||
 | 
			
		||||
    constructor(tags: UIEventSource<any>, imagePrefix: string = "image") {
 | 
			
		||||
    constructor(tagsSource: UIEventSource<any>, imagePrefix: string = "image") {
 | 
			
		||||
        super(State.state.osmConnection.userDetails);
 | 
			
		||||
        this._tags = tags;
 | 
			
		||||
        this._imagePrefix = imagePrefix;
 | 
			
		||||
 | 
			
		||||
        this.ListenTo(this._isUploading);
 | 
			
		||||
        this.ListenTo(this._didFail);
 | 
			
		||||
        this.ListenTo(this._allDone);
 | 
			
		||||
 | 
			
		||||
        const licensePicker = new DropDown(Translations.t.image.willBePublished,
 | 
			
		||||
            [
 | 
			
		||||
                {value: "CC0", shown: Translations.t.image.cco},
 | 
			
		||||
                {value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs},
 | 
			
		||||
                {value: "CC-BY 4.0", shown: Translations.t.image.ccb}
 | 
			
		||||
            ],
 | 
			
		||||
            State.state.osmConnection.GetPreference("pictures-license")
 | 
			
		||||
        ).SetClass("flex flex-col sm:flex-row");
 | 
			
		||||
        licensePicker.SetStyle("float:left");
 | 
			
		||||
        const uploader = new ImgurUploader(url => {
 | 
			
		||||
            // A file was uploaded - we add it to the tags of the object
 | 
			
		||||
 | 
			
		||||
        const t = Translations.t.image;
 | 
			
		||||
 | 
			
		||||
        this._licensePicker = licensePicker;
 | 
			
		||||
        this._selectedLicence = licensePicker.GetValue();
 | 
			
		||||
 | 
			
		||||
        this._connectButton = t.pleaseLogin.Clone()
 | 
			
		||||
            .onClick(() => State.state.osmConnection.AttemptLogin())
 | 
			
		||||
            .SetClass("login-button-friendly");
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
        
 | 
			
		||||
        if(!State.state.featureSwitchUserbadge.data){
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const t = Translations.t.image;
 | 
			
		||||
        if (State.state.osmConnection.userDetails === undefined) {
 | 
			
		||||
            return ""; // No user details -> logging in is probably disabled or smthing
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!State.state.osmConnection.userDetails.data.loggedIn) {
 | 
			
		||||
            return this._connectButton.Render();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let currentState: UIElement[] = [];
 | 
			
		||||
        if (this._isUploading.data == 1) {
 | 
			
		||||
            currentState.push(t.uploadingPicture);
 | 
			
		||||
        } else if (this._isUploading.data > 0) {
 | 
			
		||||
            currentState.push(t.uploadingMultiple.Subs({count: ""+this._isUploading.data}));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this._didFail.data) {
 | 
			
		||||
            currentState.push(t.uploadFailed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this._allDone.data) {
 | 
			
		||||
            currentState.push(t.uploadDone)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let currentStateHtml : UIElement = new FixedUiElement("");
 | 
			
		||||
        if (currentState.length > 0) {
 | 
			
		||||
            currentStateHtml = new Combine(currentState);
 | 
			
		||||
            if (!this._allDone.data) {
 | 
			
		||||
                currentStateHtml.SetClass("alert");
 | 
			
		||||
            }else{
 | 
			
		||||
                currentStateHtml.SetClass("thanks");
 | 
			
		||||
            const tags = tagsSource.data
 | 
			
		||||
            let key = imagePrefix
 | 
			
		||||
            if (tags[imagePrefix] !== undefined) {
 | 
			
		||||
                let freeIndex = 0;
 | 
			
		||||
                while (tags[imagePrefix + ":" + freeIndex] !== undefined) {
 | 
			
		||||
                    freeIndex++;
 | 
			
		||||
                }
 | 
			
		||||
                key = imagePrefix + ":" + freeIndex;
 | 
			
		||||
            }
 | 
			
		||||
            currentStateHtml.SetStyle("display:block ruby")
 | 
			
		||||
        }
 | 
			
		||||
            console.log("Adding image:" + key, url);
 | 
			
		||||
            State.state.changes.addTag(tags.id, new Tag(key, url));
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        const extraInfo = new Combine([
 | 
			
		||||
            Translations.t.image.respectPrivacy.SetStyle("font-size:small;"),
 | 
			
		||||
            "<br/>",
 | 
			
		||||
            this._licensePicker,
 | 
			
		||||
            "<br/>",
 | 
			
		||||
            currentStateHtml,
 | 
			
		||||
            "<br/>"
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        const licensePicker = new LicensePicker()
 | 
			
		||||
 | 
			
		||||
        const t = Translations.t.image;
 | 
			
		||||
        const label = new Combine([
 | 
			
		||||
            Svg.camera_plus_svg().SetStyle("width: 36px;height: 36px;padding: 0.1em;margin-top: 5px;border-radius: 0;float: left;display:block"),
 | 
			
		||||
            Translations.t.image.addPicture
 | 
			
		||||
        ]).SetClass("image-upload-flow-button")
 | 
			
		||||
    
 | 
			
		||||
        const actualInputElement =
 | 
			
		||||
            `<input style='display: none' id='fileselector-${this.id}' type='file' accept='image/*' name='picField' multiple='multiple' alt=''/>`;
 | 
			
		||||
        
 | 
			
		||||
        const form = "<form id='fileselector-form-" + this.id + "'>" +
 | 
			
		||||
            `<label for='fileselector-${this.id}'>` +
 | 
			
		||||
            label.Render() +
 | 
			
		||||
            "</label>" +
 | 
			
		||||
            actualInputElement +
 | 
			
		||||
            "</form>";
 | 
			
		||||
        const fileSelector = new FileSelectorButton(label)
 | 
			
		||||
        fileSelector.GetValue().addCallback(filelist => {
 | 
			
		||||
            if (filelist === undefined) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        return new Combine([
 | 
			
		||||
            form,
 | 
			
		||||
            extraInfo
 | 
			
		||||
            console.log("Received images from the user, starting upload")
 | 
			
		||||
            const license = this._selectedLicence.data ?? "CC0"
 | 
			
		||||
 | 
			
		||||
            const tags = this._tags.data;
 | 
			
		||||
 | 
			
		||||
            const layout = State.state.layoutToUse.data
 | 
			
		||||
            let matchingLayer: LayerConfig = undefined
 | 
			
		||||
            for (const layer of layout.layers) {
 | 
			
		||||
                if (layer.source.osmTags.matchesProperties(tags)) {
 | 
			
		||||
                    matchingLayer = layer;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            const title = matchingLayer?.title?.GetRenderValue(tags)?.ConstructElement().innerText ?? tags.name ?? "Unknown area";
 | 
			
		||||
            const description = [
 | 
			
		||||
                "author:" + State.state.osmConnection.userDetails.data.name,
 | 
			
		||||
                "license:" + license,
 | 
			
		||||
                "osmid:" + tags.id,
 | 
			
		||||
            ].join("\n");
 | 
			
		||||
 | 
			
		||||
            uploader.uploadMany(title, description, filelist)
 | 
			
		||||
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const uploadStateUi = new UploadFlowStateUI(uploader.queue, uploader.failed, uploader.success)
 | 
			
		||||
 | 
			
		||||
        const uploadFlow: BaseUIElement = new Combine([
 | 
			
		||||
            fileSelector,
 | 
			
		||||
            Translations.t.image.respectPrivacy.SetStyle("font-size:small;"),
 | 
			
		||||
            licensePicker,
 | 
			
		||||
            uploadStateUi
 | 
			
		||||
        ]).SetClass("image-upload-flow")
 | 
			
		||||
            .SetStyle("margin-top: 1em;margin-bottom: 2em;text-align: center;")
 | 
			
		||||
            .Render();
 | 
			
		||||
    }
 | 
			
		||||
            .SetStyle("margin-top: 1em;margin-bottom: 2em;text-align: center;");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private handleSuccessfulUpload(url) {
 | 
			
		||||
        const tags = this._tags.data;
 | 
			
		||||
        let key = this._imagePrefix;
 | 
			
		||||
        if (tags[this._imagePrefix] !== undefined) {
 | 
			
		||||
 | 
			
		||||
            let freeIndex = 0;
 | 
			
		||||
            while (tags[this._imagePrefix + ":" + freeIndex] !== undefined) {
 | 
			
		||||
                freeIndex++;
 | 
			
		||||
            }
 | 
			
		||||
            key = this._imagePrefix + ":" + freeIndex;
 | 
			
		||||
        }
 | 
			
		||||
        console.log("Adding image:" + key, url);
 | 
			
		||||
        State.state.changes.addTag(tags.id, new Tag(key, url));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private handleFiles(files) {
 | 
			
		||||
        console.log("Received images from the user, starting upload")
 | 
			
		||||
        this._isUploading.setData(files.length);
 | 
			
		||||
        this._allDone.setData(false);
 | 
			
		||||
 | 
			
		||||
        if (this._selectedLicence.data === undefined) {
 | 
			
		||||
            this._selectedLicence.setData("CC0");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const tags = this._tags.data;
 | 
			
		||||
        const title = tags.name ?? "Unknown area";
 | 
			
		||||
        const description = [
 | 
			
		||||
            "author:" + State.state.osmConnection.userDetails.data.name,
 | 
			
		||||
            "license:" + (this._selectedLicence.data ?? "CC0"),
 | 
			
		||||
            "wikidata:" + tags.wikidata,
 | 
			
		||||
            "osmid:" + tags.id,
 | 
			
		||||
            "name:" + tags.name
 | 
			
		||||
        ].join("\n");
 | 
			
		||||
 | 
			
		||||
        const self = this;
 | 
			
		||||
 | 
			
		||||
        Imgur.uploadMultiple(title,
 | 
			
		||||
            description,
 | 
			
		||||
            files,
 | 
			
		||||
            function (url) {
 | 
			
		||||
                console.log("File saved at", url);
 | 
			
		||||
                self._isUploading.setData(self._isUploading.data - 1);
 | 
			
		||||
                self.handleSuccessfulUpload(url);
 | 
			
		||||
            },
 | 
			
		||||
            function () {
 | 
			
		||||
                console.log("All uploads completed");
 | 
			
		||||
                self._allDone.setData(true);
 | 
			
		||||
            },
 | 
			
		||||
            function (failReason) {
 | 
			
		||||
                console.log("Upload failed due to ", failReason)
 | 
			
		||||
                // No need to call something from the options -> we handle this here
 | 
			
		||||
                self._didFail.setData(true);
 | 
			
		||||
                self._isUploading.data--;
 | 
			
		||||
                self._isUploading.ping();
 | 
			
		||||
            }, 0
 | 
			
		||||
        const pleaseLoginButton = t.pleaseLogin.Clone()
 | 
			
		||||
            .onClick(() => State.state.osmConnection.AttemptLogin())
 | 
			
		||||
            .SetClass("login-button-friendly");
 | 
			
		||||
        this._element = new Toggle(
 | 
			
		||||
            new Toggle(
 | 
			
		||||
                /*We can show the actual upload button!*/
 | 
			
		||||
                uploadFlow,
 | 
			
		||||
                /* User not logged in*/ pleaseLoginButton,
 | 
			
		||||
                State.state.osmConnection.userDetails.map(userinfo => userinfo.loggedIn)
 | 
			
		||||
            ),
 | 
			
		||||
            undefined /* Nothing as the user badge is disabled*/, State.state.featureSwitchUserbadge
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerUpdate(htmlElement: HTMLElement) {
 | 
			
		||||
        this._licensePicker.Update()
 | 
			
		||||
        const form = document.getElementById('fileselector-form-' + this.id) as HTMLFormElement
 | 
			
		||||
        const selector = document.getElementById('fileselector-' + this.id)
 | 
			
		||||
        const self = this
 | 
			
		||||
 | 
			
		||||
        function submitHandler() {
 | 
			
		||||
            self.handleFiles($(selector).prop('files'))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (selector != null && form != null) {
 | 
			
		||||
            selector.onchange = function () {
 | 
			
		||||
                submitHandler()
 | 
			
		||||
            }
 | 
			
		||||
            form.addEventListener('submit', e => {
 | 
			
		||||
                e.preventDefault()
 | 
			
		||||
                submitHandler()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    protected InnerRender(): string | BaseUIElement {
 | 
			
		||||
        return this._element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,8 @@ import {LicenseInfo} from "../../Logic/Web/Wikimedia";
 | 
			
		|||
import {Imgur} from "../../Logic/Web/Imgur";
 | 
			
		||||
import Combine from "../Base/Combine";
 | 
			
		||||
import Attribution from "./Attribution";
 | 
			
		||||
import {SimpleImageElement} from "./SimpleImageElement";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
import Img from "../Base/Img";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class ImgurImage extends UIElement {
 | 
			
		||||
| 
						 | 
				
			
			@ -35,11 +36,11 @@ export class ImgurImage extends UIElement {
 | 
			
		|||
      
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
        const image = new SimpleImageElement( new UIEventSource (this._imageLocation));
 | 
			
		||||
    InnerRender(): BaseUIElement {
 | 
			
		||||
        const image = new Img( this._imageLocation);
 | 
			
		||||
        
 | 
			
		||||
        if(this._imageMeta.data === null){
 | 
			
		||||
            return image.Render();
 | 
			
		||||
            return image;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const meta = this._imageMeta.data;
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +49,7 @@ export class ImgurImage extends UIElement {
 | 
			
		|||
            new Attribution(meta.artist, meta.license, undefined),
 | 
			
		||||
            
 | 
			
		||||
        ]).SetClass('block relative')
 | 
			
		||||
            .Render();
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,9 +3,10 @@ import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		|||
import {LicenseInfo} from "../../Logic/Web/Wikimedia";
 | 
			
		||||
import {Mapillary} from "../../Logic/Web/Mapillary";
 | 
			
		||||
import Svg from "../../Svg";
 | 
			
		||||
import {SimpleImageElement} from "./SimpleImageElement";
 | 
			
		||||
import Combine from "../Base/Combine";
 | 
			
		||||
import Attribution from "./Attribution";
 | 
			
		||||
import Img from "../Base/Img";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class MapillaryImage extends UIElement {
 | 
			
		||||
| 
						 | 
				
			
			@ -40,19 +41,19 @@ export class MapillaryImage extends UIElement {
 | 
			
		|||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
    InnerRender(): BaseUIElement {
 | 
			
		||||
        const url = `https://images.mapillary.com/${this._imageLocation}/thumb-640.jpg?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2`;
 | 
			
		||||
        const image = new SimpleImageElement(new UIEventSource<string>(url))
 | 
			
		||||
        const image = new Img(url)
 | 
			
		||||
        
 | 
			
		||||
        const meta = this._imageMeta?.data;
 | 
			
		||||
        if (!meta) {
 | 
			
		||||
            return image.Render();
 | 
			
		||||
            return image;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Combine([
 | 
			
		||||
            image,
 | 
			
		||||
            new Attribution(meta.artist, meta.license, Svg.mapillary_svg())
 | 
			
		||||
        ]).SetClass("relative block").Render();
 | 
			
		||||
        ]).SetClass("relative block");
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +0,0 @@
 | 
			
		|||
import {UIElement} from "../UIElement";
 | 
			
		||||
import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class SimpleImageElement extends UIElement {
 | 
			
		||||
 | 
			
		||||
    constructor(source: UIEventSource<string>) {
 | 
			
		||||
        super(source);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
        return "<img src='" + this._source.data + "' alt='img'>";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,46 +1,22 @@
 | 
			
		|||
import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		||||
import {UIElement} from "../UIElement";
 | 
			
		||||
import Combine from "../Base/Combine";
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import $ from "jquery"
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
 | 
			
		||||
export class SlideShow extends UIElement {
 | 
			
		||||
export class SlideShow extends BaseUIElement {
 | 
			
		||||
 | 
			
		||||
    private readonly _embeddedElements: UIEventSource<UIElement[]>
 | 
			
		||||
 | 
			
		||||
    private  readonly _element: HTMLElement;
 | 
			
		||||
    
 | 
			
		||||
    constructor(
 | 
			
		||||
        embeddedElements: UIEventSource<UIElement[]>) {
 | 
			
		||||
        super(embeddedElements);
 | 
			
		||||
        this._embeddedElements = embeddedElements;
 | 
			
		||||
        this._embeddedElements.addCallbackAndRun(elements => {
 | 
			
		||||
            for (const element of elements ?? []) {
 | 
			
		||||
                element.SetClass("slick-carousel-content")
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
        return new Combine(
 | 
			
		||||
                this._embeddedElements.data,
 | 
			
		||||
            ).SetClass("block slick-carousel")
 | 
			
		||||
            .Render();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Update() {
 | 
			
		||||
        super.Update();
 | 
			
		||||
        for (const uiElement of this._embeddedElements.data) {
 | 
			
		||||
            uiElement.Update();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected InnerUpdate(htmlElement: HTMLElement) {
 | 
			
		||||
        embeddedElements: UIEventSource<BaseUIElement[]>) {
 | 
			
		||||
        super()
 | 
			
		||||
        
 | 
			
		||||
        const el = document.createElement("div")
 | 
			
		||||
        this._element = el;
 | 
			
		||||
        
 | 
			
		||||
        el.classList.add("slick-carousel")
 | 
			
		||||
        require("slick-carousel")
 | 
			
		||||
        if(this._embeddedElements.data.length == 0){
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        $('.slick-carousel').not('.slick-initialized').slick({
 | 
			
		||||
        el.slick({
 | 
			
		||||
            autoplay: true,
 | 
			
		||||
            arrows: true,
 | 
			
		||||
            dots: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -48,8 +24,18 @@ export class SlideShow extends UIElement {
 | 
			
		|||
            variableWidth: true,
 | 
			
		||||
            centerMode: true,
 | 
			
		||||
            centerPadding: "60px",
 | 
			
		||||
            adaptive: true  
 | 
			
		||||
            adaptive: true
 | 
			
		||||
        });
 | 
			
		||||
        embeddedElements.addCallbackAndRun(elements => {
 | 
			
		||||
            for (const element of elements ?? []) {
 | 
			
		||||
                element.SetClass("slick-carousel-content")
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected InnerConstructElement(): HTMLElement {
 | 
			
		||||
        return this._element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,8 +4,9 @@ import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		|||
import Svg from "../../Svg";
 | 
			
		||||
import Link from "../Base/Link";
 | 
			
		||||
import Combine from "../Base/Combine";
 | 
			
		||||
import {SimpleImageElement} from "./SimpleImageElement";
 | 
			
		||||
import Attribution from "./Attribution";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
import Img from "../Base/Img";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class WikimediaImage extends UIElement {
 | 
			
		||||
| 
						 | 
				
			
			@ -34,14 +35,14 @@ export class WikimediaImage extends UIElement {
 | 
			
		|||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
    InnerRender(): BaseUIElement {
 | 
			
		||||
        const url = Wikimedia.ImageNameToUrl(this._imageLocation, 500, 400)
 | 
			
		||||
            .replace(/'/g, '%27');
 | 
			
		||||
        const image = new SimpleImageElement(new UIEventSource<string>(url))
 | 
			
		||||
        const image = new Img(url)
 | 
			
		||||
        const meta = this._imageMeta?.data;
 | 
			
		||||
 | 
			
		||||
        if (!meta) {
 | 
			
		||||
            return image.Render();
 | 
			
		||||
            return image;
 | 
			
		||||
        }
 | 
			
		||||
        new Link(Svg.wikimedia_commons_white_img,
 | 
			
		||||
            `https://commons.wikimedia.org/wiki/${this._imageLocation}`, true)
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +51,7 @@ export class WikimediaImage extends UIElement {
 | 
			
		|||
        return new Combine([
 | 
			
		||||
            image,
 | 
			
		||||
            new Attribution(meta.artist, meta.license, Svg.wikimedia_commons_white_svg())
 | 
			
		||||
        ]).SetClass("relative block").Render()
 | 
			
		||||
        ]).SetClass("relative block")
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ import {InputElement} from "./InputElement";
 | 
			
		|||
import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		||||
import {Utils} from "../../Utils";
 | 
			
		||||
import {UIElement} from "../UIElement";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Supports multi-input
 | 
			
		||||
| 
						 | 
				
			
			@ -10,15 +11,24 @@ export default class CheckBoxes extends InputElement<number[]> {
 | 
			
		|||
    IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
			
		||||
 | 
			
		||||
    private readonly value: UIEventSource<number[]>;
 | 
			
		||||
    private readonly _elements: UIElement[]
 | 
			
		||||
    private readonly _elements: BaseUIElement[]
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
private readonly _element : HTMLElement
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor(elements: UIElement[]) {
 | 
			
		||||
        super(undefined);
 | 
			
		||||
    constructor(elements: BaseUIElement[]) {
 | 
			
		||||
        super();
 | 
			
		||||
        this._elements = Utils.NoNull(elements);
 | 
			
		||||
 | 
			
		||||
        this.value = new UIEventSource<number[]>([])
 | 
			
		||||
        this.ListenTo(this.value);
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        const el = document.createElement()
 | 
			
		||||
        this._element = el;
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected InnerConstructElement(): HTMLElement {
 | 
			
		||||
        return this._element
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,46 +4,33 @@ import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		|||
export default class ColorPicker extends InputElement<string> {
 | 
			
		||||
 | 
			
		||||
    private readonly value: UIEventSource<string>
 | 
			
		||||
 | 
			
		||||
private readonly _element : HTMLElement
 | 
			
		||||
    constructor(
 | 
			
		||||
        value?: UIEventSource<string>
 | 
			
		||||
        value: UIEventSource<string> = new UIEventSource<string>(undefined)
 | 
			
		||||
    ) {
 | 
			
		||||
        super();
 | 
			
		||||
        this.value = value ?? new UIEventSource<string>(undefined);
 | 
			
		||||
        const self = this;
 | 
			
		||||
        this.value = value ;
 | 
			
		||||
        
 | 
			
		||||
        const el = document.createElement("input")
 | 
			
		||||
        this._element = el;
 | 
			
		||||
        
 | 
			
		||||
        el.type = "color"
 | 
			
		||||
        
 | 
			
		||||
        this.value.addCallbackAndRun(v => {
 | 
			
		||||
            if(v === undefined){
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            self.SetValue(v);
 | 
			
		||||
           el.value =v
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        el.oninput = () => {
 | 
			
		||||
            const hex = el.value;
 | 
			
		||||
            value.setData(hex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string {
 | 
			
		||||
        return `<span id="${this.id}"><input type='color' id='color-${this.id}'></span>`;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private SetValue(color: string){
 | 
			
		||||
        const field = document.getElementById("color-" + this.id);
 | 
			
		||||
        if (field === undefined || field === null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        field.value = color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected InnerUpdate() {
 | 
			
		||||
        const field = document.getElementById("color-" + this.id);
 | 
			
		||||
        if (field === undefined || field === null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const self = this;
 | 
			
		||||
        field.oninput = () => {
 | 
			
		||||
            const hex = field["value"];
 | 
			
		||||
            self.value.setData(hex);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    protected InnerConstructElement(): HTMLElement {
 | 
			
		||||
        return this._element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    GetValue(): UIEventSource<string> {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ import Combine from "../Base/Combine";
 | 
			
		|||
import {SubstitutedTranslation} from "../SubstitutedTranslation";
 | 
			
		||||
import {Translation} from "../i18n/Translation";
 | 
			
		||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
 | 
			
		||||
/***
 | 
			
		||||
 * Displays the correct value for a known tagrendering
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +14,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils";
 | 
			
		|||
export default class TagRenderingAnswer extends UIElement {
 | 
			
		||||
    private readonly _tags: UIEventSource<any>;
 | 
			
		||||
    private _configuration: TagRenderingConfig;
 | 
			
		||||
    private _content: UIElement;
 | 
			
		||||
    private _content: BaseUIElement;
 | 
			
		||||
    private readonly _contentClass: string;
 | 
			
		||||
    private _contentStyle: string;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +31,7 @@ export default class TagRenderingAnswer extends UIElement {
 | 
			
		|||
        this.SetStyle("word-wrap: anywhere;");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerRender(): string | UIElement{
 | 
			
		||||
    InnerRender(): string | BaseUIElement{
 | 
			
		||||
        if (this._configuration.condition !== undefined) {
 | 
			
		||||
            if (!this._configuration.condition.matchesProperties(this._tags.data)) {
 | 
			
		||||
                return "";
 | 
			
		||||
| 
						 | 
				
			
			@ -74,8 +75,7 @@ export default class TagRenderingAnswer extends UIElement {
 | 
			
		|||
                    this._content = valuesToRender[0];
 | 
			
		||||
                } else {
 | 
			
		||||
                    this._content = new Combine(["<ul>",
 | 
			
		||||
                        ...valuesToRender.map(tr => new Combine(["<li>", tr, "</li>"]))
 | 
			
		||||
                        ,
 | 
			
		||||
                        ...valuesToRender.map(tr => new Combine(["<li>", tr, "</li>"]))                        ,
 | 
			
		||||
                        "</ul>"
 | 
			
		||||
                    ])
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,13 +8,14 @@ import {UIElement} from "../UIElement";
 | 
			
		|||
import Combine from "../Base/Combine";
 | 
			
		||||
import Translations from "../i18n/Translations";
 | 
			
		||||
import SingleReview from "./SingleReview";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
 | 
			
		||||
export default class ReviewElement extends UIElement {
 | 
			
		||||
    private readonly _reviews: UIEventSource<Review[]>;
 | 
			
		||||
    private readonly _subject: string;
 | 
			
		||||
    private readonly _middleElement: UIElement;
 | 
			
		||||
    private readonly _middleElement: BaseUIElement;
 | 
			
		||||
 | 
			
		||||
    constructor(subject: string, reviews: UIEventSource<Review[]>, middleElement: UIElement) {
 | 
			
		||||
    constructor(subject: string, reviews: UIEventSource<Review[]>, middleElement: BaseUIElement) {
 | 
			
		||||
        super(reviews);
 | 
			
		||||
        this._middleElement = middleElement;
 | 
			
		||||
        if (reviews === undefined) {
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +27,7 @@ export default class ReviewElement extends UIElement {
 | 
			
		|||
 | 
			
		||||
   
 | 
			
		||||
 | 
			
		||||
    InnerRender(): UIElement {
 | 
			
		||||
    InnerRender(): BaseUIElement {
 | 
			
		||||
 | 
			
		||||
        const elements = [];
 | 
			
		||||
        const revs = this._reviews.data;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,3 @@
 | 
			
		|||
import {UIElement} from "../UIElement";
 | 
			
		||||
import {InputElement} from "../Input/InputElement";
 | 
			
		||||
import {Review} from "../../Logic/Web/Review";
 | 
			
		||||
import {UIEventSource} from "../../Logic/UIEventSource";
 | 
			
		||||
| 
						 | 
				
			
			@ -10,16 +9,18 @@ import {VariableUiElement} from "../Base/VariableUIElement";
 | 
			
		|||
import {SaveButton} from "../Popup/SaveButton";
 | 
			
		||||
import CheckBoxes from "../Input/Checkboxes";
 | 
			
		||||
import UserDetails from "../../Logic/Osm/OsmConnection";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
import Toggle from "../Input/Toggle";
 | 
			
		||||
 | 
			
		||||
export default class ReviewForm extends InputElement<Review> {
 | 
			
		||||
 | 
			
		||||
    private readonly _value: UIEventSource<Review>;
 | 
			
		||||
    private readonly _comment: UIElement;
 | 
			
		||||
    private readonly _stars: UIElement;
 | 
			
		||||
    private _saveButton: UIElement;
 | 
			
		||||
    private readonly _isAffiliated: UIElement;
 | 
			
		||||
    private readonly _comment: BaseUIElement;
 | 
			
		||||
    private readonly _stars: BaseUIElement;
 | 
			
		||||
    private _saveButton: BaseUIElement;
 | 
			
		||||
    private readonly _isAffiliated: BaseUIElement;
 | 
			
		||||
    private userDetails: UIEventSource<UserDetails>;
 | 
			
		||||
    private readonly _postingAs: UIElement;
 | 
			
		||||
    private readonly _postingAs: BaseUIElement;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor(onSave: ((r: Review, doneSaving: (() => void)) => void), userDetails: UIEventSource<UserDetails>) {
 | 
			
		||||
| 
						 | 
				
			
			@ -86,13 +87,9 @@ export default class ReviewForm extends InputElement<Review> {
 | 
			
		|||
        return this._value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    InnerRender(): UIElement {
 | 
			
		||||
    InnerConstructElement(): HTMLElement {
 | 
			
		||||
 | 
			
		||||
        if(!this.userDetails.data.loggedIn){
 | 
			
		||||
            return Translations.t.reviews.plz_login;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Combine([
 | 
			
		||||
        const form = new Combine([
 | 
			
		||||
            new Combine([this._stars, this._postingAs]).SetClass("review-form-top"),
 | 
			
		||||
            this._comment,
 | 
			
		||||
            new Combine([
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +100,11 @@ export default class ReviewForm extends InputElement<Review> {
 | 
			
		|||
            Translations.t.reviews.tos.SetClass("subtle")
 | 
			
		||||
        ])
 | 
			
		||||
            .SetClass("review-form")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return new Toggle(form, Translations.t.reviews.plz_login, 
 | 
			
		||||
            this.userDetails.map(userdetails => userdetails.loggedIn))
 | 
			
		||||
            .ConstructElement()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import {FixedUiElement} from "../Base/FixedUiElement";
 | 
			
		|||
import Translations from "../i18n/Translations";
 | 
			
		||||
import {Utils} from "../../Utils";
 | 
			
		||||
import ReviewElement from "./ReviewElement";
 | 
			
		||||
import BaseUIElement from "../BaseUIElement";
 | 
			
		||||
 | 
			
		||||
export default class SingleReview extends UIElement{
 | 
			
		||||
    private _review: Review;
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +14,7 @@ export default class SingleReview extends UIElement{
 | 
			
		|||
        this._review = review;
 | 
			
		||||
      
 | 
			
		||||
    }
 | 
			
		||||
    public static GenStars(rating: number): UIElement {
 | 
			
		||||
    public static GenStars(rating: number): BaseUIElement {
 | 
			
		||||
        if (rating === undefined) {
 | 
			
		||||
            return Translations.t.reviews.no_rating;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +27,7 @@ export default class SingleReview extends UIElement{
 | 
			
		|||
            scoreTen % 2 == 1 ? "<img src='./assets/svg/star_half.svg' class='h-8 md:h-12'/>" : ""
 | 
			
		||||
        ]).SetClass("flex w-max")
 | 
			
		||||
    }
 | 
			
		||||
    InnerRender(): UIElement {
 | 
			
		||||
    InnerRender(): BaseUIElement {
 | 
			
		||||
        const d = this._review.date;
 | 
			
		||||
        let review = this._review;
 | 
			
		||||
        const el=  new Combine(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,3 @@
 | 
			
		|||
import {UIElement} from "./UIElement";
 | 
			
		||||
import {UIEventSource} from "../Logic/UIEventSource";
 | 
			
		||||
import {VariableUiElement} from "./Base/VariableUIElement";
 | 
			
		||||
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
 | 
			
		||||
| 
						 | 
				
			
			@ -17,12 +16,15 @@ import OpeningHoursVisualization from "./OpeningHours/OhVisualization";
 | 
			
		|||
 | 
			
		||||
import State from "../State";
 | 
			
		||||
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
 | 
			
		||||
import BaseUIElement from "./BaseUIElement";
 | 
			
		||||
 | 
			
		||||
export default class SpecialVisualizations {
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public static specialVisualizations: {
 | 
			
		||||
        funcName: string,
 | 
			
		||||
        constr: ((state: State, tagSource: UIEventSource<any>, argument: string[]) => UIElement),
 | 
			
		||||
        constr: ((state: State, tagSource: UIEventSource<any>, argument: string[]) => BaseUIElement),
 | 
			
		||||
        docs: string,
 | 
			
		||||
        example?: string,
 | 
			
		||||
        args: { name: string, defaultValue?: string, doc: string }[]
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +38,9 @@ export default class SpecialVisualizations {
 | 
			
		|||
                return new VariableUiElement(tags.map(tags => {
 | 
			
		||||
                    const parts = [];
 | 
			
		||||
                    for (const key in tags) {
 | 
			
		||||
                        if(!tags.hasOwnProperty(key)){
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        parts.push(key + "=" + tags[key]);
 | 
			
		||||
                    }
 | 
			
		||||
                    return parts.join("<br/>")
 | 
			
		||||
| 
						 | 
				
			
			@ -179,7 +184,7 @@ export default class SpecialVisualizations {
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
        ]
 | 
			
		||||
    static HelpMessage: UIElement = SpecialVisualizations.GenHelpMessage();
 | 
			
		||||
    static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
 | 
			
		||||
 | 
			
		||||
    private static GenHelpMessage() {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,11 +6,12 @@ import Combine from "./Base/Combine";
 | 
			
		|||
import State from "../State";
 | 
			
		||||
import {FixedUiElement} from "./Base/FixedUiElement";
 | 
			
		||||
import SpecialVisualizations from "./SpecialVisualizations";
 | 
			
		||||
import BaseUIElement from "./BaseUIElement";
 | 
			
		||||
 | 
			
		||||
export class SubstitutedTranslation extends UIElement {
 | 
			
		||||
    private readonly tags: UIEventSource<any>;
 | 
			
		||||
    private readonly translation: Translation;
 | 
			
		||||
    private content: UIElement[];
 | 
			
		||||
    private content: BaseUIElement[];
 | 
			
		||||
 | 
			
		||||
    private constructor(
 | 
			
		||||
        translation: Translation,
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +55,7 @@ export class SubstitutedTranslation extends UIElement {
 | 
			
		|||
        return new Combine(this.content);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private CreateContent(): UIElement[] {
 | 
			
		||||
    private CreateContent(): BaseUIElement[] {
 | 
			
		||||
        let txt = this.translation?.txt;
 | 
			
		||||
        if (txt === undefined) {
 | 
			
		||||
            return []
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +65,7 @@ export class SubstitutedTranslation extends UIElement {
 | 
			
		|||
        return this.EvaluateSpecialComponents(txt);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private EvaluateSpecialComponents(template: string): UIElement[] {
 | 
			
		||||
    private EvaluateSpecialComponents(template: string): BaseUIElement[] {
 | 
			
		||||
 | 
			
		||||
        for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue