code refactors

This commit is contained in:
Nathan Wang 2020-07-17 01:30:09 -07:00
parent 92dd089b9a
commit 2ea225cf71
17 changed files with 165 additions and 129 deletions

View file

@ -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";

View file

@ -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 = {

View file

@ -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]);

View file

@ -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,

View file

@ -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 = () => {
/>
</svg>
<Link
to={`/${module.division}`}
to={`/${module.section}`}
className="text-gray-500 hover:text-gray-700 transition duration-150 ease-in-out"
>
{SECTION_LABELS[module.division]}
{SECTION_LABELS[module.section]}
</Link>
<svg
className="flex-shrink-0 mx-2 h-5 w-5 text-gray-400"
@ -170,7 +174,7 @@ const NavBar = ({ alignNavButtonsRight = true }) => {
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 (
<li key={prerequisite}>
<Link to={moduleLink.url} className="underline text-black">
{SECTION_LABELS[moduleLink.division]} - {moduleLink.title}
{SECTION_LABELS[moduleLink.section]} - {moduleLink.title}
</Link>
</li>
);
@ -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'];

View file

@ -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`;

View file

@ -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 (
<>

View file

@ -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';

View file

@ -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';

View file

@ -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 (
<Tooltip

View file

@ -1,6 +1,6 @@
import { createContext } from 'react';
import * as React from 'react';
import { ModuleInfo, ModuleLinkInfo } from '../module';
import { ModuleInfo, ModuleLinkInfo } from '../models/module';
const ModuleLayoutContext = createContext<{
module: ModuleInfo;

View file

@ -1,50 +1,30 @@
import { createContext, useState } from 'react';
import * as React from 'react';
import { createContext, useState } from 'react';
import { Problem } from '../../content/models';
import { ModuleProgress } from '../models/module';
import { ProblemProgress } from '../models/problem';
export type ModuleProgress =
| 'Not Started'
| 'Reading'
| 'Practicing'
| 'Complete'
| 'Skipped';
export const ModuleProgressOptions: ModuleProgress[] = [
'Not Started',
'Reading',
'Practicing',
'Complete',
'Skipped',
];
export type UserProgress = { [key: string]: ModuleProgress };
export type UserLang = 'showAll' | 'cpp' | 'java' | 'py';
export type ProblemStatus =
| 'Not Attempted'
| 'Solving'
| 'Solved'
| "Can't Solve"
| 'Skipped';
export const NEXT_PROBLEM_STATUS: { [key in ProblemStatus]: ProblemStatus } = {
'Not Attempted': 'Solving',
Solving: 'Solved',
Solved: "Can't Solve",
"Can't Solve": 'Skipped',
Skipped: 'Not Attempted',
};
const UserDataContext = createContext<{
lang: UserLang;
setLang: (lang: UserLang) => 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<UserLang>('showAll');
const [userProgress, setUserProgress] = useState<UserProgress>({});
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,

View file

@ -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',
];

86
src/models/problem.ts Normal file
View file

@ -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',
};

View file

@ -15,7 +15,7 @@ export default function Template(props) {
return (
<Layout>
<SEO
title={`${module.title} (${SECTION_LABELS[module.division]})`}
title={`${module.title} (${SECTION_LABELS[module.section]})`}
description={module.description}
/>

View file

@ -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 => {