Trying to get the checkboxlogic right
This commit is contained in:
parent
c944156d87
commit
e0f2f70c2e
7 changed files with 113 additions and 97 deletions
|
@ -462,6 +462,7 @@ export class TagUtils {
|
||||||
let leftoverTag = undefined;
|
let leftoverTag = undefined;
|
||||||
if (keyValues[freeformKey] !== undefined && keyValues[freeformKey].length !== 0) {
|
if (keyValues[freeformKey] !== undefined && keyValues[freeformKey].length !== 0) {
|
||||||
leftoverTag = new Tag(freeformKey, keyValues[freeformKey].join(";"));
|
leftoverTag = new Tag(freeformKey, keyValues[freeformKey].join(";"));
|
||||||
|
console.log("Leftovers are ", leftoverTag)
|
||||||
if (freeformExtraTags !== undefined) {
|
if (freeformExtraTags !== undefined) {
|
||||||
leftoverTag = new And([
|
leftoverTag = new And([
|
||||||
leftoverTag,
|
leftoverTag,
|
||||||
|
|
|
@ -1,52 +1,36 @@
|
||||||
import {InputElement} from "./InputElement";
|
import {InputElement} from "./InputElement";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
import {UIElement} from "../UIElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supports multi-input
|
* Supports multi-input
|
||||||
*/
|
*/
|
||||||
export class CheckBoxes<T> extends InputElement<T[]> {
|
export class CheckBoxes extends InputElement<number[]> {
|
||||||
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
|
||||||
private readonly _selectedElementIndex: UIEventSource<number>
|
private readonly value: UIEventSource<number[]>;
|
||||||
= new UIEventSource<number>(null);
|
private readonly _elements: UIElement[]
|
||||||
|
|
||||||
private readonly value: UIEventSource<T[]>;
|
|
||||||
private readonly _elements: InputElement<T>[]
|
|
||||||
|
|
||||||
|
|
||||||
constructor(elements: InputElement<T>[]) {
|
constructor(elements: UIElement[]) {
|
||||||
super(undefined);
|
super(undefined);
|
||||||
this._elements = Utils.NoNull(elements);
|
this._elements = Utils.NoNull(elements);
|
||||||
this.dumbMode = false;
|
this.dumbMode = false;
|
||||||
|
|
||||||
this.value = new UIEventSource<T[]>([])
|
this.value = new UIEventSource<number[]>([])
|
||||||
this.ListenTo(this.value);
|
this.ListenTo(this.value);
|
||||||
this.value.addCallback(latest => console.log("Latest is ", latest))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IsValid(ts: T[]): boolean {
|
|
||||||
|
IsValid(ts: number[]): boolean {
|
||||||
if (ts === undefined) {
|
if (ts === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (const t of ts) {
|
|
||||||
let matchFound = false;
|
|
||||||
for (const element of this._elements) {
|
|
||||||
if (element.IsValid(t)) {
|
|
||||||
element.GetValue().setData(t);
|
|
||||||
matchFound = true;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!matchFound) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GetValue(): UIEventSource<T[]> {
|
GetValue(): UIEventSource<number[]> {
|
||||||
return this.value;
|
return this.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,37 +56,23 @@ export class CheckBoxes<T> extends InputElement<T[]> {
|
||||||
super.InnerUpdate(htmlElement);
|
super.InnerUpdate(htmlElement);
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < this._elements.length; i++) {
|
for (let i = 0; i < this._elements.length; i++) {
|
||||||
const el = document.getElementById(this.IdFor(i));
|
const el = document.getElementById(this.IdFor(i));
|
||||||
const inputEl = this._elements[i];
|
|
||||||
|
if(this.value.data.indexOf(i) >= 0){
|
||||||
for (const t of this.value.data ?? []) {
|
|
||||||
if(t === undefined){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let isValid = inputEl.IsValid(t);
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
el.checked = isValid;
|
el.checked = true;
|
||||||
if(isValid){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
el.onchange = e => {
|
el.onchange = () => {
|
||||||
const v = inputEl.GetValue().data;
|
const index = self.value.data.indexOf(i);
|
||||||
const index = self.value.data.indexOf(v);
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (el.checked) {
|
if(el.checked && index < 0){
|
||||||
if (index < 0) {
|
self.value.data.push(i);
|
||||||
self.value.data.push(v);
|
self.value.ping();
|
||||||
self.value.ping();
|
}else if(index >= 0){
|
||||||
}
|
self.value.data.splice(index,1);
|
||||||
} else {
|
self.value.ping();
|
||||||
if (index >= 0) {
|
|
||||||
self.value.data.splice(index, 1);
|
|
||||||
self.value.ping();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,6 @@ export class FixedInputElement<T> extends InputElement<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
IsValid(t: T): boolean {
|
IsValid(t: T): boolean {
|
||||||
|
|
||||||
console.log("Comparing ",t, "with", this.value.data);
|
|
||||||
return this._comparator(t, this.value.data);
|
return this._comparator(t, this.value.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,9 @@ export class TextField<T> extends InputElement<T> {
|
||||||
this.ListenTo(this._placeholder._source);
|
this.ListenTo(this._placeholder._source);
|
||||||
this._toString = options.toString ?? ((t) => ("" + t));
|
this._toString = options.toString ?? ((t) => ("" + t));
|
||||||
|
|
||||||
|
this.onClick(() => {
|
||||||
|
self.IsSelected.setData(true)
|
||||||
|
});
|
||||||
this.mappedValue.addCallback((t) => {
|
this.mappedValue.addCallback((t) => {
|
||||||
if (t === undefined || t === null) {
|
if (t === undefined || t === null) {
|
||||||
return;
|
return;
|
||||||
|
@ -202,6 +204,7 @@ export class TextField<T> extends InputElement<T> {
|
||||||
field.addEventListener("focusin", () => self.IsSelected.setData(true));
|
field.addEventListener("focusin", () => self.IsSelected.setData(true));
|
||||||
field.addEventListener("focusout", () => self.IsSelected.setData(false));
|
field.addEventListener("focusout", () => self.IsSelected.setData(false));
|
||||||
|
|
||||||
|
|
||||||
field.addEventListener("keyup", function (event) {
|
field.addEventListener("keyup", function (event) {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -205,7 +205,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
InputElement<TagsFilter> {
|
InputElement<TagsFilter> {
|
||||||
|
|
||||||
|
|
||||||
let freeformElement = undefined;
|
let freeformElement: InputElement<TagsFilter> = undefined;
|
||||||
if (options.freeform !== undefined) {
|
if (options.freeform !== undefined) {
|
||||||
freeformElement = this.InputForFreeForm(options.freeform);
|
freeformElement = this.InputForFreeForm(options.freeform);
|
||||||
}
|
}
|
||||||
|
@ -235,45 +235,61 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
const possibleTags = elements.map(el => el.GetValue().data);
|
const possibleTags = elements.map(el => el.GetValue().data);
|
||||||
const checkBoxes = new CheckBoxes(elements);
|
const checkBoxes = new CheckBoxes(elements);
|
||||||
|
|
||||||
// In order to let this work, we are cheating a lot here
|
|
||||||
// First of all, it is very tricky to let the mapping stabilize
|
const inputEl = new InputElementMap<number[], TagsFilter>(checkBoxes,
|
||||||
// The selection gets added as list and needs to be flattened into a new 'and'
|
(t0, t1) => {
|
||||||
// This new and causes the mapping to have a 'set value', which is unpacked to update the UI
|
return t0?.isEquivalent(t1) ?? false
|
||||||
// AFter which the UI reupdates and reapplies the value
|
|
||||||
// So, instead we opt to _always return the 'value' below which is statefully updated
|
|
||||||
// But, then we still have to figure out when to update...
|
|
||||||
// For this, we use the original inputElement
|
|
||||||
// This is very dirty code, I know
|
|
||||||
const value = new And([]);
|
|
||||||
const inputElement = new InputElementMap(checkBoxes,
|
|
||||||
(t0: And, t1: And) => {
|
|
||||||
return t0?.isEquivalent(t1) ?? t0 === t1
|
|
||||||
},
|
},
|
||||||
(fromUI) => {
|
(indices) => {
|
||||||
if (fromUI === undefined) {
|
if (indices.length === 0) {
|
||||||
value.and = [];
|
return undefined;
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
const flattened = TagUtils.FlattenMultiAnswer(fromUI);
|
let tags: TagsFilter[] = indices.map(i => elements[i].GetValue().data);
|
||||||
value.and = flattened.and;
|
return TagUtils.FlattenMultiAnswer(tags);
|
||||||
return value;
|
|
||||||
},
|
},
|
||||||
(fromTags) => {
|
(tags: TagsFilter) => {
|
||||||
return TagUtils.SplitMultiAnswer(fromTags, possibleTags, this._freeform?.key, this._freeform?.extraTags);
|
const splitUpValues = TagUtils.SplitMultiAnswer(tags, possibleTags, this._freeform?.key, this._freeform?.extraTags);
|
||||||
|
const indices: number[] = []
|
||||||
|
|
||||||
|
for (let i = 0; i < splitUpValues.length; i++) {
|
||||||
|
let splitUpValue = splitUpValues[i];
|
||||||
|
|
||||||
|
for (let j = 0; j < elements.length; j++) {
|
||||||
|
let inputElement = elements[j];
|
||||||
|
if (inputElement.IsValid(splitUpValue)) {
|
||||||
|
indices.push(j);
|
||||||
|
inputElement.GetValue().setData(splitUpValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indices;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let previousSelectionCount = -1;
|
freeformElement?.IsSelected.addCallback((isSelected) => {
|
||||||
checkBoxes.GetValue().addCallback(selected => {
|
console.log("SELECTED FF", isSelected)
|
||||||
const newSelectionCount = selected.length;
|
if (isSelected) {
|
||||||
if (newSelectionCount != previousSelectionCount) {
|
const es = checkBoxes.GetValue();
|
||||||
previousSelectionCount = newSelectionCount;
|
const i = elements.length - 1
|
||||||
inputElement.GetValue().ping();
|
if (es.data.indexOf(i) >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
es.data.push(i);
|
||||||
|
es.ping();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return inputElement;
|
freeformElement?.GetValue()?.addCallback(() => {
|
||||||
|
const es = checkBoxes.GetValue();
|
||||||
|
const i = elements.length - 1
|
||||||
|
if (es.data.indexOf(i) < 0) {
|
||||||
|
es.data.push(i);
|
||||||
|
es.ping();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return inputEl;
|
||||||
}
|
}
|
||||||
return new RadioButton(elements, false);
|
return new RadioButton(elements, false);
|
||||||
}
|
}
|
||||||
|
@ -340,22 +356,20 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toString =
|
const toString = (tag) => {
|
||||||
(tag) => {
|
if (tag instanceof And) {
|
||||||
console.log("Decoding ", tag, "in freeform text element")
|
for (const subtag of tag.and) {
|
||||||
if (tag instanceof And) {
|
if (subtag instanceof Tag && subtag.key === freeform.key) {
|
||||||
for (const subtag of tag.and) {
|
return subtag.value;
|
||||||
if(subtag instanceof Tag && subtag.key === freeform.key){
|
|
||||||
return subtag.value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
|
||||||
} else if (tag instanceof Tag) {
|
|
||||||
return tag.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
} else if (tag instanceof Tag) {
|
||||||
|
return tag.value
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const textField = new TextField({
|
const textField = new TextField({
|
||||||
|
|
|
@ -95,8 +95,12 @@ export abstract class UIElement extends UIEventSource<string> {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setData(this.lastInnerRender ?? this.InnerRender());
|
const newRender = this.InnerRender();
|
||||||
element.innerHTML = this.data;
|
if (newRender !== this.lastInnerRender) {
|
||||||
|
this.setData(this.InnerRender());
|
||||||
|
element.innerHTML = this.data;
|
||||||
|
this.lastInnerRender = newRender;
|
||||||
|
}
|
||||||
|
|
||||||
if (this._hideIfEmpty) {
|
if (this._hideIfEmpty) {
|
||||||
if (element.innerHTML === "") {
|
if (element.innerHTML === "") {
|
||||||
|
|
26
clean.sh
Executable file
26
clean.sh
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
# clean up the mess we made
|
||||||
|
# rm *.js
|
||||||
|
# rm Logic/*.js
|
||||||
|
# rm Logic/*.js
|
||||||
|
# rm Logic/*/*.js
|
||||||
|
# rm Logic/*/*/*.js
|
||||||
|
# rm UI/*.js
|
||||||
|
# rm UI/*/*.js
|
||||||
|
# rm UI/*/*/*.js
|
||||||
|
# rm Customizations/*.js
|
||||||
|
# rm Customizations/*/*.js
|
||||||
|
# rm Customizations/*/*/*.js
|
||||||
|
|
||||||
|
rm *.webmanifest
|
||||||
|
rm assets/generated/*
|
||||||
|
|
||||||
|
for f in ./*.html; do
|
||||||
|
if [[ "$f" == "./index.html" ]] || [[ "$f" == "./land.html" ]] || [[ "$f" == "./test.html" ]] || [[ "$f" == "./preferences.html" ]] || [[ "$f" == "./customGenerator.html" ]]
|
||||||
|
then
|
||||||
|
echo "Not removing $f"
|
||||||
|
else
|
||||||
|
rm $f
|
||||||
|
fi
|
||||||
|
done
|
Loading…
Reference in a new issue