Merge pull request #254 from cy/customize-github-stats

[add]: feature to customize the GitHub stats and top languages card
This commit is contained in:
Rahul Jain
2020-10-11 17:11:13 +05:30
committed by GitHub
5 changed files with 238 additions and 69 deletions
+182 -40
View File
@@ -5,35 +5,54 @@ import links from "../constants/page-links"
import { isMediumUsernameValid, isGitHubUsernameValid } from "../utils/validation"
import { ToolsIcon, XCircleIcon } from "@primer/octicons-react";
const AddonsItem = ({inputId, inputChecked, onInputChange, Icon, onIconClick, ...props}) => {
const AddonsItem = ({ inputId, inputChecked, onInputChange, Options, onIconClick, ...props }) => {
const [open, setOpen] = useState(false);
const Icon = open ? XCircleIcon : ToolsIcon;
return (
<div className="py-2 flex justify-start items-center text-sm sm:text-lg">
<label htmlFor={inputId} className="cursor-pointer flex items-center">
<input
type="checkbox"
id={inputId}
checked={inputChecked}
onChange={onInputChange}
/>
<span className="pl-4">{props.children}</span>
</label>
{
Icon?
<button onClick={onIconClick} className="flex ml-3 focus:bg-gray-400" style={{outline: "none"}}>
<>
<div className="py-2 flex justify-start items-center text-sm sm:text-lg">
<label htmlFor={inputId} className="cursor-pointer flex items-center">
<input
type="checkbox"
id={inputId}
checked={inputChecked}
onChange={onInputChange}
/>
<span className="pl-4">{props.children}</span>
</label>
{Options && (
<button
onClick={() => setOpen(!open)}
className="flex ml-3 focus:bg-gray-400"
style={{ outline: "none" }}
>
<Icon className="transform scale-100 md:scale-125" />
</button>
:''
}
)}
</div>
{Options && open && Options}
</>
);
};
const CustomizeOptions = ({ title, CustomizationOptions }) => (
<div
className={`border-2 border-solid border-gray-900 bg-gray-100 p-2 ml-8`}
style={{ maxWidth: "21rem" }}
>
<header className="text-base sm:text-lg">{title}</header>
<hr className="border-gray-500" />
<div className="text-sm sm:text-lg flex flex-col mt-2 ml-0 md:ml-4">
{CustomizationOptions}
</div>
)
}
</div>
);
const CustomizeBadge = ({githubName, badgeOptions, onBadgeUpdate}) => {
return (
<div className={`border-2 border-solid border-gray-900 bg-gray-100 p-2 ml-8`} style={{maxWidth: '21rem'}}>
<header className="text-base sm:text-lg">Customize Badge</header>
<hr className="border-gray-500"/>
<div className="text-sm sm:text-lg flex flex-col mt-2 ml-0 md:ml-4">
<>
<label htmlFor="badge-style">Style:&nbsp;
<select
id="badge-style"
@@ -83,13 +102,90 @@ const CustomizeBadge = ({githubName, badgeOptions, onBadgeUpdate}) => {
}
</span>
</div>
</div>
</>
)
}
const CustomizeGithubStatsBase = ({ prefix, options, onUpdate }) =>
<>
<label htmlFor={`${prefix}-theme`}>Theme:&nbsp;
<select
id={`${prefix}-theme`}
onChange={({target: { value }}) => onUpdate("theme", value)}
defaultValue={options.theme}
>
<option value="none">none</option>
<option value="dark">Dark</option>
<option value="radical">Radical</option>
<option value="merko">Merko</option>
<option value="gruvbox">Gruvbox</option>
<option value="tokyonight">Tokyonight</option>
<option value="onedark">Onedark</option>
<option value="cobalt">Cobalt</option>
<option value="synthwave">Synthwave</option>
<option value="highcontrast">Highcontrast</option>
<option value="dracula">Dracula</option>
</select>
</label>
<label htmlFor={`${prefix}-title-color`}>Title Color:&nbsp;
<input
type="color"
id={`${prefix}-title-color`}
defaultValue={`#${options.titleColor}`}
className="w-6"
onChange={(e) => onUpdate('titleColor', e.target.value.replace('#', ''))}
/>
</label>
<label htmlFor={`${prefix}-text-color`}>Text Color:&nbsp;
<input
type="color"
id={`${prefix}-text-color`}
defaultValue={`#${options.textColor}`}
className="w-6"
onChange={(e) => onUpdate('textColor', e.target.value.replace('#', ''))}
/>
</label>
<label htmlFor={`${prefix}-bg-color`}>Background Color:&nbsp;
<input
type="color"
id={`${prefix}-bg-color`}
defaultValue={`#${options.bgColor}`}
className="w-6"
onChange={(e) => onUpdate('bgColor', e.target.value.replace('#', ''))}
/>
</label>
<label htmlFor={`${prefix}-hide-border`}>Hide border:&nbsp;
<input
id={`${prefix}-hide-border`}
type="checkbox"
checked={options.hideBorder}
onChange={(e) => onUpdate('hideBorder', e.target.checked)}
/>
</label>
<label htmlFor={`${prefix}-cache-seconds`}>Cache Seconds:&nbsp;
<input
id={`${prefix}-cache-seconds`}
type="number"
min={1800}
max={86400}
placeholder={1800}
defaultValue={options.cacheSeconds}
onChange={(e) => onUpdate('cacheSeconds', e.target.value)}
/>
</label>
<label htmlFor={`${prefix}-locale`}>Locale:&nbsp;
<input
id={`${prefix}-locale`}
type="text"
placeholder="en"
defaultValue={options.locale}
onChange={(e) => onUpdate('locale', e.target.value)}
size="2"
/>
</label>
</>
const Addons = props => {
const [customizeBadgeOpen, setCustomizeOpen] = useState(false);
const [debounce, setDebounce] = useState(undefined);
const [badgeOptions, setBadgeOptions] = useState({
badgeStyle: props.data.badgeStyle,
@@ -105,6 +201,26 @@ const Addons = props => {
})
}, [props.data.badgeStyle, props.data.badgeColor, props.data.badgeLabel])
const [githubStatsOptions, setGithubStatsOptions] = useState({
...props.data.githubStatsOptions,
});
useEffect(() => {
setGithubStatsOptions({
...props.data.githubStatsOptions
})
}, [props.data.githubStatsOptions])
const [topLanguagesOptions, setTopLanguagesOptions] = useState({
...props.data.topLanguagesOptions,
});
useEffect(() => {
setTopLanguagesOptions({
...props.data.topLanguagesOptions
})
}, [props.data.topLanguagesOptions])
const blogPostPorkflow = () => {
let payload = {
dev: {
@@ -133,10 +249,6 @@ const Addons = props => {
document.body.removeChild(tempElement)
}
const onCustomizeClick = () => {
setCustomizeOpen(!customizeBadgeOpen);
}
const onBadgeUpdate = (option, value) => {
const callback = () => {
let newVal = (option==='badgeLabel' && value==='')?'Profile views':value;
@@ -146,6 +258,19 @@ const Addons = props => {
clearTimeout(debounce);
setDebounce(setTimeout(callback, 300));
}
const onStatsUpdate = (option, value) => {
const newStatsOptions = {...githubStatsOptions, [option]: value}
setGithubStatsOptions(newStatsOptions)
props.handleDataChange("githubStatsOptions", {target: {value: newStatsOptions}})
}
const onTopLangUpdate = (option, value) => {
const newLangOptions = {...topLanguagesOptions, [option]: value}
setTopLanguagesOptions(newLangOptions)
props.handleDataChange("topLanguagesOptions", {target: {value: newLangOptions}})
}
return (
<div className="flex justify-center items-start flex-col w-full px-2 sm:px-6 mb-10">
<div className="text-xl sm:text-2xl font-bold font-title mt-2 mb-2">
@@ -155,20 +280,21 @@ const Addons = props => {
inputId="visitors-count"
inputChecked={props.data.visitorsBadge}
onInputChange={() => props.handleCheckChange("visitorsBadge")}
Icon={ customizeBadgeOpen ? XCircleIcon : ToolsIcon }
onIconClick={onCustomizeClick}
Options={
<CustomizeOptions
title="Customize Badge"
CustomizationOptions={
<CustomizeBadge
githubName={props.social.github}
badgeOptions={badgeOptions}
onBadgeUpdate={onBadgeUpdate}
/>
}
/>
}
>
display visitors count badge
</AddonsItem>
{
customizeBadgeOpen?
<CustomizeBadge
githubName={props.social.github}
badgeOptions={badgeOptions}
onBadgeUpdate={onBadgeUpdate}
/>
: ''
}
<AddonsItem
inputId="github-profile-trophy"
inputChecked={props.data.githubProfileTrophy}
@@ -180,6 +306,14 @@ const Addons = props => {
inputId="github-stats"
inputChecked={props.data.githubStats}
onInputChange={() => props.handleCheckChange("githubStats")}
Options={
<CustomizeOptions
title="Customize Github Stats Card"
CustomizationOptions={
<CustomizeGithubStatsBase prefix="stats" options={githubStatsOptions} onUpdate={onStatsUpdate}/>
}
/>
}
>
display github profile stats card
</AddonsItem>
@@ -187,6 +321,14 @@ const Addons = props => {
inputId="top-languages"
inputChecked={props.data.topLanguages}
onInputChange={() => props.handleCheckChange("topLanguages")}
Options={
<CustomizeOptions
title="Customize Top Skills Card"
CustomizationOptions={
<CustomizeGithubStatsBase prefix="top-lang" options={topLanguagesOptions} onUpdate={onTopLangUpdate}/>
}
/>
}
>
display top skills
</AddonsItem>
+8 -14
View File
@@ -1,6 +1,7 @@
import React from "react"
import { isMediumUsernameValid } from "../utils/validation"
import { icons, skills, skillWebsites } from "../constants/skills"
import { githubStatsLinkGenerator, topLanguagesLinkGenerator } from "../utils/link-generators"
const Markdown = props => {
const Title = props => {
@@ -123,15 +124,11 @@ const Markdown = props => {
}
return ""
}
const GitHubStats = props => {
let link =
"https://github-readme-stats.vercel.app/api?username=" +
props.github +
"&show_icons=true"
if (props.show) {
const GitHubStats = ({ show, github, options }) => {
if (show) {
return (
<>
{`<p>&nbsp;<img align="center" src="${link}" alt="${props.github}" /></p>`}
{`<p>&nbsp;<img align="center" src="${githubStatsLinkGenerator({github: github, options})}" alt="${github}" /></p>`}
<br />
<br />
</>
@@ -204,15 +201,11 @@ const Markdown = props => {
return ""
}
const DisplayTopLanguages = props => {
let link =
"https://github-readme-stats.vercel.app/api/top-langs/?username=" +
props.github +
"&layout=compact"
if (props.show) {
if (!props.showStats) {
return (
<>
{`<p><img align="center" src="${link}" alt="${props.github}" /></p>`}
{`<p><img align="center" src="${topLanguagesLinkGenerator({github: props.github, options: props.options})}" alt="${props.github}" /></p>`}
<br />
<br />
</>
@@ -220,7 +213,7 @@ const Markdown = props => {
}
return (
<>
{`<p><img align="left" src="${link}" alt="${props.github}" /></p>`}
{`<p><img align="left" src="${topLanguagesLinkGenerator({github: props.github, options: props.options })}" alt="${props.github}" /></p>`}
<br />
<br />
</>
@@ -491,16 +484,17 @@ const Markdown = props => {
show={props.data.topLanguages}
showStats={props.data.githubStats}
github={props.social.github}
options={props.data.topLanguagesOptions}
/>
</>
<>
<GitHubStats
show={props.data.githubStats}
github={props.social.github}
options={props.data.githubStatsOptions}
/>
</>
</div>
)
}
export default Markdown
+10 -14
View File
@@ -1,5 +1,6 @@
import React from "react"
import { icons, skills, skillWebsites } from "../constants/skills"
import { githubStatsLinkGenerator, topLanguagesLinkGenerator } from "../utils/link-generators"
const MarkdownPreview = props => {
const TitlePreview = props => {
@@ -267,29 +268,22 @@ const MarkdownPreview = props => {
}
return null
}
const GitHubStatsPreview = props => {
let link =
"https://github-readme-stats.vercel.app/api?username=" +
props.github +
"&show_icons=true"
if (props.show) {
const GitHubStatsPreview = ({github, options, show })=> {
if (show) {
return (
<div className="text-center mx-4 mb-4">
<img src={link} alt={props.github} />
<img src={githubStatsLinkGenerator({github, options})} alt={github} />
</div>
)
}
return null
}
const TopLanguagesPreview = props => {
let link =
"https://github-readme-stats.vercel.app/api/top-langs/?username=" +
props.github +
"&layout=compact"
if (props.show) {
const TopLanguagesPreview = ({github, options, show})=> {
if (show) {
return (
<div className="text-center mx-4 mb-4">
<img src={link} alt={props.github} />
<img src={topLanguagesLinkGenerator({github, options})} alt={props.github} />
</div>
)
}
@@ -348,10 +342,12 @@ const MarkdownPreview = props => {
<TopLanguagesPreview
show={props.data.topLanguages}
github={props.social.github}
options={props.data.topLanguagesOptions}
/>
<GitHubStatsPreview
show={props.data.githubStats}
github={props.social.github}
options={props.data.githubStatsOptions}
/>
</div>
</div>
+18 -1
View File
@@ -63,7 +63,25 @@ const DEFAULT_DATA = {
badgeLabel: "Profile views",
githubProfileTrophy: false,
githubStats: false,
githubStatsOptions: {
theme: "",
titleColor: "",
textColor: "",
bgColor: "",
hideBorder: false,
cacheSeconds: null,
locale: "en",
},
topLanguages: false,
topLanguagesOptions: {
theme: "",
titleColor: "",
textColor: "",
bgColor: "",
hideBorder: false,
cacheSeconds: null,
locale: "en",
},
devDynamicBlogs: false,
mediumDynamicBlogs: false,
rssDynamicBlogs: false,
@@ -455,7 +473,6 @@ const IndexPage = () => {
setRestore("")
}
}
return (
<Layout>
<div className="m-4 sm:p-4">
+20
View File
@@ -0,0 +1,20 @@
const githubStatsStylingQueryString = options => {
const params = {
show_icons: true,
...(options.theme && options.theme !== "none") && { theme: options.theme },
...options.titleColor && { "title_color": options.titleColor },
...options.textColor && { "text_color": options.textColor},
...options.bgColor && { "bg_color": options.bgColor},
...options.hideBorder && { "hide_border": options.hideBorder},
...options.cacheSeconds && { "cache_seconds": options.cacheSeconds},
...options.locale && { "locale": options.locale},
}
const query_string = Object.entries(params).map(([key, value]) => `${key}=${value}`).join("&")
return query_string
}
export const githubStatsLinkGenerator = ({github, options}) =>
`https://github-readme-stats.vercel.app/api?username=${github}&${githubStatsStylingQueryString(options)}`
export const topLanguagesLinkGenerator = ({github, options}) =>
`https://github-readme-stats.vercel.app/api/top-langs?username=${github}&${githubStatsStylingQueryString(options)}&layout=compact`