import { getBestRoute } from "@snk/solver/getBestRoute" ; import { Color , copyGrid , Grid } from "@snk/types/grid" ; import { step } from "@snk/solver/step" ; import { isStableAndBound , stepSpring } from "./springUtils" ; import { Res } from "@snk/github-user-contribution" ; import { Snake } from "@snk/types/snake" ; import { drawLerpWorld , getCanvasWorldSize , Options , } from "@snk/draw/drawWorld" ; import { userContributionToGrid } from "../action/userContributionToGrid" ; import { snake4 as snake } from "@snk/types/__fixtures__/snake" ; import { getPathToPose } from "@snk/solver/getPathToPose" ; import { createSvg } from "../svg-creator" ; const createForm = ( { onSubmit , onChangeUserName , } ) => { const form = document . createElement ( "form" ) ; form . style . position = "relative" ; form . style . display = "flex" ; form . style . flexDirection = "row" ; const input = document . createElement ( "input" ) ; input . addEventListener ( "input" , ( ) => onChangeUserName ( input . value ) ) ; input . style . padding = "16px" ; input . placeholder = "github user" ; const submit = document . createElement ( "button" ) ; submit . style . padding = "16px" ; submit . type = "submit" ; submit . innerText = "ok" ; const label = document . createElement ( "label" ) ; label . style . position = "absolute" ; label . style . textAlign = "center" ; label . style . top = "60px" ; label . style . left = "0" ; label . style . right = "0" ; form . appendChild ( input ) ; form . appendChild ( submit ) ; document . body . appendChild ( form ) ; form . addEventListener ( "submit" , ( event ) => { event . preventDefault ( ) ; onSubmit ( input . value ) . catch ( ( err ) => { label . innerText = "error :(" ; throw err ; } ) ; input . disabled = true ; submit . disabled = true ; form . appendChild ( label ) ; label . innerText = "loading ..." ; } ) ; const dispose = ( ) => { document . body . removeChild ( form ) ; } ; return { dispose } ; } ; const clamp = ( x , a , b ) => Math . max ( a , Math . min ( b , x ) ) ; const createGithubProfile = ( ) => { const container = document . createElement ( "div" ) ; container . style . padding = "20px" ; container . style . opacity = "0" ; container . style . display = "flex" ; container . style . flexDirection = "column" ; container . style . alignItems = "flex-start" ; const image = document . createElement ( "img" ) ; image . style . width = "100px" ; image . style . height = "100px" ; image . style . borderRadius = "50px" ; const name = document . createElement ( "a" ) ; name . style . padding = "4px 0 0 0" ; document . body . appendChild ( container ) ; container . appendChild ( image ) ; container . appendChild ( name ) ; image . addEventListener ( "load" , ( ) => { container . style . opacity = "1" ; } ) ; const onChangeUser = ( userName ) => { container . style . opacity = "0" ; name . innerText = userName ; name . href = `https://github.com/${ userName }` ; image . src = `https://github.com/${ userName }.png` ; } ; const dispose = ( ) => { document . body . removeChild ( container ) ; } ; return { dispose , onChangeUser } ; } ; const createViewer = ( { grid0 , chain , drawOptions , } ) => { const canvas = document . createElement ( "canvas" ) ; const { width , height } = getCanvasWorldSize ( grid0 , drawOptions ) ; canvas . width = width ; canvas . height = height ; const w = Math . min ( width , window . innerWidth ) ; const h = ( height / width ) * w ; canvas . style . width = w + "px" ; canvas . style . height = h + "px" ; canvas . style . pointerEvents = "none" ; document . body . appendChild ( canvas ) ; let animationFrame ; const spring = { x : 0 , v : 0 , target : 0 } ; const springParams = { tension : 120 , friction : 20 , maxVelocity : 50 } ; const ctx = canvas . getContext ( "2d" ) ! ; const loop = ( ) => { cancelAnimationFrame ( animationFrame ) ; stepSpring ( spring , springParams , spring . target ) ; const stable = isStableAndBound ( spring , spring . target ) ; const grid = copyGrid ( grid0 ) ; const stack = [ ] ; for ( let i = 0 ; i < Math . min ( chain . length , spring . x ) ; i ++ ) step ( grid , stack , chain [ i ] ) ; const snake0 = chain [ clamp ( Math . floor ( spring . x ) , 0 , chain . length - 1 ) ] ; const snake1 = chain [ clamp ( Math . ceil ( spring . x ) , 0 , chain . length - 1 ) ] ; const k = spring . x % 1 ; ctx . clearRect ( 0 , 0 , 9999 , 9999 ) ; drawLerpWorld ( ctx , grid , snake0 , snake1 , stack , k , drawOptions ) ; if ( ! stable ) animationFrame = requestAnimationFrame ( loop ) ; } ; loop ( ) ; const input = document . createElement ( "input" ) as any ; input . type = "range" ; input . value = 0 ; input . step = 1 ; input . min = 0 ; input . max = chain . length ; input . style . width = "calc( 100% - 20px )" ; input . addEventListener ( "input" , ( ) => { spring . target = + input . value ; cancelAnimationFrame ( animationFrame ) ; animationFrame = requestAnimationFrame ( loop ) ; } ) ; const onClickBackground = ( e ) => { if ( e . target === document . body || e . target === document . body . parentElement ) input . focus ( ) ; } ; window . addEventListener ( "click" , onClickBackground ) ; document . body . append ( input ) ; const svgLink = document . createElement ( "a" ) ; const svgString = createSvg ( grid0 , chain , drawOptions , { frameDuration : 100 } ) ; const svgImageUri = `data:image/*;charset=utf-8;base64,${ btoa ( svgString ) }` ; svgLink . href = svgImageUri ; svgLink . innerText = "github-user-contribution.svg" ; svgLink . download = "github-user-contribution.svg" ; svgLink . addEventListener ( "click" , ( e ) => { const w = window . open ( "" ) ! ; w . document . write ( `<a href="${ svgImageUri }" download="github-user-contribution.svg">` + svgString + "<a/>" ) ; e . preventDefault ( ) ; } ) ; svgLink . style . padding = "20px" ; svgLink . style . paddingTop = "60px" ; svgLink . style . alignSelf = "flex-start" ; document . body . append ( svgLink ) ; const dispose = ( ) => { window . removeEventListener ( "click" , onClickBackground ) ; cancelAnimationFrame ( animationFrame ) ; document . body . removeChild ( canvas ) ; document . body . removeChild ( input ) ; document . body . removeChild ( svgLink ) ; } ; return { dispose } ; } ; const onSubmit = async ( userName ) => { const res = await fetch ( process . env . GITHUB_USER_CONTRIBUTION_API_ENDPOINT + userName ) ; const { cells , colorScheme } = ( await res . json ( ) ) as Res ; const drawOptions = { sizeBorderRadius : 2 , sizeCell : 16 , sizeDot : 12 , colorBorder : "#1b1f230a" , colorDots : colorScheme as any , colorEmpty : colorScheme [ 0 ] , colorSnake : "purple" , cells } ; const grid = userContributionToGrid ( cells , colorScheme ) ; const chain = getBestRoute ( grid , snake ) ! ; chain . push ( ... getPathToPose ( chain . slice ( - 1 ) [ 0 ] , snake ) ! ) ; dispose ( ) ; createViewer ( { grid0 : grid , chain , drawOptions } ) ; } ; const profile = createGithubProfile ( ) ; const { dispose } = createForm ( { onSubmit , onChangeUserName : profile . onChangeUser } ) ; document . body . style . margin = "0" ; document . body . style . display = "flex" ; document . body . style . flexDirection = "column" ; document . body . style . alignItems = "center" ; document . body . style . justifyContent = "center" ; document . body . style . height = "100%" ; document . body . style . width = "100%" ; document . body . style . position = "absolute" ;