code refactors
This commit is contained in:
parent
92dd089b9a
commit
2ea225cf71
17 changed files with 165 additions and 129 deletions
|
@ -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";
|
|
@ -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 = {
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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`;
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
86
src/models/problem.ts
Normal 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',
|
||||
};
|
|
@ -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}
|
||||
/>
|
||||
|
||||
|
|
|
@ -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 => {
|
||||
|
|
Reference in a new issue