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