the browser-facing portion of osu!
at master 3.9 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2// See the LICENCE file in the repository root for full licence text. 3 4import { padStart } from 'lodash'; 5import { CSSProperties } from 'react'; 6import { urlPresence } from './css'; 7 8const byteSuffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 9const kilo = 1000; 10 11export function bottomPage() { 12 return bottomPageDistance() === 0; 13} 14 15export function bottomPageDistance() { 16 const page = document.documentElement; 17 18 return page.scrollHeight - page.scrollTop - page.clientHeight; 19} 20 21export function createClickCallback(target: unknown) { 22 if (target instanceof HTMLElement) { 23 // plain javascript here doesn't trigger submit events 24 // which means jquery-ujs handler won't be triggered 25 // reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit 26 if (target instanceof HTMLFormElement) { 27 return () => $(target).submit(); 28 } 29 30 // inversely, using jquery here won't actually click the thing 31 // reference: https://github.com/jquery/jquery/blob/f5aa89af7029ae6b9203c2d3e551a8554a0b4b89/src/event.js#L586 32 return () => target.click(); 33 } 34} 35 36export function cssVar2x(url?: string | null) { 37 if (url == null) return; 38 39 return { 40 '--bg': urlPresence(url), 41 '--bg-2x': urlPresence(make2x(url)), 42 } as CSSProperties; 43} 44 45function padTimeComponent(time: number) { 46 return padStart(time.toString(), 2, '0'); 47} 48 49export function formatBytes(bytes: number, decimals = 2) { 50 if (bytes < kilo) { 51 return `${bytes} B`; 52 } 53 54 const i = Math.floor(Math.log(bytes) / Math.log(kilo)); 55 return `${formatNumber(bytes / Math.pow(kilo, i), decimals)} ${byteSuffixes[i]}`; 56} 57 58export function formatDuration(valueSecond: number) { 59 const s = valueSecond % 60; 60 const m = Math.floor(valueSecond / 60) % 60; 61 const h = Math.floor(valueSecond / 3600); 62 63 if (h > 0) { 64 return `${h}:${padTimeComponent(m)}:${padTimeComponent(s)}`; 65 } 66 67 return `${m}:${padTimeComponent(s)}`; 68} 69 70const defaultNumberFormatter = new Intl.NumberFormat(window.currentLocale); 71 72export function formatNumber(num: number, precision?: number, options?: Intl.NumberFormatOptions, locale?: string) { 73 if (precision == null && options == null && locale == null) { 74 return defaultNumberFormatter.format(num); 75 } 76 77 options ??= {}; 78 79 if (precision != null) { 80 options.minimumFractionDigits = precision; 81 options.maximumFractionDigits = precision; 82 } 83 84 return num.toLocaleString(locale ?? window.currentLocale, options); 85} 86 87const defaultSuffixedNumberOptions = { 88 maximumFractionDigits: 1, 89 minimumFractionDigits: 0, 90 notation: 'compact', 91} as const; 92const defaultSuffixedNumberFormatter = new Intl.NumberFormat(window.currentLocale, defaultSuffixedNumberOptions); 93 94export function formatNumberSuffixed(num?: number) { 95 return num == null 96 ? undefined 97 : defaultSuffixedNumberFormatter.format(num); 98} 99 100export function htmlElementOrNull(thing: unknown) { 101 if (thing instanceof HTMLElement) { 102 return thing; 103 } 104 105 return null; 106} 107 108export function isClickable(maybeEl: unknown): boolean { 109 const el = htmlElementOrNull(maybeEl); 110 111 if (el == null) { 112 return false; 113 } 114 115 if (isInputElement(el) || ['A', 'BUTTON'].includes(el.tagName)) { 116 return true; 117 } 118 119 const parentEl = htmlElementOrNull(el.parentNode); 120 if (parentEl != null) { 121 return isClickable(parentEl); 122 } 123 124 return false; 125} 126 127export function isInputElement(el: HTMLElement) { 128 return ['INPUT', 'OPTION', 'SELECT', 'TEXTAREA'].includes(el.tagName) || el.isContentEditable; 129} 130 131export const transparentGif = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; 132 133export function make2x(url?: string) { 134 if (url == null) return; 135 136 return url.replace(/(\.[^.]+)$/, '@2x$1'); 137} 138 139export function stripTags(str: string) { 140 return str.replace(/<[^>]*>/g, ''); 141}