import { DEFAULT_SKIN } from "./constants" ; import { WindowInfo } from "./types" ; interface Time { minutesFirstDigit : string ; minutesSecondDigit : string ; secondsFirstDigit : string ; secondsSecondDigit : string ; } interface IniData { [ section ] : { [ key ] : string ; } ; } export function imgFromUrl ( url ) { return new Promise ( ( resolve , reject ) => { const img = new Image ( ) ; img . onload = ( ) => { resolve ( img ) ; } ; img . onerror = reject ; img . src = url ; } ) ; } export const getTimeObj = ( time ) : Time => { if ( time == null ) { return { minutesFirstDigit : " " , minutesSecondDigit : " " , secondsFirstDigit : " " , secondsSecondDigit : " " , } ; } const minutes = Math . floor ( time / 60 ) ; const seconds = time % 60 ; const digits = time == null ? [ " " , " " , " " , " " ] : [ String ( Math . floor ( minutes / 10 ) ) , String ( Math . floor ( minutes % 10 ) ) , String ( Math . floor ( seconds / 10 ) ) , String ( Math . floor ( seconds % 10 ) ) , ] ; const [ minutesFirstDigit , minutesSecondDigit , secondsFirstDigit , secondsSecondDigit , ] = digits ; return { minutesFirstDigit , minutesSecondDigit , secondsFirstDigit , secondsSecondDigit , } ; } ; export const getTimeStr = ( time , truncate = true ) : string => { if ( time == null ) { return "" ; } const { minutesFirstDigit , minutesSecondDigit , secondsFirstDigit , secondsSecondDigit , } = getTimeObj ( time ) ; return [ truncate && minutesFirstDigit === "0" ? "" : minutesFirstDigit , minutesSecondDigit , ":" , secondsFirstDigit , secondsSecondDigit , ] . join ( "" ) ; } ; export const parseViscolors = ( text ) : string [ ] => { const entries = text . split ( "\n" ) ; const regex = /^\s*(\d+)\s*,?\s*(\d+)\s*,?\s*(\d+)/ ; const colors = [ ... DEFAULT_SKIN . colors ] ; entries . map ( ( line ) => regex . exec ( line ) ) . filter ( Boolean ) . map ( ( matches ) => ( matches as RegExpExecArray ) . slice ( 1 , 4 ) . join ( "," ) ) . map ( ( rgb , i ) => { colors [ i ] = `rgb(${ rgb })` ; } ) ; return colors ; } ; const SECTION_REGEX = /^\s*\[(.+?)\]\s*$/ ; const PROPERTY_REGEX = /^\s*([^;][^=]*)\s*=\s*(.*)\s*$/ ; export const parseIni = ( text ) : IniData => { let section , match ; return text . split ( /[\r\n]+/g ) . reduce ( ( data , line ) => { if ( ( match = line . match ( PROPERTY_REGEX ) ) && section != null ) { const key = match [ 1 ] . trim ( ) . toLowerCase ( ) ; const value = match [ 2 ] . replace ( /\=.*$/g , "" ) . trim ( ) . replace ( /(^")|("$)|(^')|('$)/g , "" ) ; data [ section ] [ key ] = value ; } else if ( ( match = line . match ( SECTION_REGEX ) ) ) { section = match [ 1 ] . trim ( ) . toLowerCase ( ) ; data [ section ] = { } ; } return data ; } , { } ) ; } ; export const clamp = ( value , min , max ) : number => Math . min ( Math . max ( value , min ) , max ) ; export const sum = ( values ) : number => values . reduce ( ( total , value ) => total + value , 0 ) ; export function base64FromDataArray ( dataArray ) { return window . btoa ( Array . from ( dataArray ) . map ( ( byte ) => String . fromCharCode ( byte ) ) . join ( "" ) ) ; } export const base64FromArrayBuffer = ( arrayBuffer ) : string => { return base64FromDataArray ( new Uint8Array ( arrayBuffer ) ) ; } ; export function downloadURI ( uri , name ) { const link = document . createElement ( "a" ) ; link . download = name ; link . href = uri ; window . document . body . appendChild ( link ) ; link . click ( ) ; window . document . body . removeChild ( link ) ; } export const toPercent = ( min , max , value ) : number => ( value - min ) / ( max - min ) ; export const percentToRange = ( percent , min , max ) => min + Math . round ( percent * ( max - min ) ) ; export const percentToIndex = ( percent , length ) : number => percentToRange ( percent , 0 , length - 1 ) ; const rebound = ( oldMin , oldMax , newMin , newMax ) => ( oldValue ) : number => percentToRange ( toPercent ( oldMin , oldMax , oldValue ) , newMin , newMax ) ; export const normalizeEqBand = rebound ( 1 , 64 , 0 , 100 ) ; export const denormalizeEqBand = rebound ( 0 , 100 , 1 , 64 ) ; export function merge < T extends object , S extends object > ( target , source ) { const s = source as any ; const t = target as any ; for ( const key of Object . keys ( s as any ) ) { if ( s [ key ] instanceof Object ) Object . assign ( s [ key ] , merge ( t [ key ] , s [ key ] ) ) ; } Object . assign ( target || { } , source ) ; return target as any ; } export function segment < V > ( min , max , value , newValues ) { const ratio = toPercent ( min , max , value ) ; return newValues [ percentToIndex ( ratio , newValues . length ) ] ; } export function shuffle < T > ( array ) { const sorted = [ ... array ] ; let m = sorted . length ; while ( m ) { const i = Math . floor ( Math . random ( ) * m -- ) ; const val = sorted [ m ] ; sorted [ m ] = sorted [ i ] ; sorted [ i ] = val ; } return sorted ; } export function sort < T > ( array , iteratee ) { return [ ... array ] . sort ( ( a , b ) => { const aKey = iteratee ( a ) ; const bKey = iteratee ( b ) ; if ( aKey < bKey ) { return - 1 ; } else if ( aKey > bKey ) { return 1 ; } return 0 ; } ) ; } export function moveSelected < V > ( arr , isSelected , offset ) { const newArr = new Array ( arr . length ) ; let next = 0 ; for ( let i = 0 ; i < newArr . length ; i ++ ) { const from = i - offset ; if ( from >= 0 && from < arr . length && isSelected ( from ) ) { newArr [ i ] = arr [ from ] ; } else { while ( next < arr . length && isSelected ( next ) ) { next ++ ; } newArr [ i ] = arr [ next ] ; next ++ ; } } return newArr ; } export function spliceIn < T > ( original , start , newValues ) { const newArr = [ ... original ] ; newArr . splice ( start , 0 , ... newValues ) ; return newArr ; } export function replaceAtIndex < T > ( arr , index , newValue ) { return [ ... arr . slice ( 0 , index ) , newValue , ... arr . slice ( index + 1 ) ] ; } export function debounce ( func , delay ) { let timeout ; let callbackArgs = [ ] ; return function ( context , ... args ) : void { callbackArgs = args ; if ( timeout != null ) { clearTimeout ( timeout ) ; } timeout = window . setTimeout ( ( ) => { func . apply ( context , callbackArgs ) ; } , delay ) ; } ; } export function throttle ( func , delay ) { let timeout = null ; let callbackArgs = [ ] ; return function ( context , ... args ) : void { callbackArgs = args ; if ( ! timeout ) { timeout = window . setTimeout ( ( ) => { func . apply ( context , callbackArgs ) ; timeout = null ; } , delay ) ; } } ; } let counter = 0 ; export function uniqueId ( ) { return counter ++ ; } export function objectForEach < V > ( obj , cb ) { Object . keys ( obj ) . forEach ( ( key ) => cb ( obj [ key ] , key ) ) ; } export function objectMap < V , N > ( obj , cb ) { const modified = { } ; Object . keys ( obj ) . forEach ( ( key ) => ( modified [ key ] = cb ( obj [ key ] , key ) ) ) ; return modified ; } export function objectFilter < V > ( obj , predicate ) { return Object . keys ( obj ) . reduce ( ( newObj , key ) => { if ( predicate ( obj [ key ] , key ) ) { newObj [ key ] = obj [ key ] ; } return newObj ; } , { } ) ; } export const calculateBoundingBox = ( windows ) => { if ( windows . length === 0 ) { return null ; } const windowSizes = windows . map ( ( w ) => ( { left : w . x , top : w . y , bottom : w . y + w . height , right : w . x + w . width , } ) ) ; return windowSizes . reduce ( ( b , w ) => ( { left : Math . min ( b . left , w . left ) , top : Math . min ( b . top , w . top ) , bottom : Math . max ( b . bottom , w . bottom ) , right : Math . max ( b . right , w . right ) , } ) ) ; } ; export function findLastIndex < T > ( arr , cb ) { for ( let i = arr . length - 1 ; i >= 0 ; i -- ) { if ( cb ( arr [ i ] ) ) { return i ; } } return - 1 ; } export function getWindowSize ( ) { return { width : Math . max ( document . body . scrollWidth , document . documentElement ! . scrollWidth , document . body . offsetWidth , document . documentElement ! . offsetWidth , document . body . clientWidth , document . documentElement ! . clientWidth ) , height : Math . max ( document . body . scrollHeight , document . documentElement ! . scrollHeight , document . body . offsetHeight , document . documentElement ! . offsetHeight , document . body . clientHeight , document . documentElement ! . clientHeight ) , } ; } export function getScreenSize ( ) { return { width : window . screen . width , height : window . screen . height , } ; } type PosEvent = MouseEvent | TouchEvent | React . MouseEvent < HTMLElement > | React . TouchEvent < HTMLElement > ; function getPos ( e ) { switch ( e . type ) { case "touchstart" : case "touchmove" : { const touch = ( e as TouchEvent ) . targetTouches [ 0 ] ?? ( e as TouchEvent ) . touches [ 0 ] ; if ( touch == null ) { throw new Error ( "Unexpected touch event with zero touch targets." ) ; } return touch ; } case "mousedown" : case "mousemove" : { return e as MouseEvent ; } default : throw new Error ( `Unexpected event type: ${ e . type }` ) ; } } export function getX ( e ) { return getPos ( e ) . clientX ; } export function getY ( e ) { return getPos ( e ) . clientY ; } export function weakMapMemoize < T extends object , R > ( func ) { const cache = new WeakMap ( ) ; return ( value ) => { if ( ! cache . has ( value ) ) { cache . set ( value , func ( value ) ) ; } return cache . get ( value ) ; } ; }