Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loading and done screen #73

Merged
merged 6 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Rating } from "@/schema";

/**
* The limit below which we should refetch cards.
*/
export const THRESHOLD_CARDS_FOR_REFETCH = 10;

export const KEY_RATING_AGAIN = "1";
export const KEY_RATING_HARD = "2";
export const KEY_RATING_GOOD = "3";
export const KEY_RATING_EASY = "4";

export const KEY_TO_RATING: Record<string, Rating> = {
[KEY_RATING_AGAIN]: "Again",
[KEY_RATING_HARD]: "Hard",
[KEY_RATING_GOOD]: "Good",
[KEY_RATING_EASY]: "Easy",
[" "]: "Good",
};

export const RATING_TO_KEY: Record<Rating, string | undefined> = {
Again: KEY_RATING_AGAIN,
Hard: KEY_RATING_HARD,
Good: KEY_RATING_GOOD,
Easy: KEY_RATING_EASY,
Manual: undefined,
};
2 changes: 2 additions & 0 deletions src/components/client-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"use client";
import { FetchIndicator } from "@/components/fetch-indicator";
// See https://stackoverflow.com/questions/74992326/does-use-client-in-next-js-13-root-layout-make-whole-routes-client-component

import { NavigationBar } from "@/components/nav-bar";
Expand Down Expand Up @@ -27,6 +28,7 @@ function ClientLayout({ children }: PropsWithChildren<{}>) {
duration: 1000,
}}
/>
<FetchIndicator />
</ThemeProvider>
</FlashcardSessionProvider>
</HistoryProvider>
Expand Down
26 changes: 26 additions & 0 deletions src/components/fetch-indicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Button } from "@/components/ui/button";
import { trpc } from "@/utils/trpc";
import { cn } from "@/utils/ui";
import { useMediaQuery } from "@uidotdev/usehooks";
import { Loader2 } from "lucide-react";

export function FetchIndicator() {
const { isLoading, isFetching } = trpc.card.sessionData.useQuery();

const isMobile = useMediaQuery("only screen and (max-width: 640px)");

return isMobile ? null : (
<Button
className={cn(
"fixed right-4 top-16 cursor-default transition duration-500",
!isLoading && isFetching ? "opacity-100" : "opacity-0",
)}
variant="secondary"
>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Fetching Cards...
</Button>
);
}

FetchIndicator.displayName = "FetchIndicator";
18 changes: 7 additions & 11 deletions src/components/flashcard/main/editable-flashcard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,19 @@ export function EditableFlashcard({ form, setOpen, open, editing }: Props) {
if (milkdown) milkdown.focus();
};

const editorContainerClasses = cn(
"col-span-8 flex h-full min-h-80 w-full items-center justify-center overflow-y-auto rounded-md border border-input transition duration-300 sm:col-span-4 sm:min-h-96",
editing ? "bg-muted/70" : "",
);

return (
<Form {...form}>
<div
className={cn(
"col-span-8 flex h-full min-h-80 w-full items-center justify-center overflow-y-auto rounded-md border border-input sm:col-span-4 sm:min-h-96",
editing ? "bg-muted" : "",
)}
onClick={onContainerFocus}
>
<div className={cn(editorContainerClasses)} onClick={onContainerFocus}>
<FormMarkdownEditor name="question" form={form} disabled={!editing} />
</div>

<div
className={cn(
"relative col-span-8 flex h-full min-h-80 w-full items-center justify-center rounded-md border border-input sm:col-span-4 sm:min-h-96",
editing ? "bg-muted" : "",
)}
className={cn(editorContainerClasses, "relative")}
onClick={onContainerFocus}
>
<div
Expand Down
21 changes: 14 additions & 7 deletions src/components/flashcard/main/flashcard-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Flashcard from "@/components/flashcard/main/flashcard";
import { useFlashcardSession } from "@/providers/flashcard-session";
import { getReviewDateForEachRating } from "@/utils/fsrs";
import { Loader2 } from "lucide-react";
import { Bug, Telescope } from "lucide-react";

type Props = {};

Expand All @@ -21,24 +21,31 @@ export default function FlashcardBox({}: Props) {

if (isLoading) {
return (
<div className="col-span-12 flex h-96 items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-accentblue" />
<div className="col-span-12 flex h-2/3 flex-col items-center justify-center">
<Telescope className="h-24 w-24 animate-bounce" strokeWidth={1.5} />
<div className="text-muted-foreground">
Fetching cards, hold on tight...
</div>
</div>
);
}

if (error) {
return (
<div className="col-span-12 flex h-96 items-center justify-center">
<div>{error}</div>
<div className="col-span-12 flex h-2/3 flex-col items-center justify-center gap-2">
<Bug className="h-24 w-24" strokeWidth={1.5} />
<div>Uh oh, something went wrong:</div>
<div className="font-mono">{error ?? "Error message here."}</div>
</div>
);
}

if (!currentCard) {
return (
<div className="flex h-96 w-full items-center justify-center">
<div>All done for today!</div>
<div className="col-span-12 flex h-2/3 flex-col items-center justify-center">
<Telescope className="h-24 w-24" strokeWidth={1.5} />
<div className="mt-2 text-muted-foreground">All done for now!</div>
<div className="text-muted-foreground">Check back later?</div>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/flashcard/main/flashcard-menu-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function FlashcardMenuBar({

<TooltipProvider>
<Tooltip>
<TooltipTrigger className="cursor-text" onClick={onSkip}>
<TooltipTrigger className="cursor-text">
<Toggle
aria-label="toggle edit"
pressed={editing}
Expand Down
6 changes: 3 additions & 3 deletions src/components/flashcard/main/flashcard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import AnswerButtons from "@/components/flashcard/main/answer-buttons";
import RatingButtons from "@/components/flashcard/main/rating-buttons";
import { EditableFlashcard } from "@/components/flashcard/main/editable-flashcard";
import { FlashcardMenuBar } from "@/components/flashcard/main/flashcard-menu-bar";
import { SwipeActionText } from "@/components/flashcard/main/swipe-action";
Expand Down Expand Up @@ -289,10 +289,10 @@ export default function Flashcard({
></div>

<div
className="z-20 mb-6 w-full sm:static sm:mx-auto sm:w-max"
className="z-20 mb-6 w-full sm:static sm:mx-auto sm:mt-2 sm:w-max"
ref={answerButtonsContainerRef}
>
<AnswerButtons
<RatingButtons
schemaRatingToReviewDay={schemaRatingToReviewDay}
onRating={onRating}
open={open}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { RATING_TO_KEY } from "@/common";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import {
Tooltip,
TooltipContent,
Expand All @@ -17,7 +19,7 @@ type Props = {
setOpen: (open: boolean) => void;
};

function AnswerButton({
function RatingButton({
rating,
onRating,
dateString,
Expand All @@ -28,6 +30,7 @@ function AnswerButton({
onRating: (rating: Rating) => void;
dateString: string;
}) {
const key = RATING_TO_KEY[rating] ?? "";
return (
<TooltipProvider key={rating}>
<Tooltip>
Expand All @@ -41,7 +44,8 @@ function AnswerButton({
<div className="sm:hidden">{dateString}</div>
</Button>
</TooltipTrigger>
<TooltipContent>
<TooltipContent className="flex items-center">
<Kbd className="text-md mr-2">{key}</Kbd>
<p>{dateString}</p>
</TooltipContent>
</Tooltip>
Expand All @@ -50,9 +54,9 @@ function AnswerButton({
}

/**
* The buttons to answer a flashcard.
* The buttons to rate a flashcard.
*/
export default function AnswerButtons({
export default function RatingButtons({
schemaRatingToReviewDay,
onRating,
open,
Expand All @@ -70,7 +74,7 @@ export default function AnswerButtons({
{open ? (
ratingsToShow.map((rating) => {
return (
<AnswerButton
<RatingButton
key={rating}
rating={rating}
onRating={onRating}
Expand All @@ -95,4 +99,4 @@ export default function AnswerButtons({
);
}

AnswerButtons.displayName = "AnswerButtons";
RatingButtons.displayName = "RatingButtons";
5 changes: 2 additions & 3 deletions src/hooks/card/use-delete-card.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { THRESHOLD_CARDS_FOR_REFETCH } from "@/common";
import { type Card } from "@/schema";
import { removeCardFromSessionData } from "@/utils/session";
import { ReactQueryOptions, trpc } from "@/utils/trpc";

type DeleteMutationOptions = ReactQueryOptions["card"]["delete"];
type DeleteMutation = ReturnType<typeof trpc.card.delete.useMutation>;

const THRESHOLD_FOR_REFETCH = 10;

/**
* Hook to delete a {@link Card}.
*/
Expand Down Expand Up @@ -50,7 +49,7 @@ export function useDeleteCard(options?: DeleteMutationOptions): DeleteMutation {
if (
!sessionData ||
sessionData.reviewCards.length + sessionData.newCards.length >
THRESHOLD_FOR_REFETCH
THRESHOLD_CARDS_FOR_REFETCH
)
return;
await utils.card.sessionData.invalidate();
Expand Down
1 change: 0 additions & 1 deletion src/hooks/card/use-edit-card.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getSessionCardWithContentId } from "@/utils/session";
import { ReactQueryOptions, trpc } from "@/utils/trpc";
import { produce } from "immer";
import { toast } from "sonner";
Expand Down
5 changes: 2 additions & 3 deletions src/hooks/card/use-grade-card.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { THRESHOLD_CARDS_FOR_REFETCH } from "@/common";
import { removeCardFromSessionData } from "@/utils/session";
import { ReactQueryOptions, trpc } from "@/utils/trpc";
import { toast } from "sonner";

type GradeMutationOptions = ReactQueryOptions["card"]["grade"];
type GradeMutation = ReturnType<typeof trpc.card.grade.useMutation>;

const THRESHOLD_FOR_REFETCH = 10;

/**
* Hook to grade a card.
*/
Expand Down Expand Up @@ -39,7 +38,7 @@ export function useGradeCard(options?: GradeMutationOptions): GradeMutation {
if (
!sessionData ||
sessionData.reviewCards.length + sessionData.newCards.length >
THRESHOLD_FOR_REFETCH
THRESHOLD_CARDS_FOR_REFETCH
)
return;

Expand Down
6 changes: 0 additions & 6 deletions src/hooks/card/use-manual-grade-card.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import {
removeCardFromSessionData,
updateCardInSessionData,
} from "@/utils/session";
import { ReactQueryOptions, trpc } from "@/utils/trpc";
import { toast } from "sonner";

type ManualGradeMutationOptions = ReactQueryOptions["card"]["manualGrade"];
type ManualGradeMutation = ReturnType<typeof trpc.card.manualGrade.useMutation>;

const THRESHOLD_FOR_REFETCH = 10;

/**
* Hook to grade a card.
*/
Expand Down
5 changes: 2 additions & 3 deletions src/hooks/card/use-suspend.card.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { THRESHOLD_CARDS_FOR_REFETCH } from "@/common";
import { removeCardFromSessionData } from "@/utils/session";
import { ReactQueryOptions, trpc } from "@/utils/trpc";
import { isBefore } from "date-fns";
Expand All @@ -6,8 +7,6 @@ import { toast } from "sonner";
type SuspendCardMutationOptions = ReactQueryOptions["card"]["suspend"];
type SuspendMutation = ReturnType<typeof trpc.card.suspend.useMutation>;

const THRESHOLD_FOR_REFETCH = 10;

export function useSuspendCard(
options?: SuspendCardMutationOptions,
): SuspendMutation {
Expand All @@ -32,7 +31,7 @@ export function useSuspendCard(
if (
!sessionData ||
sessionData.reviewCards.length + sessionData.newCards.length >
THRESHOLD_FOR_REFETCH
THRESHOLD_CARDS_FOR_REFETCH
)
return;

Expand Down
24 changes: 7 additions & 17 deletions src/hooks/use-keydown-rating.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { KEY_TO_RATING } from "@/common";
import { Rating, ratings } from "@/schema";
import { useEffect } from "react";

Expand All @@ -15,27 +16,16 @@ export default function useKeydownRating(
onOpen();
return;
}

switch (event.key) {
case "1":
onRating(ratings[1]);
break;
case "2":
onRating(ratings[2]);
break;
case "3":
case " ":
onRating(ratings[3]);
break;
case "4":
onRating(ratings[4]);
break;
const rating = KEY_TO_RATING[event.key];
if (!rating) {
return;
}
onRating(rating);
};

window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyDown);
};
}, [onRating, open, onOpen]);
}
1 change: 1 addition & 0 deletions src/providers/history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export function HistoryProvider({ children }: PropsWithChildren<{}>) {
document.removeEventListener("keydown", handler);
};
});

const state = {
entries,
add,
Expand Down