kurita-0: client/src/Comic.tsx
import flatten from 'lodash/flatten'
import ms from 'ms'
import React, {useEffect, useCallback, useMemo, useState} from 'react'
import styled from 'styled-components'
import KuritaClient from './KuritaClient'
import {useKurita} from './KuritaState'
import Emoji, {EmojiPath} from './Emoji'
import HeartCounter from './HeartCounter'
import Text from './Text'
import useTime from './useTime'
const unknownEmoji = '❓'
function timeUntil(now: Date, time: Date) {
const nowMs = now.getTime()
const timeMs = time.getTime()
if (timeMs - nowMs < 3000) {
return 'time\'s up!'
}
return ms(time.getTime() - now.getTime(), {long: true})
}
const Comic: React.FC<{
altText: string,
client: KuritaClient,
emojiBasePath: string,
}> = ({altText, client, emojiBasePath}) => {
const [state, dispatch] = useKurita(client)
const now = useTime(1000)
const voteCallback = useCallback(
(emoji: string) => { dispatch({type: 'send-vote', payload: emoji}) },
[dispatch],
)
const current = state.bracket && state.bracket.current
const played = state.bracket && state.bracket.played
const history = useMemo(() => (
state.bracket && flatten(played).map(gameState => {
const [emoji1, emoji2] = gameState.game
const firstWins = emoji1.score > emoji2.score
return (
<div key={emoji1.competitor + emoji2.competitor} style={{display: 'flex', margin: '8px 0'}}>
<HistoryEffect underline={firstWins} grayscale={!firstWins}>
<Emoji emoji={emoji1.competitor} size={32} />
</HistoryEffect>
<Text size={16} style={{alignSelf: 'center', margin: '0 8px'}}>vs</Text>
<HistoryEffect underline={!firstWins} grayscale={firstWins}>
<Emoji emoji={emoji2.competitor} size={32} />
</HistoryEffect>
<Text parseEmoji size={16} style={{alignSelf: 'center', marginLeft: '14px'}}>
"{gameState.extra.commentary[0]}"
</Text>
</div>
)
})
), [played])
return (
<EmojiPath.Provider value={emojiBasePath}>
<ComicOutline title={altText}>
<div style={{display: 'flex', flex: 1, flexDirection: 'column', alignItems: 'center', justifyContent: 'center'}}>
<Text parseEmoji size={20} style={{margin: 24, height: '1em'}}>
{current && `🤖 "${current.extra.commentary[0]}"`}
</Text>
<div style={{display: 'flex', justifyContent: 'center', opacity: current ? 1 : .5}}>
<EmojiVoteBox disabled={!current} score={current && current.game[0].score} emoji={current ? current.game[0].competitor : unknownEmoji} onVote={voteCallback} />
<Text size={32} style={{alignSelf: 'center', margin: 20}}>vs</Text>
<EmojiVoteBox disabled={!current} score={current && current.game[1].score} emoji={current ? current.game[1].competitor : unknownEmoji} onVote={voteCallback} />
</div>
<div style={{marginTop: 20}}>
<Text size={20}>Remaining time: </Text>
<Text size={20} style={{display: 'inline-block', width: '5.25em', textAlign: 'right', opacity: .75, whiteSpace: 'nowrap'}}>{current && timeUntil(now, current.extra.end_time)}</Text>
</div>
</div>
<div style={{margin: 16, marginTop: 0}}>
<Text size={16}>Past bouts:</Text>
<ScrollingHistoryBox>
{history}
</ScrollingHistoryBox>
</div>
<a href="/2131/emojidome_bracket_256.png" style={{alignSelf: 'center', marginBottom: 8}}><Text size={16}>Full bracket for today's comic (round 2)</Text></a>
</ComicOutline>
</EmojiPath.Provider>
)
}
const EmojiVoteBox: React.FC<{
disabled?: boolean,
emoji: string,
score?: number,
onVote: (emoji: string) => void,
}> = ({disabled, emoji, onVote, score}) => {
const onVoteCallback = useCallback(
() => { onVote(emoji) },
[onVote, emoji],
)
return (
<div style={{position: 'relative', display: 'flex', flexDirection: 'column'}}>
<HeartCounter count={score} />
<Emoji emoji={emoji} size={128} />
<VoteButton
disabled={disabled}
onClick={onVoteCallback}
aria-label={`Vote for ${emoji}`}
>
Vote
</VoteButton>
</div>
)
}
const ComicOutline = styled.div`
display: inline-flex;
flex-direction: column;
box-sizing: border-box;
border: 2px solid black;
width: 740px;
height: 500px;
user-select: none;
text-align: left;
`
const ScrollingHistoryBox = styled.div`
height: 138px;
overflow-y: auto;
`
interface HistoryEffectProps {
grayscale?: boolean,
underline?: boolean,
}
const HistoryEffect = styled.div<HistoryEffectProps>`
display: flex;
padding-bottom: 3px;
${({grayscale}: HistoryEffectProps) => grayscale ? 'filter: grayscale(100%)' : ''}
${({underline}: HistoryEffectProps) => underline ? 'border-bottom: 2px solid green' : ''}
`
const VoteButton = styled.button`
position: relative;
z-index: 9999;
border: 2px solid black;
border-radius: 4px;
background: #ccc;
font-family: xkcd-Regular-v2;
font-size: 24px;
line-height: 24px;
padding: 4px 8px;
margin-top: 20px;
cursor: pointer;
transition: background-color .25s ease-out;
&:active {
transition: none;
background-color: red;
}
`
export default Comic