|
|
|
import Big from "big.js";
|
|
|
|
import { fromUnixTime, intlFormat } from "date-fns";
|
|
|
|
import { createMemo, JSX } from "solid-js";
|
|
|
|
|
|
|
|
export const sleep = (timeout: number) =>
|
|
|
|
new Promise((res) => setTimeout(res, timeout));
|
|
|
|
|
|
|
|
// Source: https://stackoverflow.com/a/34591063
|
|
|
|
export const roundToStep = (value: number, step = 1.0) => {
|
|
|
|
const inv = new Big(1.0).div(step);
|
|
|
|
return inv.mul(value).round().div(inv).toNumber();
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getDisplayDate = function (date: Date) {
|
|
|
|
return intlFormat(
|
|
|
|
date,
|
|
|
|
{
|
|
|
|
day: "2-digit",
|
|
|
|
month: "2-digit",
|
|
|
|
year: "numeric",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
locale: "de-CH",
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getDisplayDateFromUnix = function (unix: number) {
|
|
|
|
return getDisplayDate(fromUnixTime(unix));
|
|
|
|
};
|
|
|
|
|
|
|
|
export const resetInput =
|
|
|
|
(defaultValue: any, eventName = "input") =>
|
|
|
|
(evt: FocusEvent) => {
|
|
|
|
const el = evt.target as HTMLInputElement | null;
|
|
|
|
if (!el) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (el.value !== "") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
el.value = defaultValue;
|
|
|
|
const event = new Event(eventName, { bubbles: true });
|
|
|
|
el.dispatchEvent(event);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getDomain = () =>
|
|
|
|
import.meta.env.SSR ? process.env.DOMAIN || "localhost" : location.hostname;
|
|
|
|
|
|
|
|
export const getHost = () => `https://${getDomain()}`;
|
|
|
|
|
|
|
|
export const onClickFocus: JSX.EventHandlerUnion<
|
|
|
|
HTMLAnchorElement,
|
|
|
|
MouseEvent
|
|
|
|
> = (evt) => {
|
|
|
|
const el = evt.currentTarget!;
|
|
|
|
const id = el.getAttribute("href");
|
|
|
|
if (id == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const targetEl = document.querySelector<HTMLElement>(id);
|
|
|
|
if (targetEl == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
targetEl.focus();
|
|
|
|
};
|
|
|
|
|
|
|
|
export const externalLink = { target: "_blank", rel: "noopener" };
|
|
|
|
|
|
|
|
export const createOptionalNumberInputHandler = (
|
|
|
|
onInput: (v: number | undefined) => void
|
|
|
|
) => {
|
|
|
|
return (e: InputEvent & { currentTarget: HTMLInputElement }) => {
|
|
|
|
if (e.currentTarget.validity.badInput) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let value =
|
|
|
|
e.currentTarget.value == ""
|
|
|
|
? undefined
|
|
|
|
: parseNumberInput(e.currentTarget.value);
|
|
|
|
|
|
|
|
if (Number.isNaN(value)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
onInput(value);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const parseNumberInput = (v: string): number => parseFloat(v.replace(",", "."));
|
|
|
|
|
|
|
|
export const createNativeInputValue = (
|
|
|
|
getEl: () => HTMLInputElement,
|
|
|
|
signal: () => any
|
|
|
|
) =>
|
|
|
|
createMemo(function (prev) {
|
|
|
|
const value = signal();
|
|
|
|
const el = getEl();
|
|
|
|
|
|
|
|
if (!el) {
|
|
|
|
return value != null ? value : "";
|
|
|
|
}
|
|
|
|
|
|
|
|
const elValue = parseNumberInput(el.value);
|
|
|
|
|
|
|
|
// If the element value and signal value are equal, we can skip triggering the memo change by reusing the prev value
|
|
|
|
let result = elValue == value ? prev : value;
|
|
|
|
|
|
|
|
// NaN is always != NaN in js, we have to replace it with a value which has a proper identity
|
|
|
|
if (Number.isNaN(result)) {
|
|
|
|
result = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result == null) {
|
|
|
|
result = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
// If both the value and prev value are the same, but the element value is different, we have to update it manually
|
|
|
|
if (value === prev && elValue != value) {
|
|
|
|
el.value = result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
});
|