import {StorageKeys} from "$shared/lib/configuration"; import {TimeEntryDto} from "$shared/lib/models/TimeEntryDto"; import {UnwrappedEntryDateTime} from "$shared/lib/models/UnwrappedEntryDateTime"; import {Temporal} from "@js-temporal/polyfill"; export const EMAIL_REGEX = new RegExp(/^([a-z0-9]+(?:([._\-])[a-z0-9]+)*@(?:[a-z0-9]+(?:(-)[a-z0-9]+)?\.)+[a-z0-9](?:[a-z0-9]*[a-z0-9])?)$/i); export const URL_REGEX = new RegExp(/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-.][a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/gm); export const GUID_REGEX = new RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i); export const NORWEGIAN_PHONE_NUMBER_REGEX = new RegExp(/(0047|\+47|47)?\d{8,12}/); export function is_email(value: string): boolean { return EMAIL_REGEX.test(String(value).toLowerCase()); } export function is_url(value: string): boolean { return URL_REGEX.test(String(value).toLowerCase()); } export function is_norwegian_phone_number(value: string): boolean { if (value.length < 8 || value.length > 12) { return false; } return NORWEGIAN_PHONE_NUMBER_REGEX.test(String(value)); } export function switch_theme() { const html = document.querySelector("html"); if (html) { if (html.dataset.theme === "dark") { html.dataset.theme = "light"; } else { html.dataset.theme = "dark"; } localStorage.setItem(StorageKeys.theme, html.dataset.theme); } } export function unwrap_date_time_from_entry(entry: TimeEntryDto): UnwrappedEntryDateTime { if (!entry) throw new Error("entry was undefined"); const currentTimeZone = Temporal.Now.timeZone().id; const startInstant = Temporal.Instant.from(entry.start).toZonedDateTimeISO(currentTimeZone); const stopInstant = Temporal.Instant.from(entry.stop).toZonedDateTimeISO(currentTimeZone); return { start_date: startInstant.toPlainDate(), stop_date: stopInstant.toPlainDate(), start_time: startInstant.toPlainTime(), stop_time: stopInstant.toPlainTime(), duration: Temporal.Duration.from({ hours: stopInstant.hour, minutes: stopInstant.minute }).subtract(Temporal.Duration.from({ hours: startInstant.hour, minutes: startInstant.minute })) }; } export function is_guid(value: string): boolean { if (!value) { return false; } if (value[0] === "{") { value = value.substring(1, value.length - 1); } return GUID_REGEX.test(value); } export function is_empty_object(obj: object): boolean { return obj !== void 0 && Object.keys(obj).length > 0; } export function merge_obj_arr(a: Array, b: Array, props: Array): Array { let start = 0; let merge = []; while (start < a.length) { if (a[start] === b[start]) { //pushing the merged objects into array merge.push({...a[start], ...b[start]}); } //incrementing start value start = start + 1; } return merge; } export function set_favicon(url: string) { // Find the current favicon element const favicon = document.querySelector("link[rel=\"icon\"]") as HTMLLinkElement; if (favicon) { // Update the new link favicon.href = url; } else { // Create new `link` const link = document.createElement("link"); link.rel = "icon"; link.href = url; // Append to the `head` element document.head.appendChild(link); } } export function set_emoji_favicon(emoji: string) { // Create a canvas element const canvas = document.createElement("canvas"); canvas.height = 64; canvas.width = 64; // Get the canvas context const context = canvas.getContext("2d") as CanvasRenderingContext2D; context.font = "64px serif"; context.fillText(emoji, 0, 64); // Get the custom URL const url = canvas.toDataURL(); // Update the favicon set_favicon(url); } // https://stackoverflow.com/a/48400665/11961742 export function seconds_to_hour_minute_string(seconds: number) { const hours = Math.floor(seconds / (60 * 60)); seconds -= hours * (60 * 60); const minutes = Math.floor(seconds / 60); return hours + "h" + minutes + "m"; } export function get_query_string(params: any = {}): string { const map = Object.keys(params).reduce((arr: Array, key: string) => { if (params[key] !== undefined) { return arr.concat(`${key}=${encodeURIComponent(params[key])}`); } return arr; }, [] as any); if (map.length) { return `?${map.join("&")}`; } return ""; } export function make_url(url: string, params: object): string { return `${url}${get_query_string(params)}`; } export function load_script(url: string) { unload_script(url, () => { return new Promise(function (resolve, reject) { const script = document.createElement("script"); script.src = url; script.addEventListener("load", function () { // The script is loaded completely resolve(true); }); document.body.appendChild(script); }); }); } export function unload_script(src: string, callback?: Function): void { document.querySelectorAll("script[src='" + src + "']").forEach(el => el.remove()); if (typeof callback === "function") { callback(); } } export function noop() { } export async function run_async(functionToRun: Function): Promise { return new Promise((greatSuccess, graveFailure) => { try { greatSuccess(functionToRun()); } catch (exception) { graveFailure(exception); } }); } // https://stackoverflow.com/a/45215694/11961742 export function get_selected_options(domElement: HTMLSelectElement): Array { const ret = []; // fast but not universally supported if (domElement.selectedOptions !== undefined) { for (let i = 0; i < domElement.selectedOptions.length; i++) { ret.push(domElement.selectedOptions[i].value); } // compatible, but can be painfully slow } else { for (let i = 0; i < domElement.options.length; i++) { if (domElement.options[i].selected) { ret.push(domElement.options[i].value); } } } return ret; } export function uuid_v4(): string { // @ts-ignore return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); } export function random_string(length: number): string { if (!length) { throw new Error("length is undefined"); } let result = ""; const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } interface CreateElementOptions { name: string, properties?: object, children?: Array } export function create_element_from_object(elementOptions: CreateElementOptions): HTMLElement { return create_element(elementOptions.name, elementOptions.properties, elementOptions.children); } export function create_element(name: string, properties?: object, children?: Array): HTMLElement { if (!name || name.length < 1) { throw new Error("name is required"); } const node = document.createElement(name); if (properties) { for (const [key, value] of Object.entries(properties)) { // @ts-ignore node[key] = value; } } if (children && children.length > 0) { let actualChildren = children; if (typeof children === "function") { // @ts-ignore actualChildren = children(); } for (const child of actualChildren) { node.appendChild(child as Node); } } return node; } export function get_element_position(element: HTMLElement|any) { if (!element) return {x: 0, y: 0}; let x = 0; let y = 0; while (true) { x += element.offsetLeft; y += element.offsetTop; if (element.offsetParent === null) { break; } element = element.offsetParent; } return {x, y}; } export function restrict_input_to_numbers(element: HTMLElement, specials: Array = [], mergeSpecialsWithDefaults: boolean = false): void { if (element) { element.addEventListener("keydown", (e) => { const defaultSpecials = ["Backspace", "ArrowLeft", "ArrowRight", "Tab",]; let keys = specials.length > 0 ? specials : defaultSpecials; if (mergeSpecialsWithDefaults && specials) { keys = [...specials, ...defaultSpecials]; } if (keys.indexOf(e.key) !== -1) { return; } if (isNaN(parseInt(e.key))) { e.preventDefault(); } }); } } export function element_has_focus(element: HTMLElement): boolean { return element === document.activeElement; } export function move_focus(element: HTMLElement): void { if (!element) { element = document.getElementsByTagName("body")[0]; } element.focus(); // @ts-ignore if (!element_has_focus(element)) { element.setAttribute("tabindex", "-1"); element.focus(); } } export function get_url_parameter(name: string): string { // @ts-ignore return new RegExp("[?&]" + name + "=([^&#]*)")?.exec(window.location.href)[1]; } export function update_url_parameter(param: string, newVal: string): void { let newAdditionalURL = ""; let tempArray = location.href.split("?"); const baseURL = tempArray[0]; const additionalURL = tempArray[1]; let temp = ""; if (additionalURL) { tempArray = additionalURL.split("&"); for (let i = 0; i < tempArray.length; i++) { if (tempArray[i].split("=")[0] !== param) { newAdditionalURL += temp + tempArray[i]; temp = "&"; } } } const rows_txt = temp + "" + param + "=" + newVal; const newUrl = baseURL + "?" + newAdditionalURL + rows_txt; window.history.replaceState("", "", newUrl); } export function get_style_string(rules: CSSRuleList) { let styleString = ""; for (const [key, value] of Object.entries(rules)) { styleString += key + ":" + value + ";"; } return styleString; } export function get_local_time_zone_date(date: Date): Date { const timeOffsetInMS = new Date().getTimezoneOffset() * 60000; date.setTime(date.getTime() - timeOffsetInMS); return date; } export function parse_iso_local(s: string) { const b = s.split(/\D/); //@ts-ignore return new Date(b[0], b[1] - 1, b[2], b[3], b[4], b[5]); } export function resolve_references(json: any) { if (!json) return; if (typeof json === "string") { json = JSON.parse(json ?? "{}"); } const byid = {}, refs = []; json = function recurse(obj, prop, parent) { if (typeof obj !== "object" || !obj) { return obj; } if (Object.prototype.toString.call(obj) === "[object Array]") { for (let i = 0; i < obj.length; i++) { if (typeof obj[i] !== "object" || !obj[i]) { continue; } else if ("$ref" in obj[i]) { // @ts-ignore obj[i] = recurse(obj[i], i, obj); } else { obj[i] = recurse(obj[i], prop, obj); } } return obj; } if ("$ref" in obj) { let ref = obj.$ref; if (ref in byid) { // @ts-ignore return byid[ref]; } refs.push([parent, prop, ref]); return; } else if ("$id" in obj) { let id = obj.$id; delete obj.$id; if ("$values" in obj) { obj = obj.$values.map(recurse); } else { for (let prop2 in obj) { // @ts-ignore obj[prop2] = recurse(obj[prop2], prop2, obj); } } // @ts-ignore byid[id] = obj; } return obj; }(json); for (let i = 0; i < refs.length; i++) { let ref = refs[i]; // @ts-ignore ref[0][ref[1]] = byid[ref[2]]; } return json; } export function to_readable_date_string(date: Date, locale = "nb-NO"): string { date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); return date.toLocaleString(locale); } export function get_random_int(min: number, max: number): number { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } export function to_readable_bytes(bytes: number): string { const s = ["bytes", "kB", "MB", "GB", "TB", "PB"]; const e = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, e)).toFixed(2) + " " + s[e]; } export function can_use_dom(): boolean { return !!(typeof window !== "undefined" && window.document && window.document.createElement); } export function session_storage_remove_regex(regex: RegExp): void { let n = sessionStorage.length; while (n--) { const key = sessionStorage.key(n); if (key && regex.test(key)) { sessionStorage.removeItem(key); } } } export function local_storage_remove_regex(regex: RegExp): void { let n = localStorage.length; while (n--) { const key = localStorage.key(n); if (key && regex.test(key)) { localStorage.removeItem(key); } } } export function session_storage_set_json(key: string, value: object): void { sessionStorage.setItem(key, JSON.stringify(value)); } export function session_storage_get_json(key: string): object { return JSON.parse(sessionStorage.getItem(key) ?? "{}"); } export function local_storage_set_json(key: string, value: object): void { localStorage.setItem(key, JSON.stringify(value)); } export function local_storage_get_json(key: string): object { return JSON.parse(localStorage.getItem(key) ?? "{}"); } export function get_hash_code(value: string): number|undefined { let hash = 0; if (value.length === 0) { return; } for (let i = 0; i < value.length; i++) { const char = value.charCodeAt(i); hash = (hash << 5) - hash + char; hash |= 0; } return hash; } export function $(selector: string): HTMLElement|null { return document.querySelector(selector); } export function $$(selector: string): NodeListOf { return document.querySelectorAll(selector); }