diff options
Diffstat (limited to 'code/app/src/lib/helpers.ts')
| -rw-r--r-- | code/app/src/lib/helpers.ts | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/code/app/src/lib/helpers.ts b/code/app/src/lib/helpers.ts new file mode 100644 index 0000000..3fa1653 --- /dev/null +++ b/code/app/src/lib/helpers.ts @@ -0,0 +1,497 @@ +import { browser } from "$app/environment"; +import type { TimeEntryDto } from "$lib/models/TimeEntryDto"; +import type { UnwrappedEntryDateTime } from "$lib/models/UnwrappedEntryDateTime"; +import { logInfo } from "$lib/logger"; +import { Temporal } from "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 get_default_sorted(unsorted: Array<TimeEntryDto>): Array<TimeEntryDto> { + if (unsorted.length < 1) return unsorted; + const byStart = unsorted.sort((a, b) => { + return Temporal.Instant.compare(Temporal.Instant.from(b.start), Temporal.Instant.from(a.start)); + }); + + return byStart.sort((a, b) => { + return Temporal.Instant.compare(Temporal.Instant.from(b.stop), Temporal.Instant.from(a.stop)); + }); +} + +export function get_element_by_pw_key(key: string): HTMLElement | null { + return document.querySelector("[pw-key='" + key + "']"); +} + +export function get_pw_key_selector(key: string): string { + return "[pw-key='" + key + "']"; +} + +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 get_cookie(name: string) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop()?.split(";").shift(); +} + +export function set_cookie(name: string, value: string, baseDomain = window.location.hostname) { + document.cookie = name + "=" + encodeURIComponent(value) + (baseDomain ? ";domain=" + baseDomain : ""); +} + +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<T>(a: Array<T>, b: Array<T>, props: Array<string>): Array<T> { + 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 no_type_check(x: any) { + return x; +} +export function capitalise(value: string): string { + return value.charAt(0).toUpperCase() + value.slice(1); +} + +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, hourChar = "h", minuteChar = "m") { + const hours = Math.floor(seconds / (60 * 60)); + seconds -= hours * (60 * 60); + const minutes = Math.floor(seconds / 60); + return hours + "h" + minutes + "m"; +} + +export function seconds_to_hour_minute(seconds: number) { + const hours = Math.floor(seconds / (60 * 60)); + seconds -= hours * (60 * 60); + const minutes = Math.floor(seconds / 60); + return { hours, minutes }; +} + +export function get_query_string(params: any = {}): string { + const map = Object.keys(params).reduce((arr: Array<string>, 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 noop() { +} + +export async function run_async(functionToRun: Function): Promise<any> { + 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<string> { + 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 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<HTMLElement | Function | Node> +} + +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 | any>): 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<string> = [], 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 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 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 { + if (!browser) { + logInfo("sessionStorage is not available in non-browser contexts"); + return; + } + 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 { + if (!browser) { + logInfo("sessionStorage is not available in non-browser contexts"); + return; + } + 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 { + if (!browser) { + console.warn("sessionStorage is not available in non-browser contexts"); + return; + } + sessionStorage.setItem(key, JSON.stringify(value)); +} + +export function session_storage_get_json(key: string): object { + if (!browser) { + console.warn("sessionStorage is not available in non-browser contexts"); + return {}; + } + return JSON.parse(sessionStorage.getItem(key) ?? "{}"); +} + +export function local_storage_set_json(key: string, value: object): void { + if (!browser) { + console.warn("sessionStorage is not available in non-browser contexts"); + return; + } + localStorage.setItem(key, JSON.stringify(value)); +} + +export function local_storage_get_json(key: string): object { + if (!browser) { + console.warn("sessionStorage is not available in non-browser contexts"); + return {}; + } + 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; +} |
