forked from MapComplete/MapComplete
Reformat all files with prettier
This commit is contained in:
parent
e22d189376
commit
b541d3eab4
382 changed files with 50893 additions and 35566 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue