import React from 'react' import { kFormatter, encodeHTML, getProgress, getLevel, calculateCircleProgress, getColor } from '../common/utils' import icons from '../common/icons' import Card, { CardOptions } from '../common/Card' import FlexLayout from '../components/FlexLayout' interface ProfileCardOptions extends CardOptions { hide?: Array show_icons?: boolean hide_rank?: boolean line_height?: number icon_color?: string text_color?: string } export default class ProfileCard extends Card { private stats: Record private defaults = { line_height: 25, hide: [] } public constructor( private username: string, private xp: number, private recentXp: number, private options: ProfileCardOptions ) { super( options ) // This Element this.stats = { xp: { icon: icons.star, label: 'XP', value: xp }, recent_xp: { icon: icons.commits, label: 'Recent xp', value: this.recentXp } } // Card Settings this.width = 495 this.height = Math.max( 45 + (Object.keys(this.stats).length + 1) * (this.options.line_height || this.defaults.line_height), options.hide_rank ? 0 : 120 ) this.title = `${encodeHTML(this.username)}${ ['x', 's'].includes(this.username.slice(-1)) ? '\'' : '\'s' } Code::Stats Profile` const textColor = getColor('text_color', options.text_color, options.theme) const iconColor = getColor('icon_color', options.icon_color, options.theme) if (!this.options.hide_rank) { this.css += RankCircle.getCSS(textColor, iconColor, getProgress(xp)) } if ((this.options.hide || []) < Object.keys(this.stats)) { this.css += TextNode.getCSS(textColor, this.options.show_icons ? iconColor : undefined) } } public render() { return super.render( <> {!this.options.hide_rank && ( )} !(this.options.hide || []).includes(item)) .map((el, index) => { const item = this.stats[el] return ( ) }) } gap={this.options.line_height || this.defaults.line_height} direction="column" /> ) } } class RankCircle extends React.Component<{ xp: number }> { public static getCSS = (textColor: string, titleColor: string, progress: number) => ` .rank-text { font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor}; animation: scaleInAnimation 0.3s ease-in-out forwards; } .rank-circle-rim { stroke: ${titleColor}; fill: none; stroke-width: 6; opacity: 0.2; } .rank-circle { stroke: ${titleColor}; stroke-dasharray: 250; fill: none; stroke-width: 6; stroke-linecap: round; opacity: 0.8; transform-origin: -10px 8px; transform: rotate(-90deg); animation: rankAnimation 1s forwards ease-in-out; } @keyframes rankAnimation { from { stroke-dashoffset: ${calculateCircleProgress(0)}; } to { stroke-dashoffset: ${calculateCircleProgress(progress)}; } } ` public render = () => ( lv {getLevel(this.props.xp)} ) } class TextNode extends React.Component<{ icon?: JSX.Element label: string value: number index: number }> { public static getCSS = (textColor: string, iconColor?: string) => ` ${ iconColor ? `.icon { fill: ${iconColor}; // display: block; }` : '' } .stagger { opacity: 0; animation: fadeInAnimation 0.3s ease-in-out forwards; } .stat { font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor}; } .bold { font-weight: 700 } ` public render() { const delay = (this.props.index + 3 * 150) // Icon prefixing line const icon = this.props.icon ? ( {this.props.icon} ) : undefined return ( {icon} {this.props.label}: {kFormatter(this.props.value)} ) } }