diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-06-01 22:10:32 +0200 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-06-01 22:10:32 +0200 |
| commit | a640703f2da8815dc26ad1600a6f206be1624379 (patch) | |
| tree | dbda195fb5783d16487e557e06471cf848b75427 /apps/web-shared/src/lib/helpers.ts | |
| download | greatoffice-a640703f2da8815dc26ad1600a6f206be1624379.tar.xz greatoffice-a640703f2da8815dc26ad1600a6f206be1624379.zip | |
feat: Initial after clean slate
Diffstat (limited to 'apps/web-shared/src/lib/helpers.ts')
| -rw-r--r-- | apps/web-shared/src/lib/helpers.ts | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/apps/web-shared/src/lib/helpers.ts b/apps/web-shared/src/lib/helpers.ts new file mode 100644 index 0000000..650bccf --- /dev/null +++ b/apps/web-shared/src/lib/helpers.ts @@ -0,0 +1,489 @@ +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<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 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<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 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<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 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<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 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<Element> { + return document.querySelectorAll(selector); +} |
