/**
 * Copyright 2019 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ import { Result , Topic , Topics } from 'shared/state' ; import { Champions } from '../shared/state' ; /**
 * Returns a random 12 char base64 string
 */ export function generateRandomId ( ) { const randomValues = crypto . getRandomValues ( new Uint8Array ( 9 ) ) ; const binaryString = String . fromCharCode ( ... randomValues ) ; return btoa ( binaryString ) ; } param pathComponent export function escapePatchPathComponent ( pathComponent ) { if ( pathComponent . indexOf ( '/' ) === - 1 && pathComponent . indexOf ( '~' ) === - 1 ) { return pathComponent ; } return pathComponent . replace ( /~/g , '~0' ) . replace ( /\//g , '~1' ) ; } param pathComponent export function unescapePathComponent ( pathComponent ) { if ( ! pathComponent . includes ( '~' ) ) return pathComponent ; return pathComponent . replace ( /~1/g , '/' ) . replace ( /~0/g , '~' ) ; } param obj param path export function getValueByPath ( obj , path ) { if ( path === '/' ) return obj ; const keys = path . split ( '/' ) ; let currentObj = obj ; for ( const key of keys . slice ( 1 ) ) { let calculatedKey = key ; if ( Array . isArray ( obj ) ) { if ( calculatedKey === '-' ) { calculatedKey = obj . length ; } else { calculatedKey = Number ( calculatedKey ) ; } } else { calculatedKey = unescapePathComponent ( calculatedKey ) ; } currentObj = currentObj [ calculatedKey ] ; } return currentObj ; } const resultNameCache = new WeakMap < Result , Topic | undefined > ( ) ; export function getResultTopic ( topics , result ) { if ( typeof result === 'string' ) return topics [ result ] ; if ( ! resultNameCache . has ( result ) ) { if ( ! result . items [ result . winningIndex ] ) { resultNameCache . set ( result , undefined ) ; } else { resultNameCache . set ( result , getResultTopic ( topics , result . items [ result . winningIndex ] ) ) ; } } return resultNameCache . get ( result ) ! ; } export function getResultLabel ( topics , result ) { const topic = getResultTopic ( topics , result ) ; return topic ? topic . label : '' ; } export function getResultChampionName ( topics , result , champions ) { const topic = getResultTopic ( topics , result ) ; return champions [ topic ?. championId || '' ] ?. name || '' ; } export function keepServerAlive ( ) { setInterval ( ( ) => { fetch ( '/ping' ) ; } , 30 * 1000 ) ; } interface AnimateOptions { duration ? : number ; delay ? : number ; easing ? : string ; fill ? : 'backwards' | 'none' ; } interface AnimateKeyframe { [ prop ] : string ; } interface AnimateKeyframes { [ pos ] : AnimateKeyframe ; } export function animate ( el , keyframes , { duration = 1000 , delay = 0 , easing = 'linear' , fill = 'none' , } = { } ) { const animName = 'a' + Math . floor ( Math . random ( ) * Number . MAX_SAFE_INTEGER ) . toString ( 16 ) ; let computedKeyframes ; if ( ! Array . isArray ( keyframes ) ) { computedKeyframes = keyframes ; } else { computedKeyframes = Object . fromEntries ( keyframes . map ( ( keyframe , i ) => [ ( i / keyframes . length ) * 100 + '%' , keyframe , ] ) ) ; } const style = document . createElement ( 'style' ) ; style . textContent = `@keyframes ${ animName } {` + Object . entries ( computedKeyframes ) . map ( ( [ pos , keyframe ] ) => pos + '{' + Object . entries ( keyframe ) . map ( ( [ prop , val ] ) => `${ prop }: ${ val };` ) . join ( '' ) + '}' ) . join ( '' ) + '}' ; document . head . append ( style ) ; el . style . animation = `${ duration }ms ${ easing } ${ delay }ms ${ fill } ${ animName }` ; return new Promise ( resolve => { function completeListener ( event ) { if ( event . target !== el ) return ; resolve ( ) ; el . removeEventListener ( 'animationend' , completeListener ) ; el . removeEventListener ( 'animationcancel' , completeListener ) ; style . remove ( ) ; } el . addEventListener ( 'animationend' , completeListener ) ; el . addEventListener ( 'animationcancel' , completeListener ) ; } ) ; } function getRGB ( color ) { const mask = 0x0000ff ; return [ color >> 16 , ( color >> 8 ) & mask , color & mask ] ; } export function getMidColor ( color1 , color2 ) { const [ c1r , c1g , c1b ] = getRGB ( color1 ) ; const [ c2r , c2g , c2b ] = getRGB ( color2 ) ; return ( ( Math . round ( ( c1r + c2r ) / 2 ) << 16 ) + ( Math . round ( ( c1g + c2g ) / 2 ) << 8 ) + Math . round ( ( c1b + c2b ) / 2 ) ) ; } export function getHexColor ( color ) { return '#' + color . toString ( 16 ) . padStart ( 6 , '0' ) ; } interface FetchJSONOptions { body ? : any ; method ? : string ; } export async function fetchJSON ( path , { body , method = 'GET' } = { } ) { const response = await fetch ( path , { body : body ? JSON . stringify ( body ) : undefined , method , headers : { 'Content-Type' : 'application/json' } , } ) ; const data = await response . json ( ) ; if ( data . err ) throw Error ( data . err ) ; return data ; }