forked from MapComplete/MapComplete
		
	WIP
This commit is contained in:
		
							parent
							
								
									4246221e8e
								
							
						
					
					
						commit
						dd992a1e0d
					
				
					 2 changed files with 248 additions and 96 deletions
				
			
		|  | @ -858,6 +858,10 @@ video { | |||
|   margin-bottom: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .mr-4 { | ||||
|   margin-right: 1rem; | ||||
| } | ||||
| 
 | ||||
| .ml-3 { | ||||
|   margin-left: 0.75rem; | ||||
| } | ||||
|  | @ -886,10 +890,6 @@ video { | |||
|   margin-bottom: 6rem; | ||||
| } | ||||
| 
 | ||||
| .mr-4 { | ||||
|   margin-right: 1rem; | ||||
| } | ||||
| 
 | ||||
| .mt-2 { | ||||
|   margin-top: 0.5rem; | ||||
| } | ||||
|  | @ -1038,6 +1038,10 @@ video { | |||
|   height: 6rem; | ||||
| } | ||||
| 
 | ||||
| .h-8 { | ||||
|   height: 2rem; | ||||
| } | ||||
| 
 | ||||
| .h-full { | ||||
|   height: 100%; | ||||
| } | ||||
|  | @ -1050,10 +1054,6 @@ video { | |||
|   height: 3rem; | ||||
| } | ||||
| 
 | ||||
| .h-8 { | ||||
|   height: 2rem; | ||||
| } | ||||
| 
 | ||||
| .h-1\/2 { | ||||
|   height: 50%; | ||||
| } | ||||
|  | @ -1122,6 +1122,14 @@ video { | |||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| .w-8 { | ||||
|   width: 2rem; | ||||
| } | ||||
| 
 | ||||
| .w-1\/2 { | ||||
|   width: 50%; | ||||
| } | ||||
| 
 | ||||
| .w-24 { | ||||
|   width: 6rem; | ||||
| } | ||||
|  | @ -1138,10 +1146,6 @@ video { | |||
|   width: 3rem; | ||||
| } | ||||
| 
 | ||||
| .w-8 { | ||||
|   width: 2rem; | ||||
| } | ||||
| 
 | ||||
| .w-0 { | ||||
|   width: 0px; | ||||
| } | ||||
|  | @ -1163,10 +1167,6 @@ video { | |||
|   width: min-content; | ||||
| } | ||||
| 
 | ||||
| .w-1\/2 { | ||||
|   width: 50%; | ||||
| } | ||||
| 
 | ||||
| .w-max { | ||||
|   width: -webkit-max-content; | ||||
|   width: max-content; | ||||
|  | @ -1400,6 +1400,10 @@ video { | |||
|   border-bottom-left-radius: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .border-4 { | ||||
|   border-width: 4px; | ||||
| } | ||||
| 
 | ||||
| .border { | ||||
|   border-width: 1px; | ||||
| } | ||||
|  | @ -1408,10 +1412,6 @@ video { | |||
|   border-width: 2px; | ||||
| } | ||||
| 
 | ||||
| .border-4 { | ||||
|   border-width: 4px; | ||||
| } | ||||
| 
 | ||||
| .border-l-4 { | ||||
|   border-left-width: 4px; | ||||
| } | ||||
|  | @ -1420,16 +1420,16 @@ video { | |||
|   border-bottom-width: 1px; | ||||
| } | ||||
| 
 | ||||
| .border-gray-500 { | ||||
|   --tw-border-opacity: 1; | ||||
|   border-color: rgba(107, 114, 128, var(--tw-border-opacity)); | ||||
| } | ||||
| 
 | ||||
| .border-black { | ||||
|   --tw-border-opacity: 1; | ||||
|   border-color: rgba(0, 0, 0, var(--tw-border-opacity)); | ||||
| } | ||||
| 
 | ||||
| .border-gray-500 { | ||||
|   --tw-border-opacity: 1; | ||||
|   border-color: rgba(107, 114, 128, var(--tw-border-opacity)); | ||||
| } | ||||
| 
 | ||||
| .border-gray-400 { | ||||
|   --tw-border-opacity: 1; | ||||
|   border-color: rgba(156, 163, 175, var(--tw-border-opacity)); | ||||
|  | @ -1508,14 +1508,14 @@ video { | |||
|   padding: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .p-4 { | ||||
|   padding: 1rem; | ||||
| } | ||||
| 
 | ||||
| .p-1 { | ||||
|   padding: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .p-4 { | ||||
|   padding: 1rem; | ||||
| } | ||||
| 
 | ||||
| .p-2 { | ||||
|   padding: 0.5rem; | ||||
| } | ||||
|  | @ -1528,11 +1528,20 @@ video { | |||
|   padding: 0.125rem; | ||||
| } | ||||
| 
 | ||||
| .px-4 { | ||||
|   padding-left: 1rem; | ||||
|   padding-right: 1rem; | ||||
| } | ||||
| 
 | ||||
| .px-0 { | ||||
|   padding-left: 0px; | ||||
|   padding-right: 0px; | ||||
| } | ||||
| 
 | ||||
| .pr-2 { | ||||
|   padding-right: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .pb-12 { | ||||
|   padding-bottom: 3rem; | ||||
| } | ||||
|  | @ -1601,10 +1610,6 @@ video { | |||
|   padding-top: 0.125rem; | ||||
| } | ||||
| 
 | ||||
| .pr-2 { | ||||
|   padding-right: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .pl-6 { | ||||
|   padding-left: 1.5rem; | ||||
| } | ||||
|  | @ -1668,10 +1673,6 @@ video { | |||
|   font-weight: 600; | ||||
| } | ||||
| 
 | ||||
| .font-medium { | ||||
|   font-weight: 500; | ||||
| } | ||||
| 
 | ||||
| .uppercase { | ||||
|   text-transform: uppercase; | ||||
| } | ||||
|  | @ -1701,10 +1702,6 @@ video { | |||
|   --tw-ordinal: ordinal; | ||||
| } | ||||
| 
 | ||||
| .leading-6 { | ||||
|   line-height: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .tracking-tight { | ||||
|   letter-spacing: -0.025em; | ||||
| } | ||||
|  | @ -2507,16 +2504,6 @@ input { | |||
|   color: var(--unsubtle-detail-color-contrast); | ||||
| } | ||||
| 
 | ||||
| .group:hover .group-hover\:text-blue-800 { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgba(30, 64, 175, var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .group:hover .group-hover\:text-blue-900 { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgba(30, 58, 138, var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| @media (min-width: 640px) { | ||||
|   .sm\:mx-auto { | ||||
|     margin-left: auto; | ||||
|  |  | |||
							
								
								
									
										251
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										251
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1,51 +1,216 @@ | |||
| import {UIEventSource} from "./Logic/UIEventSource"; | ||||
| import TagRenderingQuestion from "./UI/Popup/TagRenderingQuestion"; | ||||
| import TagRenderingConfig from "./Models/ThemeConfig/TagRenderingConfig"; | ||||
| import {RadioButton} from "./UI/Input/RadioButton"; | ||||
| import {FixedInputElement} from "./UI/Input/FixedInputElement"; | ||||
| import * as shops from "./assets/generated/layers/shops.json" | ||||
| import Combine from "./UI/Base/Combine"; | ||||
| import Img from "./UI/Base/Img"; | ||||
| import BaseUIElement from "./UI/BaseUIElement"; | ||||
| import Svg from "./Svg"; | ||||
| import {TextField} from "./UI/Input/TextField"; | ||||
| import {Store, UIEventSource} from "./Logic/UIEventSource"; | ||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||
| import ValidatedTextField from "./UI/Input/ValidatedTextField"; | ||||
| import VariableInputElement from "./UI/Input/VariableInputElement"; | ||||
| import Locale from "./UI/i18n/Locale"; | ||||
| import LanguagePicker from "./UI/LanguagePicker"; | ||||
| import {InputElement} from "./UI/Input/InputElement"; | ||||
| import {UIElement} from "./UI/UIElement"; | ||||
| import Translations from "./UI/i18n/Translations"; | ||||
| import TagRenderingConfig, {Mapping} from "./Models/ThemeConfig/TagRenderingConfig"; | ||||
| import {MappingConfigJson} from "./Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"; | ||||
| import {FixedUiElement} from "./UI/Base/FixedUiElement"; | ||||
| import {TagsFilter} from "./Logic/Tags/TagsFilter"; | ||||
| 
 | ||||
| const config = new TagRenderingConfig({ | ||||
|     question: "What is the name?", | ||||
|     render: "The name is {name}", | ||||
|     freeform: { | ||||
|         key: 'name', | ||||
|         inline:true | ||||
|     }, | ||||
|     mappings:[ | ||||
|         { | ||||
|             if:"noname=yes", | ||||
|             then: "This feature has no name" | ||||
| const mappingsRaw: MappingConfigJson[] = <any>shops.tagRenderings.find(tr => tr.id == "shop_types").mappings | ||||
| const mappings = mappingsRaw.map((m, i) => TagRenderingConfig.ExtractMapping(m, i, "test", "test")) | ||||
| 
 | ||||
| 
 | ||||
| export class SelfHidingToggle extends UIElement implements InputElement<boolean> { | ||||
|     private readonly _shown: BaseUIElement; | ||||
|     private readonly _searchTerms: Record<string, string[]>; | ||||
|     private readonly _search: Store<string>; | ||||
| 
 | ||||
|     private readonly _selected: UIEventSource<boolean> | ||||
| 
 | ||||
|     public constructor( | ||||
|         shown: string | BaseUIElement, | ||||
|         mainTerm: Record<string, string>, | ||||
|         search: Store<string>, | ||||
|         searchTerms?: Record<string, string[]>, | ||||
|         selected: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||
|     ) { | ||||
|         super(); | ||||
|         this._shown = Translations.W(shown); | ||||
|         this._search = search; | ||||
|         this._searchTerms = {}; | ||||
|         for (const lng in searchTerms ?? []) { | ||||
|             if (lng === "_context") { | ||||
|                 continue | ||||
|             } | ||||
|             this._searchTerms[lng] = searchTerms[lng].map(t => t.trim().toLowerCase()) | ||||
|         } | ||||
|     ] | ||||
| }) | ||||
| 
 | ||||
| const tags = new UIEventSource<any>({ | ||||
|     name: "current feature name" | ||||
| }) | ||||
| 
 | ||||
| /*new TagRenderingQuestion( | ||||
|     tags, config, undefined).AttachTo("maindiv")*/ | ||||
| const options = new UIEventSource<string[]>([]) | ||||
| const rb = | ||||
|     new VariableInputElement( | ||||
|         options.map(options  => { | ||||
|             console.trace("Construction an input element for", options) | ||||
|            return new RadioButton( | ||||
|                 [ | ||||
|                     ...options.map(o => new FixedInputElement(o,o)), | ||||
|                     new FixedInputElement<string>("abc", "abc"), | ||||
|                     ValidatedTextField.ForType().ConstructInputElement() | ||||
|                 ]) | ||||
|         for (const lng in mainTerm) { | ||||
|             if (lng === "_context") { | ||||
|                 continue | ||||
|             } | ||||
|             this._searchTerms[lng] = [mainTerm[lng]].concat(this._searchTerms[lng] ?? []) | ||||
|         } | ||||
|         this._selected = selected; | ||||
|     } | ||||
| 
 | ||||
|     ) | ||||
| 
 | ||||
|     GetValue(): UIEventSource<boolean> { | ||||
|         return this._selected | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: boolean): boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerRender(): string | BaseUIElement { | ||||
|         let el: BaseUIElement = this._shown; | ||||
|         const selected = this._selected; | ||||
|         const search = this._search; | ||||
|         const terms = this._searchTerms; | ||||
|         const applySearch = () => { | ||||
|             const s = search.data?.trim()?.toLowerCase() | ||||
|             if (s === undefined || s.length === 0 || selected.data) { | ||||
|                 el.RemoveClass("hidden") | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (terms[Locale.language.data].some(t => t.toLowerCase().indexOf(s) >= 0)) { | ||||
|                 el.RemoveClass("hidden"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             el.SetClass("hidden") | ||||
|         } | ||||
|         search.addCallbackAndRun(_ => { | ||||
|             applySearch() | ||||
|         }) | ||||
|         Locale.language.addCallback(_ => { | ||||
|             applySearch() | ||||
|         }) | ||||
| 
 | ||||
|         selected.addCallbackAndRun(selected => { | ||||
|             if (selected) { | ||||
|                 el.SetClass("border-4") | ||||
|                 el.RemoveClass("border") | ||||
|                 el.SetStyle("margin: calc( 0.25rem )") | ||||
|             } else { | ||||
|                 el.SetStyle("margin: calc( 0.25rem + 3px )") | ||||
|                 el.SetClass("border") | ||||
|                 el.RemoveClass("border-4") | ||||
|             } | ||||
|             applySearch() | ||||
|         }) | ||||
| 
 | ||||
|         el.onClick(() => selected.setData(!selected.data)) | ||||
| 
 | ||||
|         return el.SetClass("border border-black rounded-full p-1 px-4") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class SearchablePresets<T> extends Combine implements InputElement<T[]> { | ||||
|     private selectedElements: UIEventSource<T[]>; | ||||
| 
 | ||||
|     constructor( | ||||
|         values: { show: BaseUIElement, value: T, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> }[], | ||||
|         mode: "select-one" | "select-many", | ||||
|         selectedElements: UIEventSource<T[]> = new UIEventSource<T[]>([])) { | ||||
| 
 | ||||
|         const search = new TextField({}) | ||||
| 
 | ||||
|         const searchBar = new Combine([Svg.search_svg().SetClass("w-8"), search.SetClass("mr-4 w-full")]) | ||||
|             .SetClass("flex rounded-full border-2 border-black items-center my-2 w-1/2") | ||||
| 
 | ||||
|         const searchValue = search.GetValue().map(s => s?.trim()?.toLowerCase()) | ||||
| 
 | ||||
| 
 | ||||
|         values = values.map(v => { | ||||
| 
 | ||||
|             const vIsSelected = new UIEventSource(false); | ||||
| 
 | ||||
|             selectedElements.addCallbackAndRunD(selectedElements => { | ||||
|                 vIsSelected.setData(selectedElements.some(t => t === v.value)) | ||||
|             }) | ||||
| 
 | ||||
|             vIsSelected.addCallback(selected => { | ||||
|                 if (selected) { | ||||
|                     if (mode === "select-one") { | ||||
|                         selectedElements.setData([v.value]) | ||||
|                     } else if (!selectedElements.data.some(t => t === v.value)) { | ||||
|                         selectedElements.data.push(v.value); | ||||
|                         selectedElements.ping() | ||||
|                     } | ||||
|                 }else{ | ||||
|                     for (let i = 0; i < selectedElements.data.length; i++) { | ||||
|                         const t = selectedElements.data[i] | ||||
|                         if(t == v.value){ | ||||
|                             selectedElements.data.splice(i, 1) | ||||
|                             selectedElements.ping() | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
| 
 | ||||
|             return { | ||||
|                 ...v, | ||||
|                 show: new SelfHidingToggle(v.show, v.mainTerm, searchValue, v.searchTerms, vIsSelected) | ||||
|             }; | ||||
|         }) | ||||
| 
 | ||||
|         super([ | ||||
|             searchBar, | ||||
|             new VariableUiElement(Locale.language.map(lng => { | ||||
|                 values.sort((a, b) => a.mainTerm[lng] < b.mainTerm[lng] ? -1 : 1) | ||||
|                 return new Combine(values.map(e => e.show)) | ||||
|                     .SetClass("flex flex-wrap w-full") | ||||
|             })) | ||||
| 
 | ||||
|         ]) | ||||
|         this.selectedElements = selectedElements; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public GetValue(): UIEventSource<T[]> { | ||||
|         return this.selectedElements; | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: T[]): boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function fromMapping(m: Mapping): { show: BaseUIElement, value: TagsFilter, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> } { | ||||
|     const el: BaseUIElement = m.then | ||||
|     let icon: BaseUIElement | ||||
|     if (m.icon !== undefined) { | ||||
|         icon = new Img(m.icon).SetClass("h-8 w-8 pr-2") | ||||
|     } else { | ||||
|         icon = new FixedUiElement("").SetClass("h-8 w-1") | ||||
|     } | ||||
|     const show = new Combine([ | ||||
|         icon, | ||||
|         el.SetClass("block-ruby") | ||||
|     ]).SetClass("flex items-center") | ||||
| 
 | ||||
|     return {show, mainTerm: m.then.translations, searchTerms: m.searchTerms, value: m.if}; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| const sp = new SearchablePresets( | ||||
|     mappings.map(m => fromMapping(m)), | ||||
|     "select-one" | ||||
| ) | ||||
| rb.AttachTo("maindiv") | ||||
| rb.GetValue().addCallbackAndRun(v => console.log("Current value is",v)) | ||||
| new VariableUiElement(rb.GetValue()).AttachTo("extradiv") | ||||
| 
 | ||||
| window.setTimeout(() => {options.setData(["xyz","foo","bar"])},10000) | ||||
| sp.AttachTo("maindiv") | ||||
| 
 | ||||
| const lp = new LanguagePicker(["en", "nl"], "") | ||||
| 
 | ||||
| new Combine([ | ||||
|     new VariableUiElement(sp.GetValue().map(tf => new FixedUiElement("Selected tags: " + tf.map(tf => tf.asHumanString(false, false, {})).join(", ")))), | ||||
|     lp | ||||
| ]).SetClass("flex flex-col") | ||||
|     .AttachTo("extradiv") | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue