import { rankItem } from "@tanstack/match-sorter-utils"; import domtoimage from "dom-to-image"; export function formatNumberNoMagnitude(value: number | string) { if (typeof value === "string") { const suffix = value.replace(/[^a-zA-Z]/g, "").trim(); const magnitude = ["", "K", "M", "B", "T"].indexOf( suffix.replace(/\s/g, ""), ); value = Number(value.replace(/[^0-9.]/g, "").trim()) * Math.pow(10, magnitude * 3); } return value; } export function formatNumberMagnitude(value: number | string, column?: string) { if (typeof value === "string") { value = Number(formatNumberNoMagnitude(value)); } if (value % 1 !== 0) { const decimalPlaces = Math.max( 2, value.toString().split(".")[1]?.length || 0, ); const toFixed = Math.min(4, decimalPlaces); if (value < 5) { return value.toFixed(toFixed) || 0; } value = Number(value.toFixed(2)); } if ( (value > 100_000 || value < -100_000) && !includesPriceNames(column || "") ) { const magnitude = Math.min(4, Math.floor(Math.log10(Math.abs(value)) / 3)); const suffix = ["", "K", "M", "B", "T"][magnitude]; const formatted = (value / 10 ** (magnitude * 3)).toFixed(3); return `${formatted.replace(/\.?0+$/, "")} ${suffix}`; } if (value > 1000 || value < -1000) return formatNumber(value); return value; } export function formatNumber(value: number) { if (value > 1000 || value < -1000) { const parts = value.toString().split("."); const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); const decimalPart = parts[1] ? `.${parts[1]}` : ""; return `${integerPart}${decimalPart}`; } return value; } export function includesDateNames(column: string) { return ["date", "day", "time", "timestamp", "year"].some((dateName) => column?.toLowerCase().includes(dateName), ); } export function includesPriceNames(column: string) { return ["price", "open", "close"].some((priceName) => column?.toLowerCase().includes(priceName), ); } function loadingOverlay(message?: string, is_close?: boolean) { const loading = window.document.getElementById("loading") as HTMLElement; const loading_text = window.document.getElementById( "loading_text", ) as HTMLElement; return new Promise((resolve) => { if (is_close) { loading.classList.remove("show"); } else { // @ts-ignore loading_text.innerHTML = message; loading.classList.add("show"); } const is_loaded = setInterval(function () { if ( is_close ? !loading.classList.contains("show") : loading.classList.contains("show") ) { clearInterval(is_loaded); resolve(true); } }, 0.01); }); } export function isEqual(a: any, b: any) { if (a === b) return true; if (a == null || b == null) return false; if (a?.length !== b?.length) return false; for (let i = 0; i < a?.length; ++i) { if (a[i] !== b[i]) return false; } return true; } export const fuzzyFilter = ( row: any, columnId: string, value: string, addMeta: any, ): any => { const itemRank = rankItem(row.getValue(columnId), value); addMeta(itemRank); return itemRank; }; const exportNativeFileSystem = async ({ fileHandle, blob, }: { fileHandle?: FileSystemFileHandle | null; blob: Blob; }) => { if (!fileHandle) { return; } await writeFileHandler({ fileHandle, blob }); }; const writeFileHandler = async ({ fileHandle, blob, }: { fileHandle: FileSystemFileHandle; blob: Blob; }) => { const writer = await fileHandle.createWritable(); await writer.write(blob); await writer.close(); }; const IMAGE_TYPE: FilePickerAcceptType[] = [ { description: "PNG Image", accept: { "image/png": [".png"], }, }, { description: "JPEG Image", accept: { "image/jpeg": [".jpeg"], }, }, ]; const getNewFileHandle = ({ filename, is_image, }: { filename: string; is_image?: boolean; }): Promise => { try { if ("showSaveFilePicker" in window) { const opts: SaveFilePickerOptions = { suggestedName: filename, types: is_image ? IMAGE_TYPE : [ { description: "CSV File", accept: { "image/csv": [".csv"], }, }, ], excludeAcceptAllOption: true, }; return showSaveFilePicker(opts); } } catch (error) { console.error(error); } return new Promise((resolve) => { resolve(null); }); }; export const saveToFile = ( blob: Blob, fileName: string, fileHandle?: FileSystemFileHandle, ) => { try { if (fileHandle === null) { throw new Error("Cannot access filesystem"); } exportNativeFileSystem({ fileHandle, blob }); } catch (error) { console.error("oops, something went wrong!", error); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", fileName); link.style.visibility = "hidden"; document.body.appendChild(link); link.click(); document.body.removeChild(link); } return new Promise((resolve) => { resolve(true); }); }; export async function downloadData( type: "csv", columns: any, data: any, downloadFinished: (changed: boolean) => void, ) { const headers = columns; const rows = data.map((row: any) => headers.map((column: any) => row[column]), ); const csvData = [headers, ...rows]; if (type === "csv") { const csvContent = csvData.map((e) => e.join(",")).join("\n"); const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); const filename = `${window.title}.csv`; try { const fileHandle = await getNewFileHandle({ filename: filename, }); let ext = "csv"; if (fileHandle !== null) { // @ts-ignore ext = fileHandle.name.split(".").pop(); } await loadingOverlay(`Saving ${ext.toUpperCase()}`); // @ts-ignore non_blocking(async function () { // @ts-ignore saveToFile(blob, filename, fileHandle).then(async function () { await new Promise((resolve) => setTimeout(resolve, 1500)); await loadingOverlay("", true); if (!fileHandle) { downloadFinished(true); } }); }, 2)(); } catch (error) { console.error(error); } return; } } export async function downloadImage( id: string, downloadFinished: (change: boolean) => void, ) { const table = document.getElementById(id); const filename = `${window.title}.png`; try { const fileHandle = await getNewFileHandle({ filename: filename, is_image: true, }); let extension = "png"; if (fileHandle !== null) { // @ts-ignore extension = fileHandle.name.split(".").pop(); } await loadingOverlay(`Saving ${extension.toUpperCase()}`); non_blocking(async function () { // @ts-ignore domtoimage.toBlob(table).then(function (blob: Blob) { // @ts-ignore saveToFile(blob, filename, fileHandle).then(async function () { await new Promise((resolve) => setTimeout(resolve, 1500)); await loadingOverlay("", true); if (!fileHandle) { downloadFinished(true); } }); }); }, 2)(); } catch (error) { console.error(error); } } export const non_blocking = (func: Function, delay: number) => { let timeout: number; return function () { // @ts-ignore const context = this; const args = arguments; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), delay); }; };