Skip to content

Commit

Permalink
Merge pull request #73 from zsh-eng/loading-and-done-screen
Browse files Browse the repository at this point in the history
loading and done screen
  • Loading branch information
zsh-eng authored Apr 25, 2024
2 parents aaf391b + 15f243a commit f4e0344
Show file tree
Hide file tree
Showing 15 changed files with 104 additions and 61 deletions.
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

0 comments on commit f4e0344

Please sign in to comment.