diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 24652a5..7d390eb 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -88,6 +88,24 @@ export const i18nData = { "User set as user successfully": "User set as user successfully", "User set as upload permission successfully": "User set as upload permission successfully", "User removed upload permission successfully": "User removed upload permission successfully", + + // Resource details page + "Resource ID is required": "Resource ID is required", + "Files": "Files", + "Comments": "Comments", + "Upload": "Upload", + "Create File": "Create File", + "Please select a file type": "Please select a file type", + "Please fill in all fields": "Please fill in all fields", + "File created successfully": "File created successfully", + "Successfully create uploading task.": "Successfully create uploading task.", + "Please select a file and storage": "Please select a file and storage", + "Redirect": "Redirect", + "User who click the file will be redirected to the URL": "User who click the file will be redirected to the URL", + "File Name": "File Name", + "URL": "URL", + "Upload a file to server, then the file will be moved to the selected storage.": "Upload a file to server, then the file will be moved to the selected storage.", + "Select Storage": "Select Storage", } }, "zh-CN": { @@ -179,6 +197,24 @@ export const i18nData = { "User set as user successfully": "用户已成功设为普通用户", "User set as upload permission successfully": "用户已成功授予上传权限", "User removed upload permission successfully": "用户已成功移除上传权限", + + // Resource details page + "Resource ID is required": "资源ID是必需的", + "Files": "文件", + "Comments": "评论", + "Upload": "上传", + "Create File": "创建文件", + "Please select a file type": "请选择文件类型", + "Please fill in all fields": "请填写所有字段", + "File created successfully": "文件创建成功", + "Successfully create uploading task.": "成功创建上传任务。", + "Please select a file and storage": "请选择文件和存储", + "Redirect": "重定向", + "User who click the file will be redirected to the URL": "点击文件的用户将被重定向到URL", + "File Name": "文件名", + "URL": "URL", + "Upload a file to server, then the file will be moved to the selected storage.": "将文件上传到服务器,然后文件将被移动到选定的存储中。", + "Select Storage": "选择存储", } }, "zh-TW": { @@ -270,6 +306,24 @@ export const i18nData = { "User set as user successfully": "用戶已成功設為普通用戶", "User set as upload permission successfully": "用戶已成功授予上傳權限", "User removed upload permission successfully": "用戶已成功移除上傳權限", + + // Resource details page + "Resource ID is required": "資源ID是必需的", + "Files": "檔案", + "Comments": "評論", + "Upload": "上傳", + "Create File": "創建檔案", + "Please select a file type": "請選擇檔案類型", + "Please fill in all fields": "請填寫所有欄位", + "File created successfully": "檔案創建成功", + "Successfully create uploading task.": "成功創建上傳任務。", + "Please select a file and storage": "請選擇檔案和儲存", + "Redirect": "重定向", + "User who click the file will be redirected to the URL": "點擊檔案的用戶將被重定向到URL", + "File Name": "檔案名", + "URL": "URL", + "Upload a file to server, then the file will be moved to the selected storage.": "將檔案上傳到伺服器,然後檔案將被移動到選定的儲存中。", + "Select Storage": "選擇儲存", } } } diff --git a/frontend/src/network/uploading.ts b/frontend/src/network/uploading.ts new file mode 100644 index 0000000..1eb5bad --- /dev/null +++ b/frontend/src/network/uploading.ts @@ -0,0 +1,10 @@ +import {Response} from "./models.ts"; + +class UploadingManager { + async addTask(file: File, resourceID: number, storageID: number, description: string): Promise> { + // TODO: implement this + throw new Error("Not implemented"); + } +} + +export const uploadingManager = new UploadingManager(); \ No newline at end of file diff --git a/frontend/src/pages/resource_details_page.tsx b/frontend/src/pages/resource_details_page.tsx index 0abde45..e2180d1 100644 --- a/frontend/src/pages/resource_details_page.tsx +++ b/frontend/src/pages/resource_details_page.tsx @@ -1,23 +1,27 @@ -import { useParams } from "react-router"; -import {createContext, useCallback, useEffect, useState} from "react"; -import {ResourceDetails, RFile} from "../network/models.ts"; -import { network } from "../network/network.ts"; +import {useParams} from "react-router"; +import {createContext, useCallback, useContext, useEffect, useRef, useState} from "react"; +import {ResourceDetails, RFile, Storage} from "../network/models.ts"; +import {network} from "../network/network.ts"; import showToast from "../components/toast.ts"; import Markdown from "react-markdown"; import "../markdown.css"; import Loading from "../components/loading.tsx"; -import {MdAdd, MdOutlineArticle, MdOutlineComment, MdOutlineDataset} from "react-icons/md"; +import {MdAdd, MdOutlineArticle, MdOutlineComment, MdOutlineDataset, MdOutlineDownload} from "react-icons/md"; import {app} from "../app.ts"; +import {uploadingManager} from "../network/uploading.ts"; +import {ErrorAlert} from "../components/alert.tsx"; +import { useTranslation } from "react-i18next"; export default function ResourcePage() { const params = useParams() + const { t } = useTranslation(); const idStr = params.id const id = idStr ? parseInt(idStr) : NaN const [resource, setResource] = useState(null) - + const reload = useCallback(async () => { if (!isNaN(id)) { setResource(null) @@ -25,7 +29,7 @@ export default function ResourcePage() { if (res.success) { setResource(res.data!) } else { - showToast({ message: res.message, type: "error" }) + showToast({message: res.message, type: "error"}) } } }, [id]) @@ -36,7 +40,7 @@ export default function ResourcePage() { if (res.success) { setResource(res.data!) } else { - showToast({ message: res.message, type: "error" }) + showToast({message: res.message, type: "error"}) } }) } @@ -45,28 +49,29 @@ export default function ResourcePage() { if (isNaN(id)) { return
- Resource ID is required + {t("Resource ID is required")}
} if (!resource) { - return + return } return

{resource.title}

{ - resource.alternativeTitles.map((e) => { - return

{e}

+ resource.alternativeTitles.map((e, i) => { + return

{e}

}) } - +
+ + } -function Files({files}: { files: RFile[]}) { +function Files({files, resourceID}: { files: RFile[], resourceID: number }) { return
{ files.map((file) => { return }) } +
{ app.isAdmin() &&
- +
}
-} \ No newline at end of file +} + +enum FileType { + redirect = "redirect", + upload = "upload", +} + +function CreateFileDialog({resourceId}: { resourceId: number }) { + const { t } = useTranslation(); + const [isLoading, setLoading] = useState(false) + const storages = useRef(null) + const mounted = useRef(true) + + const [fileType, setFileType] = useState(null) + + const [filename, setFilename] = useState("") + const [redirectUrl, setRedirectUrl] = useState("") + const [storage, setStorage] = useState(null) + const [file, setFile] = useState(null) + const [description, setDescription] = useState("") + + const reload = useContext(context) + + const [isSubmitting, setSubmitting] = useState(false) + + const [error, setError] = useState(null) + + useEffect(() => { + mounted.current = true + return () => { + mounted.current = false + } + }, []); + + const submit = async () => { + if (isSubmitting) { + return + } + if (!fileType) { + setError(t("Please select a file type")) + return + } + setSubmitting(true) + if (fileType === FileType.redirect) { + if (!redirectUrl || !filename || !description) { + setError(t("Please fill in all fields")); + setSubmitting(false); + return; + } + const res = await network.createRedirectFile(filename, description, resourceId, redirectUrl); + if (res.success) { + const dialog = document.getElementById("upload_dialog") as HTMLDialogElement + dialog.close() + showToast({message: t("File created successfully"), type: "success"}) + reload() + } else { + setError(res.message) + } + } else { + if (!file || !storage) { + setError(t("Please select a file and storage")) + setSubmitting(false) + return + } + const res = await uploadingManager.addTask(file, resourceId, storage.id, description); + if (res.success) { + const dialog = document.getElementById("upload_dialog") as HTMLDialogElement + dialog.close() + showToast({message: t("Successfully create uploading task."), type: "success"}) + reload() + } else { + setError(res.message) + } + } + } + + return <> + + +
+

{t("Create File")}

+ +

{t("Type")}

+
+ { + setFileType(null); + }}/> + { + setFileType(FileType.redirect); + }}/> + { + setFileType(FileType.upload); + }}/> +
+ + { + fileType === FileType.redirect && <> +

{t("User who click the file will be redirected to the URL")}

+ { + setFilename(e.target.value) + }}/> + { + setRedirectUrl(e.target.value) + }}/> + { + setDescription(e.target.value) + }}/> + + } + + { + fileType === FileType.upload && <> +

{t("Upload a file to server, then the file will be moved to the selected storage.")}

+ + + { + if (e.target.files) { + setFile(e.target.files[0]) + } + }}/> + + { + setDescription(e.target.value) + }}/> + + } + + {error && } + +
+
+ +
+ +
+
+
+ +} +