Skip to content
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
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@fontsource/noto-sans-arabic": "5.2.10",
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-brands-svg-icons": "6.6.0",
"@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@google-cloud/translate": "9.3.0",
Expand Down
3 changes: 3 additions & 0 deletions src/components/Icon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { config } from "@fortawesome/fontawesome-svg-core";
import * as FaSolid from "@fortawesome/free-solid-svg-icons";
import * as FaRegular from "@fortawesome/free-regular-svg-icons";
import * as FaBrands from "@fortawesome/free-brands-svg-icons";
import {
FontAwesomeIcon,
Expand Down Expand Up @@ -30,6 +31,8 @@ const iconMap = {
"chevron-down": FaSolid.faChevronDown,
"chevron-up": FaSolid.faChevronUp,
"chart-line": FaSolid.faChartLine,
circle: FaSolid.faCircle,
"circle-hollow": FaRegular.faCircle,
"circle-play": FaSolid.faCirclePlay,
close: FaSolid.faXmark,
database: FaSolid.faDatabase,
Expand Down
6 changes: 6 additions & 0 deletions src/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,12 @@
"title": "اختر الفصل",
"reference": "مرجع الفصل",
"back_to_books": "العودة إلى الأسفار",
"book_column": "السفر",
"glosses_column": "اللمعات",
"status_complete": "مكتمل",
"status_none": "لا شيء",
"status_many": "كثير",
"status_some": "بعض",
"invalid": "يرجى إدخال مرجع فصل صحيح",
"cancel": "إلغاء",
"go": "اذهب",
Expand Down
6 changes: 6 additions & 0 deletions src/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,12 @@
"title": "Choose chapter",
"reference": "Chapter reference",
"back_to_books": "Back to books",
"book_column": "Book",
"glosses_column": "Glosses",
"status_complete": "Complete",
"status_none": "None",
"status_many": "Many",
"status_some": "Some",
"invalid": "Please enter a valid chapter reference",
"cancel": "Cancel",
"go": "Go",
Expand Down
90 changes: 73 additions & 17 deletions src/ui/study/components/ChapterPickerDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ import {
} from "@/verse-utils";
import { SubmitEvent, useEffect, useMemo, useRef, useState } from "react";
import { useTranslations } from "use-intl";
import { ProgressByBookIdReadModel } from "../readModels/getReadBookProgressReadModel";

interface ChapterPickerDialogProps {
chapterId: string;
progressByBookId: ProgressByBookIdReadModel;
onCancel(): void;
onSubmit(chapterId: string): void;
}

export default function ChapterPickerDialog({
chapterId,
progressByBookId,
onCancel,
onSubmit,
}: ChapterPickerDialogProps) {
Expand Down Expand Up @@ -159,24 +162,77 @@ export default function ChapterPickerDialog({
</ol>
<div className="flex-1" />
</>
: <ol className="flex-1 min-h-0 overflow-y-auto px-3">
{options.books.map((book) => {
return (
<li key={book.id}>
<Button
variant="tertiary"
className="w-full h-9 justify-start"
tabIndex={-1}
onClick={() => {
setReference(book.name);
}}
: <div className="relative flex-1 min-h-0 grid grid-cols-[auto_1fr] overflow-y-auto gap-x-4">
<div className="sticky z-10 top-0 bg-white dark:bg-gray-900 grid grid-cols-subgrid col-span-2 border-b-2 border-green-300">
<div className="ps-3 uppercase font-bold text-sm">
{t("book_column")}
</div>
<div className="pe-3 uppercase font-bold text-sm">
{t("glosses_column")}
</div>
</div>
<ol className="grid grid-cols-subgrid col-span-2 px-3">
{options.books.map((book) => {
const progress = progressByBookId[book.id.toString()];

return (
<li
key={book.id}
className="grid grid-cols-subgrid col-span-2 items-baseline py-0.5"
>
{book.name}
</Button>
</li>
);
})}
</ol>
<Button
variant="tertiary"
className="justify-start"
tabIndex={-1}
onClick={() => {
setReference(book.name);
}}
>
{book.name}
</Button>
<div className="text-sm">
{progress.approvedWords === progress.totalWords ?
<>
<Icon
icon="check-circle"
className="mt-1 text-green-500 me-1"
fixedWidth
/>
{t("status_complete")}
</>
: progress.approvedWords === 0 ?
<>
<Icon
icon="xmark"
className="mt-1 text-red-500 me-1"
fixedWidth
/>
{t("status_none")}
</>
: progress.approvedWords / progress.totalWords > 0.8 ?
<>
<Icon
icon="circle"
className="mt-1 text-brown-500 me-1"
fixedWidth
/>
{t("status_many")}
</>
: <>
<Icon
icon="circle-hollow"
className="mt-1 text-brown-500 me-1"
fixedWidth
/>
{t("status_some")}
</>
}
</div>
</li>
);
})}
</ol>
</div>
}

<div className="flex justify-end gap-2">
Expand Down
8 changes: 7 additions & 1 deletion src/ui/study/components/CommandInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ import {
} from "@/verse-utils";
import { useNavigate, useParams } from "@tanstack/react-router";
import ChapterPickerDialog from "./ChapterPickerDialog";
import { ProgressByBookIdReadModel } from "../readModels/getReadBookProgressReadModel";

export default function CommandInput() {
export default function CommandInput({
progressByBookId,
}: {
progressByBookId: ProgressByBookIdReadModel;
}) {
const { chapterId, code: languageCode } = useParams({
from: "/_main/read/$code/$chapterId",
});
Expand Down Expand Up @@ -123,6 +128,7 @@ export default function CommandInput() {
{openPicker && (
<ChapterPickerDialog
chapterId={chapterId}
progressByBookId={progressByBookId}
onCancel={() => setOpenPicker(false)}
onSubmit={async (nextChapterId) => {
await navigate({
Expand Down
5 changes: 4 additions & 1 deletion src/ui/study/components/ReadingToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ import SettingsMenu from "./SettingsMenu";
import CommandInput from "./CommandInput";
import { useFlash } from "@/flash";
import { useNavigate, useParams } from "@tanstack/react-router";
import { ProgressByBookIdReadModel } from "../readModels/getReadBookProgressReadModel";

export interface TranslationToolbarProps {
languages: { englishName: string; localName: string; code: string }[];
progressByBookId: ProgressByBookIdReadModel;
children: ReactNode;
}

export default function ReadingToolbar({
languages,
progressByBookId,
children,
}: TranslationToolbarProps) {
const t = useTranslations("ReadingToolbar");
Expand Down Expand Up @@ -48,7 +51,7 @@ export default function ReadingToolbar({
shadow-md dark:shadow-none dark:border-b dark:border-gray-700 bg-white dark:bg-gray-900
"
>
<CommandInput />
<CommandInput progressByBookId={progressByBookId} />
<ComboboxInput
id="target-language"
items={languages.map((l) => ({
Expand Down
60 changes: 60 additions & 0 deletions src/ui/study/readModels/getReadBookProgressReadModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { getDb } from "@/db";

export interface BookProgressReadModel {
totalWords: number;
approvedWords: number;
}

export type ProgressByBookIdReadModel = Record<string, BookProgressReadModel>;

export async function getReadBookProgressReadModel(
code: string,
): Promise<ProgressByBookIdReadModel> {
const language = await getDb()
.selectFrom("language")
.select("id")
.where("code", "=", code)
.executeTakeFirst();

if (!language) {
return {};
}

const bookProgressRows = await getDb()
.with("word_count", (db) =>
db
.selectFrom("book_word_map")
.groupBy("book_id")
.select(["book_id", (eb) => eb.fn.countAll<number>().as("count")]),
)
.with("book_progress", (db) =>
db
.selectFrom("book_completion_progress")
.where("language_id", "=", language.id)
.groupBy("book_id")
.select([
"book_id",
(eb) => eb.fn.sum<number>("word_count").as("count"),
]),
)
.selectFrom("book")
.innerJoin("word_count", "word_count.book_id", "book.id")
.leftJoin("book_progress", "book_progress.book_id", "book.id")
.select([
"book.id as bookId",
"word_count.count as totalWords",
(eb) =>
eb.fn.coalesce("book_progress.count", eb.lit(0)).as("approvedWords"),
])
.execute();

const progressByBookId: ProgressByBookIdReadModel = {};
for (const row of bookProgressRows) {
progressByBookId[row.bookId.toString()] = {
totalWords: row.totalWords,
approvedWords: row.approvedWords,
};
}

return progressByBookId;
}
4 changes: 2 additions & 2 deletions src/ui/study/routes/$code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ export const Route = createFileRoute("/_main/read/$code")({
});

function ReadingLayout() {
const { languages } = Route.useLoaderData();
const { languages, progressByBookId } = Route.useLoaderData();

return (
<ReadingToolbar languages={languages}>
<ReadingToolbar languages={languages} progressByBookId={progressByBookId}>
<Outlet />
</ReadingToolbar>
);
Expand Down
6 changes: 4 additions & 2 deletions src/ui/study/serverFns/getReadLayoutData.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { createServerFn } from "@tanstack/react-start";
import * as z from "zod";
import { getReadLanguagesReadModel } from "../readModels/getReadLanguagesReadModel";
import { getReadBookProgressReadModel } from "../readModels/getReadBookProgressReadModel";

const requestSchema = z.object({
code: z.string(),
});

export const getReadLayoutData = createServerFn({ method: "GET" })
.inputValidator((input: unknown) => requestSchema.parse(input))
.handler(async () => {
.handler(async ({ data }) => {
const languages = await getReadLanguagesReadModel();
const progressByBookId = await getReadBookProgressReadModel(data.code);

return { languages };
return { languages, progressByBookId };
});
Loading