import React from 'react' import axios, { AxiosPromise } from 'axios' import wrap from 'word-wrap' import themes from '../../themes' import { CodeStatsResponse } from '../fetcher/interface'; export const renderError = (message: string, secondaryMessage = "") => { return ( Something went wrong! file an issue at https://dze.io/3nL29 {encodeHTML(message)} {secondaryMessage} ) }; // https://stackoverflow.com/a/48073476/10629172 export function encodeHTML(str: string) { return str .replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => { return "&#" + i.charCodeAt(0) + ";"; }) .replace(/\u0008/gim, ""); } export function kFormatter(num: number) { return Math.abs(num) > 999 ? Math.sign(num) * parseInt((Math.abs(num) / 1000).toFixed(1)) + "k" : Math.sign(num) * Math.abs(num); } export function isValidHexColor(hexColor: string) { return new RegExp( /^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/ ).test(hexColor); } export function parseBoolean(value: string | undefined) { if (value === "true") { return true; } else { return false; } } export function parseArray(str: string | undefined) { if (!str) return []; return str.split(","); } export function clampValue(number: number, min: number, max: number) { return Math.max(min, Math.min(number, max)); } export function isValidGradient(colors: Array) { return isValidHexColor(colors[1]) && isValidHexColor(colors[2]); } export function fallbackColor(color: string, fallbackColor: Array| string) { let colors = color.split(","); let gradient = null; if (colors.length > 1 && isValidGradient(colors)) { gradient = colors; } return ( (gradient ? gradient : isValidHexColor(color) && `#${color}`) || fallbackColor ); } export function request(data: {login: string}): AxiosPromise { return axios({ url: "https://codestats.net/api/users/" + data.login, method: "get", }); } /** * * @param {String[]} items * @param {Number} gap * @param {string} direction * * @description * Auto layout utility, allows us to layout things * vertically or horizontally with proper gaping */ export function FlexLayout({ items, gap, direction }: {items: Array, gap: number, direction?: string}) { // filter() for filtering out empty strings return items.filter(Boolean).map((item, i) => { let transform = `translate(${gap * i}, 0)`; if (direction === "column") { transform = `translate(0, ${gap * i})`; } return ( {item} ); }); } // returns theme based colors with proper overrides and defaults export function getCardColors({ title_color, text_color, icon_color, bg_color, theme, fallbackTheme = "default", }: {title_color?: string, text_color?: string, icon_color?: string, bg_color?: string, theme?: keyof typeof themes, fallbackTheme?: keyof typeof themes}) { const defaultTheme = themes[fallbackTheme]; const selectedTheme = themes[theme as 'default'] || defaultTheme; // get the color provided by the user else the theme color // finally if both colors are invalid fallback to default theme const titleColor = fallbackColor( title_color || selectedTheme.title_color, "#" + defaultTheme.title_color ); const iconColor = fallbackColor( icon_color || selectedTheme.icon_color, "#" + defaultTheme.icon_color ); const textColor = fallbackColor( text_color || selectedTheme.text_color, "#" + defaultTheme.text_color ); const bgColor = fallbackColor( bg_color || selectedTheme.bg_color, "#" + defaultTheme.bg_color ); return { titleColor, iconColor, textColor, bgColor }; } export function wrapTextMultiline(text: string, width = 60, maxLines = 3) { const wrapped = wrap(encodeHTML(text), { width }) .split("\n") // Split wrapped lines to get an array of lines .map((line) => line.trim()); // Remove leading and trailing whitespace of each line const lines = wrapped.slice(0, maxLines); // Only consider maxLines lines // Add "..." to the last line if the text exceeds maxLines if (wrapped.length > maxLines) { lines[maxLines - 1] += "..."; } // Remove empty lines if text fits in less than maxLines lines const multiLineText = lines.filter(Boolean); return multiLineText; } export const noop = () => {}; // return console instance based on the environment export const logger = process.env.NODE_ENV !== "test" ? console : { log: noop, error: noop }; export const CONSTANTS = { THIRTY_MINUTES: 1800, TWO_HOURS: 7200, FOUR_HOURS: 14400, ONE_DAY: 86400, LEVEL_FACTOR: 0.025, }; export const SECONDARY_ERROR_MESSAGES = { MAX_RETRY: "Make sur your profile is not private" }; export class CustomError extends Error { public secondaryMessage: string constructor(message: string, public type: keyof typeof SECONDARY_ERROR_MESSAGES) { super(message); this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || "adsad"; } static MAX_RETRY = "MAX_RETRY"; static USER_NOT_FOUND = "USER_NOT_FOUND"; } export function getLevel(xp: number): number { return Math.trunc(Math.floor(CONSTANTS.LEVEL_FACTOR * Math.sqrt(xp))) } /** * Return the progress (0-99)% til next level * @param xp Xp number */ export function getProgress(xp: number): number { const currentLvl = getLevel(xp) return Math.trunc((CONSTANTS.LEVEL_FACTOR * Math.sqrt(xp) - currentLvl) * 100) }