Reformat all files with prettier

This commit is contained in:
Pieter Vander Vennet 2022-09-08 21:40:48 +02:00
parent e22d189376
commit b541d3eab4
382 changed files with 50893 additions and 35566 deletions

View file

@ -1,18 +1,18 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Utils} from "../../Utils";
import BaseUIElement from "../BaseUIElement";
import InputElementMap from "./InputElementMap";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Utils } from "../../Utils"
import BaseUIElement from "../BaseUIElement"
import InputElementMap from "./InputElementMap"
export class CheckBox extends InputElementMap<number[], boolean> {
constructor(el: BaseUIElement , defaultValue?: boolean) {
constructor(el: BaseUIElement, defaultValue?: boolean) {
super(
new CheckBoxes([el]),
(x0, x1) => x0 === x1,
t => t.length > 0,
x => x ? [0] : [],
);
if(defaultValue !== undefined){
(t) => t.length > 0,
(x) => (x ? [0] : [])
)
if (defaultValue !== undefined) {
this.GetValue().setData(defaultValue)
}
}
@ -23,94 +23,78 @@ export class CheckBox extends InputElementMap<number[], boolean> {
* The value will contain the indexes of the selected checkboxes
*/
export default class CheckBoxes extends InputElement<number[]> {
private static _nextId = 0;
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly value: UIEventSource<number[]>;
private readonly _elements: BaseUIElement[];
private static _nextId = 0
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
private readonly value: UIEventSource<number[]>
private readonly _elements: BaseUIElement[]
constructor(
elements: BaseUIElement[],
value = new UIEventSource<number[]>([])
) {
super();
this.value = value;
this._elements = Utils.NoNull(elements);
this.SetClass("flex flex-col");
constructor(elements: BaseUIElement[], value = new UIEventSource<number[]>([])) {
super()
this.value = value
this._elements = Utils.NoNull(elements)
this.SetClass("flex flex-col")
}
IsValid(ts: number[]): boolean {
return ts !== undefined;
return ts !== undefined
}
GetValue(): UIEventSource<number[]> {
return this.value;
return this.value
}
protected InnerConstructElement(): HTMLElement {
const formTag = document.createElement("form");
const formTag = document.createElement("form")
const value = this.value;
const elements = this._elements;
const value = this.value
const elements = this._elements
for (let i = 0; i < elements.length; i++) {
let inputI = elements[i];
const input = document.createElement("input");
const id = CheckBoxes._nextId;
CheckBoxes._nextId++;
input.id = "checkbox" + id;
let inputI = elements[i]
const input = document.createElement("input")
const id = CheckBoxes._nextId
CheckBoxes._nextId++
input.id = "checkbox" + id
input.type = "checkbox";
input.classList.add("p-1", "cursor-pointer", "m-3", "pl-3", "mr-0");
input.type = "checkbox"
input.classList.add("p-1", "cursor-pointer", "m-3", "pl-3", "mr-0")
const label = document.createElement("label");
label.htmlFor = input.id;
label.appendChild(inputI.ConstructElement());
label.classList.add(
"block",
"w-full",
"p-2",
"cursor-pointer",
"bg-red"
);
const label = document.createElement("label")
label.htmlFor = input.id
label.appendChild(inputI.ConstructElement())
label.classList.add("block", "w-full", "p-2", "cursor-pointer", "bg-red")
const wrapper = document.createElement("div");
wrapper.classList.add(
"wrapper",
"flex",
"w-full",
"border",
"border-gray-400",
"mb-1"
);
wrapper.appendChild(input);
wrapper.appendChild(label);
formTag.appendChild(wrapper);
const wrapper = document.createElement("div")
wrapper.classList.add("wrapper", "flex", "w-full", "border", "border-gray-400", "mb-1")
wrapper.appendChild(input)
wrapper.appendChild(label)
formTag.appendChild(wrapper)
value.addCallbackAndRunD((selectedValues) => {
input.checked = selectedValues.indexOf(i) >= 0;
input.checked = selectedValues.indexOf(i) >= 0
if (input.checked) {
wrapper.classList.remove("border-gray-400");
wrapper.classList.add("border-black");
wrapper.classList.remove("border-gray-400")
wrapper.classList.add("border-black")
} else {
wrapper.classList.add("border-gray-400");
wrapper.classList.remove("border-black");
wrapper.classList.add("border-gray-400")
wrapper.classList.remove("border-black")
}
});
})
input.onchange = () => {
// Index = index in the list of already checked items
const index = value.data.indexOf(i);
const index = value.data.indexOf(i)
if (input.checked && index < 0) {
value.data.push(i);
value.ping();
value.data.push(i)
value.ping()
} else if (index >= 0) {
value.data.splice(index, 1);
value.ping();
value.data.splice(index, 1)
value.ping()
}
};
}
}
return formTag;
return formTag
}
}

View file

@ -1,43 +1,39 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
export default class ColorPicker extends InputElement<string> {
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
private readonly value: UIEventSource<string>
private readonly _element: HTMLElement
constructor(
value: UIEventSource<string> = new UIEventSource<string>(undefined)
) {
super();
this.value = value;
constructor(value: UIEventSource<string> = new UIEventSource<string>(undefined)) {
super()
this.value = value
const el = document.createElement("input")
this._element = el;
this._element = el
el.type = "color"
this.value.addCallbackAndRunD(v => {
this.value.addCallbackAndRunD((v) => {
el.value = v
});
})
el.oninput = () => {
const hex = el.value;
value.setData(hex);
const hex = el.value
value.setData(hex)
}
}
GetValue(): UIEventSource<string> {
return this.value;
return this.value
}
IsValid(t: string): boolean {
return false;
return false
}
protected InnerConstructElement(): HTMLElement {
return this._element;
return this._element
}
}
}

View file

@ -1,28 +1,30 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import BaseUIElement from "../BaseUIElement";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import BaseUIElement from "../BaseUIElement"
export default class CombinedInputElement<T, J, X> extends InputElement<X> {
private readonly _a: InputElement<T>;
private readonly _b: InputElement<J>;
private readonly _combined: BaseUIElement;
private readonly _a: InputElement<T>
private readonly _b: InputElement<J>
private readonly _combined: BaseUIElement
private readonly _value: UIEventSource<X>
private readonly _split: (x: X) => [T, J];
private readonly _split: (x: X) => [T, J]
constructor(a: InputElement<T>, b: InputElement<J>,
combine: (t: T, j: J) => X,
split: (x: X) => [T, J]) {
super();
this._a = a;
this._b = b;
this._split = split;
this._combined = new Combine([this._a, this._b]);
constructor(
a: InputElement<T>,
b: InputElement<J>,
combine: (t: T, j: J) => X,
split: (x: X) => [T, J]
) {
super()
this._a = a
this._b = b
this._split = split
this._combined = new Combine([this._a, this._b])
this._value = this._a.GetValue().sync(
t => combine(t, this._b?.GetValue()?.data),
(t) => combine(t, this._b?.GetValue()?.data),
[this._b.GetValue()],
x => {
(x) => {
const [t, j] = split(x)
this._b.GetValue()?.setData(j)
return t
@ -31,16 +33,15 @@ export default class CombinedInputElement<T, J, X> extends InputElement<X> {
}
GetValue(): UIEventSource<X> {
return this._value;
return this._value
}
IsValid(x: X): boolean {
const [t, j] = this._split(x)
return this._a.IsValid(t) && this._b.IsValid(j);
return this._a.IsValid(t) && this._b.IsValid(j)
}
protected InnerConstructElement(): HTMLElement {
return this._combined.ConstructElement();
return this._combined.ConstructElement()
}
}
}

View file

@ -1,120 +1,118 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import Svg from "../../Svg";
import BaseUIElement from "../BaseUIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
import {Utils} from "../../Utils";
import Loc from "../../Models/Loc";
import Minimap from "../Base/Minimap";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import Svg from "../../Svg"
import BaseUIElement from "../BaseUIElement"
import { FixedUiElement } from "../Base/FixedUiElement"
import { Utils } from "../../Utils"
import Loc from "../../Models/Loc"
import Minimap from "../Base/Minimap"
/**
* Selects a direction in degrees
*/
export default class DirectionInput extends InputElement<string> {
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly _location: UIEventSource<Loc>;
private readonly value: UIEventSource<string>;
private background;
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
private readonly _location: UIEventSource<Loc>
private readonly value: UIEventSource<string>
private background
constructor(mapBackground: UIEventSource<any>,
location: UIEventSource<Loc>,
value?: UIEventSource<string>) {
super();
this._location = location;
this.value = value ?? new UIEventSource<string>(undefined);
this.background = mapBackground;
constructor(
mapBackground: UIEventSource<any>,
location: UIEventSource<Loc>,
value?: UIEventSource<string>
) {
super()
this._location = location
this.value = value ?? new UIEventSource<string>(undefined)
this.background = mapBackground
}
GetValue(): UIEventSource<string> {
return this.value;
return this.value
}
IsValid(str: string): boolean {
const t = Number(str);
return !isNaN(t) && t >= 0 && t <= 360;
const t = Number(str)
return !isNaN(t) && t >= 0 && t <= 360
}
protected InnerConstructElement(): HTMLElement {
let map: BaseUIElement = new FixedUiElement("")
if (!Utils.runningFromConsole) {
map = Minimap.createMiniMap({
background: this.background,
allowMoving: false,
location: this._location
location: this._location,
})
}
const element = new Combine([
Svg.direction_stroke_svg().SetStyle(
`position: absolute;top: 0;left: 0;width: 100%;height: 100%;transform:rotate(${this.value.data ?? 0}deg);`)
Svg.direction_stroke_svg()
.SetStyle(
`position: absolute;top: 0;left: 0;width: 100%;height: 100%;transform:rotate(${
this.value.data ?? 0
}deg);`
)
.SetClass("direction-svg relative")
.SetStyle("z-index: 1000"),
map.SetStyle(
`position: absolute;top: 0;left: 0;width: 100%;height: 100%;`)
map.SetStyle(`position: absolute;top: 0;left: 0;width: 100%;height: 100%;`),
])
.SetStyle("width: min(100%, 25em); height: 0; padding-bottom: 100%") // A bit a weird CSS , see https://stackoverflow.com/questions/13851940/pure-css-solution-square-elements#19448481
.SetClass("relative block bg-white border border-black overflow-hidden rounded-full")
.ConstructElement()
this.value.addCallbackAndRunD(rotation => {
this.value.addCallbackAndRunD((rotation) => {
const cone = element.getElementsByClassName("direction-svg")[0] as HTMLElement
cone.style.transform = `rotate(${rotation}deg)`;
cone.style.transform = `rotate(${rotation}deg)`
})
this.RegisterTriggers(element)
element.style.overflow = "hidden"
element.style.display = "block"
return element;
return element
}
private RegisterTriggers(htmlElement: HTMLElement) {
const self = this;
const self = this
function onPosChange(x: number, y: number) {
const rect = htmlElement.getBoundingClientRect();
const dx = -(rect.left + rect.right) / 2 + x;
const dy = (rect.top + rect.bottom) / 2 - y;
const angle = 180 * Math.atan2(dy, dx) / Math.PI;
const angleGeo = Math.floor((450 - angle) % 360);
const rect = htmlElement.getBoundingClientRect()
const dx = -(rect.left + rect.right) / 2 + x
const dy = (rect.top + rect.bottom) / 2 - y
const angle = (180 * Math.atan2(dy, dx)) / Math.PI
const angleGeo = Math.floor((450 - angle) % 360)
self.value.setData("" + angleGeo)
}
htmlElement.ontouchmove = (ev: TouchEvent) => {
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY);
ev.preventDefault();
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY)
ev.preventDefault()
}
htmlElement.ontouchstart = (ev: TouchEvent) => {
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY);
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY)
}
let isDown = false;
let isDown = false
htmlElement.onmousedown = (ev: MouseEvent) => {
isDown = true;
onPosChange(ev.clientX, ev.clientY);
ev.preventDefault();
isDown = true
onPosChange(ev.clientX, ev.clientY)
ev.preventDefault()
}
htmlElement.onmouseup = (ev) => {
isDown = false;
ev.preventDefault();
isDown = false
ev.preventDefault()
}
htmlElement.onmousemove = (ev: MouseEvent) => {
if (isDown) {
onPosChange(ev.clientX, ev.clientY);
onPosChange(ev.clientX, ev.clientY)
}
ev.preventDefault();
ev.preventDefault()
}
}
}
}

View file

@ -1,59 +1,58 @@
import {InputElement} from "./InputElement";
import Translations from "../i18n/Translations";
import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import { InputElement } from "./InputElement"
import Translations from "../i18n/Translations"
import { UIEventSource } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
export class DropDown<T> extends InputElement<T> {
private static _nextDropdownId = 0
public IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
private static _nextDropdownId = 0;
public IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly _element: HTMLElement
private readonly _element: HTMLElement;
private readonly _value: UIEventSource<T>;
private readonly _values: { value: T; shown: string | BaseUIElement }[];
private readonly _value: UIEventSource<T>
private readonly _values: { value: T; shown: string | BaseUIElement }[]
/**
*
*
* const dropdown = new DropDown<number>("test",[{value: 42, shown: "the answer"}])
* dropdown.GetValue().data // => 42
*/
constructor(label: string | BaseUIElement,
values: { value: T, shown: string | BaseUIElement }[],
value: UIEventSource<T> = undefined,
options?: {
select_class?: string
}
constructor(
label: string | BaseUIElement,
values: { value: T; shown: string | BaseUIElement }[],
value: UIEventSource<T> = undefined,
options?: {
select_class?: string
}
) {
super();
super()
value = value ?? new UIEventSource<T>(values[0].value)
this._value = value
this._values = values;
this._values = values
if (values.length <= 1) {
return;
return
}
const id = DropDown._nextDropdownId;
DropDown._nextDropdownId++;
const id = DropDown._nextDropdownId
DropDown._nextDropdownId++
const el = document.createElement("form")
this._element = el;
el.id = "dropdown" + id;
this._element = el
el.id = "dropdown" + id
{
const labelEl = Translations.W(label)?.ConstructElement()
if (labelEl !== undefined) {
const labelHtml = document.createElement("label")
labelHtml.appendChild(labelEl)
labelHtml.htmlFor = el.id;
labelHtml.htmlFor = el.id
el.appendChild(labelHtml)
}
}
options = options ?? {}
options.select_class = options.select_class ?? 'w-full bg-indigo-100 p-1 rounded hover:bg-indigo-200'
options.select_class =
options.select_class ?? "w-full bg-indigo-100 p-1 rounded hover:bg-indigo-200"
{
const select = document.createElement("select")
@ -66,42 +65,38 @@ export class DropDown<T> extends InputElement<T> {
}
el.appendChild(select)
select.onchange = () => {
const index = select.selectedIndex
value.setData(values[index].value)
}
select.onchange = (() => {
const index = select.selectedIndex;
value.setData(values[index].value);
});
value.addCallbackAndRun(selected => {
value.addCallbackAndRun((selected) => {
for (let i = 0; i < values.length; i++) {
const value = values[i].value;
const value = values[i].value
if (value === selected) {
select.selectedIndex = i;
select.selectedIndex = i
}
}
})
}
this.onClick(() => {
}) // by registering a click, the click event is consumed and doesn't bubble further to other elements, e.g. checkboxes
this.onClick(() => {}) // by registering a click, the click event is consumed and doesn't bubble further to other elements, e.g. checkboxes
}
GetValue(): UIEventSource<T> {
return this._value;
return this._value
}
IsValid(t: T): boolean {
for (const value of this._values) {
if (value.value === t) {
return true;
return true
}
}
return false
}
protected InnerConstructElement(): HTMLElement {
return this._element;
return this._element
}
}
}

View file

@ -1,54 +1,55 @@
import BaseUIElement from "../BaseUIElement";
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement"
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
export default class FileSelectorButton extends InputElement<FileList> {
private static _nextid
IsSelected: UIEventSource<boolean>
private readonly _value = new UIEventSource<FileList>(undefined)
private readonly _label: BaseUIElement
private readonly _acceptType: string
private readonly allowMultiple: boolean
private static _nextid;
IsSelected: UIEventSource<boolean>;
private readonly _value = new UIEventSource<FileList>(undefined);
private readonly _label: BaseUIElement;
private readonly _acceptType: string;
private readonly allowMultiple: boolean;
constructor(label: BaseUIElement, options?:
{
acceptType: "image/*" | string,
constructor(
label: BaseUIElement,
options?: {
acceptType: "image/*" | string
allowMultiple: true | boolean
}) {
super();
this._label = label;
this._acceptType = options?.acceptType ?? "image/*";
}
) {
super()
this._label = label
this._acceptType = options?.acceptType ?? "image/*"
this.SetClass("block cursor-pointer")
label.SetClass("cursor-pointer")
this.allowMultiple = options?.allowMultiple ?? true
}
GetValue(): UIEventSource<FileList> {
return this._value;
return this._value
}
IsValid(t: FileList): boolean {
return true;
return true
}
protected InnerConstructElement(): HTMLElement {
const self = this;
const self = this
const el = document.createElement("form")
const label = document.createElement("label")
label.appendChild(this._label.ConstructElement())
el.appendChild(label)
const actualInputElement = document.createElement("input");
actualInputElement.style.cssText = "display:none";
actualInputElement.type = "file";
actualInputElement.accept = this._acceptType;
actualInputElement.name = "picField";
actualInputElement.multiple = this.allowMultiple;
actualInputElement.id = "fileselector" + FileSelectorButton._nextid;
FileSelectorButton._nextid++;
const actualInputElement = document.createElement("input")
actualInputElement.style.cssText = "display:none"
actualInputElement.type = "file"
actualInputElement.accept = this._acceptType
actualInputElement.name = "picField"
actualInputElement.multiple = this.allowMultiple
actualInputElement.id = "fileselector" + FileSelectorButton._nextid
FileSelectorButton._nextid++
label.htmlFor = actualInputElement.id;
label.htmlFor = actualInputElement.id
actualInputElement.onchange = () => {
if (actualInputElement.files !== null) {
@ -56,7 +57,7 @@ export default class FileSelectorButton extends InputElement<FileList> {
}
}
el.addEventListener('submit', e => {
el.addEventListener("submit", (e) => {
if (actualInputElement.files !== null) {
self._value.setData(actualInputElement.files)
}
@ -65,22 +66,20 @@ export default class FileSelectorButton extends InputElement<FileList> {
el.appendChild(actualInputElement)
el.addEventListener('dragover', (event) => {
event.stopPropagation();
event.preventDefault();
el.addEventListener("dragover", (event) => {
event.stopPropagation()
event.preventDefault()
// Style the drag-and-drop as a "copy file" operation.
event.dataTransfer.dropEffect = 'copy';
});
event.dataTransfer.dropEffect = "copy"
})
el.addEventListener('drop', (event) => {
event.stopPropagation();
event.preventDefault();
const fileList = event.dataTransfer.files;
el.addEventListener("drop", (event) => {
event.stopPropagation()
event.preventDefault()
const fileList = event.dataTransfer.files
this._value.setData(fileList)
});
})
return el;
return el
}
}
}

View file

@ -1,23 +1,25 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import BaseUIElement from "../BaseUIElement";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations"
import BaseUIElement from "../BaseUIElement"
export class FixedInputElement<T> extends InputElement<T> {
private readonly value: UIEventSource<T>;
private readonly _comparator: (t0: T, t1: T) => boolean;
private readonly value: UIEventSource<T>
private readonly _comparator: (t0: T, t1: T) => boolean
private readonly _el: HTMLElement;
private readonly _el: HTMLElement
constructor(rendering: BaseUIElement | string,
value: T | UIEventSource<T>,
comparator: ((t0: T, t1: T) => boolean) = undefined) {
super();
this._comparator = comparator ?? ((t0, t1) => t0 == t1);
constructor(
rendering: BaseUIElement | string,
value: T | UIEventSource<T>,
comparator: (t0: T, t1: T) => boolean = undefined
) {
super()
this._comparator = comparator ?? ((t0, t1) => t0 == t1)
if (value instanceof UIEventSource) {
this.value = value
} else {
this.value = new UIEventSource<T>(value);
this.value = new UIEventSource<T>(value)
}
this._el = document.createElement("span")
@ -25,18 +27,17 @@ export class FixedInputElement<T> extends InputElement<T> {
if (e) {
this._el.appendChild(e)
}
}
GetValue(): UIEventSource<T> {
return this.value;
return this.value
}
IsValid(t: T): boolean {
return this._comparator(t, this.value.data);
return this._comparator(t, this.value.data)
}
protected InnerConstructElement(): HTMLElement {
return this._el;
return this._el
}
}

View file

@ -1,89 +1,94 @@
import {InputElement} from "./InputElement";
import {Store, Stores, UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import Slider from "./Slider";
import {ClickableToggle} from "./Toggle";
import {FixedUiElement} from "../Base/FixedUiElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import BaseUIElement from "../BaseUIElement";
export default class FloorLevelInputElement extends VariableUiElement implements InputElement<string> {
private readonly _value: UIEventSource<string>;
constructor(currentLevels: Store<Record<string, number>>, options?: {
value?: UIEventSource<string>
}) {
import { InputElement } from "./InputElement"
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import Slider from "./Slider"
import { ClickableToggle } from "./Toggle"
import { FixedUiElement } from "../Base/FixedUiElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import BaseUIElement from "../BaseUIElement"
export default class FloorLevelInputElement
extends VariableUiElement
implements InputElement<string>
{
private readonly _value: UIEventSource<string>
constructor(
currentLevels: Store<Record<string, number>>,
options?: {
value?: UIEventSource<string>
}
) {
const value = options?.value ?? new UIEventSource<string>("0")
super(currentLevels.map(levels => {
super(
currentLevels.map((levels) => {
const allLevels = Object.keys(levels)
allLevels.sort((a, b) => {
const an = Number(a)
const bn = Number(b)
if (isNaN(an) || isNaN(bn)) {
return a < b ? -1 : 1;
return a < b ? -1 : 1
}
return an - bn;
return an - bn
})
return FloorLevelInputElement.constructPicker(allLevels, value)
}
))
})
)
this._value = value
}
private static constructPicker(levels: string[], value: UIEventSource<string>): BaseUIElement {
let slider = new Slider(0, levels.length - 1, {vertical: true});
const toggleClass = "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center border-box"
slider.SetClass("flex elevator w-10").SetStyle(`height: ${2.5 * levels.length}rem; background: #00000000`)
let slider = new Slider(0, levels.length - 1, { vertical: true })
const toggleClass =
"flex border-2 border-blue-500 w-10 h-10 place-content-center items-center border-box"
slider
.SetClass("flex elevator w-10")
.SetStyle(`height: ${2.5 * levels.length}rem; background: #00000000`)
const values = levels.map((data, i) => new ClickableToggle(
new FixedUiElement(data).SetClass("font-bold active bg-subtle " + toggleClass),
new FixedUiElement(data).SetClass("normal-background " + toggleClass),
slider.GetValue().sync(
(sliderVal) => {
return sliderVal === i
},
[],
(isSelected) => {
return isSelected ? i : slider.GetValue().data
}
))
.ToggleOnClick()
.SetClass("flex w-10 h-10"))
const values = levels.map((data, i) =>
new ClickableToggle(
new FixedUiElement(data).SetClass("font-bold active bg-subtle " + toggleClass),
new FixedUiElement(data).SetClass("normal-background " + toggleClass),
slider.GetValue().sync(
(sliderVal) => {
return sliderVal === i
},
[],
(isSelected) => {
return isSelected ? i : slider.GetValue().data
}
)
)
.ToggleOnClick()
.SetClass("flex w-10 h-10")
)
values.reverse(/* This is a new list, no side-effects */)
const combine = new Combine([new Combine(values), slider])
combine.SetClass("flex flex-row overflow-hidden");
combine.SetClass("flex flex-row overflow-hidden")
slider.GetValue().addCallbackD(i => {
slider.GetValue().addCallbackD((i) => {
if (levels === undefined) {
return
}
if(levels[i] == undefined){
if (levels[i] == undefined) {
return
}
value.setData(levels[i]);
value.setData(levels[i])
})
value.addCallbackAndRunD(level => {
const i = levels.findIndex(l => l === level)
value.addCallbackAndRunD((level) => {
const i = levels.findIndex((l) => l === level)
slider.GetValue().setData(i)
})
return combine
}
GetValue(): UIEventSource<string> {
return this._value;
return this._value
}
IsValid(t: string): boolean {
return false;
return false
}
}
}

View file

@ -1,13 +1,12 @@
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
export interface ReadonlyInputElement<T> extends BaseUIElement{
GetValue(): Store<T>;
IsValid(t: T): boolean;
export interface ReadonlyInputElement<T> extends BaseUIElement {
GetValue(): Store<T>
IsValid(t: T): boolean
}
export abstract class InputElement<T> extends BaseUIElement implements ReadonlyInputElement<any>{
abstract GetValue(): UIEventSource<T>;
abstract IsValid(t: T): boolean;
export abstract class InputElement<T> extends BaseUIElement implements ReadonlyInputElement<any> {
abstract GetValue(): UIEventSource<T>
abstract IsValid(t: T): boolean
}

View file

@ -1,56 +1,58 @@
import {InputElement} from "./InputElement";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import { InputElement } from "./InputElement"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
export default class InputElementMap<T, X> extends InputElement<X> {
private readonly _inputElement: InputElement<T>;
private isSame: (x0: X, x1: X) => boolean;
private readonly fromX: (x: X) => T;
private readonly toX: (t: T) => X;
private readonly _value: UIEventSource<X>;
private readonly _inputElement: InputElement<T>
private isSame: (x0: X, x1: X) => boolean
private readonly fromX: (x: X) => T
private readonly toX: (t: T) => X
private readonly _value: UIEventSource<X>
constructor(inputElement: InputElement<T>,
isSame: (x0: X, x1: X) => boolean,
toX: (t: T) => X,
fromX: (x: X) => T,
extraSources: Store<any>[] = []
constructor(
inputElement: InputElement<T>,
isSame: (x0: X, x1: X) => boolean,
toX: (t: T) => X,
fromX: (x: X) => T,
extraSources: Store<any>[] = []
) {
super();
this.isSame = isSame;
this.fromX = fromX;
this.toX = toX;
this._inputElement = inputElement;
const self = this;
super()
this.isSame = isSame
this.fromX = fromX
this.toX = toX
this._inputElement = inputElement
const self = this
this._value = inputElement.GetValue().sync(
(t => {
const newX = toX(t);
const currentX = self.GetValue()?.data;
(t) => {
const newX = toX(t)
const currentX = self.GetValue()?.data
if (isSame(currentX, newX)) {
return currentX;
return currentX
}
return newX;
}), extraSources, x => {
return fromX(x);
});
return newX
},
extraSources,
(x) => {
return fromX(x)
}
)
}
GetValue(): UIEventSource<X> {
return this._value;
return this._value
}
IsValid(x: X): boolean {
if (x === undefined) {
return false;
return false
}
const t = this.fromX(x);
const t = this.fromX(x)
if (t === undefined) {
return false;
return false
}
return this._inputElement.IsValid(t);
return this._inputElement.IsValid(t)
}
protected InnerConstructElement(): HTMLElement {
return this._inputElement.ConstructElement();
return this._inputElement.ConstructElement()
}
}
}

View file

@ -1,37 +1,43 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {Translation} from "../i18n/Translation";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
import { Translation } from "../i18n/Translation"
import { SubstitutedTranslation } from "../SubstitutedTranslation"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
export default class InputElementWrapper<T> extends InputElement<T> {
private readonly _inputElement: InputElement<T>;
private readonly _inputElement: InputElement<T>
private readonly _renderElement: BaseUIElement
constructor(inputElement: InputElement<T>, translation: Translation, key: string,
tags: UIEventSource<any>, state: FeaturePipelineState) {
constructor(
inputElement: InputElement<T>,
translation: Translation,
key: string,
tags: UIEventSource<any>,
state: FeaturePipelineState
) {
super()
this._inputElement = inputElement;
this._inputElement = inputElement
const mapping = new Map<string, BaseUIElement>()
mapping.set(key, inputElement)
// Bit of a hack: the SubstitutedTranslation expects a special rendering, but those are formatted '{key()}' instead of '{key}', so we substitute it first
translation = translation.OnEveryLanguage((txt) => txt.replace("{" + key + "}", "{" + key + "()}"))
translation = translation.OnEveryLanguage((txt) =>
txt.replace("{" + key + "}", "{" + key + "()}")
)
this._renderElement = new SubstitutedTranslation(translation, tags, state, mapping)
}
GetValue(): UIEventSource<T> {
return this._inputElement.GetValue();
return this._inputElement.GetValue()
}
IsValid(t: T): boolean {
return this._inputElement.IsValid(t);
return this._inputElement.IsValid(t)
}
protected InnerConstructElement(): HTMLElement {
return this._renderElement.ConstructElement();
return this._renderElement.ConstructElement()
}
}
}

View file

@ -1,47 +1,46 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import Svg from "../../Svg";
import {Utils} from "../../Utils";
import Loc from "../../Models/Loc";
import {GeoOperations} from "../../Logic/GeoOperations";
import Minimap, {MinimapObj} from "../Base/Minimap";
import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch";
import BaseUIElement from "../BaseUIElement";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import Svg from "../../Svg"
import { Utils } from "../../Utils"
import Loc from "../../Models/Loc"
import { GeoOperations } from "../../Logic/GeoOperations"
import Minimap, { MinimapObj } from "../Base/Minimap"
import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch"
import BaseUIElement from "../BaseUIElement"
/**
* Selects a length after clicking on the minimap, in meters
*/
export default class LengthInput extends InputElement<string> {
private readonly _location: UIEventSource<Loc>;
private readonly value: UIEventSource<string>;
private readonly background: UIEventSource<any>;
private readonly _location: UIEventSource<Loc>
private readonly value: UIEventSource<string>
private readonly background: UIEventSource<any>
constructor(mapBackground: UIEventSource<any>,
location: UIEventSource<Loc>,
value?: UIEventSource<string>) {
super();
this._location = location;
this.value = value ?? new UIEventSource<string>(undefined);
this.background = mapBackground;
constructor(
mapBackground: UIEventSource<any>,
location: UIEventSource<Loc>,
value?: UIEventSource<string>
) {
super()
this._location = location
this.value = value ?? new UIEventSource<string>(undefined)
this.background = mapBackground
this.SetClass("block")
}
GetValue(): UIEventSource<string> {
return this.value;
return this.value
}
IsValid(str: string): boolean {
const t = Number(str)
return !isNaN(t) && t >= 0;
return !isNaN(t) && t >= 0
}
protected InnerConstructElement(): HTMLElement {
let map : (BaseUIElement & MinimapObj) = undefined
let layerControl : BaseUIElement = undefined
let map: BaseUIElement & MinimapObj = undefined
let layerControl: BaseUIElement = undefined
if (!Utils.runningFromConsole) {
map = Minimap.createMiniMap({
background: this.background,
@ -49,139 +48,157 @@ export default class LengthInput extends InputElement<string> {
location: this._location,
attribution: true,
leafletOptions: {
tap: true
tap: true,
},
})
layerControl = new BackgroundMapSwitch(
{
locationControl: this._location,
backgroundLayer: this.background,
},
this.background,
{
allowedCategories: ["map", "photo"],
}
})
layerControl = new BackgroundMapSwitch({
locationControl: this._location,
backgroundLayer: this.background,
}, this.background,{
allowedCategories: ["map","photo"]
})
)
}
const crosshair = new Combine([Svg.length_crosshair_svg().SetStyle(
`position: absolute;top: 0;left: 0;transform:rotate(${this.value.data ?? 0}deg);`)
]) .SetClass("block length-crosshair-svg relative pointer-events-none")
const crosshair = new Combine([
Svg.length_crosshair_svg().SetStyle(
`position: absolute;top: 0;left: 0;transform:rotate(${this.value.data ?? 0}deg);`
),
])
.SetClass("block length-crosshair-svg relative pointer-events-none")
.SetStyle("z-index: 1000; visibility: hidden")
const element = new Combine([
crosshair,
layerControl?.SetStyle("position: absolute; bottom: 0.25rem; left: 0.25rem; z-index: 1000"),
layerControl?.SetStyle(
"position: absolute; bottom: 0.25rem; left: 0.25rem; z-index: 1000"
),
map?.SetClass("w-full h-full block absolute top-0 left-O overflow-hidden"),
])
.SetClass("relative block bg-white border border-black rounded-xl overflow-hidden")
.ConstructElement()
this.RegisterTriggers(map?.ConstructElement(), map?.leafletMap, crosshair.ConstructElement())
this.RegisterTriggers(
map?.ConstructElement(),
map?.leafletMap,
crosshair.ConstructElement()
)
element.style.overflow = "hidden"
element.style.display = "block"
return element
}
private RegisterTriggers(htmlElement: HTMLElement, leafletMap: UIEventSource<L.Map>, measurementCrosshair: HTMLElement) {
private RegisterTriggers(
htmlElement: HTMLElement,
leafletMap: UIEventSource<L.Map>,
measurementCrosshair: HTMLElement
) {
let firstClickXY: [number, number] = undefined
let lastClickXY: [number, number] = undefined
const self = this;
const self = this
function onPosChange(x: number, y: number, isDown: boolean, isUp?: boolean) {
if (x === undefined || y === undefined) {
// Touch end
firstClickXY = undefined;
lastClickXY = undefined;
return;
firstClickXY = undefined
lastClickXY = undefined
return
}
const rect = htmlElement.getBoundingClientRect();
const rect = htmlElement.getBoundingClientRect()
// From the central part of location
const dx = x - rect.left;
const dy = y - rect.top;
const dx = x - rect.left
const dy = y - rect.top
if (isDown) {
if (lastClickXY === undefined && firstClickXY === undefined) {
firstClickXY = [dx, dy];
firstClickXY = [dx, dy]
} else if (firstClickXY !== undefined && lastClickXY === undefined) {
lastClickXY = [dx, dy]
} else if (firstClickXY !== undefined && lastClickXY !== undefined) {
// we measure again
firstClickXY = [dx, dy]
lastClickXY = undefined;
lastClickXY = undefined
}
}
if (firstClickXY === undefined) {
measurementCrosshair.style.visibility = "hidden"
return;
return
}
const distance = Math.sqrt((dy - firstClickXY[1]) * (dy - firstClickXY[1]) + (dx - firstClickXY[0]) * (dx - firstClickXY[0]))
const distance = Math.sqrt(
(dy - firstClickXY[1]) * (dy - firstClickXY[1]) +
(dx - firstClickXY[0]) * (dx - firstClickXY[0])
)
if (isUp) {
if (distance > 15) {
lastClickXY = [dx, dy]
}
} else if (lastClickXY !== undefined) {
return;
return
}
measurementCrosshair.style.visibility = "unset"
measurementCrosshair.style.left = firstClickXY[0] + "px";
measurementCrosshair.style.left = firstClickXY[0] + "px"
measurementCrosshair.style.top = firstClickXY[1] + "px"
const angle = 180 * Math.atan2(firstClickXY[1] - dy, firstClickXY[0] - dx) / Math.PI;
const angle = (180 * Math.atan2(firstClickXY[1] - dy, firstClickXY[0] - dx)) / Math.PI
const angleGeo = (angle + 270) % 360
const measurementCrosshairInner: HTMLElement = <HTMLElement>measurementCrosshair.firstChild
measurementCrosshairInner.style.transform = `rotate(${angleGeo}deg)`;
const measurementCrosshairInner: HTMLElement = <HTMLElement>(
measurementCrosshair.firstChild
)
measurementCrosshairInner.style.transform = `rotate(${angleGeo}deg)`
measurementCrosshairInner.style.width = (distance * 2) + "px"
measurementCrosshairInner.style.width = distance * 2 + "px"
measurementCrosshairInner.style.marginLeft = -distance + "px"
measurementCrosshairInner.style.marginTop = -distance + "px"
const leaflet = leafletMap?.data
if (leaflet) {
const first = leaflet.layerPointToLatLng(firstClickXY)
const last = leaflet.layerPointToLatLng([dx, dy])
const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 10) / 10
const geoDist =
Math.floor(
GeoOperations.distanceBetween(
[first.lng, first.lat],
[last.lng, last.lat]
) * 10
) / 10
self.value.setData("" + geoDist)
}
}
htmlElement.ontouchstart = (ev: TouchEvent) => {
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, true);
ev.preventDefault();
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, true)
ev.preventDefault()
}
htmlElement.ontouchmove = (ev: TouchEvent) => {
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, false);
ev.preventDefault();
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, false)
ev.preventDefault()
}
htmlElement.ontouchend = (ev: TouchEvent) => {
onPosChange(undefined, undefined, false, true);
ev.preventDefault();
onPosChange(undefined, undefined, false, true)
ev.preventDefault()
}
htmlElement.onmousedown = (ev: MouseEvent) => {
onPosChange(ev.clientX, ev.clientY, true);
ev.preventDefault();
onPosChange(ev.clientX, ev.clientY, true)
ev.preventDefault()
}
htmlElement.onmouseup = (ev) => {
onPosChange(ev.clientX, ev.clientY, false, true);
ev.preventDefault();
onPosChange(ev.clientX, ev.clientY, false, true)
ev.preventDefault()
}
htmlElement.onmousemove = (ev: MouseEvent) => {
onPosChange(ev.clientX, ev.clientY, false);
ev.preventDefault();
onPosChange(ev.clientX, ev.clientY, false)
ev.preventDefault()
}
}
}
}

View file

@ -1,33 +1,39 @@
import {ReadonlyInputElement} from "./InputElement";
import Loc from "../../Models/Loc";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Minimap, {MinimapObj} from "../Base/Minimap";
import BaseLayer from "../../Models/BaseLayer";
import Combine from "../Base/Combine";
import Svg from "../../Svg";
import State from "../../State";
import {GeoOperations} from "../../Logic/GeoOperations";
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import {BBox} from "../../Logic/BBox";
import {FixedUiElement} from "../Base/FixedUiElement";
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
import BaseUIElement from "../BaseUIElement";
import Toggle from "./Toggle";
import { ReadonlyInputElement } from "./InputElement"
import Loc from "../../Models/Loc"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Minimap, { MinimapObj } from "../Base/Minimap"
import BaseLayer from "../../Models/BaseLayer"
import Combine from "../Base/Combine"
import Svg from "../../Svg"
import State from "../../State"
import { GeoOperations } from "../../Logic/GeoOperations"
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { BBox } from "../../Logic/BBox"
import { FixedUiElement } from "../Base/FixedUiElement"
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
import BaseUIElement from "../BaseUIElement"
import Toggle from "./Toggle"
import * as matchpoint from "../../assets/layers/matchpoint/matchpoint.json"
export default class LocationInput extends BaseUIElement implements ReadonlyInputElement<Loc>, MinimapObj {
private static readonly matchLayer = new LayerConfig(matchpoint, "LocationInput.matchpoint", true)
export default class LocationInput
extends BaseUIElement
implements ReadonlyInputElement<Loc>, MinimapObj
{
private static readonly matchLayer = new LayerConfig(
matchpoint,
"LocationInput.matchpoint",
true
)
public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined)
public readonly _matching_layer: LayerConfig;
public readonly _matching_layer: LayerConfig
public readonly leafletMap: UIEventSource<any>
public readonly bounds;
public readonly location;
private _centerLocation: UIEventSource<Loc>;
private readonly mapBackground: UIEventSource<BaseLayer>;
public readonly bounds
public readonly location
private _centerLocation: UIEventSource<Loc>
private readonly mapBackground: UIEventSource<BaseLayer>
/**
* The features to which the input should be snapped
* @private
@ -36,33 +42,35 @@ export default class LocationInput extends BaseUIElement implements ReadonlyInpu
private readonly _value: Store<Loc>
private readonly _snappedPoint: Store<any>
private readonly _maxSnapDistance: number
private readonly _snappedPointTags: any;
private readonly _bounds: UIEventSource<BBox>;
private readonly map: BaseUIElement & MinimapObj;
private readonly clickLocation: UIEventSource<Loc>;
private readonly _minZoom: number;
private readonly _snappedPointTags: any
private readonly _bounds: UIEventSource<BBox>
private readonly map: BaseUIElement & MinimapObj
private readonly clickLocation: UIEventSource<Loc>
private readonly _minZoom: number
constructor(options: {
minZoom?: number,
mapBackground?: UIEventSource<BaseLayer>,
snapTo?: UIEventSource<{ feature: any }[]>,
maxSnapDistance?: number,
snappedPointTags?: any,
requiresSnapping?: boolean,
centerLocation: UIEventSource<Loc>,
minZoom?: number
mapBackground?: UIEventSource<BaseLayer>
snapTo?: UIEventSource<{ feature: any }[]>
maxSnapDistance?: number
snappedPointTags?: any
requiresSnapping?: boolean
centerLocation: UIEventSource<Loc>
bounds?: UIEventSource<BBox>
}) {
super();
this._snapTo = options.snapTo?.map(features => features?.filter(feat => feat.feature.geometry.type !== "Point"))
super()
this._snapTo = options.snapTo?.map((features) =>
features?.filter((feat) => feat.feature.geometry.type !== "Point")
)
this._maxSnapDistance = options.maxSnapDistance
this._centerLocation = options.centerLocation;
this._centerLocation = options.centerLocation
this._snappedPointTags = options.snappedPointTags
this._bounds = options.bounds;
this._bounds = options.bounds
this._minZoom = options.minZoom
if (this._snapTo === undefined) {
this._value = this._centerLocation;
this._value = this._centerLocation
} else {
const self = this;
const self = this
if (self._snappedPointTags !== undefined) {
const layout = State.state.layoutToUse
@ -73,89 +81,96 @@ export default class LocationInput extends BaseUIElement implements ReadonlyInpu
matchingLayer = layer
}
}
this._matching_layer = matchingLayer;
this._matching_layer = matchingLayer
} else {
this._matching_layer = LocationInput.matchLayer
}
this._snappedPoint = options.centerLocation.map(loc => {
if (loc === undefined) {
return undefined;
}
// We reproject the location onto every 'snap-to-feature' and select the closest
let min = undefined;
let matchedWay = undefined;
for (const feature of self._snapTo.data ?? []) {
try {
const nearestPointOnLine = GeoOperations.nearestPoint(feature.feature, [loc.lon, loc.lat])
if (min === undefined) {
min = nearestPointOnLine
matchedWay = feature.feature;
continue;
}
if (min.properties.dist > nearestPointOnLine.properties.dist) {
min = nearestPointOnLine
matchedWay = feature.feature;
}
} catch (e) {
console.log("Snapping to a nearest point failed for ", feature.feature, "due to ", e)
}
}
if (min === undefined || min.properties.dist * 1000 > self._maxSnapDistance) {
if (options.requiresSnapping) {
this._snappedPoint = options.centerLocation.map(
(loc) => {
if (loc === undefined) {
return undefined
} else {
return {
"type": "Feature",
"properties": options.snappedPointTags ?? min.properties,
"geometry": {"type": "Point", "coordinates": [loc.lon, loc.lat]}
}
// We reproject the location onto every 'snap-to-feature' and select the closest
let min = undefined
let matchedWay = undefined
for (const feature of self._snapTo.data ?? []) {
try {
const nearestPointOnLine = GeoOperations.nearestPoint(feature.feature, [
loc.lon,
loc.lat,
])
if (min === undefined) {
min = nearestPointOnLine
matchedWay = feature.feature
continue
}
if (min.properties.dist > nearestPointOnLine.properties.dist) {
min = nearestPointOnLine
matchedWay = feature.feature
}
} catch (e) {
console.log(
"Snapping to a nearest point failed for ",
feature.feature,
"due to ",
e
)
}
}
}
min.properties = options.snappedPointTags ?? min.properties
self.snappedOnto.setData(matchedWay)
return min
}, [this._snapTo])
this._value = this._snappedPoint.map(f => {
const [lon, lat] = f.geometry.coordinates;
if (min === undefined || min.properties.dist * 1000 > self._maxSnapDistance) {
if (options.requiresSnapping) {
return undefined
} else {
return {
type: "Feature",
properties: options.snappedPointTags ?? min.properties,
geometry: { type: "Point", coordinates: [loc.lon, loc.lat] },
}
}
}
min.properties = options.snappedPointTags ?? min.properties
self.snappedOnto.setData(matchedWay)
return min
},
[this._snapTo]
)
this._value = this._snappedPoint.map((f) => {
const [lon, lat] = f.geometry.coordinates
return {
lon: lon, lat: lat, zoom: undefined
lon: lon,
lat: lat,
zoom: undefined,
}
})
}
this.mapBackground = options.mapBackground ?? State.state?.backgroundLayer
this.SetClass("block h-full")
this.clickLocation = new UIEventSource<Loc>(undefined);
this.map = Minimap.createMiniMap(
{
location: this._centerLocation,
background: this.mapBackground,
attribution: this.mapBackground !== State.state?.backgroundLayer,
lastClickLocation: this.clickLocation,
bounds: this._bounds,
addLayerControl: true
}
)
this.clickLocation = new UIEventSource<Loc>(undefined)
this.map = Minimap.createMiniMap({
location: this._centerLocation,
background: this.mapBackground,
attribution: this.mapBackground !== State.state?.backgroundLayer,
lastClickLocation: this.clickLocation,
bounds: this._bounds,
addLayerControl: true,
})
this.leafletMap = this.map.leafletMap
this.location = this.map.location;
this.location = this.map.location
}
GetValue(): Store<Loc> {
return this._value;
return this._value
}
IsValid(t: Loc): boolean {
return t !== undefined;
return t !== undefined
}
installBounds(factor: number | BBox, showRange?: boolean): void {
@ -168,37 +183,39 @@ export default class LocationInput extends BaseUIElement implements ReadonlyInpu
protected InnerConstructElement(): HTMLElement {
try {
const self = this;
const self = this
const hasMoved = new UIEventSource(false)
const startLocation = {...this._centerLocation.data}
this._centerLocation.addCallbackD(newLocation => {
const startLocation = { ...this._centerLocation.data }
this._centerLocation.addCallbackD((newLocation) => {
const f = 100000
console.log(newLocation.lon, startLocation.lon)
const diff = (Math.abs(newLocation.lon * f - startLocation.lon * f) + Math.abs(newLocation.lat * f - startLocation.lat * f))
const diff =
Math.abs(newLocation.lon * f - startLocation.lon * f) +
Math.abs(newLocation.lat * f - startLocation.lat * f)
if (diff < 1) {
return;
return
}
hasMoved.setData(true)
return true;
return true
})
this.clickLocation.addCallbackAndRunD(location => this._centerLocation.setData(location))
this.clickLocation.addCallbackAndRunD((location) =>
this._centerLocation.setData(location)
)
if (this._snapTo !== undefined) {
// Show the lines to snap to
console.log("Constructing the snap-to layer", this._snapTo)
new ShowDataMultiLayer({
features: StaticFeatureSource.fromDateless(this._snapTo),
zoomToFeatures: false,
leafletMap: this.map.leafletMap,
layers: State.state.filteredLayers
}
)
features: StaticFeatureSource.fromDateless(this._snapTo),
zoomToFeatures: false,
leafletMap: this.map.leafletMap,
layers: State.state.filteredLayers,
})
// Show the central point
const matchPoint = this._snappedPoint.map(loc => {
const matchPoint = this._snappedPoint.map((loc) => {
if (loc === undefined) {
return []
}
return [{feature: loc}];
return [{ feature: loc }]
})
console.log("Constructing the match layer", matchPoint)
@ -208,21 +225,22 @@ export default class LocationInput extends BaseUIElement implements ReadonlyInpu
leafletMap: this.map.leafletMap,
layerToShow: this._matching_layer,
state: State.state,
selectedElement: State.state.selectedElement
selectedElement: State.state.selectedElement,
})
}
this.mapBackground.map(layer => {
const leaflet = this.map.leafletMap.data
if (leaflet === undefined || layer === undefined) {
return;
}
this.mapBackground.map(
(layer) => {
const leaflet = this.map.leafletMap.data
if (leaflet === undefined || layer === undefined) {
return
}
leaflet.setMaxZoom(layer.max_zoom)
leaflet.setMinZoom(self._minZoom ?? layer.max_zoom - 2)
leaflet.setZoom(layer.max_zoom - 1)
}, [this.map.leafletMap])
leaflet.setMaxZoom(layer.max_zoom)
leaflet.setMinZoom(self._minZoom ?? layer.max_zoom - 2)
leaflet.setZoom(layer.max_zoom - 1)
},
[this.map.leafletMap]
)
const animatedHand = Svg.hand_ui()
.SetStyle("width: 2rem; height: unset;")
@ -232,23 +250,24 @@ export default class LocationInput extends BaseUIElement implements ReadonlyInpu
new Combine([
Svg.move_arrows_ui()
.SetClass("block relative pointer-events-none")
.SetStyle("left: -2.5rem; top: -2.5rem; width: 5rem; height: 5rem")
]).SetClass("block w-0 h-0 z-10 relative")
.SetStyle("background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%; opacity: 0.5"),
.SetStyle("left: -2.5rem; top: -2.5rem; width: 5rem; height: 5rem"),
])
.SetClass("block w-0 h-0 z-10 relative")
.SetStyle(
"background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%; opacity: 0.5"
),
new Toggle(undefined,
animatedHand, hasMoved)
new Toggle(undefined, animatedHand, hasMoved)
.SetClass("block w-0 h-0 z-10 relative")
.SetStyle("left: calc(50% + 3rem); top: calc(50% + 2rem); opacity: 0.7"),
this.map
.SetClass("z-0 relative block w-full h-full bg-gray-100")
]).ConstructElement();
this.map.SetClass("z-0 relative block w-full h-full bg-gray-100"),
]).ConstructElement()
} catch (e) {
console.error("Could not generate LocationInputElement:", e)
return new FixedUiElement("Constructing a locationInput failed due to" + e).SetClass("alert").ConstructElement();
return new FixedUiElement("Constructing a locationInput failed due to" + e)
.SetClass("alert")
.ConstructElement()
}
}
}
}

View file

@ -1,178 +1,157 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Utils} from "../../Utils";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Utils } from "../../Utils"
export class RadioButton<T> extends InputElement<T> {
private static _nextId = 0;
private readonly value: UIEventSource<T>;
private _elements: InputElement<T>[];
private _selectFirstAsDefault: boolean;
private static _nextId = 0
private readonly value: UIEventSource<T>
private _elements: InputElement<T>[]
private _selectFirstAsDefault: boolean
private _dontStyle: boolean
constructor(
elements: InputElement<T>[],
options?: {
selectFirstAsDefault?: true | boolean,
selectFirstAsDefault?: true | boolean
dontStyle?: boolean
}
) {
super();
super()
options = options ?? {}
this._selectFirstAsDefault = options.selectFirstAsDefault ?? true;
this._elements = Utils.NoNull(elements);
this.value = new UIEventSource<T>(undefined);
this._selectFirstAsDefault = options.selectFirstAsDefault ?? true
this._elements = Utils.NoNull(elements)
this.value = new UIEventSource<T>(undefined)
this._dontStyle = options.dontStyle ?? false
}
IsValid(t: T): boolean {
for (const inputElement of this._elements) {
if (inputElement.IsValid(t)) {
return true;
return true
}
}
return false;
return false
}
GetValue(): UIEventSource<T> {
return this.value;
return this.value
}
protected InnerConstructElement(): HTMLElement {
const elements = this._elements;
const selectFirstAsDefault = this._selectFirstAsDefault;
const elements = this._elements
const selectFirstAsDefault = this._selectFirstAsDefault
const selectedElementIndex: UIEventSource<number> =
new UIEventSource<number>(null);
const selectedElementIndex: UIEventSource<number> = new UIEventSource<number>(null)
const value = UIEventSource.flatten(
selectedElementIndex.map((selectedIndex) => {
if (selectedIndex !== undefined && selectedIndex !== null) {
return elements[selectedIndex].GetValue();
return elements[selectedIndex].GetValue()
}
}),
elements.map((e) => e?.GetValue())
);
value.syncWith(this.value);
)
value.syncWith(this.value)
if (selectFirstAsDefault) {
value.addCallbackAndRun((selected) => {
if (selected === undefined) {
for (const element of elements) {
const v = element.GetValue().data;
const v = element.GetValue().data
if (v !== undefined) {
value.setData(v);
break;
value.setData(v)
break
}
}
}
});
})
}
for (let i = 0; i < elements.length; i++) {
// If an element is clicked, the radio button corresponding with it should be selected as well
elements[i]?.onClick(() => {
selectedElementIndex.setData(i);
});
selectedElementIndex.setData(i)
})
elements[i].GetValue().addCallback(() => {
selectedElementIndex.setData(i);
});
selectedElementIndex.setData(i)
})
}
const groupId = "radiogroup" + RadioButton._nextId;
RadioButton._nextId++;
const groupId = "radiogroup" + RadioButton._nextId
RadioButton._nextId++
const form = document.createElement("form");
const form = document.createElement("form")
const inputs = [];
const wrappers: HTMLElement[] = [];
const inputs = []
const wrappers: HTMLElement[] = []
for (let i1 = 0; i1 < elements.length; i1++) {
let element = elements[i1];
const labelHtml = element.ConstructElement();
let element = elements[i1]
const labelHtml = element.ConstructElement()
if (labelHtml === undefined) {
continue;
continue
}
const input = document.createElement("input");
input.id = "radio" + groupId + "-" + i1;
input.name = groupId;
input.type = "radio";
input.classList.add(
"cursor-pointer",
"p-1",
"mr-2"
);
const input = document.createElement("input")
input.id = "radio" + groupId + "-" + i1
input.name = groupId
input.type = "radio"
input.classList.add("cursor-pointer", "p-1", "mr-2")
if (!this._dontStyle) {
input.classList.add(
"p-1",
"ml-2",
"pl-2",
"pr-0",
"m-3",
"mr-0"
);
input.classList.add("p-1", "ml-2", "pl-2", "pr-0", "m-3", "mr-0")
}
input.onchange = () => {
if (input.checked) {
selectedElementIndex.setData(i1);
selectedElementIndex.setData(i1)
}
};
}
inputs.push(input);
inputs.push(input)
const label = document.createElement("label");
label.appendChild(labelHtml);
label.htmlFor = input.id;
label.classList.add("flex", "w-full", "cursor-pointer", "bg-red");
const label = document.createElement("label")
label.appendChild(labelHtml)
label.htmlFor = input.id
label.classList.add("flex", "w-full", "cursor-pointer", "bg-red")
if (!this._dontStyle) {
labelHtml.classList.add("p-2")
}
const block = document.createElement("div");
block.appendChild(input);
block.appendChild(label);
block.classList.add(
"flex",
"w-full",
);
const block = document.createElement("div")
block.appendChild(input)
block.appendChild(label)
block.classList.add("flex", "w-full")
if (!this._dontStyle) {
block.classList.add(
"m-1",
"border",
"border-gray-400"
)
block.classList.add("m-1", "border", "border-gray-400")
}
block.style.borderRadius = "1.5rem"
wrappers.push(block);
wrappers.push(block)
form.appendChild(block);
form.appendChild(block)
}
value.addCallbackAndRun((selected:T) => {
let somethingChecked = false;
value.addCallbackAndRun((selected: T) => {
let somethingChecked = false
for (let i = 0; i < inputs.length; i++) {
let input = inputs[i];
input.checked = !somethingChecked && elements[i].IsValid(selected);
somethingChecked = somethingChecked || input.checked;
let input = inputs[i]
input.checked = !somethingChecked && elements[i].IsValid(selected)
somethingChecked = somethingChecked || input.checked
if (input.checked) {
wrappers[i].classList.remove("border-gray-400");
wrappers[i].classList.add("border-attention");
wrappers[i].classList.remove("border-gray-400")
wrappers[i].classList.add("border-attention")
} else {
wrappers[i].classList.add("border-gray-400");
wrappers[i].classList.remove("border-attention");
wrappers[i].classList.add("border-gray-400")
wrappers[i].classList.remove("border-attention")
}
}
});
})
this.SetClass("flex flex-col");
this.SetClass("flex flex-col")
return form;
return form
}
}

View file

@ -1,39 +1,38 @@
import {UIElement} from "../UIElement";
import {InputElement} from "./InputElement";
import BaseUIElement from "../BaseUIElement";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import Locale from "../i18n/Locale";
import Combine from "../Base/Combine";
import {TextField} from "./TextField";
import Svg from "../../Svg";
import {VariableUiElement} from "../Base/VariableUIElement";
import { UIElement } from "../UIElement"
import { InputElement } from "./InputElement"
import BaseUIElement from "../BaseUIElement"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations"
import Locale from "../i18n/Locale"
import Combine from "../Base/Combine"
import { TextField } from "./TextField"
import Svg from "../../Svg"
import { VariableUiElement } from "../Base/VariableUIElement"
/**
* A single 'pill' which can hide itself if the search criteria is not met
*/
class SelfHidingToggle extends UIElement implements InputElement<boolean> {
private readonly _shown: BaseUIElement;
private readonly _shown: BaseUIElement
public readonly _selected: UIEventSource<boolean>
public readonly isShown: Store<boolean> = new UIEventSource<boolean>(true);
public readonly isShown: Store<boolean> = new UIEventSource<boolean>(true)
public readonly forceSelected: UIEventSource<boolean>
private readonly _squared: boolean;
private readonly _squared: boolean
public constructor(
shown: string | BaseUIElement,
mainTerm: Record<string, string>,
search: Store<string>,
options?: {
searchTerms?: Record<string, string[]>,
selected?: UIEventSource<boolean>,
forceSelected?: UIEventSource<boolean>,
searchTerms?: Record<string, string[]>
selected?: UIEventSource<boolean>
forceSelected?: UIEventSource<boolean>
squared?: boolean
}
) {
super();
this._shown = Translations.W(shown);
this._squared = options?.squared ?? false;
const searchTerms: Record<string, string[]> = {};
super()
this._shown = Translations.W(shown)
this._squared = options?.squared ?? false
const searchTerms: Record<string, string[]> = {}
for (const lng in options?.searchTerms ?? []) {
if (lng === "_context") {
continue
@ -44,30 +43,34 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
if (lng === "_context") {
continue
}
const main = SelfHidingToggle.clean( mainTerm[lng])
const main = SelfHidingToggle.clean(mainTerm[lng])
searchTerms[lng] = [main].concat(searchTerms[lng] ?? [])
}
const selected = this._selected = options?.selected ?? new UIEventSource<boolean>(false);
const forceSelected = this.forceSelected = options?.forceSelected ?? new UIEventSource<boolean>(false)
this.isShown = search.map(s => {
if (s === undefined || s.length === 0) {
return true;
}
if (selected.data && !forceSelected.data) {
return true
}
s = s?.trim()?.toLowerCase()
if(searchTerms[Locale.language.data]?.some(t => t.indexOf(s) >= 0)){
return true
}
if(searchTerms["*"]?.some(t => t.indexOf(s) >= 0)){
return true
}
return false;
}, [selected, Locale.language])
const selected = (this._selected = options?.selected ?? new UIEventSource<boolean>(false))
const forceSelected = (this.forceSelected =
options?.forceSelected ?? new UIEventSource<boolean>(false))
this.isShown = search.map(
(s) => {
if (s === undefined || s.length === 0) {
return true
}
if (selected.data && !forceSelected.data) {
return true
}
s = s?.trim()?.toLowerCase()
if (searchTerms[Locale.language.data]?.some((t) => t.indexOf(s) >= 0)) {
return true
}
if (searchTerms["*"]?.some((t) => t.indexOf(s) >= 0)) {
return true
}
return false
},
[selected, Locale.language]
)
const self = this;
this.isShown.addCallbackAndRun(shown => {
const self = this
this.isShown.addCallbackAndRun((shown) => {
if (shown) {
self.RemoveClass("hidden")
} else {
@ -75,25 +78,24 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
}
})
}
private static clean(s: string) : string{
private static clean(s: string): string {
return s?.trim()?.toLowerCase()?.replace(/[-]/, "")
}
GetValue(): UIEventSource<boolean> {
return this._selected
}
IsValid(t: boolean): boolean {
return true;
return true
}
protected InnerRender(): string | BaseUIElement {
let el: BaseUIElement = this._shown;
const selected = this._selected;
let el: BaseUIElement = this._shown
const selected = this._selected
selected.addCallbackAndRun(selected => {
selected.addCallbackAndRun((selected) => {
if (selected) {
el.SetClass("border-4")
el.RemoveClass("border")
@ -107,77 +109,88 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
const forcedSelection = this.forceSelected
el.onClick(() => {
if(forcedSelection.data){
if (forcedSelection.data) {
selected.setData(true)
}else{
selected.setData(!selected.data);
} else {
selected.setData(!selected.data)
}
})
if(!this._squared){
if (!this._squared) {
el.SetClass("rounded-full")
}
return el.SetClass("border border-black p-1 px-4")
}
}
/**
* The searchable mappings selector is a selector which shows various pills from which one (or more) options can be chosen.
* A searchfield can be used to filter the values
*/
export class SearchablePillsSelector<T> extends Combine implements InputElement<T[]> {
private readonly selectedElements: UIEventSource<T[]>;
private readonly selectedElements: UIEventSource<T[]>
public readonly someMatchFound: Store<boolean>;
public readonly someMatchFound: Store<boolean>
/**
*
*
* @param values
* @param options
*/
constructor(
values: { show: BaseUIElement, value: T, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> }[],
values: {
show: BaseUIElement
value: T
mainTerm: Record<string, string>
searchTerms?: Record<string, string[]>
}[],
options?: {
mode?: "select-one" | "select-many",
selectedElements?: UIEventSource<T[]>,
searchValue?: UIEventSource<string>,
onNoMatches?: BaseUIElement,
onNoSearchMade?: BaseUIElement,
mode?: "select-one" | "select-many"
selectedElements?: UIEventSource<T[]>
searchValue?: UIEventSource<string>
onNoMatches?: BaseUIElement
onNoSearchMade?: BaseUIElement
/**
* Shows this if there are many (>200) possible mappings
*/
onManyElements?: BaseUIElement,
onManyElementsValue?: UIEventSource<T[]>,
selectIfSingle?: false | boolean,
searchAreaClass?: string,
onManyElements?: BaseUIElement
onManyElementsValue?: UIEventSource<T[]>
selectIfSingle?: false | boolean
searchAreaClass?: string
hideSearchBar?: false | boolean
}) {
}
) {
const search = new TextField({ value: options?.searchValue })
const search = new TextField({value: options?.searchValue})
const searchBar = options?.hideSearchBar
? undefined
: new Combine([
Svg.search_svg().SetClass("w-8 normal-background"),
search.SetClass("w-full"),
]).SetClass("flex items-center border-2 border-black m-2")
const searchBar = options?.hideSearchBar ? undefined : new Combine([Svg.search_svg().SetClass("w-8 normal-background"), search.SetClass("w-full")])
.SetClass("flex items-center border-2 border-black m-2")
const searchValue = search.GetValue().map(s => s?.trim()?.toLowerCase())
const selectedElements = options?.selectedElements ?? new UIEventSource<T[]>([]);
const mode = options?.mode ?? "select-one";
const searchValue = search.GetValue().map((s) => s?.trim()?.toLowerCase())
const selectedElements = options?.selectedElements ?? new UIEventSource<T[]>([])
const mode = options?.mode ?? "select-one"
const onEmpty = options?.onNoMatches ?? Translations.t.general.noMatchingMapping
const mappedValues: { show: SelfHidingToggle, mainTerm: Record<string, string>, value: T }[] = values.map(v => {
const mappedValues: {
show: SelfHidingToggle
mainTerm: Record<string, string>
value: T
}[] = values.map((v) => {
const vIsSelected = new UIEventSource(false)
const vIsSelected = new UIEventSource(false);
selectedElements.addCallbackAndRunD(selectedElements => {
vIsSelected.setData(selectedElements.some(t => t === v.value))
selectedElements.addCallbackAndRunD((selectedElements) => {
vIsSelected.setData(selectedElements.some((t) => t === v.value))
})
vIsSelected.addCallback(selected => {
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);
} else if (!selectedElements.data.some((t) => t === v.value)) {
selectedElements.data.push(v.value)
selectedElements.ping()
}
} else {
@ -186,7 +199,7 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
if (t == v.value) {
selectedElements.data.splice(i, 1)
selectedElements.ping()
break;
break
}
}
}
@ -195,89 +208,99 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
const toggle = new SelfHidingToggle(v.show, v.mainTerm, searchValue, {
searchTerms: v.searchTerms,
selected: vIsSelected,
squared: mode === "select-many"
squared: mode === "select-many",
})
return {
...v,
show: toggle
};
show: toggle,
}
})
let totalShown: Store<number>
if (options.selectIfSingle) {
let forcedSelection : { value: T, show: SelfHidingToggle } = undefined
totalShown = searchValue.map(_ => {
let totalShown = 0;
let lastShownValue: { value: T, show: SelfHidingToggle }
for (const mv of mappedValues) {
const valueIsShown = mv.show.isShown.data
if (valueIsShown) {
totalShown++;
lastShownValue = mv
let forcedSelection: { value: T; show: SelfHidingToggle } = undefined
totalShown = searchValue.map(
(_) => {
let totalShown = 0
let lastShownValue: { value: T; show: SelfHidingToggle }
for (const mv of mappedValues) {
const valueIsShown = mv.show.isShown.data
if (valueIsShown) {
totalShown++
lastShownValue = mv
}
}
}
if (totalShown == 1) {
if (selectedElements.data?.indexOf(lastShownValue.value) < 0) {
selectedElements.setData([lastShownValue.value])
lastShownValue.show.forceSelected.setData(true)
forcedSelection = lastShownValue
if (totalShown == 1) {
if (selectedElements.data?.indexOf(lastShownValue.value) < 0) {
selectedElements.setData([lastShownValue.value])
lastShownValue.show.forceSelected.setData(true)
forcedSelection = lastShownValue
}
} else if (forcedSelection != undefined) {
forcedSelection?.show?.forceSelected?.setData(false)
forcedSelection = undefined
selectedElements.setData([])
}
} else if (forcedSelection != undefined) {
forcedSelection?.show?.forceSelected?.setData(false)
forcedSelection = undefined;
selectedElements.setData([])
}
return totalShown
}, mappedValues.map(mv => mv.show.GetValue()))
return totalShown
},
mappedValues.map((mv) => mv.show.GetValue())
)
} else {
totalShown = searchValue.map(_ => mappedValues.filter(mv => mv.show.isShown.data).length, mappedValues.map(mv => mv.show.GetValue()))
totalShown = searchValue.map(
(_) => mappedValues.filter((mv) => mv.show.isShown.data).length,
mappedValues.map((mv) => mv.show.GetValue())
)
}
const tooMuchElementsCutoff = 200;
options?.onManyElementsValue?.map(value => {
console.log("Installing toMuchElementsValue", value)
if(tooMuchElementsCutoff <= totalShown.data){
selectedElements.setData(value)
selectedElements.ping()
}
}, [totalShown])
const tooMuchElementsCutoff = 200
options?.onManyElementsValue?.map(
(value) => {
console.log("Installing toMuchElementsValue", value)
if (tooMuchElementsCutoff <= totalShown.data) {
selectedElements.setData(value)
selectedElements.ping()
}
},
[totalShown]
)
super([
searchBar,
new VariableUiElement(Locale.language.map(lng => {
if(totalShown.data >= 200){
return options?.onManyElements ?? Translations.t.general.useSearch;
}
if (options?.onNoSearchMade !== undefined && (searchValue.data === undefined || searchValue.data.length === 0)) {
return options?.onNoSearchMade
}
if (totalShown.data == 0) {
return onEmpty
}
mappedValues.sort((a, b) => a.mainTerm[lng] < b.mainTerm[lng] ? -1 : 1)
return new Combine(mappedValues.map(e => e.show))
.SetClass("flex flex-wrap w-full content-start")
.SetClass(options?.searchAreaClass ?? "")
}, [totalShown, searchValue]))
new VariableUiElement(
Locale.language.map(
(lng) => {
if (totalShown.data >= 200) {
return options?.onManyElements ?? Translations.t.general.useSearch
}
if (
options?.onNoSearchMade !== undefined &&
(searchValue.data === undefined || searchValue.data.length === 0)
) {
return options?.onNoSearchMade
}
if (totalShown.data == 0) {
return onEmpty
}
mappedValues.sort((a, b) => (a.mainTerm[lng] < b.mainTerm[lng] ? -1 : 1))
return new Combine(mappedValues.map((e) => e.show))
.SetClass("flex flex-wrap w-full content-start")
.SetClass(options?.searchAreaClass ?? "")
},
[totalShown, searchValue]
)
),
])
this.selectedElements = selectedElements;
this.someMatchFound = totalShown.map(t => t > 0);
this.selectedElements = selectedElements
this.someMatchFound = totalShown.map((t) => t > 0)
}
public GetValue(): UIEventSource<T[]> {
return this.selectedElements;
return this.selectedElements
}
IsValid(t: T[]): boolean {
return true;
return true
}
}

View file

@ -1,45 +1,38 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
export default class SimpleDatePicker extends InputElement<string> {
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
private readonly value: UIEventSource<string>
private readonly _element: HTMLElement;
private readonly _element: HTMLElement
constructor(
value?: UIEventSource<string>
) {
super();
this.value = value ?? new UIEventSource<string>(undefined);
const self = this;
constructor(value?: UIEventSource<string>) {
super()
this.value = value ?? new UIEventSource<string>(undefined)
const self = this
const el = document.createElement("input")
this._element = el;
this._element = el
el.type = "date"
el.oninput = () => {
// Already in YYYY-MM-DD value!
self.value.setData(el.value);
// Already in YYYY-MM-DD value!
self.value.setData(el.value)
}
this.value.addCallbackAndRunD(v => {
el.value = v;
});
this.value.addCallbackAndRunD((v) => {
el.value = v
})
}
GetValue(): UIEventSource<string> {
return this.value;
return this.value
}
IsValid(t: string): boolean {
return !isNaN(new Date(t).getTime());
return !isNaN(new Date(t).getTime())
}
protected InnerConstructElement(): HTMLElement {
return this._element
}
}
}

View file

@ -1,13 +1,12 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import { InputElement } from "./InputElement"
import { UIEventSource } from "../../Logic/UIEventSource"
export default class Slider extends InputElement<number> {
private readonly _value: UIEventSource<number>
private readonly min: number;
private readonly max: number;
private readonly step: number;
private readonly vertical: boolean;
private readonly min: number
private readonly max: number
private readonly step: number
private readonly vertical: boolean
/**
* Constructs a slider input element for natural numbers
@ -15,21 +14,25 @@ export default class Slider extends InputElement<number> {
* @param max: the max value that is allowed, inclusive
* @param options: value: injectable value; step: the step size of the slider
*/
constructor(min: number, max: number, options?: {
value?: UIEventSource<number>,
step?: 1 | number,
vertical?: false | boolean
}) {
super();
this.max = max;
this.min = min;
constructor(
min: number,
max: number,
options?: {
value?: UIEventSource<number>
step?: 1 | number
vertical?: false | boolean
}
) {
super()
this.max = max
this.min = min
this._value = options?.value ?? new UIEventSource<number>(min)
this.step = options?.step ?? 1;
this.vertical = options?.vertical ?? false;
this.step = options?.step ?? 1
this.vertical = options?.vertical ?? false
}
GetValue(): UIEventSource<number> {
return this._value;
return this._value
}
protected InnerConstructElement(): HTMLElement {
@ -42,16 +45,15 @@ export default class Slider extends InputElement<number> {
el.oninput = () => {
valuestore.setData(Number(el.value))
}
if(this.vertical){
if (this.vertical) {
el.classList.add("vertical")
el.setAttribute('orient','vertical'); // firefox only workaround...
el.setAttribute("orient", "vertical") // firefox only workaround...
}
valuestore.addCallbackAndRunD(v => el.value = ""+valuestore.data)
return el;
valuestore.addCallbackAndRunD((v) => (el.value = "" + valuestore.data))
return el
}
IsValid(t: number): boolean {
return Math.round(t) == t && t >= this.min && t <= this.max;
return Math.round(t) == t && t >= this.min && t <= this.max
}
}
}

View file

@ -1,82 +1,79 @@
import {InputElement} from "./InputElement";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {Translation} from "../i18n/Translation";
import Locale from "../i18n/Locale";
import { InputElement } from "./InputElement"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
import { Translation } from "../i18n/Translation"
import Locale from "../i18n/Locale"
interface TextFieldOptions {
placeholder?: string | Store<string> | Translation,
value?: UIEventSource<string>,
htmlType?: "area" | "text" | "time" | string,
inputMode?: string,
label?: BaseUIElement,
textAreaRows?: number,
inputStyle?: string,
placeholder?: string | Store<string> | Translation
value?: UIEventSource<string>
htmlType?: "area" | "text" | "time" | string
inputMode?: string
label?: BaseUIElement
textAreaRows?: number
inputStyle?: string
isValid?: (s: string) => boolean
}
export class TextField extends InputElement<string> {
public readonly enterPressed = new UIEventSource<string>(undefined);
private readonly value: UIEventSource<string>;
private _actualField : HTMLElement
private readonly _isValid: (s: string) => boolean;
private readonly _rawValue: UIEventSource<string>
private _isFocused = false;
private readonly _options : TextFieldOptions;
public readonly enterPressed = new UIEventSource<string>(undefined)
private readonly value: UIEventSource<string>
private _actualField: HTMLElement
private readonly _isValid: (s: string) => boolean
private readonly _rawValue: UIEventSource<string>
private _isFocused = false
private readonly _options: TextFieldOptions
constructor(options?: TextFieldOptions) {
super();
super()
this._options = options ?? {}
options = options ?? {};
this.value = options?.value ?? new UIEventSource<string>(undefined);
options = options ?? {}
this.value = options?.value ?? new UIEventSource<string>(undefined)
this._rawValue = new UIEventSource<string>("")
this._isValid = options.isValid ?? (_ => true);
this._isValid = options.isValid ?? ((_) => true)
}
private static SetCursorPosition(textfield: HTMLElement, i: number) {
if (textfield === undefined || textfield === null) {
return;
return
}
if (i === -1) {
// @ts-ignore
i = textfield.value.length;
i = textfield.value.length
}
textfield.focus();
textfield.focus()
// @ts-ignore
textfield.setSelectionRange(i, i);
textfield.setSelectionRange(i, i)
}
GetValue(): UIEventSource<string> {
return this.value;
return this.value
}
GetRawValue(): UIEventSource<string>{
GetRawValue(): UIEventSource<string> {
return this._rawValue
}
IsValid(t: string): boolean {
if (t === undefined || t === null) {
return false
}
return this._isValid(t);
return this._isValid(t)
}
private static test(){
private static test() {
const placeholder = new UIEventSource<string>("placeholder")
const tf = new TextField({
placeholder
placeholder,
})
const html = <HTMLInputElement> tf.InnerConstructElement().children[0];
const html = <HTMLInputElement>tf.InnerConstructElement().children[0]
html.placeholder // => 'placeholder'
placeholder.setData("another piece of text")
html.placeholder// => "another piece of text"
html.placeholder // => "another piece of text"
}
/**
*
*
* // should update placeholders dynamically
* const placeholder = new UIEventSource<string>("placeholder")
* const tf = new TextField({
@ -86,7 +83,7 @@ export class TextField extends InputElement<string> {
* html.placeholder // => 'placeholder'
* placeholder.setData("another piece of text")
* html.placeholder// => "another piece of text"
*
*
* // should update translated placeholders dynamically
* const placeholder = new Translation({nl: "Nederlands", en: "English"})
* Locale.language.setData("nl");
@ -99,26 +96,32 @@ export class TextField extends InputElement<string> {
* html.placeholder // => 'English'
*/
protected InnerConstructElement(): HTMLElement {
const options = this._options;
const self = this;
const options = this._options
const self = this
let placeholderStore: Store<string>
let placeholder : string = "";
if(options.placeholder){
if(typeof options.placeholder === "string"){
placeholder = options.placeholder;
placeholderStore = undefined;
}else {
if ((options.placeholder instanceof Store) && options.placeholder["data"] !== undefined) {
placeholderStore = options.placeholder;
} else if ((options.placeholder instanceof Translation) && options.placeholder["translations"] !== undefined) {
placeholderStore = <Store<string>>Locale.language.map(l => (<Translation>options.placeholder).textFor(l))
let placeholder: string = ""
if (options.placeholder) {
if (typeof options.placeholder === "string") {
placeholder = options.placeholder
placeholderStore = undefined
} else {
if (
options.placeholder instanceof Store &&
options.placeholder["data"] !== undefined
) {
placeholderStore = options.placeholder
} else if (
options.placeholder instanceof Translation &&
options.placeholder["translations"] !== undefined
) {
placeholderStore = <Store<string>>(
Locale.language.map((l) => (<Translation>options.placeholder).textFor(l))
)
}
placeholder = placeholderStore?.data ?? placeholder ?? "";
placeholder = placeholderStore?.data ?? placeholder ?? ""
}
}
this.SetClass("form-text-field")
let inputEl: HTMLElement
if (options.htmlType === "area") {
@ -128,9 +131,9 @@ export class TextField extends InputElement<string> {
el.rows = options.textAreaRows
el.cols = 50
el.style.width = "100%"
inputEl = el;
if(placeholderStore){
placeholderStore.addCallbackAndRunD(placeholder => el.placeholder = placeholder)
inputEl = el
if (placeholderStore) {
placeholderStore.addCallbackAndRunD((placeholder) => (el.placeholder = placeholder))
}
} else {
const el = document.createElement("input")
@ -139,86 +142,81 @@ export class TextField extends InputElement<string> {
el.placeholder = placeholder
el.style.cssText = options.inputStyle ?? "width: 100%;"
inputEl = el
if(placeholderStore){
placeholderStore.addCallbackAndRunD(placeholder => el.placeholder = placeholder)
if (placeholderStore) {
placeholderStore.addCallbackAndRunD((placeholder) => (el.placeholder = placeholder))
}
}
const form = document.createElement("form")
form.appendChild(inputEl)
form.onsubmit = () => false;
form.onsubmit = () => false
if (options.label) {
form.appendChild(options.label.ConstructElement())
}
const field = inputEl
const field = inputEl;
this.value.addCallbackAndRunD(value => {
this.value.addCallbackAndRunD((value) => {
// We leave the textfield as is in the case of undefined or null (handled by addCallbackAndRunD) - make sure we do not erase it!
field["value"] = value;
field["value"] = value
if (self.IsValid(value)) {
self.RemoveClass("invalid")
} else {
self.SetClass("invalid")
}
})
field.oninput = () => {
// How much characters are on the right, not including spaces?
// @ts-ignore
const endDistance = field.value.substring(field.selectionEnd).replace(/ /g, '').length;
const endDistance = field.value.substring(field.selectionEnd).replace(/ /g, "").length
// @ts-ignore
let val: string = field.value;
let val: string = field.value
self._rawValue.setData(val)
if (!self.IsValid(val)) {
self.value.setData(undefined);
self.value.setData(undefined)
} else {
self.value.setData(val);
self.value.setData(val)
}
// Setting the value might cause the value to be set again. We keep the distance _to the end_ stable, as phone number formatting might cause the start to change
// See https://github.com/pietervdvn/MapComplete/issues/103
// We reread the field value - it might have changed!
// @ts-ignore
val = field.value;
let newCursorPos = val.length - endDistance;
while (newCursorPos >= 0 &&
val = field.value
let newCursorPos = val.length - endDistance
while (
newCursorPos >= 0 &&
// We count the number of _actual_ characters (non-space characters) on the right of the new value
// This count should become bigger then the end distance
val.substr(newCursorPos).replace(/ /g, '').length < endDistance
) {
newCursorPos--;
val.substr(newCursorPos).replace(/ /g, "").length < endDistance
) {
newCursorPos--
}
TextField.SetCursorPosition(field, newCursorPos);
};
TextField.SetCursorPosition(field, newCursorPos)
}
field.addEventListener("keyup", function (event) {
if (event.key === "Enter") {
// @ts-ignore
self.enterPressed.setData(field.value);
self.enterPressed.setData(field.value)
}
});
})
if(this._isFocused){
if (this._isFocused) {
field.focus()
}
this._actualField = field;
return form;
this._actualField = field
return form
}
public focus() {
if(this._actualField === undefined){
if (this._actualField === undefined) {
this._isFocused = true
}else{
} else {
this._actualField.focus()
}
}
}
}

View file

@ -1,20 +1,21 @@
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import Lazy from "../Base/Lazy";
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import Lazy from "../Base/Lazy"
/**
* The 'Toggle' is a UIElement showing either one of two elements, depending on the state.
* It can be used to implement e.g. checkboxes or collapsible elements
*/
export default class Toggle extends VariableUiElement {
public readonly isEnabled: Store<boolean>
public readonly isEnabled: Store<boolean>;
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: Store<boolean>) {
super(
isEnabled?.map(isEnabled => isEnabled ? showEnabled : showDisabled)
);
constructor(
showEnabled: string | BaseUIElement,
showDisabled: string | BaseUIElement,
isEnabled: Store<boolean>
) {
super(isEnabled?.map((isEnabled) => (isEnabled ? showEnabled : showDisabled)))
this.isEnabled = isEnabled
}
@ -22,35 +23,30 @@ export default class Toggle extends VariableUiElement {
if (constructor === undefined) {
return undefined
}
return new Toggle(
new Lazy(constructor),
undefined,
condition
)
return new Toggle(new Lazy(constructor), undefined, condition)
}
}
/**
* Same as `Toggle`, but will swap on click
*/
export class ClickableToggle extends Toggle {
public readonly isEnabled: UIEventSource<boolean>
public readonly isEnabled: UIEventSource<boolean>;
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)) {
super(
showEnabled, showDisabled, isEnabled
);
constructor(
showEnabled: string | BaseUIElement,
showDisabled: string | BaseUIElement,
isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)
) {
super(showEnabled, showDisabled, isEnabled)
this.isEnabled = isEnabled
}
public ToggleOnClick(): ClickableToggle {
const self = this;
const self = this
this.onClick(() => {
self.isEnabled.setData(!self.isEnabled.data);
self.isEnabled.setData(!self.isEnabled.data)
})
return this;
return this
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,31 +1,32 @@
import {ReadonlyInputElement} from "./InputElement";
import {Store} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import { ReadonlyInputElement } from "./InputElement"
import { Store } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"
export default class VariableInputElement<T> extends BaseUIElement implements ReadonlyInputElement<T> {
private readonly value: Store<T>;
export default class VariableInputElement<T>
extends BaseUIElement
implements ReadonlyInputElement<T>
{
private readonly value: Store<T>
private readonly element: BaseUIElement
private readonly upstream: Store<ReadonlyInputElement<T>>;
private readonly upstream: Store<ReadonlyInputElement<T>>
constructor(upstream: Store<ReadonlyInputElement<T>>) {
super()
this.upstream = upstream;
this.value = upstream.bind(v => v.GetValue())
this.upstream = upstream
this.value = upstream.bind((v) => v.GetValue())
this.element = new VariableUiElement(upstream)
}
GetValue(): Store<T> {
return this.value;
return this.value
}
IsValid(t: T): boolean {
return this.upstream.data.IsValid(t);
return this.upstream.data.IsValid(t)
}
protected InnerConstructElement(): HTMLElement {
return this.element.ConstructElement();
return this.element.ConstructElement()
}
}
}