diff --git a/web/src/components/ResourceCard.tsx b/web/src/components/ResourceCard.tsx index c9aa51f2..ba72a6b9 100644 --- a/web/src/components/ResourceCard.tsx +++ b/web/src/components/ResourceCard.tsx @@ -1,6 +1,5 @@ import { getDateTimeString } from "@/helpers/datetime"; import ResourceIcon from "./ResourceIcon"; -import ResourceItemDropdown from "./ResourceItemDropdown"; import "@/less/resource-card.less"; interface Props { @@ -10,13 +9,8 @@ interface Props { const ResourceCard = ({ resource }: Props) => { return (
-
-
- -
-
- +
{resource.filename}
diff --git a/web/src/components/ResourceItemDropdown.tsx b/web/src/components/ResourceItemDropdown.tsx deleted file mode 100644 index 88e4caaa..00000000 --- a/web/src/components/ResourceItemDropdown.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import copy from "copy-to-clipboard"; -import React from "react"; -import toast from "react-hot-toast"; -import { useResourceStore } from "@/store/module"; -import { useTranslate } from "@/utils/i18n"; -import { getResourceType, getResourceUrl } from "@/utils/resource"; -import showChangeResourceFilenameDialog from "./ChangeResourceFilenameDialog"; -import { showCommonDialog } from "./Dialog/CommonDialog"; -import Icon from "./Icon"; -import showPreviewImageDialog from "./PreviewImageDialog"; -import Dropdown from "./kit/Dropdown"; - -interface Props { - resource: Resource; -} - -const ResourceItemDropdown = ({ resource }: Props) => { - const t = useTranslate(); - const resourceStore = useResourceStore(); - - const handlePreviewBtnClick = (resource: Resource) => { - const resourceUrl = getResourceUrl(resource); - if (getResourceType(resource).startsWith("image")) { - showPreviewImageDialog([getResourceUrl(resource)], 0); - } else { - window.open(resourceUrl); - } - }; - - const handleCopyResourceLinkBtnClick = (resource: Resource) => { - const url = getResourceUrl(resource); - copy(url); - toast.success(t("message.succeed-copy-resource-link")); - }; - - const handleRenameBtnClick = (resource: Resource) => { - showChangeResourceFilenameDialog(resource.id, resource.filename); - }; - - const handleDeleteResourceBtnClick = (resource: Resource) => { - let warningText = t("resource.warning-text"); - if (resource.linkedMemoAmount > 0) { - warningText = warningText + `\n${t("resource.linked-amount")}: ${resource.linkedMemoAmount}`; - } - - showCommonDialog({ - title: t("resource.delete-resource"), - content: warningText, - style: "warning", - dialogName: "delete-resource-dialog", - onConfirm: async () => { - await resourceStore.deleteResourceById(resource.id); - }, - }); - }; - - return ( - } - actions={ - <> - - - - - - } - /> - ); -}; - -export default React.memo(ResourceItemDropdown); diff --git a/web/src/components/ResourceSearchBar.tsx b/web/src/components/ResourceSearchBar.tsx deleted file mode 100644 index 8a52df98..00000000 --- a/web/src/components/ResourceSearchBar.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useRef, useState } from "react"; -import useDebounce from "react-use/lib/useDebounce"; -import { useTranslate } from "@/utils/i18n"; -import Icon from "./Icon"; - -interface ResourceSearchBarProps { - setQuery: (queryText: string) => void; -} -const ResourceSearchBar = ({ setQuery }: ResourceSearchBarProps) => { - const t = useTranslate(); - const [queryText, setQueryText] = useState(""); - const inputRef = useRef(null); - - const handleTextQueryInput = (event: React.FormEvent) => { - const text = event.currentTarget.value; - setQueryText(text); - }; - - useDebounce( - () => { - setQuery(queryText); - }, - 200, - [queryText] - ); - - return ( -
-
- - -
-
- ); -}; - -export default ResourceSearchBar; diff --git a/web/src/pages/Resources.tsx b/web/src/pages/Resources.tsx new file mode 100644 index 00000000..75de982f --- /dev/null +++ b/web/src/pages/Resources.tsx @@ -0,0 +1,67 @@ +import { useEffect } from "react"; +import { toast } from "react-hot-toast"; +import Empty from "@/components/Empty"; +import Icon from "@/components/Icon"; +import MobileHeader from "@/components/MobileHeader"; +import ResourceCard from "@/components/ResourceCard"; +import useLoading from "@/hooks/useLoading"; +import { useResourceStore } from "@/store/module"; +import { useTranslate } from "@/utils/i18n"; + +const Resources = () => { + const t = useTranslate(); + const loadingState = useLoading(); + const resourceStore = useResourceStore(); + const resources = resourceStore.state.resources; + + useEffect(() => { + resourceStore + .fetchResourceList() + .then(() => { + loadingState.setFinish(); + }) + .catch((error) => { + console.error(error); + toast.error(error.response.data.message); + }); + }, []); + + return ( +
+ +
+
+

+ {t("common.resources")} +

+
+
+ {loadingState.isLoading ? ( +
+

{t("resource.fetching-data")}

+
+ ) : ( +
+ {resources.length === 0 ? ( +
+ +

{t("message.no-data")}

+
+ ) : ( + resources.map((resource) => ) + )} +
+ )} +
+
+
+ ); +}; + +export default Resources; diff --git a/web/src/pages/ResourcesDashboard.tsx b/web/src/pages/ResourcesDashboard.tsx deleted file mode 100644 index ddfa6468..00000000 --- a/web/src/pages/ResourcesDashboard.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import { Button } from "@mui/joy"; -import { useEffect, useMemo, useState } from "react"; -import { toast } from "react-hot-toast"; -import showCreateResourceDialog from "@/components/CreateResourceDialog"; -import { showCommonDialog } from "@/components/Dialog/CommonDialog"; -import Empty from "@/components/Empty"; -import Icon from "@/components/Icon"; -import MobileHeader from "@/components/MobileHeader"; -import ResourceCard from "@/components/ResourceCard"; -import ResourceSearchBar from "@/components/ResourceSearchBar"; -import Dropdown from "@/components/kit/Dropdown"; -import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; -import useLoading from "@/hooks/useLoading"; -import { useResourceStore } from "@/store/module"; -import { useTranslate } from "@/utils/i18n"; - -const ResourcesDashboard = () => { - const t = useTranslate(); - const loadingState = useLoading(); - const resourceStore = useResourceStore(); - const resources = resourceStore.state.resources; - const [queryText, setQueryText] = useState(""); - const [dragActive, setDragActive] = useState(false); - const [isComplete, setIsComplete] = useState(false); - - useEffect(() => { - resourceStore - .fetchResourceListWithLimit(DEFAULT_MEMO_LIMIT) - .then((fetchedResource) => { - if (fetchedResource.length < DEFAULT_MEMO_LIMIT) { - setIsComplete(true); - } - loadingState.setFinish(); - }) - .catch((error) => { - console.error(error); - toast.error(error.response.data.message); - }); - }, []); - - const handleDeleteUnusedResourcesBtnClick = async () => { - let warningText = t("resource.warning-text-unused"); - const allResources = await fetchAllResources(); - const unusedResources = allResources.filter((resource) => { - if (resource.linkedMemoAmount === 0) { - warningText = warningText + `\n- ${resource.filename}`; - return true; - } - return false; - }); - if (unusedResources.length === 0) { - toast.success(t("resource.no-unused-resources")); - return; - } - - showCommonDialog({ - title: t("resource.delete-resource"), - content: warningText, - style: "warning", - dialogName: "delete-unused-resources", - onConfirm: async () => { - for (const resource of unusedResources) { - await resourceStore.deleteResourceById(resource.id); - } - }, - }); - }; - - const handleFetchMoreResourceBtnClick = async () => { - try { - const fetchedResource = await resourceStore.fetchResourceListWithLimit(DEFAULT_MEMO_LIMIT, resources.length); - if (fetchedResource.length < DEFAULT_MEMO_LIMIT) { - setIsComplete(true); - } else { - setIsComplete(false); - } - } catch (error: any) { - console.error(error); - toast.error(error.response.data.message); - } - }; - - const fetchAllResources = async () => { - if (isComplete) { - return resources; - } - - loadingState.setLoading(); - try { - const allResources = await resourceStore.fetchResourceList(); - loadingState.setFinish(); - setIsComplete(true); - return allResources; - } catch (error: any) { - console.error(error); - toast.error(error.response.data.message); - return resources; - } - }; - - const handleSearchResourceInputChange = async (query: string) => { - // to prevent first tiger when page is loaded - if (query === queryText) return; - await fetchAllResources(); - setQueryText(query); - }; - - const handleDrag = (e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - if (e.type === "dragenter" || e.type === "dragover") { - setDragActive(true); - } else if (e.type === "dragleave") { - setDragActive(false); - } - }; - - const resourceList = useMemo( - () => - resources - .filter((res: Resource) => (queryText === "" ? true : res.filename.toLowerCase().includes(queryText.toLowerCase()))) - .map((resource) => ), - [resources, queryText] - ); - - const handleDrop = async (e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setDragActive(false); - if (e.dataTransfer.files && e.dataTransfer.files[0]) { - await resourceStore.createResourcesWithBlob(e.dataTransfer.files).then( - (res) => { - for (const resource of res) { - toast.success(`${resource.filename} ${t("resource.upload-successfully")}`); - } - }, - (reason) => { - toast.error(reason); - } - ); - } - }; - - return ( -
- -
- {dragActive && ( -
-
-

{t("resource.file-drag-drop-prompt")}

-
-
- )} - -
-
-

- {t("common.resources")} -

- -
-
- - - - - } - actions={ - <> - - - } - /> -
-
- {loadingState.isLoading ? ( -
-

{t("resource.fetching-data")}

-
- ) : ( -
- {resourceList.length === 0 ? ( -
- -

{t("message.no-data")}

-
- ) : ( - resourceList - )} -
- )} -
-
-

- {!isComplete && ( - - {t("memo.fetch-more")} - - )} -

-
-
-
-
- ); -}; - -export default ResourcesDashboard; diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx index 5504d122..10b2abf5 100644 --- a/web/src/router/index.tsx +++ b/web/src/router/index.tsx @@ -3,7 +3,7 @@ import { createBrowserRouter, redirect } from "react-router-dom"; import App from "@/App"; import Archived from "@/pages/Archived"; import DailyReview from "@/pages/DailyReview"; -import ResourcesDashboard from "@/pages/ResourcesDashboard"; +import Resources from "@/pages/Resources"; import Setting from "@/pages/Setting"; import { initialGlobalState, initialUserState } from "@/store/module"; @@ -92,7 +92,7 @@ const router = createBrowserRouter([ }, { path: "resources", - element: , + element: , loader: userStateLoader, }, { diff --git a/web/src/store/module/resource.ts b/web/src/store/module/resource.ts index 614df474..c5af9bdb 100644 --- a/web/src/store/module/resource.ts +++ b/web/src/store/module/resource.ts @@ -1,8 +1,7 @@ import * as api from "@/helpers/api"; -import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; import { useTranslate } from "@/utils/i18n"; import store, { useAppSelector } from "../"; -import { deleteResource, patchResource, setResources, upsertResources } from "../reducer/resource"; +import { deleteResource, patchResource, setResources } from "../reducer/resource"; import { useGlobalStore } from "./global"; const convertResponseModelResource = (resource: Resource): Resource => { @@ -30,16 +29,6 @@ export const useResourceStore = () => { store.dispatch(setResources(resourceList)); return resourceList; }, - async fetchResourceListWithLimit(limit = DEFAULT_MEMO_LIMIT, offset?: number): Promise { - const resourceFind: ResourceFind = { - limit, - offset, - }; - const { data } = await api.getResourceListWithLimit(resourceFind); - const resourceList = data.map((m) => convertResponseModelResource(m)); - store.dispatch(upsertResources(resourceList)); - return resourceList; - }, async createResource(resourceCreate: ResourceCreate): Promise { const { data } = await api.createResource(resourceCreate); const resource = convertResponseModelResource(data);