Merge pull request #72 from MauricioHernanCabrera/cache

Keep data when refresh page, btn reset form and load backup json
This commit is contained in:
Rahul Jain
2020-08-23 10:24:41 +05:30
committed by GitHub
2 changed files with 559 additions and 225 deletions
+37 -1
View File
@@ -183,6 +183,7 @@ input:focus {
width: 60%;
justify-self: center;
padding: 2% 0%;
align-self: center;
}
.download-button {
@@ -193,11 +194,12 @@ input:focus {
width: 60%;
justify-self: center;
padding: 2% 0%;
align-self: center;
}
.utils {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
margin-top: 4%;
}
@@ -209,6 +211,7 @@ input:focus {
width: 60%;
justify-self: end;
padding: 2% 0%;
align-self: center;
}
.loader {
@@ -228,6 +231,7 @@ input:focus {
width: 60%;
justify-self: start;
padding: 2% 0%;
align-self: center;
}
.title {
@@ -397,6 +401,32 @@ a {
padding: 1%;
background: var(--grey-05);
}
.actions {
margin-top: 24px;
font-weight: 700;
font-size: 12px;
display: flex;
padding-left: 2%;
}
.actions .button {
max-width: 120px;
padding: 8px 16px;
justify-content: center;
text-align: center;
}
.actions .data {
display: flex;
align-items: center;
}
.actions .data input {
flex: 1;
}
@media only screen and (max-width: 1199px) {
.section-title {
font-size: 18px;
@@ -488,4 +518,10 @@ a {
.skills-category-title {
font-size: 14px;
}
.actions {
flex-direction: column;
}
}
+522 -224
View File
@@ -1,317 +1,615 @@
import React, { useState, useEffect } from "react"
import MarkdownPreview from "../components/markdownPreview";
import Markdown from '../components/markdown';
import Header from '../components/header';
import Title from '../components/title';
import Subtitle from '../components/subtitle';
import Work from '../components/work';
import Social from '../components/social';
import Addons from '../components/addons';
import Skills from '../components/skills';
import { initialSkillState } from '../constants/skills';
import gsap from 'gsap';
import Loader from '../components/loader';
import Footer from '../components/footer';
import './index.css'
import { ArrowLeftIcon, CopyIcon, DownloadIcon, EyeIcon, CheckIcon, MarkdownIcon } from '@primer/octicons-react';
import SEO from '../components/seo';
import { isGithubUsernameValid, isMediumUsernameValid } from '../utils/validation';
import MarkdownPreview from "../components/markdownPreview"
import Markdown from "../components/markdown"
import Header from "../components/header"
import Title from "../components/title"
import Subtitle from "../components/subtitle"
import Work from "../components/work"
import Social from "../components/social"
import Addons from "../components/addons"
import Skills from "../components/skills"
import { initialSkillState } from "../constants/skills"
import gsap from "gsap"
import Loader from "../components/loader"
import Footer from "../components/footer"
import "./index.css"
import {
ArrowLeftIcon,
CopyIcon,
DownloadIcon,
EyeIcon,
CheckIcon,
MarkdownIcon,
} from "@primer/octicons-react"
import SEO from "../components/seo"
import {
isGithubUsernameValid,
isMediumUsernameValid,
} from "../utils/validation"
const DEFAULT_PREFIX = {
title: "Hi 👋, I'm",
currentWork: "🔭 Im currently working on",
currentLearn: "🌱 Im currently learning",
collaborateOn: "👯 Im looking to collaborate on",
helpWith: "🤝 Im looking for help with",
ama: "💬 Ask me about",
contact: "📫 How to reach me",
funFact: "⚡ Fun fact",
portfolio: "👨‍💻 All of my projects are available at",
blog: "📝 I regulary write articles on",
}
const DEFAULT_DATA = {
title: "",
subtitle: "A passionate frontend developer from India",
currentWork: "",
currentLearn: "",
collaborateOn: "",
helpWith: "",
ama: "",
contact: "",
funFact: "",
visitorsBadge: false,
githubStats: false,
topLanguages: false,
devDynamicBlogs: false,
mediumDynamicBlogs: false,
}
const DEFAULT_LINK = {
currentWork: "",
collaborateOn: "",
helpWith: "",
portfolio: "",
blog: "",
}
const DEFAULT_SOCIAL = {
github: "",
dev: "",
linkedin: "",
codepen: "",
stackoverflow: "",
kaggle: "",
codesandbox: "",
fb: "",
instagram: "",
twitter: "",
dribbble: "",
behance: "",
medium: "",
youtube: "",
}
const KeepCacheUpdated = ({ prefix, data, link, social, skills }) => {
useEffect(() => {
localStorage.setItem(
"cache",
JSON.stringify({
prefix,
data,
link,
social,
skills,
})
)
}, [prefix, data, link, social, skills])
}
const DEFAULT_SKILLS = initialSkillState
const IndexPage = () => {
const [prefix, setPrefix] = useState({
title: "Hi 👋, I'm",
currentWork: '🔭 Im currently working on',
currentLearn: '🌱 Im currently learning',
collaborateOn: '👯 Im looking to collaborate on',
helpWith: '🤝 Im looking for help with',
ama: '💬 Ask me about',
contact: '📫 How to reach me',
funFact: '⚡ Fun fact',
portfolio: '👨‍💻 All of my projects are available at',
blog: '📝 I regulary write articles on',
});
const [data, setData] = useState({
title: '',
subtitle: 'A passionate frontend developer from India',
currentWork: '',
currentLearn: '',
collaborateOn: '',
helpWith: '',
ama: '',
contact: '',
funFact: '',
visitorsBadge: false,
githubStats: false,
topLanguages: false,
devDynamicBlogs: false,
mediumDynamicBlogs: false,
});
const [link, setLink] = useState({
currentWork: '',
collaborateOn: '',
helpWith: '',
portfolio: '',
blog: '',
});
const [social, setSocial] = useState({
github: '',
dev: '',
linkedin: '',
codepen: '',
stackoverflow: '',
kaggle: '',
codesandbox: '',
fb: '',
instagram: '',
twitter: '',
dribbble: '',
behance: '',
medium: '',
youtube: ''
});
const [skills, setSkills] = useState(initialSkillState)
const [generatePreview, setGeneratePreview] = useState(false);
const [generateMarkdown, setGenerateMarkdown] = useState(false);
const [displayLoader, setDisplayLoader] = useState(false);
const [prefix, setPrefix] = useState(DEFAULT_PREFIX)
const [data, setData] = useState(DEFAULT_DATA)
const [link, setLink] = useState(DEFAULT_LINK)
const [social, setSocial] = useState(DEFAULT_SOCIAL)
const [skills, setSkills] = useState(DEFAULT_SKILLS)
const [restore, setRestore] = useState('')
const [generatePreview, setGeneratePreview] = useState(false)
const [generateMarkdown, setGenerateMarkdown] = useState(false)
const [displayLoader, setDisplayLoader] = useState(false)
const [copyObj, setcopyObj] = useState({
isCopied: false,
copiedText: 'copy-markdown'
});
copiedText: "copy-markdown",
})
const [previewMarkdown, setPreviewMarkdown] = useState({
isPreview: false,
buttonText: 'preview'
});
const handleSkillsChange = (field) => {
buttonText: "preview",
})
const handleSkillsChange = field => {
let change = { ...skills }
change[field] = !change[field];
setSkills(change);
change[field] = !change[field]
setSkills(change)
}
const handlePrefixChange = (field, e) => {
let change = { ...prefix }
change[field] = e.target.value;
setPrefix(change);
change[field] = e.target.value
setPrefix(change)
}
const handleDataChange = (field, e) => {
let change = { ...data }
change[field] = e.target.value;
setData(change);
change[field] = e.target.value
setData(change)
}
const handleLinkChange = (field, e) => {
let change = { ...link }
change[field] = e.target.value;
setLink(change);
change[field] = e.target.value
setLink(change)
}
const handleSocialChange = (field, e) => {
let change = { ...social }
change[field] = e.target.value.toLowerCase();
setSocial(change);
change[field] = e.target.value.toLowerCase()
setSocial(change)
}
const handleCheckChange = (field) => {
const handleCheckChange = field => {
let change = { ...data }
change[field] = !change[field];
setData(change);
change[field] = !change[field]
setData(change)
}
const generate = () => {
var tl = new gsap.timeline();
tl.to('.generate', {
var tl = new gsap.timeline()
tl.to(".generate", {
scale: 0,
duration: 0.5,
ease: 'Linear.easeNone',
});
tl.set('.form', { display: 'none' });
setDisplayLoader(true);
ease: "Linear.easeNone",
})
tl.set(".form", { display: "none" })
tl.set(".actions", { display: "none" })
setDisplayLoader(true)
setTimeout(() => {
setDisplayLoader(false);
setGenerateMarkdown(!generateMarkdown);
gsap.fromTo('.markdown-box', {
scale: 0.2,
}, {
scale: 1,
duration: 0.5,
ease: 'Linear.easeNone',
});
document.body.scrollTop = 0; // For Safari
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
}, 3000);
setDisplayLoader(false)
setGenerateMarkdown(!generateMarkdown)
gsap.fromTo(
".markdown-box",
{
scale: 0.2,
},
{
scale: 1,
duration: 0.5,
ease: "Linear.easeNone",
}
)
document.body.scrollTop = 0 // For Safari
document.documentElement.scrollTop = 0 // For Chrome, Firefox, IE and Opera
}, 3000)
}
const trimDataValues = (item, setItem) => {
const dataObj = { ...item };
Object.keys(dataObj).forEach(k => (typeof dataObj[k] === 'string') ? dataObj[k] = dataObj[k].trim() : null);
setItem(dataObj);
const dataObj = { ...item }
Object.keys(dataObj).forEach(k =>
typeof dataObj[k] === "string" ? (dataObj[k] = dataObj[k].trim()) : null
)
setItem(dataObj)
}
const handleGenerate = () => {
trimDataValues(data, setData);
trimDataValues(social, setSocial);
trimDataValues(link, setLink);
resetCopyMarkdownButton();
trimDataValues(data, setData)
trimDataValues(social, setSocial)
trimDataValues(link, setLink)
resetCopyMarkdownButton()
if (data.visitorsBadge || data.githubStats || data.topLanguages) {
if (social.github && isGithubUsernameValid(social.github)) {
generate();
generate()
}
} else if (social.github) {
if (isGithubUsernameValid(social.github)) {
generate();
generate()
}
} else {
generate();
generate()
}
}
const handleGeneratePreview = () => {
setGenerateMarkdown(!generateMarkdown);
setGeneratePreview(!generatePreview);
setGenerateMarkdown(!generateMarkdown)
setGeneratePreview(!generatePreview)
if (!generatePreview) {
gsap.set('.copy-button, .download-button', {
visibility: 'hidden'
});
gsap.set(".copy-button, .download-button", {
visibility: "hidden",
})
setPreviewMarkdown({
isPreview: true,
buttonText: 'markdown'
buttonText: "markdown",
})
} else {
gsap.set('.copy-button, .download-button', {
visibility: 'visible'
});
gsap.to('.copy-button', {
border: '2px solid #3b3b4f',
duration: 1
});
gsap.set(".copy-button, .download-button", {
visibility: "visible",
})
gsap.to(".copy-button", {
border: "2px solid #3b3b4f",
duration: 1,
})
setPreviewMarkdown({
isPreview: false,
buttonText: 'preview'
buttonText: "preview",
})
resetCopyMarkdownButton();
resetCopyMarkdownButton()
}
}
const resetCopyMarkdownButton = () => {
var el = document.getElementById('copy-markdown')
var el = document.getElementById("copy-markdown")
if (el) {
gsap.set('#copy-markdown', {
color: '#0a0a23',
});
gsap.set("#copy-markdown", {
color: "#0a0a23",
})
}
setcopyObj({
isCopied: false,
copiedText: 'copy-markdown'
});
copiedText: "copy-markdown",
})
}
const setCopyMarkdownButton = () => {
var el = document.getElementById('copy-markdown')
var el = document.getElementById("copy-markdown")
if (el) {
gsap.set('#copy-markdown', {
color: '#00471b',
});
gsap.set("#copy-markdown", {
color: "#00471b",
})
}
gsap.fromTo('.copy-button', {
scale: 0.5,
}, {
scale: 1,
ease: 'elastic.in',
border: '2px solid #00471b',
duration: 0.5
});
gsap.fromTo(
".copy-button",
{
scale: 0.5,
},
{
scale: 1,
ease: "elastic.in",
border: "2px solid #00471b",
duration: 0.5,
}
)
setcopyObj({
isCopied: true,
copiedText: 'copied'
});
copiedText: "copied",
})
}
const handleCopyToClipboard = () => {
var range = document.createRange();
range.selectNode(document.getElementById("markdown-content"));
window.getSelection().removeAllRanges(); // clear current selection
window.getSelection().addRange(range); // to select text
document.execCommand("copy");
window.getSelection().removeAllRanges();
var range = document.createRange()
range.selectNode(document.getElementById("markdown-content"))
window.getSelection().removeAllRanges() // clear current selection
window.getSelection().addRange(range) // to select text
document.execCommand("copy")
window.getSelection().removeAllRanges()
setCopyMarkdownButton();
setCopyMarkdownButton()
}
const handleDownload = () => {
var markdownContent = document.getElementById("markdown-content");
var tempElement = document.createElement('a');
tempElement.setAttribute('href', 'data:text/markdown;charset=utf-8,' + encodeURIComponent(markdownContent.innerText));
tempElement.setAttribute('download', 'README.md');
tempElement.style.display = 'none';
document.body.appendChild(tempElement);
tempElement.click();
document.body.removeChild(tempElement);
const handleDownloadMarkdown = () => {
var markdownContent = document.getElementById("markdown-content")
var tempElement = document.createElement("a")
tempElement.setAttribute(
"href",
"data:text/markdown;charset=utf-8," +
encodeURIComponent(markdownContent.innerText)
)
tempElement.setAttribute("download", "README.md")
tempElement.style.display = "none"
document.body.appendChild(tempElement)
tempElement.click()
document.body.removeChild(tempElement)
}
const handleDownloadJson = () => {
var tempElement = document.createElement("a")
tempElement.setAttribute(
"href",
`data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify({ prefix, data, link, social, skills }))}`
)
tempElement.setAttribute("download", "data.json")
tempElement.style.display = "none"
document.body.appendChild(tempElement)
tempElement.click()
document.body.removeChild(tempElement)
}
const handleBackToEdit = () => {
setGeneratePreview(false);
setGenerateMarkdown(false);
gsap.set('.form', {
display: ''
});
gsap.to('.generate', {
setGeneratePreview(false)
setGenerateMarkdown(false)
gsap.set(".form", {
display: "",
})
gsap.set(".actions", {
display: "",
})
gsap.to(".generate", {
scale: 1,
});
})
}
const setInitialValues = () => {
const cache = JSON.parse(localStorage.getItem("cache"))
if (!cache) {
return
}
setPrefix(cache.prefix || DEFAULT_PREFIX)
setData(cache.data || DEFAULT_DATA)
setLink(cache.link || DEFAULT_LINK)
setSocial(cache.social || DEFAULT_SOCIAL)
const cacheSkills = mergeDefaultWithNewDataSkills(DEFAULT_SKILLS, cache.skills)
setSkills(cacheSkills || DEFAULT_SKILLS)
}
useEffect(() => {
gsap.fromTo(".generate", {
boxShadow: "0 0 0 0px rgba(59, 59, 79, 0.4)"
}, {
boxShadow: "0 0 0 10px rgba(59, 59, 79, 0)",
repeat: -1,
duration: 1
});
});
gsap.fromTo(
".generate",
{
boxShadow: "0 0 0 0px rgba(59, 59, 79, 0.4)",
},
{
boxShadow: "0 0 0 10px rgba(59, 59, 79, 0)",
repeat: -1,
duration: 1,
}
)
// set initial values
setInitialValues()
}, [])
// keep cache updated
KeepCacheUpdated({ prefix, data, link, social, skills })
const handleResetForm = () => {
setPrefix(DEFAULT_PREFIX)
setData(DEFAULT_DATA)
setLink(DEFAULT_LINK)
setSocial(DEFAULT_SOCIAL)
setSkills(DEFAULT_SKILLS)
}
const mergeDefaultWithNewDataSkills = (defaultSkills, newSkills) => {
return Object.keys(defaultSkills).reduce(
(previous, currentKey) => {
let currentSelected = false
if (newSkills[currentKey]) {
currentSelected = true
}
return {
...previous,
[currentKey]: currentSelected,
}
},
{}
)
}
const handleRestore = () => {
try {
const restoreData = JSON.parse(restore)
if (!restoreData) {
return
}
setPrefix(restoreData.prefix || DEFAULT_PREFIX)
setData(restoreData.data || DEFAULT_DATA)
setLink(restoreData.link || DEFAULT_LINK)
setSocial(restoreData.social || DEFAULT_SOCIAL)
const restoreDataSkills = mergeDefaultWithNewDataSkills(DEFAULT_SKILLS, restoreData.skills)
setSkills(restoreDataSkills || DEFAULT_SKILLS)
} catch (error) {
} finally {
setRestore('')
}
}
return (
<>
<SEO title="Github Profile Readme Generator" />
<><Header heading="Github Profile README Generator" /></>
<div className="form">
<Title data={data} prefix={prefix} handleDataChange={handleDataChange} handlePrefixChange={handlePrefixChange} />
<Subtitle data={data} handleDataChange={handleDataChange} />
<Work prefix={prefix} data={data} link={link} handlePrefixChange={handlePrefixChange} handleLinkChange={handleLinkChange} handleDataChange={handleDataChange} />
<Skills skills={skills} handleSkillsChange={handleSkillsChange} />
<Social social={social} handleSocialChange={handleSocialChange} />
<Addons data={data} social={social} handleCheckChange={handleCheckChange} />
<div className="section">
{(data.visitorsBadge || data.githubStats || data.topLanguages) && !social.github ?
<div className="warning">* Please add github username to use these add-ons</div> : ''}
{social.github && !isGithubUsernameValid(social.github) ?
<div className="warning">* Github username is invalid, please add a valid username</div> : ''}
{social.medium && !isMediumUsernameValid(social.medium) ?
<div className="warning">* Medium username is invalid, please add a valid username (with @)</div> : ''}
{data.mediumDynamicBlogs && !social.medium ?
<div className="warning">* Please add medium username to display latest blogs dynamically</div> : ''}
{data.devDynamicBlogs && !social.dev ?
<div className="warning">* Please add dev.to username to display latest blogs dynamically</div> : ''}
<>
<Header heading="Github Profile README Generator" />
</>
<div className="actions">
<div className="data">
<input type="text" className="inputField md" placeholder="JSON Backup" value={restore} onChange={e => setRestore(e.target.value)}/>
<div className="button github-button" onClick={handleRestore}>
Restore
</div>
</div>
<div className="submit">
<div className="button generate" tabIndex="0" role="button" onClick={handleGenerate}>Generate README</div>
<div className="button github-button" onClick={handleResetForm}>
Reset form
</div>
</div>
{displayLoader ? <Loader /> : ''}
{(generateMarkdown || generatePreview) ?
<div className="form">
<Title
data={data}
prefix={prefix}
handleDataChange={handleDataChange}
handlePrefixChange={handlePrefixChange}
/>
<Subtitle data={data} handleDataChange={handleDataChange} />
<Work
prefix={prefix}
data={data}
link={link}
handlePrefixChange={handlePrefixChange}
handleLinkChange={handleLinkChange}
handleDataChange={handleDataChange}
/>
<Skills skills={skills} handleSkillsChange={handleSkillsChange} />
<Social social={social} handleSocialChange={handleSocialChange} />
<Addons
data={data}
social={social}
handleCheckChange={handleCheckChange}
/>
<div className="section">
{(data.visitorsBadge || data.githubStats || data.topLanguages) &&
!social.github ? (
<div className="warning">
* Please add github username to use these add-ons
</div>
) : (
""
)}
{social.github && !isGithubUsernameValid(social.github) ? (
<div className="warning">
* Github username is invalid, please add a valid username
</div>
) : (
""
)}
{social.medium && !isMediumUsernameValid(social.medium) ? (
<div className="warning">
* Medium username is invalid, please add a valid username (with @)
</div>
) : (
""
)}
{data.mediumDynamicBlogs && !social.medium ? (
<div className="warning">
* Please add medium username to display latest blogs dynamically
</div>
) : (
""
)}
{data.devDynamicBlogs && !social.dev ? (
<div className="warning">
* Please add dev.to username to display latest blogs dynamically
</div>
) : (
""
)}
</div>
<div className="submit">
<div
className="button generate"
tabIndex="0"
role="button"
onClick={handleGenerate}
>
Generate README
</div>
</div>
</div>
{displayLoader ? <Loader /> : ""}
{generateMarkdown || generatePreview ? (
<div className="markdown-section">
<div className="utils">
<div className="back-button" tabIndex="0" role="button" onClick={handleBackToEdit}>
<ArrowLeftIcon size={16} /> <span className="hide-on-mobile"> back to edit</span>
<div
className="back-button"
tabIndex="0"
role="button"
onClick={handleBackToEdit}
>
<ArrowLeftIcon size={16} />
<span className="hide-on-mobile"> back to edit</span>
</div>
<div className="copy-button" tabIndex="0" role="button" onClick={handleCopyToClipboard}>
{
copyObj.isCopied === true ?
<CheckIcon size={24} />
:
<CopyIcon size={24} />
}
<span className="hide-on-mobile" id="copy-markdown"> {copyObj.copiedText} </span>
<div
className="copy-button"
tabIndex="0"
role="button"
onClick={handleCopyToClipboard}
>
{copyObj.isCopied === true ? (
<CheckIcon size={24} />
) : (
<CopyIcon size={24} />
)}
<span className="hide-on-mobile" id="copy-markdown">
{copyObj.copiedText}
</span>
</div>
<div className="download-button" tabIndex="0" role="button" onClick={handleDownload}>
<DownloadIcon size={24} /> <span className="hide-on-mobile" id="download-markdown"> download </span>
<div
className="download-button"
tabIndex="0"
role="button"
onClick={handleDownloadMarkdown}
>
<DownloadIcon size={24} />
<span className="hide-on-mobile" id="download-markdown">
download markdown
</span>
</div>
<div className="preview-button" tabIndex="0" role="button" onClick={handleGeneratePreview}>
{previewMarkdown.isPreview ? <MarkdownIcon size={16} /> : <EyeIcon size={16} />} <span className="hide-on-mobile" id="preview-markdown">{previewMarkdown.buttonText}</span>
<div
className="download-button"
tabIndex="0"
role="button"
onClick={handleDownloadJson}
>
<DownloadIcon size={24} />
<span className="hide-on-mobile" id="download-json">
download backup
</span>
</div>
<div
className="preview-button"
tabIndex="0"
role="button"
onClick={handleGeneratePreview}
>
{previewMarkdown.isPreview ? (
<MarkdownIcon size={16} />
) : (
<EyeIcon size={16} />
)}
<span className="hide-on-mobile" id="preview-markdown">
{previewMarkdown.buttonText}
</span>
</div>
</div>
<div className="markdown">
<div className="markdown-box">
{generatePreview ? <MarkdownPreview prefix={prefix} data={data} link={link} social={social} skills={skills} /> : ''}
{generateMarkdown ? <Markdown prefix={prefix} data={data} link={link} social={social} skills={skills} /> : ''}
{generatePreview ? (
<MarkdownPreview
prefix={prefix}
data={data}
link={link}
social={social}
skills={skills}
/>
) : (
""
)}
{generateMarkdown ? (
<Markdown
prefix={prefix}
data={data}
link={link}
social={social}
skills={skills}
/>
) : (
""
)}
</div>
</div>
</div>
: ''}
) : (
""
)}
<Footer />
</>
)