forked from MapComplete/MapComplete
		
	Butchering the UI framework
This commit is contained in:
		
							parent
							
								
									8d404b1ba9
								
							
						
					
					
						commit
						6415e195d1
					
				
					 90 changed files with 1012 additions and 3101 deletions
				
			
		|  | @ -1,11 +1,11 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {FixedUiElement} from "./FixedUiElement"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export default class Combine extends UIElement { | ||||
|     private readonly uiElements: UIElement[]; | ||||
| export default class Combine extends BaseUIElement { | ||||
|     private readonly uiElements: BaseUIElement[]; | ||||
| 
 | ||||
|     constructor(uiElements: (string | UIElement)[]) { | ||||
|     constructor(uiElements: (string | BaseUIElement)[]) { | ||||
|         super(); | ||||
|         this.uiElements = Utils.NoNull(uiElements) | ||||
|             .map(el => { | ||||
|  | @ -15,18 +15,21 @@ export default class Combine extends UIElement { | |||
|                 return el; | ||||
|             }); | ||||
|     } | ||||
|      | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         const el = document.createElement("span") | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return this.uiElements.map(ui => { | ||||
|             if(ui === undefined || ui === null){ | ||||
|                 return ""; | ||||
|         for (const subEl of this.uiElements) { | ||||
|             if(subEl === undefined || subEl === null){ | ||||
|                 continue; | ||||
|             } | ||||
|             if(ui.Render === undefined){ | ||||
|                 console.error("Not a UI-element", ui); | ||||
|                 return ""; | ||||
|             const subHtml = subEl.ConstructElement() | ||||
|             if(subHtml !== undefined){ | ||||
|                 el.appendChild(subHtml) | ||||
|             } | ||||
|             return ui.Render(); | ||||
|         }).join(""); | ||||
|         } | ||||
|          | ||||
|         return el; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
| } | ||||
|  | @ -12,11 +12,11 @@ export default class FeatureSwitched extends UIElement{ | |||
|         this._swtch = swtch; | ||||
|     } | ||||
|      | ||||
|     InnerRender(): string { | ||||
|     InnerRender(): UIElement | string { | ||||
|         if(this._swtch.data){ | ||||
|             return this._upstream.Render(); | ||||
|         } | ||||
|         return ""; | ||||
|         return undefined; | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | @ -7,7 +7,7 @@ export class FixedUiElement extends UIElement { | |||
|         super(undefined); | ||||
|         this._html = html ?? ""; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     InnerRender(): string { | ||||
|         return this._html; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,19 +1,29 @@ | |||
| import Constants from "../../Models/Constants"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export default class Img { | ||||
| export default class Img extends BaseUIElement { | ||||
|     private _src: string; | ||||
| 
 | ||||
|     public static runningFromConsole = false; | ||||
|     constructor(src: string) { | ||||
|         super(); | ||||
|         this._src = src; | ||||
|     } | ||||
| 
 | ||||
|    static AsData(source:string){ | ||||
|        if(Utils.runningFromConsole){ | ||||
|            return source; | ||||
|        } | ||||
|        return `data:image/svg+xml;base64,${(btoa(source))}`; | ||||
|    } | ||||
|     static AsData(source: string) { | ||||
|         if (Utils.runningFromConsole) { | ||||
|             return source; | ||||
|         } | ||||
|         return `data:image/svg+xml;base64,${(btoa(source))}`; | ||||
|     } | ||||
| 
 | ||||
|     static AsImageElement(source: string, css_class: string = "", style=""): string{ | ||||
|     static AsImageElement(source: string, css_class: string = "", style = ""): string { | ||||
|         return `<img class="${css_class}" style="${style}" alt="" src="${Img.AsData(source)}">`; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         const el = document.createElement("img") | ||||
|         el.src = this._src; | ||||
|         return el; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,24 +1,35 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| 
 | ||||
| 
 | ||||
| export default class Link extends UIElement { | ||||
|     private readonly _embeddedShow: UIElement; | ||||
|     private readonly _target: string; | ||||
|     private readonly _newTab: string; | ||||
| export default class Link extends BaseUIElement { | ||||
|     private readonly _element: HTMLElement; | ||||
| 
 | ||||
|     constructor(embeddedShow: UIElement | string, target: string, newTab: boolean = false) { | ||||
|     constructor(embeddedShow: BaseUIElement | string, target: string | UIEventSource<string>, newTab: boolean = false) { | ||||
|         super(); | ||||
|         this._embeddedShow = Translations.W(embeddedShow); | ||||
|         this._target = target; | ||||
|         this._newTab = ""; | ||||
|         if (newTab) { | ||||
|             this._newTab = "target='_blank'" | ||||
|         const _embeddedShow = Translations.W(embeddedShow); | ||||
| 
 | ||||
| 
 | ||||
|         const el = document.createElement("a") | ||||
|          | ||||
|         if(typeof target === "string"){ | ||||
|             el.href = target | ||||
|         }else{ | ||||
|             target.addCallbackAndRun(target => { | ||||
|                 el.target = target; | ||||
|             }) | ||||
|         } | ||||
|         if (newTab) { | ||||
|             el.target = "_blank" | ||||
|         } | ||||
|         el.appendChild(_embeddedShow.ConstructElement()) | ||||
|         this._element = el | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return `<a href="${this._target}" ${this._newTab}>${this._embeddedShow.Render()}</a>`; | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
| 
 | ||||
|         return this._element; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -7,7 +7,13 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | |||
| import Hash from "../../Logic/Web/Hash"; | ||||
| 
 | ||||
| /** | ||||
|  * Wraps some contents into a panel that scrolls the content _under_ the title | ||||
|  *  | ||||
|  * The scrollableFullScreen is a bit of a peculiar component: | ||||
|  * - It shows a title and some contents, constructed from the respective functions passed into the constructor | ||||
|  * - When the element is 'activated', one clone of title+contents is attached to the fullscreen | ||||
|  * - The element itself will - upon rendering - also show the title and contents (allthough it'll be a different clone) | ||||
|  *  | ||||
|  *  | ||||
|  */ | ||||
| export default class ScrollableFullScreen extends UIElement { | ||||
|     private static readonly empty = new FixedUiElement(""); | ||||
|  | @ -40,8 +46,8 @@ export default class ScrollableFullScreen extends UIElement { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return this._component.Render(); | ||||
|     InnerRender(): UIElement { | ||||
|         return this._component; | ||||
|     } | ||||
| 
 | ||||
|     Activate(): void { | ||||
|  |  | |||
|  | @ -1,55 +1,51 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Combine from "./Combine"; | ||||
| import {FixedUiElement} from "./FixedUiElement"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import Link from "./Link"; | ||||
| import Img from "./Img"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| 
 | ||||
| 
 | ||||
| export class SubtleButton extends Combine { | ||||
| 
 | ||||
|     constructor(imageUrl: string | UIElement, message: string | UIElement, linkTo: { url: string, newTab?: boolean } = undefined) { | ||||
|     constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined) { | ||||
|         super(SubtleButton.generateContent(imageUrl, message, linkTo)); | ||||
| 
 | ||||
|         this.SetClass("block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200 link-no-underline") | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private static generateContent(imageUrl: string | UIElement, messageT: string | UIElement, linkTo: { url: string, newTab?: boolean } = undefined): (UIElement | string)[] { | ||||
|     private static generateContent(imageUrl: string | BaseUIElement, messageT: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined): (BaseUIElement   )[] { | ||||
|         const message = Translations.W(messageT); | ||||
|         if (message !== null) { | ||||
|             message.dumbMode = false; | ||||
|         } | ||||
|         let img; | ||||
|         if ((imageUrl ?? "") === "") { | ||||
|             img = new FixedUiElement(""); | ||||
|             img = undefined; | ||||
|         } else if (typeof (imageUrl) === "string") { | ||||
|             img = new FixedUiElement(`<img style="width: 100%;" src="${imageUrl}" alt="">`); | ||||
|             img = new Img(imageUrl).SetClass("w-full") | ||||
|         } else { | ||||
|             img = imageUrl; | ||||
|         } | ||||
|         img.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0") | ||||
|         img?.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0") | ||||
|         const image = new Combine([img]) | ||||
|             .SetClass("flex-shrink-0"); | ||||
| 
 | ||||
| 
 | ||||
|         if (message !== null && message.IsEmpty()) { | ||||
|             // Message == null: special case to force empty text
 | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         if (linkTo != undefined) { | ||||
|          | ||||
|         if (linkTo == undefined) { | ||||
|             return [ | ||||
|                 `<a class='flex group' href="${linkTo.url}" ${linkTo.newTab ? 'target="_blank"' : ""}>`, | ||||
|                 image, | ||||
|                 `<div class='ml-4 overflow-ellipsis'>`, | ||||
|                 message, | ||||
|                 `</div>`, | ||||
|                 `</a>` | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|          | ||||
|         return [ | ||||
|             image, | ||||
|             message, | ||||
|             new Link( | ||||
|                 new Combine([ | ||||
|                     image, | ||||
|                     message?.SetClass("block ml-4 overflow-ellipsis") | ||||
|                 ]).SetClass("flex group"), | ||||
|                 linkTo.url, | ||||
|                 linkTo.newTab ?? false | ||||
|             ) | ||||
|         ]; | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -1,39 +1,41 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Combine from "./Combine"; | ||||
| 
 | ||||
| export class TabbedComponent extends UIElement { | ||||
| 
 | ||||
|     private headers: UIElement[] = []; | ||||
|     private readonly header: UIElement; | ||||
|     private content: UIElement[] = []; | ||||
| 
 | ||||
|     constructor(elements: { header: UIElement | string, content: UIElement | string }[], openedTab: (UIEventSource<number> | number) = 0) { | ||||
|         super(typeof (openedTab) === "number" ? new UIEventSource(openedTab) : (openedTab ?? new UIEventSource<number>(0))); | ||||
|         const self = this; | ||||
|         const tabs: UIElement[] = [] | ||||
| 
 | ||||
|         for (let i = 0; i < elements.length; i++) { | ||||
|             let element = elements[i]; | ||||
|             this.headers.push(Translations.W(element.header).onClick(() => self._source.setData(i))); | ||||
|             const header = Translations.W(element.header).onClick(() => self._source.setData(i)) | ||||
|             const content = Translations.W(element.content) | ||||
|             this.content.push(content); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         let headerBar = ""; | ||||
|         for (let i = 0; i < this.headers.length; i++) { | ||||
|             let header = this.headers[i]; | ||||
| 
 | ||||
|             if (!this.content[i].IsEmpty()) { | ||||
|                 headerBar += `<div class=\'tab-single-header ${i == this._source.data ? 'tab-active' : 'tab-non-active'}\'>` + | ||||
|                     header.Render() + "</div>" | ||||
|                 const tab = header.SetClass("block tab-single-header") | ||||
|                 tabs.push(tab) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.header = new Combine(tabs).SetClass("block tabs-header-bar") | ||||
| 
 | ||||
|         headerBar = "<div class='tabs-header-bar'>" + headerBar + "</div>" | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): UIElement { | ||||
| 
 | ||||
|         const content = this.content[this._source.data]; | ||||
|         return headerBar + "<div class='tab-content'>" + (content?.Render() ?? "") + "</div>"; | ||||
|         return new Combine([ | ||||
|             this.header, | ||||
|             content.SetClass("tab-content"), | ||||
|         ]) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,16 +1,35 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export class VariableUiElement extends UIElement { | ||||
|     private _html: UIEventSource<string>; | ||||
| export class VariableUiElement extends BaseUIElement { | ||||
| 
 | ||||
|     constructor(html: UIEventSource<string>) { | ||||
|         super(html); | ||||
|         this._html = html; | ||||
|     private _element : HTMLElement; | ||||
|      | ||||
|     constructor(contents: UIEventSource<string | BaseUIElement>) { | ||||
|         super(); | ||||
|          | ||||
|         this._element = document.createElement("span") | ||||
|         const el = this._element | ||||
|         contents.addCallbackAndRun(contents => { | ||||
|             while(el.firstChild){ | ||||
|                 el.removeChild( | ||||
|                     el.lastChild | ||||
|                 ) | ||||
|             } | ||||
|              | ||||
|             if(contents === undefined){ | ||||
|                 return | ||||
|             } | ||||
|             if(typeof contents === "string"){ | ||||
|                 el.innerHTML = contents | ||||
|             }else{ | ||||
|                 el.appendChild(contents.ConstructElement()) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return this._html.data; | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         return this._element; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,20 +0,0 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| 
 | ||||
| export class VerticalCombine extends UIElement { | ||||
|     private readonly _elements: UIElement[]; | ||||
| 
 | ||||
|     constructor(elements: UIElement[]) { | ||||
|         super(undefined); | ||||
|         this._elements = elements; | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         let html = ""; | ||||
|         for (const element of this._elements) { | ||||
|             if (element !== undefined && !element.IsEmpty()) { | ||||
|                 html += "<div>" + element.Render() + "</div>"; | ||||
|             } | ||||
|         } | ||||
|         return html; | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue