diff --git a/web/src/components/ResourceCard.tsx b/web/src/components/ResourceCard.tsx index 6ed5ca75..9ff41b06 100644 --- a/web/src/components/ResourceCard.tsx +++ b/web/src/components/ResourceCard.tsx @@ -1,74 +1,26 @@ import dayjs from "dayjs"; import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { toast } from "react-hot-toast"; -import { useResourceStore } from "../store/module"; import Icon from "./Icon"; -import copy from "copy-to-clipboard"; -import { getResourceUrl } from "../utils/resource"; -import showPreviewImageDialog from "./PreviewImageDialog"; -import Dropdown from "./base/Dropdown"; import ResourceCover from "./ResourceCover"; -import { showCommonDialog } from "./Dialog/CommonDialog"; -import showChangeResourceFilenameDialog from "./ChangeResourceFilenameDialog"; import "../less/resource-card.less"; +import ResourceItemDropdown from "./ResourceItemDropdown"; -interface ResourceProps { - resource: Resource; - handlecheckClick: () => void; - handleUncheckClick: () => void; -} - -const ResourceCard = ({ resource, handlecheckClick, handleUncheckClick }: ResourceProps) => { +const ResourceCard = ({ + resource, + handleCheckClick, + handleUncheckClick, + handlePreviewBtnClick, + handleCopyResourceLinkBtnClick, + handleRenameBtnClick, + handleDeleteResourceBtnClick, +}: ResourceItemType) => { const [isSelected, setIsSelected] = useState(false); - const resourceStore = useResourceStore(); - const resources = resourceStore.state.resources; - const { t } = useTranslation(); - - const handleRenameBtnClick = (resource: Resource) => { - showChangeResourceFilenameDialog(resource.id, resource.filename); - }; - - const handleDeleteResourceBtnClick = (resource: Resource) => { - let warningText = t("resources.warning-text"); - if (resource.linkedMemoAmount > 0) { - warningText = warningText + `\n${t("resources.linked-amount")}: ${resource.linkedMemoAmount}`; - } - - showCommonDialog({ - title: t("resources.delete-resource"), - content: warningText, - style: "warning", - dialogName: "delete-resource-dialog", - onConfirm: async () => { - await resourceStore.deleteResourceById(resource.id); - }, - }); - }; - - const handlePreviewBtnClick = (resource: Resource) => { - const resourceUrl = getResourceUrl(resource); - if (resource.type.startsWith("image")) { - showPreviewImageDialog( - resources.filter((r) => r.type.startsWith("image")).map((r) => getResourceUrl(r)), - resources.findIndex((r) => r.id === resource.id) - ); - } else { - window.open(resourceUrl); - } - }; - - const handleCopyResourceLinkBtnClick = (resource: Resource) => { - const url = getResourceUrl(resource); - copy(url); - toast.success(t("message.succeed-copy-resource-link")); - }; const handleSelectBtnClick = () => { if (isSelected) { handleUncheckClick(); } else { - handlecheckClick(); + handleCheckClick(); } setIsSelected(!isSelected); }; @@ -79,40 +31,15 @@ const ResourceCard = ({ resource, handlecheckClick, handleUncheckClick }: Resour
handleSelectBtnClick()}> {isSelected ? : }
- - } - actions={ - <> - - - - - - } - /> +
+ +
diff --git a/web/src/components/ResourceItem.tsx b/web/src/components/ResourceItem.tsx new file mode 100644 index 00000000..164ac8c8 --- /dev/null +++ b/web/src/components/ResourceItem.tsx @@ -0,0 +1,46 @@ +import { useState } from "react"; +import ResourceItemDropdown from "./ResourceItemDropdown"; + +const ResourceItem = ({ + resource, + handleCheckClick, + handleUncheckClick, + handlePreviewBtnClick, + handleCopyResourceLinkBtnClick, + handleRenameBtnClick, + handleDeleteResourceBtnClick, +}: ResourceItemType) => { + const [isSelected, setIsSelected] = useState(false); + + const handleSelectBtnClick = () => { + if (isSelected) { + handleUncheckClick(); + } else { + handleCheckClick(); + } + setIsSelected(!isSelected); + }; + + return ( +
+ + + + {resource.id} + handleRenameBtnClick(resource)}> + {resource.filename} + +
+ +
+
+ ); +}; + +export default ResourceItem; diff --git a/web/src/components/ResourceItemDropdown.tsx b/web/src/components/ResourceItemDropdown.tsx new file mode 100644 index 00000000..f39dc640 --- /dev/null +++ b/web/src/components/ResourceItemDropdown.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import Dropdown from "./base/Dropdown"; +import Icon from "./Icon"; + +interface ResourceItemDropdown { + resource: Resource; + handleRenameBtnClick: (resource: Resource) => void; + handleDeleteResourceBtnClick: (resource: Resource) => void; + handlePreviewBtnClick: (resource: Resource) => void; + handleCopyResourceLinkBtnClick: (resource: Resource) => void; +} + +const ResourceItemDropdown = ({ + resource, + handlePreviewBtnClick, + handleCopyResourceLinkBtnClick, + handleRenameBtnClick, + handleDeleteResourceBtnClick, +}: ResourceItemDropdown) => { + const { t } = useTranslation(); + + return ( + } + actions={ + <> + + + + + + } + /> + ); +}; + +export default React.memo(ResourceItemDropdown); diff --git a/web/src/hooks/useListStyle.ts b/web/src/hooks/useListStyle.ts new file mode 100644 index 00000000..528bae36 --- /dev/null +++ b/web/src/hooks/useListStyle.ts @@ -0,0 +1,17 @@ +import { useState } from "react"; + +const useListStyle = () => { + // true is Table Style, false is Grid Style + const [listStyle, setListStyle] = useState(false); + + return { + listStyle: listStyle, + setToTableStyle: () => { + setListStyle(true); + }, + setToGridStyle: () => { + setListStyle(false); + }, + }; +}; +export default useListStyle; diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 7fa5c706..090bd355 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -81,7 +81,8 @@ "delete-selected-resources": "Delete Selected Resources", "no-files-selected": "No files selected❗", "upload-successfully": "Upload successfully", - "file-drag-drop-prompt": "Drag and drop your file here to upload file" + "file-drag-drop-prompt": "Drag and drop your file here to upload file", + "select": "Select" }, "archived": { "archived-memos": "Archived Memos", diff --git a/web/src/locales/zh-Hant.json b/web/src/locales/zh-Hant.json index 0b0b6365..7c902224 100644 --- a/web/src/locales/zh-Hant.json +++ b/web/src/locales/zh-Hant.json @@ -81,7 +81,8 @@ "delete-selected-resources": "刪除選中資源", "no-files-selected": "沒有文件被選中❗", "upload-successfully": "上傳成功", - "file-drag-drop-prompt": "將您的文件拖放到此處以上傳文件" + "file-drag-drop-prompt": "將您的文件拖放到此處以上傳文件", + "select": "選擇" }, "archived": { "archived-memos": "已封存的 Memo", diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index e87df674..addb45b5 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -81,7 +81,8 @@ "delete-selected-resources": "删除选中资源", "no-files-selected": "没有文件被选中❗", "upload-successfully": "上传成功", - "file-drag-drop-prompt": "将您的文件拖放到此处以上传文件" + "file-drag-drop-prompt": "将您的文件拖放到此处以上传文件", + "select": "选择" }, "archived": { "archived-memos": "已归档的 Memo", diff --git a/web/src/pages/ResourcesDashboard.tsx b/web/src/pages/ResourcesDashboard.tsx index d80a1393..84da419b 100644 --- a/web/src/pages/ResourcesDashboard.tsx +++ b/web/src/pages/ResourcesDashboard.tsx @@ -1,5 +1,5 @@ import { Button } from "@mui/joy"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import useLoading from "../hooks/useLoading"; @@ -7,10 +7,16 @@ import { useResourceStore } from "../store/module"; import Icon from "../components/Icon"; import ResourceCard from "../components/ResourceCard"; import ResourceSearchBar from "../components/ResourceSearchBar"; -import { showCommonDialog } from "../components/Dialog/CommonDialog"; -import showCreateResourceDialog from "../components/CreateResourceDialog"; import MobileHeader from "../components/MobileHeader"; import Dropdown from "../components/base/Dropdown"; +import ResourceItem from "../components/ResourceItem"; +import { showCommonDialog } from "../components/Dialog/CommonDialog"; +import showChangeResourceFilenameDialog from "../components/ChangeResourceFilenameDialog"; +import copy from "copy-to-clipboard"; +import { getResourceUrl } from "../utils/resource"; +import showPreviewImageDialog from "../components/PreviewImageDialog"; +import showCreateResourceDialog from "../components/CreateResourceDialog"; +import useListStyle from "../hooks/useListStyle"; const ResourcesDashboard = () => { const { t } = useTranslation(); @@ -20,6 +26,7 @@ const ResourcesDashboard = () => { const [selectedList, setSelectedList] = useState>([]); const [isVisible, setIsVisible] = useState(false); const [queryText, setQueryText] = useState(""); + const { listStyle, setToTableStyle, setToGridStyle } = useListStyle(); const [dragActive, setDragActive] = useState(false); useEffect(() => { @@ -95,6 +102,86 @@ const ResourcesDashboard = () => { } }; + const handleStyleChangeBtnClick = (listStyleValue: boolean) => { + if (listStyleValue) { + setToTableStyle(); + } else { + setToGridStyle(); + } + setSelectedList([]); + }; + + const handleRenameBtnClick = (resource: Resource) => { + showChangeResourceFilenameDialog(resource.id, resource.filename); + }; + + const handleDeleteResourceBtnClick = (resource: Resource) => { + let warningText = t("resources.warning-text"); + if (resource.linkedMemoAmount > 0) { + warningText = warningText + `\n${t("resources.linked-amount")}: ${resource.linkedMemoAmount}`; + } + + showCommonDialog({ + title: t("resources.delete-resource"), + content: warningText, + style: "warning", + dialogName: "delete-resource-dialog", + onConfirm: async () => { + await resourceStore.deleteResourceById(resource.id); + }, + }); + }; + + const handlePreviewBtnClick = (resource: Resource) => { + const resourceUrl = getResourceUrl(resource); + if (resource.type.startsWith("image")) { + showPreviewImageDialog( + resources.filter((r) => r.type.startsWith("image")).map((r) => getResourceUrl(r)), + resources.findIndex((r) => r.id === resource.id) + ); + } else { + window.open(resourceUrl); + } + }; + + const handleCopyResourceLinkBtnClick = (resource: Resource) => { + const url = getResourceUrl(resource); + copy(url); + toast.success(t("message.succeed-copy-resource-link")); + }; + + const resourceList = useMemo( + () => + resources + .filter((res: Resource) => (queryText === "" ? true : res.filename.toLowerCase().includes(queryText.toLowerCase()))) + .map((resource) => + listStyle ? ( + handleCheckBtnClick(resource.id)} + handleUncheckClick={() => handleUncheckBtnClick(resource.id)} + handleRenameBtnClick={handleRenameBtnClick} + handleDeleteResourceBtnClick={handleDeleteResourceBtnClick} + handlePreviewBtnClick={handlePreviewBtnClick} + handleCopyResourceLinkBtnClick={handleCopyResourceLinkBtnClick} + > + ) : ( + handleCheckBtnClick(resource.id)} + handleUncheckClick={() => handleUncheckBtnClick(resource.id)} + handleRenameBtnClick={handleRenameBtnClick} + handleDeleteResourceBtnClick={handleDeleteResourceBtnClick} + handlePreviewBtnClick={handlePreviewBtnClick} + handleCopyResourceLinkBtnClick={handleCopyResourceLinkBtnClick} + > + ) + ), + [resources, queryText, listStyle] + ); + const handleDrag = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); @@ -157,6 +244,20 @@ const ResourcesDashboard = () => { +
+
handleStyleChangeBtnClick(true)} + > + +
+
handleStyleChangeBtnClick(false)} + > + +
+
{

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

) : ( -
+
+ {listStyle && ( +
+ {t("resources.select")} + ID + {t("resources.name")} + +
+ )} {resources.length === 0 ? (

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

) : ( - resources - .filter((res: Resource) => (queryText === "" ? true : res.filename.toLowerCase().includes(queryText.toLowerCase()))) - .map((resource) => ( - handleCheckBtnClick(resource.id)} - handleUncheckClick={() => handleUncheckBtnClick(resource.id)} - > - )) + resourceList )}
)} diff --git a/web/src/types/resourceItem.d.ts b/web/src/types/resourceItem.d.ts new file mode 100644 index 00000000..4819e71e --- /dev/null +++ b/web/src/types/resourceItem.d.ts @@ -0,0 +1,11 @@ +interface ResourceProps { + resource: Resource; + handleCheckClick: () => void; + handleUncheckClick: () => void; + handleRenameBtnClick: (resource: Resource) => void; + handleDeleteResourceBtnClick: (resource: Resource) => void; + handlePreviewBtnClick: (resource: Resource) => void; + handleCopyResourceLinkBtnClick: (resource: Resource) => void; +} + +type ResourceItemType = ResourceProps;