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