From 2ea225cf7109f1f07cb381e5b07a25b1ead81a03 Mon Sep 17 00:00:00 2001 From: Nathan Wang Date: Fri, 17 Jul 2020 01:30:09 -0700 Subject: [PATCH] code refactors --- content/models.ts | 51 +---------- src/components/{Canvas => Confetti}/Canvas.js | 0 src/components/Confetti/Confetti.js | 2 +- src/components/ContactUsSlideover.tsx | 4 +- .../ModuleLayout/MarkCompleteButton.tsx | 2 +- src/components/ModuleLayout/ModuleLayout.tsx | 21 +++-- .../ModuleLayout/SidebarNav/ItemLink.tsx | 6 +- .../ModuleLayout/SidebarNav/SidebarNav.tsx | 6 +- .../ModuleLayout/TableOfContentsBlock.tsx | 2 +- .../ModuleLayout/TableOfContentsSidebar.tsx | 2 +- src/components/ProblemStatusCheckbox.tsx | 18 ++-- src/context/ModuleLayoutContext.tsx | 2 +- src/context/UserDataContext.tsx | 60 +++++-------- src/{ => models}/module.ts | 28 ++++-- src/models/problem.ts | 86 +++++++++++++++++++ src/templates/moduleTemplate.tsx | 2 +- src/utils.ts | 2 +- 17 files changed, 165 insertions(+), 129 deletions(-) rename src/components/{Canvas => Confetti}/Canvas.js (100%) rename src/{ => models}/module.ts (59%) create mode 100644 src/models/problem.ts diff --git a/content/models.ts b/content/models.ts index b40bbee..1a584ac 100644 --- a/content/models.ts +++ b/content/models.ts @@ -1,50 +1 @@ -const sources = { - "AC": "https://atcoder.jp/", - "CC": "https://www.codechef.com/problems/", - "CSA": "https://csacademy.com/contest/archive/task/", - "DMOJ": "https://dmoj.ca/problem/", - "SPOJ": "https://www.spoj.com/problems/", - "YS": "https://judge.yosupo.jp/problem/", - "CF": "https://codeforces.com/", - "Bronze": "http://www.usaco.org/index.php?page=viewproblem2&cpid=", - "Silver": "http://www.usaco.org/index.php?page=viewproblem2&cpid=", - "Gold": "http://www.usaco.org/index.php?page=viewproblem2&cpid=", - "Old Bronze": "http://www.usaco.org/index.php?page=viewproblem2&cpid=", - "Old Silver": "http://www.usaco.org/index.php?page=viewproblem2&cpid=", - "Old Gold": "http://www.usaco.org/index.php?page=viewproblem2&cpid=", - "Plat": "http://www.usaco.org/index.php?page=viewproblem2&cpid=", - "Kattis": "https://open.kattis.com/problems/", - "CSES": "https://cses.fi/problemset/task/", - "LC": "https://leetcode.com/problems/", - "ojuz": "https://oj.uz/problem/view/", - "HR": "https://www.hackerrank.com/", -}; - -export class Problem { - public url: string; - public difficulty: 'Very Easy' | 'Easy' | 'Normal' | 'Hard' | 'Very Hard' | 'Insane'; - public isIntro: boolean; - - get uniqueID() { - return this.url; - } - - constructor( - public source: string, - public name: string, - public id: string, - labels?: 'Very Easy' | 'Easy' | 'Normal' | 'Hard' | 'Very Hard' | 'Insane' | 'Intro|Very Easy' | 'Intro|Easy' | 'Intro|Normal' | 'Intro|Hard' | 'Intro|Very Hard' | 'Intro|Insane', - public starred?: boolean, - public tags?: string[], - public sketch?: string, - ) { - this.isIntro = labels && labels.includes("Intro|"); - if (labels) labels = labels.replace("Intro|", "") as any; - this.difficulty = labels as any; - if (!id.startsWith("http")) { - if (source in sources) { - this.url = sources[source] + id; - } else throw `URL ${id} is not valid. Did you make a typo in the problem source (${source}), or in the URL? Problem name: ${name}` - } else this.url = id; - } -} +export { Problem } from "../src/models/problem"; \ No newline at end of file diff --git a/src/components/Canvas/Canvas.js b/src/components/Confetti/Canvas.js similarity index 100% rename from src/components/Canvas/Canvas.js rename to src/components/Confetti/Canvas.js diff --git a/src/components/Confetti/Confetti.js b/src/components/Confetti/Confetti.js index 4f29e31..406c158 100644 --- a/src/components/Confetti/Confetti.js +++ b/src/components/Confetti/Confetti.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { random, sample, range, getDiameter } from './Confetti.helpers'; import { createCircle, createTriangle, createZigZag } from './confetti-shapes'; -import Canvas from '../Canvas/Canvas'; +import Canvas from './Canvas'; class Confetti extends Component { static propTypes = { diff --git a/src/components/ContactUsSlideover.tsx b/src/components/ContactUsSlideover.tsx index 545f972..4ba4d6c 100644 --- a/src/components/ContactUsSlideover.tsx +++ b/src/components/ContactUsSlideover.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ModuleInfo } from '../module'; +import { ModuleInfo } from '../models/module'; import { SECTION_LABELS } from '../../content/ordering'; import SlideoverForm from './Slideover/SlideoverForm'; import { useState } from 'react'; @@ -83,7 +83,7 @@ export default function ContactUsSlideover({ React.useEffect(() => { if (activeModule) setLocation( - `${activeModule.title} - ${SECTION_LABELS[activeModule.division]}` + `${activeModule.title} - ${SECTION_LABELS[activeModule.section]}` ); else setLocation(''); }, [activeModule]); diff --git a/src/components/ModuleLayout/MarkCompleteButton.tsx b/src/components/ModuleLayout/MarkCompleteButton.tsx index 107fd47..70a35d9 100644 --- a/src/components/ModuleLayout/MarkCompleteButton.tsx +++ b/src/components/ModuleLayout/MarkCompleteButton.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import Transition from '../Transition'; -import { ModuleProgressOptions } from '../../context/UserDataContext'; +import { ModuleProgressOptions } from '../../models/module'; const MarkCompleteButton = ({ state, diff --git a/src/components/ModuleLayout/ModuleLayout.tsx b/src/components/ModuleLayout/ModuleLayout.tsx index 4b795fb..8af6437 100644 --- a/src/components/ModuleLayout/ModuleLayout.tsx +++ b/src/components/ModuleLayout/ModuleLayout.tsx @@ -3,7 +3,11 @@ import Transition from '../Transition'; import { useContext, useRef, useState } from 'react'; // @ts-ignore import logo from '../../assets/logo.svg'; -import { ModuleFrequency, ModuleInfo, ModuleLinkInfo } from '../../module'; +import { + ModuleFrequency, + ModuleInfo, + ModuleLinkInfo, +} from '../../models/module'; import { graphql, Link, useStaticQuery } from 'gatsby'; import MODULE_ORDERING, { Category, @@ -86,10 +90,10 @@ const Breadcrumbs = () => { /> - {SECTION_LABELS[module.division]} + {SECTION_LABELS[module.section]} { const { module, moduleLinks } = moduleLayoutInfo; const sortedModuleLinks = React.useMemo(() => { let links: ModuleLinkInfo[] = []; - for (let group of MODULE_ORDERING[module.division]) { + for (let group of MODULE_ORDERING[module.section]) { for (let id of group.items) { links.push(moduleLinks.find(x => x.id === id)); } @@ -254,7 +258,7 @@ const renderPrerequisite = (prerequisite, moduleLinks: ModuleLinkInfo[]) => { return (
  • - {SECTION_LABELS[moduleLink.division]} - {moduleLink.title} + {SECTION_LABELS[moduleLink.section]} - {moduleLink.title}
  • ); @@ -268,12 +272,15 @@ export default function ModuleLayout({ module: ModuleInfo; children: React.ReactNode; }) { - const { userProgress, setModuleProgress, lang } = useContext(UserDataContext); + const { userProgressOnModules, setModuleProgress, lang } = useContext( + UserDataContext + ); const [isMobileNavOpen, setIsMobileNavOpen] = useState(false); const [isContactUsActive, setIsContactUsActive] = useState(false); const [isConfettiActive, setIsConfettiActive] = useState(false); const moduleProgress = - (userProgress && userProgress[module.id]) || 'Not Started'; + (userProgressOnModules && userProgressOnModules[module.id]) || + 'Not Started'; const tableOfContents = lang in module.toc ? module.toc[lang] : module.toc['cpp']; diff --git a/src/components/ModuleLayout/SidebarNav/ItemLink.tsx b/src/components/ModuleLayout/SidebarNav/ItemLink.tsx index d841e88..f5d174c 100644 --- a/src/components/ModuleLayout/SidebarNav/ItemLink.tsx +++ b/src/components/ModuleLayout/SidebarNav/ItemLink.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Link } from 'gatsby'; -import { ModuleLinkInfo } from '../../../module'; +import { ModuleLinkInfo } from '../../../models/module'; import styled from 'styled-components'; import tw from 'twin.macro'; import { useContext } from 'react'; @@ -88,8 +88,8 @@ const ItemLink = ({ link }: { link: ModuleLinkInfo }) => { } }, [isActive]); - const { userProgress } = useContext(UserDataContext); - const progress = userProgress[link.id] || 'Not Started'; + const { userProgressOnModules } = useContext(UserDataContext); + const progress = userProgressOnModules[link.id] || 'Not Started'; let lineColorStyle = tw`bg-gray-200`; let dotColorStyle = tw`bg-gray-200`; diff --git a/src/components/ModuleLayout/SidebarNav/SidebarNav.tsx b/src/components/ModuleLayout/SidebarNav/SidebarNav.tsx index 822bdcf..bf53ae8 100644 --- a/src/components/ModuleLayout/SidebarNav/SidebarNav.tsx +++ b/src/components/ModuleLayout/SidebarNav/SidebarNav.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ModuleLinkInfo } from '../../../module'; +import { ModuleLinkInfo } from '../../../models/module'; import { Link } from 'gatsby'; import ItemLink from './ItemLink'; import Accordion from './Accordion'; @@ -16,13 +16,13 @@ export const SidebarNav = () => { const { module, moduleLinks } = useContext(ModuleLayoutContext); const links: NavLinkGroup[] = React.useMemo(() => { - return MODULE_ORDERING[module.division].map((category: Category) => ({ + return MODULE_ORDERING[module.section].map((category: Category) => ({ label: category.name, children: category.items.map( moduleID => moduleLinks.find(link => link.id === moduleID) // lol O(n^2)? ), })); - }, [module.division, moduleLinks]); + }, [module.section, moduleLinks]); return ( <> diff --git a/src/components/ModuleLayout/TableOfContentsBlock.tsx b/src/components/ModuleLayout/TableOfContentsBlock.tsx index 401c7f6..dea4c43 100644 --- a/src/components/ModuleLayout/TableOfContentsBlock.tsx +++ b/src/components/ModuleLayout/TableOfContentsBlock.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { TOCHeading } from '../../module'; +import { TOCHeading } from '../../models/module'; import { Link } from 'gatsby'; import { useActiveHash } from '../../hooks/useActiveHash'; import { useMemo } from 'react'; diff --git a/src/components/ModuleLayout/TableOfContentsSidebar.tsx b/src/components/ModuleLayout/TableOfContentsSidebar.tsx index 8f5cea4..7654bc1 100644 --- a/src/components/ModuleLayout/TableOfContentsSidebar.tsx +++ b/src/components/ModuleLayout/TableOfContentsSidebar.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { TOCHeading } from '../../module'; +import { TOCHeading } from '../../models/module'; import { Link } from 'gatsby'; import { useActiveHash } from '../../hooks/useActiveHash'; import { useMemo } from 'react'; diff --git a/src/components/ProblemStatusCheckbox.tsx b/src/components/ProblemStatusCheckbox.tsx index 21e0e1f..1adef0e 100644 --- a/src/components/ProblemStatusCheckbox.tsx +++ b/src/components/ProblemStatusCheckbox.tsx @@ -2,20 +2,20 @@ import * as React from 'react'; import Tooltip from './tooltip/Tooltip'; import { Problem } from '../../content/models'; import { useContext } from 'react'; -import UserDataContext, { - NEXT_PROBLEM_STATUS, - ProblemStatus, -} from '../context/UserDataContext'; +import UserDataContext from '../context/UserDataContext'; +import { NEXT_PROBLEM_STATUS, ProblemProgress } from '../models/problem'; export default function ProblemStatusCheckbox({ problem, }: { problem: Problem; }) { - const { problemStatus, setProblemStatus } = useContext(UserDataContext); - let status: ProblemStatus = - problemStatus[problem.uniqueID] || 'Not Attempted'; - const color: { [key in ProblemStatus]: string } = { + const { userProgressOnProblems, setUserProgressOnProblems } = useContext( + UserDataContext + ); + let status: ProblemProgress = + userProgressOnProblems[problem.uniqueID] || 'Not Attempted'; + const color: { [key in ProblemProgress]: string } = { 'Not Attempted': 'bg-gray-200', Solving: 'bg-yellow-300', Solved: 'bg-green-500', @@ -23,7 +23,7 @@ export default function ProblemStatusCheckbox({ Skipped: 'bg-blue-300', }; const handleClick = () => { - setProblemStatus(problem, NEXT_PROBLEM_STATUS[status]); + setUserProgressOnProblems(problem, NEXT_PROBLEM_STATUS[status]); }; return ( void; - userProgress: UserProgress; + + userProgressOnModules: { [key: string]: ModuleProgress }; setModuleProgress: (moduleID: string, progress: ModuleProgress) => void; - problemStatus: { [key: string]: ProblemStatus }; - setProblemStatus: (problem: Problem, status: ProblemStatus) => void; + + userProgressOnProblems: { [key: string]: ProblemProgress }; + setUserProgressOnProblems: ( + problem: Problem, + status: ProblemProgress + ) => void; }>({ lang: 'showAll', setLang: null, - userProgress: null, + userProgressOnModules: null, setModuleProgress: null, - problemStatus: null, - setProblemStatus: null, + userProgressOnProblems: null, + setUserProgressOnProblems: null, }); const langKey = 'guide:userData:lang'; @@ -86,9 +66,11 @@ const getProblemStatusFromStorage = () => { export const UserDataProvider = ({ children }) => { const [lang, setLang] = useState('showAll'); - const [userProgress, setUserProgress] = useState({}); + const [userProgress, setUserProgress] = useState<{ + [key: string]: ModuleProgress; + }>({}); const [problemStatus, setProblemStatus] = useState<{ - [key: string]: ProblemStatus; + [key: string]: ProblemProgress; }>({}); React.useEffect(() => { @@ -105,7 +87,7 @@ export const UserDataProvider = ({ children }) => { window.localStorage.setItem(langKey, JSON.stringify(lang)); setLang(lang); }, - userProgress, + userProgressOnModules: userProgress, setModuleProgress: (moduleID: string, progress: ModuleProgress) => { const newProgress = { ...getProgressFromStorage(), @@ -114,8 +96,8 @@ export const UserDataProvider = ({ children }) => { window.localStorage.setItem(progressKey, JSON.stringify(newProgress)); setUserProgress(newProgress); }, - problemStatus, - setProblemStatus: (problem, status) => { + userProgressOnProblems: problemStatus, + setUserProgressOnProblems: (problem, status) => { const newStatus = { ...getProblemStatusFromStorage(), [problem.uniqueID]: status, diff --git a/src/module.ts b/src/models/module.ts similarity index 59% rename from src/module.ts rename to src/models/module.ts index 88a188b..9fe2815 100644 --- a/src/module.ts +++ b/src/models/module.ts @@ -1,12 +1,8 @@ export class ModuleLinkInfo { public url: string; - constructor( - public id: string, - public division: string, - public title: string - ) { - this.url = `/${division}/${id}`; + constructor(public id: string, public section: string, public title: string) { + this.url = `/${section}/${id}`; } } @@ -24,11 +20,10 @@ export type TableOfContents = { py: TOCHeading[]; }; -// there's probably a way to do this without the duplicated types... export class ModuleInfo extends ModuleLinkInfo { constructor( public id: string, - public division: string, + public section: string, public title: string, public body: any, public author: string, @@ -37,6 +32,21 @@ export class ModuleInfo extends ModuleLinkInfo { public frequency: ModuleFrequency, public toc: TableOfContents ) { - super(id, division, title); + super(id, section, title); } } + +export type ModuleProgress = + | 'Not Started' + | 'Reading' + | 'Practicing' + | 'Complete' + | 'Skipped'; + +export const ModuleProgressOptions: ModuleProgress[] = [ + 'Not Started', + 'Reading', + 'Practicing', + 'Complete', + 'Skipped', +]; diff --git a/src/models/problem.ts b/src/models/problem.ts new file mode 100644 index 0000000..e59f7f8 --- /dev/null +++ b/src/models/problem.ts @@ -0,0 +1,86 @@ +const sources = { + AC: 'https://atcoder.jp/', + CC: 'https://www.codechef.com/problems/', + CSA: 'https://csacademy.com/contest/archive/task/', + DMOJ: 'https://dmoj.ca/problem/', + SPOJ: 'https://www.spoj.com/problems/', + YS: 'https://judge.yosupo.jp/problem/', + CF: 'https://codeforces.com/', + Bronze: 'http://www.usaco.org/index.php?page=viewproblem2&cpid=', + Silver: 'http://www.usaco.org/index.php?page=viewproblem2&cpid=', + Gold: 'http://www.usaco.org/index.php?page=viewproblem2&cpid=', + 'Old Bronze': 'http://www.usaco.org/index.php?page=viewproblem2&cpid=', + 'Old Silver': 'http://www.usaco.org/index.php?page=viewproblem2&cpid=', + 'Old Gold': 'http://www.usaco.org/index.php?page=viewproblem2&cpid=', + Plat: 'http://www.usaco.org/index.php?page=viewproblem2&cpid=', + Kattis: 'https://open.kattis.com/problems/', + CSES: 'https://cses.fi/problemset/task/', + LC: 'https://leetcode.com/problems/', + ojuz: 'https://oj.uz/problem/view/', + HR: 'https://www.hackerrank.com/', +}; + +export class Problem { + public url: string; + public difficulty: + | 'Very Easy' + | 'Easy' + | 'Normal' + | 'Hard' + | 'Very Hard' + | 'Insane'; + public isIntro: boolean; + + get uniqueID() { + return this.url; + } + + constructor( + public source: string, + public name: string, + public id: string, + labels?: + | 'Very Easy' + | 'Easy' + | 'Normal' + | 'Hard' + | 'Very Hard' + | 'Insane' + | 'Intro|Very Easy' + | 'Intro|Easy' + | 'Intro|Normal' + | 'Intro|Hard' + | 'Intro|Very Hard' + | 'Intro|Insane', + public starred?: boolean, + public tags?: string[], + public sketch?: string + ) { + this.isIntro = labels && labels.includes('Intro|'); + if (labels) labels = labels.replace('Intro|', '') as any; + this.difficulty = labels as any; + if (!id.startsWith('http')) { + if (source in sources) { + this.url = sources[source] + id; + } else + throw `URL ${id} is not valid. Did you make a typo in the problem source (${source}), or in the URL? Problem name: ${name}`; + } else this.url = id; + } +} + +export type ProblemProgress = + | 'Not Attempted' + | 'Solving' + | 'Solved' + | "Can't Solve" + | 'Skipped'; + +export const NEXT_PROBLEM_STATUS: { + [key in ProblemProgress]: ProblemProgress; +} = { + 'Not Attempted': 'Solving', + Solving: 'Solved', + Solved: "Can't Solve", + "Can't Solve": 'Skipped', + Skipped: 'Not Attempted', +}; diff --git a/src/templates/moduleTemplate.tsx b/src/templates/moduleTemplate.tsx index fb3d1c2..c72ad71 100644 --- a/src/templates/moduleTemplate.tsx +++ b/src/templates/moduleTemplate.tsx @@ -15,7 +15,7 @@ export default function Template(props) { return ( diff --git a/src/utils.ts b/src/utils.ts index 278b779..b63c8d8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import MODULE_ORDERING from '../content/ordering'; -import { ModuleInfo, ModuleLinkInfo } from './module'; +import { ModuleInfo, ModuleLinkInfo } from './models/module'; export const getModule = (allModules, division) => { return MODULE_ORDERING[division].map(k => {