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; |   margin-bottom: 0.75rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .mr-4 { | ||||||
|  |   margin-right: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .ml-3 { | .ml-3 { | ||||||
|   margin-left: 0.75rem; |   margin-left: 0.75rem; | ||||||
| } | } | ||||||
|  | @ -886,10 +890,6 @@ video { | ||||||
|   margin-bottom: 6rem; |   margin-bottom: 6rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .mr-4 { |  | ||||||
|   margin-right: 1rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .mt-2 { | .mt-2 { | ||||||
|   margin-top: 0.5rem; |   margin-top: 0.5rem; | ||||||
| } | } | ||||||
|  | @ -1038,6 +1038,10 @@ video { | ||||||
|   height: 6rem; |   height: 6rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .h-8 { | ||||||
|  |   height: 2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .h-full { | .h-full { | ||||||
|   height: 100%; |   height: 100%; | ||||||
| } | } | ||||||
|  | @ -1050,10 +1054,6 @@ video { | ||||||
|   height: 3rem; |   height: 3rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .h-8 { |  | ||||||
|   height: 2rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .h-1\/2 { | .h-1\/2 { | ||||||
|   height: 50%; |   height: 50%; | ||||||
| } | } | ||||||
|  | @ -1122,6 +1122,14 @@ video { | ||||||
|   width: 100%; |   width: 100%; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .w-8 { | ||||||
|  |   width: 2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .w-1\/2 { | ||||||
|  |   width: 50%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .w-24 { | .w-24 { | ||||||
|   width: 6rem; |   width: 6rem; | ||||||
| } | } | ||||||
|  | @ -1138,10 +1146,6 @@ video { | ||||||
|   width: 3rem; |   width: 3rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .w-8 { |  | ||||||
|   width: 2rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .w-0 { | .w-0 { | ||||||
|   width: 0px; |   width: 0px; | ||||||
| } | } | ||||||
|  | @ -1163,10 +1167,6 @@ video { | ||||||
|   width: min-content; |   width: min-content; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .w-1\/2 { |  | ||||||
|   width: 50%; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .w-max { | .w-max { | ||||||
|   width: -webkit-max-content; |   width: -webkit-max-content; | ||||||
|   width: max-content; |   width: max-content; | ||||||
|  | @ -1400,6 +1400,10 @@ video { | ||||||
|   border-bottom-left-radius: 0.25rem; |   border-bottom-left-radius: 0.25rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .border-4 { | ||||||
|  |   border-width: 4px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .border { | .border { | ||||||
|   border-width: 1px; |   border-width: 1px; | ||||||
| } | } | ||||||
|  | @ -1408,10 +1412,6 @@ video { | ||||||
|   border-width: 2px; |   border-width: 2px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .border-4 { |  | ||||||
|   border-width: 4px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .border-l-4 { | .border-l-4 { | ||||||
|   border-left-width: 4px; |   border-left-width: 4px; | ||||||
| } | } | ||||||
|  | @ -1420,16 +1420,16 @@ video { | ||||||
|   border-bottom-width: 1px; |   border-bottom-width: 1px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .border-gray-500 { |  | ||||||
|   --tw-border-opacity: 1; |  | ||||||
|   border-color: rgba(107, 114, 128, var(--tw-border-opacity)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .border-black { | .border-black { | ||||||
|   --tw-border-opacity: 1; |   --tw-border-opacity: 1; | ||||||
|   border-color: rgba(0, 0, 0, var(--tw-border-opacity)); |   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 { | .border-gray-400 { | ||||||
|   --tw-border-opacity: 1; |   --tw-border-opacity: 1; | ||||||
|   border-color: rgba(156, 163, 175, var(--tw-border-opacity)); |   border-color: rgba(156, 163, 175, var(--tw-border-opacity)); | ||||||
|  | @ -1508,14 +1508,14 @@ video { | ||||||
|   padding: 0.75rem; |   padding: 0.75rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .p-4 { |  | ||||||
|   padding: 1rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .p-1 { | .p-1 { | ||||||
|   padding: 0.25rem; |   padding: 0.25rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .p-4 { | ||||||
|  |   padding: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .p-2 { | .p-2 { | ||||||
|   padding: 0.5rem; |   padding: 0.5rem; | ||||||
| } | } | ||||||
|  | @ -1528,11 +1528,20 @@ video { | ||||||
|   padding: 0.125rem; |   padding: 0.125rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .px-4 { | ||||||
|  |   padding-left: 1rem; | ||||||
|  |   padding-right: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .px-0 { | .px-0 { | ||||||
|   padding-left: 0px; |   padding-left: 0px; | ||||||
|   padding-right: 0px; |   padding-right: 0px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .pr-2 { | ||||||
|  |   padding-right: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .pb-12 { | .pb-12 { | ||||||
|   padding-bottom: 3rem; |   padding-bottom: 3rem; | ||||||
| } | } | ||||||
|  | @ -1601,10 +1610,6 @@ video { | ||||||
|   padding-top: 0.125rem; |   padding-top: 0.125rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .pr-2 { |  | ||||||
|   padding-right: 0.5rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .pl-6 { | .pl-6 { | ||||||
|   padding-left: 1.5rem; |   padding-left: 1.5rem; | ||||||
| } | } | ||||||
|  | @ -1668,10 +1673,6 @@ video { | ||||||
|   font-weight: 600; |   font-weight: 600; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .font-medium { |  | ||||||
|   font-weight: 500; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .uppercase { | .uppercase { | ||||||
|   text-transform: uppercase; |   text-transform: uppercase; | ||||||
| } | } | ||||||
|  | @ -1701,10 +1702,6 @@ video { | ||||||
|   --tw-ordinal: ordinal; |   --tw-ordinal: ordinal; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .leading-6 { |  | ||||||
|   line-height: 1.5rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .tracking-tight { | .tracking-tight { | ||||||
|   letter-spacing: -0.025em; |   letter-spacing: -0.025em; | ||||||
| } | } | ||||||
|  | @ -2507,16 +2504,6 @@ input { | ||||||
|   color: var(--unsubtle-detail-color-contrast); |   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) { | @media (min-width: 640px) { | ||||||
|   .sm\:mx-auto { |   .sm\:mx-auto { | ||||||
|     margin-left: auto; |     margin-left: auto; | ||||||
|  |  | ||||||
							
								
								
									
										255
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										255
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1,51 +1,216 @@ | ||||||
| import {UIEventSource} from "./Logic/UIEventSource"; | import * as shops from "./assets/generated/layers/shops.json" | ||||||
| import TagRenderingQuestion from "./UI/Popup/TagRenderingQuestion"; | import Combine from "./UI/Base/Combine"; | ||||||
| import TagRenderingConfig from "./Models/ThemeConfig/TagRenderingConfig"; | import Img from "./UI/Base/Img"; | ||||||
| import {RadioButton} from "./UI/Input/RadioButton"; | import BaseUIElement from "./UI/BaseUIElement"; | ||||||
| import {FixedInputElement} from "./UI/Input/FixedInputElement"; | import Svg from "./Svg"; | ||||||
|  | import {TextField} from "./UI/Input/TextField"; | ||||||
|  | import {Store, UIEventSource} from "./Logic/UIEventSource"; | ||||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||||
| import ValidatedTextField from "./UI/Input/ValidatedTextField"; | import Locale from "./UI/i18n/Locale"; | ||||||
| import VariableInputElement from "./UI/Input/VariableInputElement"; | 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({ | const mappingsRaw: MappingConfigJson[] = <any>shops.tagRenderings.find(tr => tr.id == "shop_types").mappings | ||||||
|     question: "What is the name?", | const mappings = mappingsRaw.map((m, i) => TagRenderingConfig.ExtractMapping(m, i, "test", "test")) | ||||||
|     render: "The name is {name}", | 
 | ||||||
|     freeform: { | 
 | ||||||
|         key: 'name', | export class SelfHidingToggle extends UIElement implements InputElement<boolean> { | ||||||
|         inline:true |     private readonly _shown: BaseUIElement; | ||||||
|     }, |     private readonly _searchTerms: Record<string, string[]>; | ||||||
|     mappings:[ |     private readonly _search: Store<string>; | ||||||
|         { | 
 | ||||||
|             if:"noname=yes", |     private readonly _selected: UIEventSource<boolean> | ||||||
|             then: "This feature has no name" | 
 | ||||||
|  |     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()) | ||||||
|         } |         } | ||||||
|     ] |         for (const lng in mainTerm) { | ||||||
| }) |             if (lng === "_context") { | ||||||
| 
 |                 continue | ||||||
| const tags = new UIEventSource<any>({ |             } | ||||||
|     name: "current feature name" |             this._searchTerms[lng] = [mainTerm[lng]].concat(this._searchTerms[lng] ?? []) | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| /*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() |  | ||||||
|                 ]) |  | ||||||
|         } |         } | ||||||
|          |         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