import { formatDateNumber, getColorOfLanguage } from "../common/utils" import Card, { CardOptions } from '../common/Card' import React from 'react' import FlexLayout from "../components/FlexLayout" interface HistoryOptions extends CardOptions { width?: number height?: number layout?: 'horizontal' hide_legend?: boolean // Put language in Other hide?: Array language_count?: number } export default class HistoryCard extends Card { private topLanguages: Array public constructor( private username: string, private days: Array<{day: string, total: number, data: Array<{xp: number, language: string}>}>, private options: HistoryOptions ) { super(options) this.height = 45 + (this.days.length + 1) * 40 this.width = options.width ?? 500 if (options.layout === 'horizontal') { this.width = 45 + (this.days.length + 1) * 40 + 100 this.height = options.height ?? 300 } const languagesToHide = options.hide || [] let languageCount: Array<{language: string, xp: number}> = [] for (const day of this.days) { for (const data of day.data) { let index = languageCount.findIndex((item) => item.language === data.language) if (index === -1) { index = languageCount.push({ language: data.language, xp: 0 }) - 1 } languageCount[index].xp += data.xp } } this.topLanguages = languageCount .sort((a, b) => b.xp - a.xp) .map((item) => item.language) .filter((lang) => !languagesToHide.includes(lang)) languagesToHide.push(...this.topLanguages.splice((options.language_count || 8 ))) if (languagesToHide.length > 0) { this.topLanguages.push('Other') } for (const day of this.days) { const toRemove: Array = [] for (let i = 0; i < day.data.length; i++) { const element = day.data[i]; if (languagesToHide.includes(element.language)) { const otherIndex = day.data.findIndex((el) => el.language === 'Other') if (otherIndex === -1) { day.data.push({ language: 'Other', xp: element.xp }) } else { day.data[otherIndex].xp += element.xp } toRemove.push(i) } } for (const index of toRemove.reverse()) { day.data.splice(index, 1) } } this.title = 'Code History Language breakdown' this.css = ProgressNode.getCSS('#000') } public render() { const totalTotal = this.days.reduce((prvs, crnt) => { if (prvs < crnt.total) { return crnt.total } return prvs }, 0) const legendWidth = Math.max(this.width * 20 / 100 + 60, 150) const historyWidth = this.width - legendWidth return super.render( { if (this.options.layout === 'horizontal') { return ( ( )) } gap={40} // direction="column" /> ) } else { return ( ( )) } gap={40} direction="column" /> ) } })(), ( ( <> {el} )) } gap={20} direction="column" /> )]} gap={this.options.layout === 'horizontal' ? this.width - 180 : historyWidth - 20} /> ) } } class ProgressNode extends React.Component<{ day: string total: number totalTotal: number data: Array<{xp: number, language: string}> width: number }> { public static getCSS = (textColor: string) => ` .lang-name { font: 400 16px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor}; } .xp-txt { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor}; } .xp-txt-invert { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: white; } .subtitle { font: 400 14px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor}; } ` public render() { let offset = 0 const maskId = `mask-${this.props.day}` return ( <> {new Date(this.props.day).toDateString().substr(4, 6)} {this.props.data.map((el, index) => { const color = getColorOfLanguage(el.language) offset += el.xp return ( ) })} {(() => { let size = this.calcSize(offset) + 6 const txtSize = (this.props.total.toString().length + 3) * 8 let classes = 'xp-txt' if (size + txtSize >= this.calcSize(this.props.totalTotal)) { size -= txtSize classes += ' xp-txt-invert' } return ( {this.props.total} XP ) })()} ) } protected calcSize(number: number) { return number * this.props.width / this.props.totalTotal } } class VerticalProgressNode extends React.Component<{ day: string total: number totalTotal: number data: Array<{xp: number, language: string}> height: number }> { public render() { let offset = this.props.totalTotal const maskId = `mask-${this.props.day}` return ( <> {this.props.data.map((el, index) => { const color = getColorOfLanguage(el.language) offset -= el.xp return ( ) })} {this.getXPTxt()} {new Date(this.props.day).toDateString().substr(4, 3)} {formatDateNumber(new Date(this.props.day).getDate())} ) } protected calcSize(number: number) { return number * this.props.height / this.props.totalTotal } private getXPTxt() { const txtLength = (this.props.total.toString().length + 3) * 13 let position = 25 + this.calcSize(this.props.totalTotal - this.props.total) - txtLength let classes = 'xp-txt' if (position <= 28) { position += txtLength + 16 classes += ' xp-txt-invert' } return ( {this.props.total} XP ) } }