408 lines
13 KiB
TypeScript
408 lines
13 KiB
TypeScript
// islands/TranslationList.tsx
|
|
import { useEffect, useState } from "preact/hooks";
|
|
import { QuizToTextFile } from "../utils/QuizToTextFile.ts";
|
|
import { TextToQuizTranslations } from "../utils/TextToQuizTranslations.ts";
|
|
import { QuizConfig } from "../utils/types.ts";
|
|
//import { QuizData } from "../utils/database.ts";
|
|
|
|
import { AsyncYesNoPop } from "./AsyncYesNoPop.tsx";
|
|
|
|
export interface Translation {
|
|
id: number;
|
|
quiz_id: number;
|
|
owner_id: string;
|
|
question_text: string;
|
|
meaning_text: string;
|
|
order_num: number;
|
|
}
|
|
|
|
interface TranslationListProps {
|
|
quiz: QuizConfig;
|
|
userName: string;
|
|
}
|
|
|
|
export default function TranslationList(
|
|
{ quiz, userName }: TranslationListProps,
|
|
) {
|
|
// Theme classes are defined in styles.css with @media (prefers-color-scheme: dark)
|
|
|
|
const [ask, popupElement] = AsyncYesNoPop();
|
|
const [quizData, setQuizData] = useState<QuizConfig>(quiz);
|
|
const [quizConfigDataStr, setQuizConfigDataStr] = useState(
|
|
JSON.stringify(quiz),
|
|
);
|
|
const [quizNameStr, setQuizNameStr] = useState(quiz.quiz_name);
|
|
const [text1Str, setText1Str] = useState("");
|
|
const [text2Str, setText2Str] = useState("");
|
|
const [deleteIndexStr, setDeleteIndexStr] = useState("");
|
|
|
|
//const [fileContent, setFileContent] = useState("");
|
|
const [quiz_nameError, setquiz_nameError] = useState(false);
|
|
const [questionsError, setQuestionsError] = useState(false);
|
|
useEffect(() => {
|
|
const lockRoute = localStorage.getItem("lock-route");
|
|
if (lockRoute) {
|
|
globalThis.location.href = lockRoute;
|
|
}
|
|
//fetchQuizData();
|
|
}, []);
|
|
|
|
const justClear = async () => {
|
|
const clearQuiz = {
|
|
active_date: Date.now(),
|
|
quiz_mode: "",
|
|
owner_id: quiz.owner_id,
|
|
quiz_config_id: crypto.randomUUID(),
|
|
quiz_name: "",
|
|
questions: [],
|
|
results: [],
|
|
};
|
|
setQuizData(clearQuiz);
|
|
setQuizConfigDataStr(JSON.stringify(clearQuiz));
|
|
setQuizNameStr("");
|
|
setText1Str("");
|
|
setText2Str("");
|
|
setDeleteIndexStr("");
|
|
};
|
|
|
|
const handleSaveNewQuiz = async () => {
|
|
};
|
|
|
|
const handleUpdateTranslation = async (translation: Translation) => {
|
|
};
|
|
const handleAddTranslation = async () => {
|
|
};
|
|
const handleDeleteTranslation = async (translationId: number) => {
|
|
};
|
|
|
|
const uploadFile = () => {
|
|
setquiz_nameError(false);
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = ".txt";
|
|
input.onchange = async (e) => {
|
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
if (file) {
|
|
const newQuizName = (file.name as string)?.split(".txt")[0]
|
|
?.trim();
|
|
if (!quizNameStr && !newQuizName) {
|
|
setquiz_nameError(true);
|
|
return;
|
|
}
|
|
|
|
if (
|
|
!quizNameStr && newQuizName && newQuizName.length > 0
|
|
) {
|
|
setQuizNameStr(newQuizName);
|
|
}
|
|
|
|
const text = (await file.text())?.trim();
|
|
|
|
if (text && text.length > 0) {
|
|
const textToQuiz = new TextToQuizTranslations(quizData);
|
|
const parsedQuizData = textToQuiz.parseText(text);
|
|
setQuizData(parsedQuizData);
|
|
setQuizConfigDataStr(JSON.stringify(parsedQuizData));
|
|
}
|
|
}
|
|
};
|
|
input.click();
|
|
};
|
|
|
|
const downloadFile = async () => {
|
|
if (quizData.questions.length === 0) {
|
|
setQuestionsError(true);
|
|
return;
|
|
}
|
|
if (!quizNameStr || quizNameStr.length === 0) {
|
|
setquiz_nameError(true);
|
|
return;
|
|
}
|
|
setQuestionsError(false);
|
|
const quizToText = new QuizToTextFile(quizData);
|
|
const content = quizToText.generateTextContent();
|
|
const blob = new Blob([content], { type: "text/plain" });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = `${quizNameStr}.txt`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
const handleSaveAddQuiz = async (event: Event) => {
|
|
event.preventDefault();
|
|
const form = event.currentTarget as HTMLFormElement;
|
|
|
|
if (deleteIndexStr) {
|
|
const userConfirmed = await ask(
|
|
"Are you sure you want to delete?",
|
|
Date.now(),
|
|
);
|
|
|
|
if (userConfirmed) {
|
|
form.submit();
|
|
}
|
|
} else {
|
|
if (!quizNameStr || quizNameStr.length === 0) {
|
|
setquiz_nameError(true);
|
|
} else {
|
|
form.submit();
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{popupElement}
|
|
<div
|
|
className={`theme-container p-2 sm:p-3 md:p-4 rounded-lg shadow-md max-w-md mx-auto w-full overflow-auto`}
|
|
>
|
|
<div className="flex space-x-2 pb-[10px]">
|
|
<button
|
|
onClick={uploadFile}
|
|
className={`flex-1 theme-button py-2 px-2 rounded-md transition duration-300`}
|
|
>
|
|
Add from file
|
|
</button>
|
|
<button
|
|
onClick={downloadFile}
|
|
className={`flex-1 theme-button py-2 px-2 rounded-md transition duration-300`}
|
|
>
|
|
Out to file
|
|
</button>
|
|
<button
|
|
onClick={justClear}
|
|
className={`flex-1 theme-button rounded-md transition duration-300`}
|
|
>
|
|
Clear
|
|
</button>
|
|
</div>
|
|
<form
|
|
action="/language/lists"
|
|
method="POST"
|
|
onSubmit={handleSaveAddQuiz}
|
|
>
|
|
<input
|
|
type="hidden"
|
|
name="quizConfigData"
|
|
value={quizConfigDataStr}
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
name="quizNameStrData"
|
|
value={quizNameStr}
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
name="text1StrData"
|
|
value={text1Str}
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
name="text2StrData"
|
|
value={text2Str}
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
name="deleteIndexStrData"
|
|
value={deleteIndexStr}
|
|
/>
|
|
<div className="mb-4 sm:mb-6">
|
|
<input
|
|
type="text"
|
|
value={quizNameStr}
|
|
onChange={(e) => {
|
|
const target = (e.target as HTMLInputElement).value.trim();
|
|
if (!target) {
|
|
setQuizNameStr(quizNameStr);
|
|
} else {
|
|
setQuizNameStr(target);
|
|
}
|
|
}}
|
|
onKeyDown={() => setquiz_nameError(false)}
|
|
placeholder="Quiz Name"
|
|
className={`w-full p-2 border rounded theme-input ${
|
|
quiz_nameError ? "border-red-500" : ""
|
|
}`}
|
|
/>
|
|
</div>
|
|
<div className="mb-4 sm:mb-6">
|
|
<h3 className={`text-lg sm:text-xl font-semibold mb-2 theme-text`}>
|
|
Add New Translation
|
|
</h3>
|
|
<div className="flex flex-col space-y-2">
|
|
<input
|
|
type="text"
|
|
value={text1Str}
|
|
onChange={(e) => {
|
|
const target = (e.target as HTMLInputElement).value.trim();
|
|
setText1Str(target);
|
|
}}
|
|
placeholder="Bisaya"
|
|
onKeyDown={() => setQuestionsError(false)}
|
|
className={`flex-1 p-2 border rounded theme-input ${
|
|
questionsError ? "border-red-500" : ""
|
|
}`}
|
|
/>
|
|
<input
|
|
type="text"
|
|
value={text2Str}
|
|
onChange={(e) => {
|
|
const target = (e.target as HTMLInputElement).value.trim();
|
|
setText2Str(target);
|
|
}}
|
|
placeholder="English"
|
|
onKeyDown={() => setQuestionsError(false)}
|
|
className={`flex-1 p-2 border rounded theme-input ${
|
|
questionsError ? "border-red-500" : ""
|
|
}`}
|
|
/>
|
|
<button
|
|
type="submit"
|
|
// onClick={handleAddTranslation}
|
|
className={`theme-button px-4 py-2 rounded w-full`}
|
|
>
|
|
Add and Save
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<ul className="space-y-2">
|
|
{quizData.questions.map((translation: any, index: number) => (
|
|
<li
|
|
key={translation.id}
|
|
className={`theme-secondary-container p-1 rounded relative group`}
|
|
>
|
|
<div className="flex flex-col scroll pr-2">
|
|
<div className={`w-full p-2 border rounded mb-2 theme-input`}>
|
|
{translation.text_1}
|
|
</div>
|
|
<div className={`w-full p-2 border rounded theme-input`}>
|
|
{translation.text_2}
|
|
</div>
|
|
</div>
|
|
{!(!!translation.audio_file_1 || !!translation.audio_file_2 ||
|
|
!!translation.video_file || !!translation.video_file_data ||
|
|
!!translation.background_file ||
|
|
!!translation.background_image_data)
|
|
? (
|
|
<button
|
|
type="submit"
|
|
onClick={() => {
|
|
setDeleteIndexStr(`${index}`);
|
|
}}
|
|
className="absolute top-1/2 -right-2 transform -translate-y-1/2 text-red-700 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
)
|
|
: (
|
|
<button
|
|
type="submit"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
}}
|
|
className="absolute top-1/2 -right-2 transform -translate-y-1/2 text-red-700 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<rect
|
|
x="5"
|
|
y="11"
|
|
width="14"
|
|
height="8"
|
|
rx="2"
|
|
stroke="currentColor"
|
|
strokeWidth={2}
|
|
fill="none"
|
|
/>
|
|
<path
|
|
d="M7 11V7a5 5 0 0110 0v4"
|
|
stroke="currentColor"
|
|
strokeWidth={2}
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
fill="none"
|
|
/>
|
|
<circle
|
|
cx="12"
|
|
cy="15"
|
|
r="1"
|
|
fill="currentColor"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</form>
|
|
</div>
|
|
<div class="fixed bottom-4 inset-x-0 flex justify-center z-40 pointer-events-none">
|
|
<div class="flex gap-2 w-11/12 max-w-md pointer-events-auto">
|
|
<span class={`w-[200px] py-3 px-6`}></span>
|
|
<span class={`w-[16px]`}></span>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
globalThis.location.href = "/language/dashboard";
|
|
}}
|
|
class={`w-[200px] theme-button font-semibold py-3 px-6 rounded-full shadow-lg text-center`}
|
|
>
|
|
Dashboard
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
async function makeQuizWithName(
|
|
owner_id: string,
|
|
quiz_name: string,
|
|
): Promise<number> {
|
|
try {
|
|
const response = await fetch("/api/quiz", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
owner_id,
|
|
quiz_name,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
console.error("Error creating quiz:", errorData.error);
|
|
return 0;
|
|
}
|
|
|
|
const result = await response.json();
|
|
return result.quiz_id;
|
|
} catch (error) {
|
|
console.error("Network error when creating quiz:", error);
|
|
return 0;
|
|
}
|
|
}
|