Skip to content

Commit

Permalink
add upload button
Browse files Browse the repository at this point in the history
  • Loading branch information
vincerubinetti committed Mar 11, 2024
1 parent c55ee93 commit 5c8ad48
Show file tree
Hide file tree
Showing 11 changed files with 584 additions and 468 deletions.
564 changes: 282 additions & 282 deletions app/src/data/completion.json

Large diffs are not rendered by default.

266 changes: 133 additions & 133 deletions app/src/data/lessons.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/src/data/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** paths read from repo directory structure */
export type Path = {
full: string;
path: string;
year: string;
lesson: string;
language: string;
Expand Down
16 changes: 7 additions & 9 deletions app/src/data/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let paths = globSync("**/**/", {

return {
/** path, minus language */
full: path.split("/").slice(0, -1).join("/"),
path: path.split("/").slice(0, -1).join("/"),
year,
lesson,
language,
Expand Down Expand Up @@ -67,23 +67,21 @@ for (const language of languageList)
completion[language]![topic]![lesson] = 0;
}

for (const { full, lesson, language } of paths) {
for (const { path, lesson, language } of paths) {
/** cross-ref topic */
const topic = topics.find((topic) => topic.lessons.includes(lesson))!;

/** init entry */
const newLesson: Lesson = {
path: full,
path: path,
lesson,
title: lesson,
topic: topic.name,
};

/** get full lesson name */
if (newLesson.title === lesson) {
const titleFile = `../${full}/${language}/title.json`;
newLesson.title = readFile<_Title>(titleFile)?.input || lesson || "";
}
const titleFile = `../${path}/${language}/title.json`;
newLesson.title = readFile<_Title>(titleFile)?.input || lesson || "";

/** find next/prev lessons */
const index = topic.lessons.indexOf(lesson);
Expand All @@ -93,13 +91,13 @@ for (const { full, lesson, language } of paths) {

/** get completion */
const translation =
readFile<_Entry[]>(`../${full}/${language}/sentence_translations.json`) ||
readFile<_Entry[]>(`../${path}/${language}/sentence_translations.json`) ||
[];
const percent = calcCompletion(translation.map(convert), language);
completion[language]![topic.name]![lesson] = clamp(percent, 0, 1);

/** add lesson to list */
lessons[full] = newLesson;
lessons[path] = newLesson;
}

/** calc totals */
Expand Down
44 changes: 34 additions & 10 deletions app/src/pages/Edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { _Entry, Entry } from "@/data/types";
import Footer from "@/pages/edit/Footer";
import Header from "@/pages/edit/Header";
import Section from "@/pages/edit/Section";
import { uploadZip } from "@/util/download";

/** translation edit page */
function Edit() {
Expand All @@ -18,9 +19,17 @@ function Edit() {
<>
<Header />
<main>
<Section label="title" entries={title} />
<Section label="captions" entries={captions} />
<Section label="description" entries={description} />
<Section label="title" entries={title} file="title.json" />
<Section
label="captions"
entries={captions}
file="sentence_translations.json"
/>
<Section
label="description"
entries={description}
file="description.json"
/>
</main>
<Footer />
</>
Expand Down Expand Up @@ -133,33 +142,48 @@ export const loader: LoaderFunction = async ({ params }) => {

setAtom(loading, 0.6);

/** load description entries */
/** load caption entries */
{
const url = `${base}/${path}/${language}/description.json`;
const url = `${base}/${path}/${language}/sentence_translations.json`;
const data = await request<_Entry[]>(url);
if (data) setAtom(description, data.map(convert));
if (data) setAtom(captions, data.map(convert));
}

setAtom(loading, 0.8);

/** load caption entries */
/** load description entries */
{
const url = `${base}/${path}/${language}/sentence_translations.json`;
const url = `${base}/${path}/${language}/description.json`;
const data = await request<_Entry[]>(url);
if (data) setAtom(captions, data.map(convert));
if (data) setAtom(description, data.map(convert));
}

setAtom(loading, 1);

return null;
};

/** import data from zip */
export function importData(files: Awaited<ReturnType<typeof uploadZip>>) {
try {
setAtom(title, [convert(files["title.json"]! as _Entry)]);
setAtom(
captions,
(files["sentence_translations.json"]! as _Entry[]).map(convert),
);
setAtom(description, (files["description.json"]! as _Entry[]).map(convert));
} catch (error) {
console.error(error);
window.alert("Error parsing uploaded file");
}
}

/** clean data for export */
export function exportData() {
return {
title: getAtom(title).map(revert)[0],
description: getAtom(description).map(revert),
sentence_translations: getAtom(captions).map(revert),
description: getAtom(description).map(revert),
};
}

Expand Down
38 changes: 34 additions & 4 deletions app/src/pages/edit/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { FaDownload, FaPaperPlane } from "react-icons/fa6";
import { useRef, useState } from "react";
import { FaDownload, FaPaperPlane, FaUpload } from "react-icons/fa6";
import { ImSpinner8 } from "react-icons/im";
import { useParams } from "react-router";
import { useLocalStorage } from "react-use";
Expand All @@ -16,18 +16,21 @@ import {
filter,
filterFuncs,
filters,
importData,
showLegacy,
sticky,
submitPr,
title,
} from "@/pages/Edit";
import { downloadZip } from "@/util/download";
import { downloadZip, uploadZip } from "@/util/download";
import classes from "./Footer.module.css";

function Footer() {
/** url params */
const { lesson = "", language = "" } = useParams();

const uploadRef = useRef<HTMLInputElement>(null);

/** local state */
const [submitting, setSubmitting] = useState(false);
const [author, setAuthor] = useLocalStorage("3b1b-translations-author", "");
Expand Down Expand Up @@ -92,9 +95,36 @@ function Footer() {
</div>

<div className={classes.row}>
<Button
icon={<FaUpload />}
data-tooltip="Import a previously downloaded zip file of edits"
onClick={(event) =>
(
(event.currentTarget as HTMLButtonElement)
.nextElementSibling as HTMLInputElement
)?.click()
}
/>
<input
ref={uploadRef}
type="file"
accept=".zip"
style={{ display: "none" }}
onChange={async (event) => {
const file = (event.target?.files || [])[0];
if (file) {
const data = await file.arrayBuffer();
importData(await uploadZip(data));
}

/** reset input */
if (uploadRef.current) uploadRef.current.value = "";
}}
/>

<Button
icon={<FaDownload />}
data-tooltip="Download your edits as a backup or to submit a pull request manually."
data-tooltip="Download your edits as a backup or to submit a pull request manually"
onClick={() => downloadZip(exportData(), `${lesson} ${language}`)}
/>

Expand Down
1 change: 1 addition & 0 deletions app/src/pages/edit/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
FaHouse,
FaLock,
FaRegCircleQuestion,
FaUpload,
} from "react-icons/fa6";
import { useParams } from "react-router";
import { Link } from "react-router-dom";
Expand Down
46 changes: 36 additions & 10 deletions app/src/pages/edit/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import {
FaStop,
FaThumbsUp,
} from "react-icons/fa6";
import { LuBraces } from "react-icons/lu";
import { Link, useParams } from "react-router-dom";
import classNames from "classnames";
import { useAtom, useAtomValue } from "jotai";
import { cloneDeep } from "lodash";
import { issueLink } from "@/api";
import { cloneDeep, truncate } from "lodash";
import { issueLink, repoFull } from "@/api";
import { playing, playSegment, stopVideo, time } from "@/components/Player";
import Textarea from "@/components/Textarea";
import { isEdited, originalMax, translationMax } from "@/data/data";
Expand All @@ -21,6 +22,7 @@ import {
captions,
completion,
description,
meta,
showLegacy,
title,
} from "@/pages/Edit";
Expand All @@ -33,10 +35,11 @@ import classes from "./Row.module.css";
type Props = {
index: number;
entries: typeof title | typeof captions | typeof description;
file: string;
};

/** editable entry row */
function Row({ index, entries }: Props) {
function Row({ index, entries, file }: Props) {
/** get/set entry from list of entries and index */
const [getEntries, setEntries] = useAtom(entries);
const entry = getEntries[index]!;
Expand Down Expand Up @@ -64,6 +67,7 @@ function Row({ index, entries }: Props) {
const { lesson = "", language = "" } = useParams();

/** page state */
const getMeta = useAtomValue(meta);
const getTime = useAtomValue(time);
const getPlaying = useAtomValue(playing);
const getShowLegacy = useAtomValue(showLegacy);
Expand All @@ -89,15 +93,29 @@ function Row({ index, entries }: Props) {
/** right to left languages need special styling */
const rtlLanguage = isRtl(language);

/** get (rough) line number in raw json */
const lineNumber = 1 + index * (6 + 2) + 2;
/** get first few words in original english, url-encoded */
const firstFewWords = window.encodeURIComponent(
startingOriginal.split(/\s+/).slice(0, 20).join(" "),
);

/** issue url params */
const issueTitle = `${lesson}/${language}`;
const issueBody = [
`**Line**:\n`,
`${startingOriginal.slice(0, 100)}...\n`,
"\n",
...(start && end ? ["**Time**:\n", `${start} - ${end}\n`, "\n"] : []),
`**Describe the issue**:\n`,
];
["**Original**", truncate(startingOriginal, { length: 300 })],
["**Translation**:", truncate(startingTranslation, { length: 300 })],
start && end ? ["**Time**:", `${start} - ${end}`] : [],
["**Line**", `Entry # ${index}`, `Line # ~${lineNumber}`],
["**Describe the issue**"],
]
.filter((line) => line.length)
.map((line) => line.join("\n"))
.join("\n\n")
.concat("\n");

/** get full path to file and line number */
const path = `${repoFull}/tree/main/${getMeta?.path}/${language}/${file}#:~:text=${firstFewWords}`;

return (
<div
Expand Down Expand Up @@ -194,10 +212,18 @@ function Row({ index, entries }: Props) {
<FaArrowsRotate />
</button>

<Link
to={path}
target="_blank"
data-tooltip="See raw JSON for this entry"
>
<LuBraces />
</Link>

<Link
to={issueLink(issueTitle, issueBody)}
target="_blank"
data-tooltip="Create an issue about this translation"
data-tooltip="Create an issue about this entry"
>
<FaFlag />
</Link>
Expand Down
5 changes: 3 additions & 2 deletions app/src/pages/edit/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import classes from "../Edit.module.css";
type Props = {
label: string;
entries: typeof title | typeof captions | typeof description;
file: string;
};

/** title/caption/description editor rows */
function Section({ label, entries }: Props) {
function Section({ label, entries, file }: Props) {
/** url params */
const { lesson = "", language = "" } = useParams();

Expand All @@ -36,7 +37,7 @@ function Section({ label, entries }: Props) {
<div className={classes.rows}>
{getEntries.map((entry, index) =>
filterFuncs[getFilter](entry) ? (
<Row key={index} index={index} entries={entries} />
<Row key={index} index={index} entries={entries} file={file} />
) : (
<Fragment key={index} />
),
Expand Down
4 changes: 3 additions & 1 deletion app/src/pages/home/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ function Search() {
const filtered = flatLessons.filter(
({ lesson, title, topic, language }) =>
nameTerms.every(
(term) => title.includes(term) || lesson.toLowerCase().includes(term),
(term) =>
title.toLowerCase().includes(term) ||
lesson.toLowerCase().includes(term),
) &&
(topic === "" || topic.toLowerCase().includes(topicSearch)) &&
language.toLowerCase().includes(languageSearch),
Expand Down
Loading

0 comments on commit 5c8ad48

Please sign in to comment.