try with context

This commit is contained in:
lingdocs 2021-09-18 00:43:00 -04:00
parent 16066df914
commit 4b820ba231
24 changed files with 355 additions and 79 deletions

View File

@ -4,7 +4,8 @@
"private": true,
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2",
"@lingdocs/pashto-inflector": "^0.9.3",
"@lingdocs/lingdocs-main": "^0.0.4",
"@lingdocs/pashto-inflector": "^1.0.5",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
@ -12,6 +13,7 @@
"@types/node": "^14.14.35",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.2",
"@types/react-router-dom": "^5.1.9",
"bootstrap": "4.5.3",
"classnames": "^2.2.6",
"markdown-to-jsx": "^7.1.3",

View File

@ -8,7 +8,7 @@
import React, { useState } from "react";
// eslint-disable-next-line
import { BrowserRouter as Router, Route, withRouter, Switch } from "react-router-dom";
import { BrowserRouter as Router, Route, withRouter, Switch, RouteComponentProps } from "react-router-dom";
import "./App.css";
import Page404 from "./pages/404";
import Chapter from "./components/Chapter";
@ -17,6 +17,8 @@ import Sidebar from "./components/Sidebar";
import Header from "./components/Header";
import TableOfContentsPage from "./pages/TableOfContentsPage";
import { useEffect } from "react";
import { useUser } from "./user-context";
import { AT } from "@lingdocs/lingdocs-main";
import ReactGA from "react-ga";
const chapters = content.reduce((chapters, item) => (
item.content
@ -31,10 +33,21 @@ if (prod) {
ReactGA.set({ anonymizeIp: true });
}
function App(props) {
function App(props: RouteComponentProps) {
const [navOpen, setNavOpen] = useState(false);
const { setUser } = useUser();
useEffect(() => {
ReactGA.pageview(window.location.pathname);
fetch("https://account.lingdocs.com/api/user").then((res) => res.json()).then((res) => {
console.log("fetched user info");
if (res.user) {
const user = res.user as AT.LingdocsUser
setUser(user);
} else {
setUser(undefined);
}
}).catch(console.error);
// eslint-disable-next-line
}, []);
useEffect(() => {
window.scroll(0, 0);
@ -57,7 +70,7 @@ function App(props) {
<Route path="/" exact>
<TableOfContentsPage />
</Route>
{chapters.map((chapter) => (
{chapters.map((chapter: any) => (
<Route key={chapter.path} path={chapter.path}>
<Chapter>{chapter}</Chapter>
</Route>

6
src/UserContext.tsx Normal file
View File

@ -0,0 +1,6 @@
import React from "react";
import { AT } from "@lingdocs/lingdocs-main";
const UserContext = React.createContext<undefined | AT.LingdocsUser>(undefined);
export default UserContext;

View File

@ -2,29 +2,31 @@ import React from "react";
import Carousel from "./Carousel";
import {
InlinePs,
removeFVariants,
removeFVarients,
InflectionsTable,
inflectWord,
defaultTextOptions as opts,
} from "@lingdocs/pashto-inflector";
function InflectionCarousel({ items }) {
function InflectionCarousel({ items }: any) {
return (
<div className="mt-3">
<Carousel items={items} render={(item) => {
const inf = inflectWord(item.entry);
if (!inf) {
<Carousel items={items} render={(item: any) => {
const infOut = inflectWord(item.entry);
if (!infOut || !infOut.inflections) {
return (
<div>Oops! No inflections for <InlinePs opts={opts} ps={item.entry} /></div>
// @ts-ignore
<div>Oops! No inflections for <InlinePs opts={opts} />{item.entry}</div>
);
}
return {
// @ts-ignore
title: <InlinePs opts={opts} ps={{
...removeFVariants(item.entry),
...removeFVarients(item.entry),
e: item.def,
}} />,
body: <InflectionsTable
inf={inf}
inf={infOut.inflections}
textOptions={opts}
/>,
};

View File

@ -7,39 +7,66 @@
*/
/* eslint-disable import/no-webpack-loader-syntax */
// @ts-ignore
import * as intro from "!babel-loader!mdx-loader!./intro.mdx";
// @ts-ignore
import * as presentEquative from "!babel-loader!mdx-loader!./equatives/present-equative.mdx"
// @ts-ignore
import * as subjunctiveHabitualEquative from "!babel-loader!mdx-loader!./equatives/subjunctive-habitual-equative.mdx";
// @ts-ignore
import * as otherEquatives from "!babel-loader!mdx-loader!./equatives/other-equatives.mdx";
// @ts-ignore
import * as nounsGender from "!babel-loader!mdx-loader!./nouns/nouns-gender.mdx";
// @ts-ignore
import * as nounsUnisex from "!babel-loader!mdx-loader!./nouns/nouns-unisex.mdx";
// @ts-ignore
import * as nounsPlural from "!babel-loader!mdx-loader!./nouns/nouns-plural.mdx";
// @ts-ignore
import * as arabicPlurals from "!babel-loader!mdx-loader!./nouns/arabic-plurals.mdx";
// @ts-ignore
import * as bundledPlurals from "!babel-loader!mdx-loader!./nouns/bundled-plurals.mdx";
// @ts-ignore
import * as verbAspect from "!babel-loader!mdx-loader!./verbs/verb-aspect.mdx";
// @ts-ignore
import * as verbsIntro from "!babel-loader!mdx-loader!./verbs/verbs-intro.mdx";
// @ts-ignore
import * as presentVerbs from "!babel-loader!mdx-loader!./verbs/present-verbs.mdx";
// @ts-ignore
import * as subjunctiveVerbs from "!babel-loader!mdx-loader!./verbs/subjunctive-verbs.mdx";
// @ts-ignore
import * as futureVerbs from "!babel-loader!mdx-loader!./verbs/future-verbs.mdx";
// @ts-ignore
import * as imperativeVerbs from "!babel-loader!mdx-loader!./verbs/imperative-verbs.mdx";
// @ts-ignore
import * as verbEndings from "!babel-loader!mdx-loader!./verbs/verb-endings.mdx";
// @ts-ignore
import * as rootsAndStems from "!babel-loader!mdx-loader!./verbs/roots-and-stems.mdx";
// @ts-ignore
import * as sentenceStructure from "!babel-loader!mdx-loader!./verbs/sentence-structure.mdx";
// @ts-ignore
import * as pronounsBasic from "!babel-loader!mdx-loader!./pronouns/pronouns-basic.mdx";
// @ts-ignore
import * as pronounsMini from "!babel-loader!mdx-loader!./pronouns/pronouns-mini.mdx";
// @ts-ignore
import * as directionalPronouns from "!babel-loader!mdx-loader!./pronouns/pronouns-directional.mdx";
// @ts-ignore
import * as inflectionIntro from "!babel-loader!mdx-loader!./inflection/inflection-intro.mdx";
// @ts-ignore
import * as inflectionPatterns from "!babel-loader!mdx-loader!./inflection/inflection-patterns.mdx";
// @ts-ignore
import * as feminineInflection from "!babel-loader!mdx-loader!./inflection/feminine-inflection.mdx";
// @ts-ignore
import * as sandwiches from "!babel-loader!mdx-loader!./sandwiches/sandwiches.mdx";
// @ts-ignore
import * as theFiveYeys from "!babel-loader!mdx-loader!./writing/the-five-yeys.mdx";
// @ts-ignore
import * as typingIssues from "!babel-loader!mdx-loader!./writing/typing-issues.mdx";
const contentTree = [
@ -196,7 +223,7 @@ const contentTree = [
];
export const content = contentTree.map((item) => {
function prepareChapter(chp, subdir) {
function prepareChapter(chp: any, subdir?: any) {
return {
path: subdir ? `/${subdir}/${chp.slug}/` : `/${chp.slug}/`,
slug: chp.slug,
@ -209,30 +236,34 @@ export const content = contentTree.map((item) => {
? prepareChapter(item)
: {
...item,
chapters: item.chapters.map((c) => {
chapters: item.chapters?.map((c) => {
return prepareChapter(c, item.subdirectory);
}),
};
}).map((item, i, items) => {
// make the next and previous page information for each chapter
function withNextPrev(current, index, arr) {
function getInfo(x) {
function withNextPrev(current: any, index: any, arr: any) {
function getInfo(x: any) {
return x.content
? { frontMatter: x.frontMatter, path: x.path }
: { frontMatter: x.chapters[0].frontMatter, path: x.chapters[0].path }; // TODO: KILL THIS?
}
function getNextOutsideItem() {
// @ts-ignore
return items[i+1].content
// if it's a single chapter section, get that chapter
? items[i+1]
// if it's a section with multiple chapters, get the first chapter
// @ts-ignore
: items[i+1].chapters[0];
}
function getPrevOutsideItem() {
// @ts-ignore
return items[i-1].content
// if it's a single chapter section, get that chapter
? items[i-1]
// if it's a section with multiple chapters, get the last chapter
// @ts-ignore
: items[i-1].chapters[items[i-1].chapters.length - 1];
}
const next = index < arr.length - 1
@ -259,11 +290,13 @@ export const content = contentTree.map((item) => {
} : {},
};
}
// @ts-ignore
if (item.content) {
return withNextPrev(item, i, items);
}
return {
...item,
// @ts-ignore
chapters: item.chapters.map((chapter, j, chapters) => (
withNextPrev(chapter, j, chapters)
)),

View File

@ -19,7 +19,7 @@ import {
Examples,
InlinePs,
grammarUnits,
removeFVariants,
removeFVarients,
InflectionsTable,
inflectWord,
} from "@lingdocs/pashto-inflector";

View File

@ -17,10 +17,6 @@ import {
defaultTextOptions as opts,
Examples,
InlinePs,
grammarUnits,
removeFVariants,
InflectionsTable,
inflectWord,
} from "@lingdocs/pashto-inflector";
import Carousel from "../../components/Carousel";
import Table from "../../components/Table";

View File

@ -8,13 +8,15 @@ import {
Examples,
} from "@lingdocs/pashto-inflector";
import genderColors from "../../lib/gender-colors";
import GenderGame from "../../games/GenderGame";
import { firstVariation } from "../../lib/text-tools";
import GenderTable from "../../components/GenderTable";
import Link from "../../components/Link";
import words from "../../words/nouns-adjs";
export const femColor = genderColors.f;
export const mascColor = genderColors.m;
import nounGenderGame1 from "../../games/games";
import nounGenderGame2 from "../../games/games";
import GameDisplay from "../../games/GameDisplay";
export const femEndingWConsonant = words.filter((w) => w.category === "consonant-fem");
@ -109,7 +111,7 @@ All nouns in Pashto are either <Masc /> or <Fem />. Thankfully, you can pretty m
- Words ending in <InlinePs opts={opts} ps={{p:"و", f:"oo"}} /> can be either <Masc /> or <Fem />.
- Words ending in <InlinePs opts={opts} ps={{ p: "ـه", f: "u" }} /> can also be <Fem /> plural, as in <InlinePs opts={opts} ps={{ p: "اوبه", f: "oobu", e: "water" }} />
<GenderGame level={1} />
<GameDisplay record={nounGenderGame1} />
## Exceptions
@ -164,4 +166,4 @@ Some words are used to describe people who obviously have a gender and they *tot
},
]} />
<GenderGame level={2} />
<GameDisplay record={nounGenderGame2} />

View File

@ -10,7 +10,8 @@ import {
import Table from "../../components/Table";
import Link from "../../components/Link";
import GenderTable from "../../components/GenderTable";
import UnisexNounGame from "../../games/UnisexNounGame";
import { unisexNounGame } from "../../games/games";
import GameDisplay from "../../games/GameDisplay";
There are many words for people and animals in Pashto that can be used in both masculine and feminine forms.
@ -209,7 +210,7 @@ If the accent comes on the end of the word, the femine form is a little differen
}
]} />
<UnisexNounGame />
<GameDisplay record={unisexNounGame} />
<!--
{

View File

@ -6,7 +6,7 @@ import {
defaultTextOptions as opts,
Examples,
InlinePs,
removeFVariants,
removeFVarients,
ConjugationViewer,
} from "@lingdocs/pashto-inflector";
import cousins from "./cousins.png";
@ -42,7 +42,7 @@ This is used to talk about something happening in the future, while thinking of
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
return {
title: <InlinePs opts={opts} ps={{
...removeFVariants(item.entry),
...removeFVarients(item.entry),
e: item.def,
}} />,
body: <div style={{ textAlign: "left" }}>
@ -74,7 +74,7 @@ This is used to talk about something happening in the future, while thinking of
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
return {
title: <InlinePs opts={opts} ps={{
...removeFVariants(item.entry),
...removeFVarients(item.entry),
e: item.def,
}} />,
body: <div style={{ textAlign: "left" }}>

View File

@ -6,7 +6,7 @@ import {
defaultTextOptions as opts,
Examples,
InlinePs,
removeFVariants,
removeFVarients,
ConjugationViewer,
} from "@lingdocs/pashto-inflector";
import psmd from "../../lib/psmd";
@ -54,7 +54,7 @@ The <i class="fas fa-camera" /> **perfective imperative** is used when you want
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
return {
title: <InlinePs opts={opts} ps={{
...removeFVariants(item.entry),
...removeFVarients(item.entry),
e: item.def,
}} />,
body: <div className="text-left">

View File

@ -6,7 +6,7 @@ import {
defaultTextOptions as opts,
Examples,
InlinePs,
removeFVariants,
removeFVarients,
ConjugationViewer,
} from "@lingdocs/pashto-inflector";
import psmd from "../../lib/psmd";
@ -38,7 +38,7 @@ The <Link to="/verbs/verb-endings/">present ending</Link> will change according
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
return {
title: <InlinePs opts={opts} ps={{
...removeFVariants(item.entry),
...removeFVarients(item.entry),
e: item.def,
}} />,
body: <div className="text-left">

View File

@ -21,7 +21,7 @@ import {
grammarUnits,
RootsAndStems,
getVerbInfo,
removeFVariants,
removeFVarients,
} from "@lingdocs/pashto-inflector";
import shuffle from "../../lib/shuffle-array";
import Carousel from "../../components/Carousel";
@ -44,7 +44,7 @@ export function InfoCarousel({ items, highlighted, hidePastParticiple }) {
: inf;
return {
title: <InlinePs opts={opts} ps={{
...removeFVariants(item.entry),
...removeFVarients(item.entry),
e: item.def,
}} />,
body: <RootsAndStems

View File

@ -6,7 +6,7 @@ import {
defaultTextOptions as opts,
Examples,
InlinePs,
removeFVariants,
removeFVarients,
ConjugationViewer,
} from "@lingdocs/pashto-inflector";
import cousins from "./cousins.png";
@ -58,7 +58,7 @@ The subjunctive is made the same way as its cousin the <Link to="/verbs/present-
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
return {
title: <InlinePs opts={opts} ps={{
...removeFVariants(item.entry),
...removeFVarients(item.entry),
e: item.def,
}} />,
body: <div className="text-left">

View File

@ -2,14 +2,27 @@ import React, { useState, useRef } from "react";
import { CountdownCircleTimer } from "react-countdown-circle-timer";
import Reward, { RewardElement } from 'react-rewards';
import Link from "../components/Link";
import { useUser } from "../user-context";
import "./timer.css";
import {
getPercentageDone,
} from "../lib/game-utils";
import {
Types as T,
} from "@lingdocs/pashto-inflector";
const errorVibration = 200;
function Game<T>({ questions, Display, timeLimit, Instructions, studyLink, label }: GameInput<T>) {
function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, id }:{
id: string,
studyLink: string,
Instructions: (props: { opts?: T.TextOptions }) => JSX.Element,
questions: () => QuestionGenerator<T>,
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
timeLimit: number;
}) {
// TODO: report pass with id to user info
const rewardRef = useRef<RewardElement | null>(null);
const { user } = useUser();
const [finish, setFinish] = useState<null | "pass" | "fail" | "time out">(null);
const [current, setCurrent] = useState<Current<T> | undefined>(undefined);
const [questionBox, setQuestionBox] = useState<QuestionGenerator<T>>(questions());
@ -30,10 +43,17 @@ function Game<T>({ questions, Display, timeLimit, Instructions, studyLink, label
else setCurrent(next.value);
}
function handleFinish() {
// post results
if (user) {
// TODO: post results
console.log(
"will post results for",
user.userId,
"results id",
id,
);
}
setFinish("pass");
rewardRef.current?.rewardMe();
setCurrent(undefined);
}
function handleQuit() {
setFinish(null);
@ -66,8 +86,8 @@ function Game<T>({ questions, Display, timeLimit, Instructions, studyLink, label
: finish === "fail"
? "danger"
: "primary";
console.log("user is", user)
return <div>
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {label}</h4>
<div className="text-center" style={{ minHeight: "200px" }}>
<div className="progress" style={{ height: "5px" }}>
<div className={`progress-bar bg-${progressColor}`} role="progressbar" style={{ width: getProgressWidth() }} />
@ -141,4 +161,4 @@ function failMessage(progress: Progress | undefined, finish: "time out" | "fail"
: `⏳ Time's Up ${face}`;
}
export default Game;
export default GameCore;

10
src/games/GameDisplay.tsx Normal file
View File

@ -0,0 +1,10 @@
import React from "react";
function GameDisplay({ record: { title, Game } }: { record: GameRecord }) {
return <div>
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {title}</h4>
<Game />
</div>
}
export default GameDisplay;

40
src/games/games.tsx Normal file
View File

@ -0,0 +1,40 @@
import React from "react";
import GenderGame from "./sub-cores/GenderGame";
import UnisexNounGame from "./sub-cores/UnisexNounGame";
const unisexNounsId = "unisex-nouns-1";
const nounGender1Id = "gender-nouns-1";
const nounGender2Id = "gender-nouns-2";
export const unisexNounGame: GameRecord = {
title: "Changing genders on unisex nouns",
id: unisexNounsId,
Game: function() {
// TODO: Why won't this.id word here??!
return <UnisexNounGame id={unisexNounsId} />;
},
}
export const nounGenderGame1: GameRecord = {
title: "Identify Noun Genders - Level 1",
id: nounGender1Id,
Game: function() {
return <GenderGame id={nounGender1Id} level={1} />;
},
}
export const nounGenderGame2: GameRecord = {
title: "Identify Noun Genders - Level 1",
id: nounGender2Id,
Game: function() {
return <GenderGame id={nounGender2Id} level={2} />;
},
}
const games: GameRecord[] = [
unisexNounGame,
nounGenderGame1,
nounGenderGame2,
];
export default games;

View File

@ -2,18 +2,18 @@ import React from "react";
import {
getRandomFromList,
makeProgress,
} from "../lib/game-utils";
import genderColors from "../lib/gender-colors";
import Game from "./Game";
} from "../../lib/game-utils";
import genderColors from "../../lib/gender-colors";
import GameCore from "../GameCore";
import {
Types as T,
Examples,
defaultTextOptions as opts,
} from "@lingdocs/pashto-inflector";
import words from "../words/nouns-adjs";
import words from "../../words/nouns-adjs";
import {
firstVariation,
} from "../lib/text-tools";
} from "../../lib/text-tools";
const genders: T.Gender[] = ["masc", "fem"];
@ -50,7 +50,7 @@ const exceptions: Record<string, CategorySet> = {
const amount = 40;
export default function({level}: { level: 1 | 2 }) {
export default function({level, id}: { level: 1 | 2, id: string}) {
function* questions () {
const wordPool = {...types};
const exceptionsPool = {...exceptions};
@ -100,10 +100,10 @@ export default function({level}: { level: 1 | 2 }) {
</div>
}
return <Game
label={level === 1 ? "Choose the right gender - Level 1" : "Choose the right gender - Level 2"}
return <GameCore
studyLink={level === 1 ? "/nouns/nouns-gender#gender-by-ending" : "/nouns/nouns-gender#exceptions"}
questions={questions}
id={id}
Display={Display}
timeLimit={level === 1 ? 65 : 85}
Instructions={Instructions}

View File

@ -3,9 +3,9 @@ import {
getRandomFromList,
makeProgress,
compareF,
} from "../lib/game-utils";
import genderColors from "../lib/gender-colors";
import Game from "./Game";
} from "../../lib/game-utils";
import genderColors from "../../lib/gender-colors";
import GameCore from "../GameCore";
import {
Types as T,
Examples,
@ -14,10 +14,10 @@ import {
standardizePashto,
// pashtoConsonants,
} from "@lingdocs/pashto-inflector";
import words from "../words/nouns-adjs";
import words from "../../words/nouns-adjs";
import {
firstVariation,
} from "../lib/text-tools";
} from "../../lib/text-tools";
const nouns = words.filter((w) => w.category === "nouns-unisex").map(x => x.entry);
// type NType = "consonant" | "eyUnstressed" | "eyStressed" | "pashtun" | "withu"
@ -34,7 +34,7 @@ const amount = 20;
type Question = { entry: T.DictionaryEntry, gender: T.Gender };
export default function() {
export default function({ id }: { id: string }) {
function* questions (): Generator<Current<Question>> {
let pool = [...nouns];
for (let i = 0; i < amount; i++) {
@ -61,19 +61,26 @@ export default function() {
return g === "masc" ? "fem" : "masc";
}
const [answer, setAnswer] = useState<string>("");
const inflected = inflectWord(question.entry) as T.UnisexInflections;
const infOut = inflectWord(question.entry);
if (!infOut) return <div>WORD ERROR</div>;
const { inflections } = infOut;
if (!inflections) return <div>WORD ERROR</div>;
const givenGender = question.gender === "masc" ? "masculine" : "feminine";
const requiredGender = question.gender === "fem" ? "masculine" : "feminine";
if (!inflected || !inflected.masc || !inflected.fem) {
if (!("masc" in inflections ) || !("fem" in inflections)) {
return <div>WORD ERROR</div>;
}
function handleInput({ target: { value }}: React.ChangeEvent<HTMLInputElement>) {
if (!inflections.masc || !inflections.fem) {
return <div>WORD ERROR</div>;
}
const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
setAnswer(value);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const given = standardizePashto(answer.trim());
const correct = inflected[flipGender(question.gender)][0].some((ps) => (
// @ts-ignore
const correct = inflections[flipGender(question.gender)][0].some((ps: T.PsString) => (
(given === ps.p) || compareF(given, ps.f)
));
if (correct) {
@ -86,7 +93,7 @@ export default function() {
<div className="pt-2 pb-1 mb-2" style={{ maxWidth: "300px", margin: "0 auto", backgroundColor: genderColors[question.gender === "masc" ? "m" : "f"]}}>
<Examples opts={opts}>{[
{
...inflected[question.gender][0][0],
...inflections[question.gender][0][0],
e: firstVariation(question.entry.e),
}
]}</Examples>
@ -119,10 +126,10 @@ export default function() {
</div>
}
return <Game
label="Changing genders on unisex nouns"
return <GameCore
studyLink="/nouns/nouns-unisex#"
questions={questions}
id={id}
Display={Display}
timeLimit={130}
Instructions={Instructions}

View File

@ -10,6 +10,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter as Router } from "react-router-dom";
import { UserProvider} from "./user-context";
import * as serviceWorker from './serviceWorker';
import "bootstrap/dist/css/bootstrap.min.css";
import "@fortawesome/fontawesome-free/css/all.css";
@ -17,7 +18,9 @@ import "@fortawesome/fontawesome-free/css/all.css";
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
<UserProvider>
<App />
</UserProvider>
</Router>
</React.StrictMode>,
document.getElementById('root')

13
src/types.d.ts vendored
View File

@ -15,11 +15,8 @@ type QuestionDisplayProps<T> = {
callback: (correct: boolean) => void,
};
type GameInput<T> = {
label: string,
studyLink: string,
Instructions: (props: { opts?: import("@lingdocs/pashto-inflector").Types.TextOptions }) => JSX.Element,
questions: () => QuestionGenerator<T>,
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
timeLimit: number;
}
type GameRecord = {
title: string,
id: string,
Game: () => JSX.Element,
};

24
src/user-context.tsx Normal file
View File

@ -0,0 +1,24 @@
import React, { useState, createContext } from "react"
import { AT } from "@lingdocs/lingdocs-main";
const UserContext = createContext<
{ user: AT.LingdocsUser | undefined, setUser: React.Dispatch<React.SetStateAction<AT.LingdocsUser | undefined>> }
| undefined
>(undefined);
function UserProvider({ children }: any) {
const [user, setUser] = useState<AT.LingdocsUser | undefined>(undefined);
return <UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>;
}
function useUser() {
const context = React.useContext(UserContext)
if (context === undefined) {
throw new Error('useCount must be used within a CountProvider')
}
return context;
}
export { UserProvider, useUser };

130
yarn.lock
View File

@ -1566,10 +1566,19 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@lingdocs/pashto-inflector@^0.9.3":
version "0.9.6"
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-0.9.6.tgz#c7b5fe2d0c253cdae48151f667b0248600603819"
integrity sha512-/fLCHUdRqCLO9bDDve59yQlRXNd+7gXOeD6aO+IKOBz2KhIzSk7KRctSdM1jfv2E356QU/JmVL/+o0EfdWI33Q==
"@lingdocs/lingdocs-main@^0.0.4":
version "0.0.4"
resolved "https://npm.lingdocs.com/@lingdocs%2flingdocs-main/-/lingdocs-main-0.0.4.tgz#7ca25f48e934f070d3c6048be1787dd8fb3133af"
integrity sha512-6tegCbI7eeq43GIAspoAfGhkCXNbXgH/9immS+WljJ3188LIXFduMikwFbJZYVI7h3eUrOz1CYCB0qbdbLd0Vw==
dependencies:
passport-github2 "^0.1.12"
passport-google-oauth "^2.0.0"
passport-twitter "^1.0.4"
"@lingdocs/pashto-inflector@^1.0.5":
version "1.0.6"
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.0.6.tgz#b61262c04916442a1023002bd7426ee39d77635f"
integrity sha512-yhijpCx1nBHnwykydOPOWzZlA388EioLr/SftNcYgRilxOjHVzXzEHL9dMNxciAiCNKFcNic2UVp+Tha4WEbSA==
dependencies:
classnames "^2.2.6"
pbf "^3.2.1"
@ -1906,6 +1915,11 @@
dependencies:
"@types/unist" "*"
"@types/history@*":
version "4.7.9"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
"@types/invariant@^2.2.29":
version "2.2.35"
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be"
@ -2022,6 +2036,23 @@
dependencies:
"@types/react" "*"
"@types/react-router-dom@^5.1.9":
version "5.1.9"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.9.tgz#e8a8f687351ecc8c68bb4161d7e4b9df4994416e"
integrity sha512-Go0vxZSigXTyXx8xPkGiBrrc3YbBs82KE14WENMLS6TSUKcRFSmYVbL19zFOnNFqJhqrPqEs2h5eUpJhSRrwZw==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
version "5.1.16"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.16.tgz#f3ba045fb96634e38b21531c482f9aeb37608a99"
integrity sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-transition-group@^4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.1.tgz#e1a3cb278df7f47f17b5082b1b3da17170bd44b1"
@ -2883,6 +2914,11 @@ base64-js@^1.0.2:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
base64url@3.x.x:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@ -8106,6 +8142,11 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
oauth@0.9.x:
version "0.9.15"
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -8521,6 +8562,68 @@ pascalcase@^0.1.1:
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
passport-github2@^0.1.12:
version "0.1.12"
resolved "https://registry.yarnpkg.com/passport-github2/-/passport-github2-0.1.12.tgz#a72ebff4fa52a35bc2c71122dcf470d1116f772c"
integrity sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==
dependencies:
passport-oauth2 "1.x.x"
passport-google-oauth1@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc"
integrity sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw=
dependencies:
passport-oauth1 "1.x.x"
passport-google-oauth20@2.x.x:
version "2.0.0"
resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef"
integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==
dependencies:
passport-oauth2 "1.x.x"
passport-google-oauth@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae"
integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA==
dependencies:
passport-google-oauth1 "1.x.x"
passport-google-oauth20 "2.x.x"
passport-oauth1@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.2.0.tgz#5229d431781bf5b265bec86ce9a9cce58a756cf9"
integrity sha512-Sv2YWodC6jN12M/OXwmR4BIXeeIHjjbwYTQw4kS6tHK4zYzSEpxBgSJJnknBjICA5cj0ju3FSnG1XmHgIhYnLg==
dependencies:
oauth "0.9.x"
passport-strategy "1.x.x"
utils-merge "1.x.x"
passport-oauth2@1.x.x:
version "1.6.0"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.0.tgz#5f599735e0ea40ea3027643785f81a3a9b4feb50"
integrity sha512-emXPLqLcVEcLFR/QvQXZcwLmfK8e9CqvMgmOFJxcNT3okSFMtUbRRKpY20x5euD+01uHsjjCa07DYboEeLXYiw==
dependencies:
base64url "3.x.x"
oauth "0.9.x"
passport-strategy "1.x.x"
uid2 "0.0.x"
utils-merge "1.x.x"
passport-strategy@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=
passport-twitter@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/passport-twitter/-/passport-twitter-1.0.4.tgz#01a799e1f760bf2de49f2ba5fba32282f18932d7"
integrity sha1-AaeZ4fdgvy3knyul+6MigvGJMtc=
dependencies:
passport-oauth1 "1.x.x"
xtraverse "0.1.x"
path-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
@ -11792,6 +11895,11 @@ typographic-quotes@^1.2.1:
dependencies:
typographic-quotes-l10n-db "^1.0.0"
uid2@0.0.x:
version "0.0.4"
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44"
integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==
uncontrollable@^7.0.0, uncontrollable@^7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738"
@ -12123,7 +12231,7 @@ utila@^0.4.0, utila@~0.4:
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
utils-merge@1.0.1:
utils-merge@1.0.1, utils-merge@1.x.x:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
@ -12674,6 +12782,11 @@ xmlchars@^2.1.1:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xmldom@0.1.x:
version "0.1.31"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
xregexp@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
@ -12686,6 +12799,13 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
xtraverse@0.1.x:
version "0.1.0"
resolved "https://registry.yarnpkg.com/xtraverse/-/xtraverse-0.1.0.tgz#b741bad018ef78d8a9d2e83ade007b3f7959c732"
integrity sha1-t0G60BjveNip0ug63gB7P3lZxzI=
dependencies:
xmldom "0.1.x"
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"