You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

396 lines
18 KiB

10 months ago
  1. import UploadFile from "../../components/Modals/ManageWorkspace/Documents/UploadFile";
  2. import PreLoader from "@/components/Preloader";
  3. import { memo, useEffect, useState } from "react";
  4. import FolderRow from "../../components/Modals/ManageWorkspace/Documents/Directory/FileRow";
  5. import System from "@/models/system";
  6. import { MagnifyingGlass, Plus, Trash } from "@phosphor-icons/react";
  7. import Document from "@/models/document";
  8. import showToast from "@/utils/toast";
  9. import FolderSelectionPopup from "../../components/Modals/ManageWorkspace/Documents/Directory/FolderSelectionPopup";
  10. import MoveToFolderIcon from "../../components/Modals/ManageWorkspace/Documents/Directory/MoveToFolderIcon";
  11. import { useModal } from "@/hooks/useModal";
  12. import NewFolderModal from "../../components/Modals/ManageWorkspace/Documents/Directory/NewFolderModal";
  13. import debounce from "lodash.debounce";
  14. import { filterFileSearchResults } from "../../components/Modals/ManageWorkspace/Documents/Directory/utils";
  15. // import ContextMenu from "../../components/Modals/ManageWorkspace/Documents/Directory/ContextMenu";
  16. import { Tooltip } from "react-tooltip";
  17. import { safeJsonParse } from "@/utils/request";
  18. import { data } from "autoprefixer";
  19. function Directory({
  20. files,
  21. setFiles,
  22. loading,
  23. setLoading,
  24. workspace,
  25. fetchKeys,
  26. selectedItems,
  27. setSelectedItems,
  28. setHighlightWorkspace,
  29. moveToWorkspace,
  30. setLoadingMessage,
  31. loadingMessage,
  32. }) {
  33. const [amountSelected, setAmountSelected] = useState(0);
  34. const [showFolderSelection, setShowFolderSelection] = useState(false);
  35. const [searchTerm, setSearchTerm] = useState("");
  36. const [dataTo, setFDataTo] = useState([]);
  37. const [show, setShow] = useState(false);
  38. const {
  39. isOpen: isFolderModalOpen,
  40. openModal: openFolderModal,
  41. closeModal: closeFolderModal,
  42. } = useModal();
  43. // const [contextMenu, setContextMenu] = useState({
  44. // visible: false,
  45. // x: 0,
  46. // y: 0,
  47. // });
  48. useEffect(() => {
  49. async function fetchUsers() {
  50. const nodata = await System.localFiles();
  51. const em0 = nodata.items[0].items
  52. const em1 = nodata.items[1].items
  53. const em2 = nodata.items[2].items
  54. const em = [...em0,...em1,...em2]
  55. setFDataTo(em)
  56. console.log(2222,nodata);
  57. }
  58. fetchUsers();
  59. }, []);
  60. const deleteFiles = async (event) => {
  61. event.stopPropagation();
  62. if (
  63. !window.confirm(
  64. "Are you sure you want to delete these files and folders?\nThis will remove the files from the system and remove them from any existing workspaces automatically.\nThis action is not reversible."
  65. )
  66. ) {
  67. return false;
  68. }
  69. try {
  70. const toRemove = [];
  71. const foldersToRemove = [];
  72. for (const itemId of Object.keys(selectedItems)) {
  73. for (const folder of files.items) {
  74. const foundItem = folder.items.find((file) => file.id === itemId);
  75. if (foundItem) {
  76. toRemove.push(`${folder.name}/${foundItem.name}`);
  77. break;
  78. }
  79. }
  80. }
  81. for (const folder of files.items) {
  82. if (folder.name === "custom-documents") {
  83. continue;
  84. }
  85. if (isSelected(folder.id, folder)) {
  86. foldersToRemove.push(folder.name);
  87. }
  88. }
  89. setLoading(true);
  90. setLoadingMessage(
  91. `Removing ${toRemove.length} documents and ${foldersToRemove.length} folders. Please wait.`
  92. );
  93. await System.deleteDocuments(toRemove);
  94. for (const folderName of foldersToRemove) {
  95. await System.deleteFolder(folderName);
  96. }
  97. await fetchKeys(true);
  98. setSelectedItems({});
  99. } catch (error) {
  100. console.error("Failed to delete files and folders:", error);
  101. } finally {
  102. setLoading(false);
  103. setSelectedItems({});
  104. }
  105. };
  106. const toggleSelection = (item) => {
  107. setSelectedItems((prevSelectedItems) => {
  108. const newSelectedItems = { ...prevSelectedItems };
  109. if (item.type === "folder") {
  110. // select all files in the folder
  111. if (newSelectedItems[item.name]) {
  112. delete newSelectedItems[item.name];
  113. item.items.forEach((file) => delete newSelectedItems[file.id]);
  114. } else {
  115. newSelectedItems[item.name] = true;
  116. item.items.forEach((file) => (newSelectedItems[file.id] = true));
  117. }
  118. } else {
  119. // single file selections
  120. if (newSelectedItems[item.id]) {
  121. delete newSelectedItems[item.id];
  122. } else {
  123. newSelectedItems[item.id] = true;
  124. }
  125. }
  126. return newSelectedItems;
  127. });
  128. };
  129. // check if item is selected based on selectedItems state
  130. const isSelected = (id, item) => {
  131. if (item && item.type === "folder") {
  132. if (!selectedItems[item.name]) {
  133. return false;
  134. }
  135. return item.items.every((file) => selectedItems[file.id]);
  136. }
  137. return !!selectedItems[id];
  138. };
  139. const moveToFolder = async (folder) => {
  140. const toMove = [];
  141. for (const itemId of Object.keys(selectedItems)) {
  142. for (const currentFolder of files.items) {
  143. const foundItem = currentFolder.items.find(
  144. (file) => file.id === itemId
  145. );
  146. if (foundItem) {
  147. toMove.push({ ...foundItem, folderName: currentFolder.name });
  148. break;
  149. }
  150. }
  151. }
  152. setLoading(true);
  153. setLoadingMessage(`Moving ${toMove.length} documents. Please wait.`);
  154. const { success, message } = await Document.moveToFolder(
  155. toMove,
  156. folder.name
  157. );
  158. if (!success) {
  159. showToast(`Error moving files: ${message}`, "error");
  160. setLoading(false);
  161. return;
  162. }
  163. if (success && message) {
  164. // show info if some files were not moved due to being embedded
  165. showToast(message, "info");
  166. } else {
  167. showToast(`Successfully moved ${toMove.length} documents.`, "success");
  168. }
  169. await fetchKeys(true);
  170. setSelectedItems({});
  171. setLoading(false);
  172. };
  173. const handleSearch = debounce((e) => {
  174. const searchValue = e.target.value;
  175. setSearchTerm(searchValue);
  176. }, 500);
  177. const filteredFiles =filterFileSearchResults(files, searchTerm)
  178. // console.log(114545,filteredFiles);
  179. const handleContextMenu = (event) => {
  180. event.preventDefault();
  181. // setContextMenu({ visible: true, x: event.clientX, y: event.clientY });
  182. };
  183. const closeContextMenu = () => {
  184. // setContextMenu({ visible: false, x: 0, y: 0 });
  185. };
  186. // 点击显示隐藏
  187. const bindUrl = () =>{
  188. setShow(!show)
  189. console.log(show);
  190. }
  191. // 返回首页
  192. const bindHome = () =>{
  193. window.location = '/'
  194. }
  195. return (
  196. <>
  197. <div className="px-8 pb-8 w-[60%] mt-[30px] ml-[20px] pt-[10px] shadow-lg shadow-[0_0_20px_0_#F4F6FC] onContextMenu={handleContextMenu}">
  198. <div className="flex flex-col gap-y-6">
  199. <div className="flex items-center justify-between w-[100%] px-5 relative">
  200. <h3 className="text-white text-base font-bold cursor-pointer text-[22px]" onClick={bindHome}>首页</h3>
  201. <div className="relative">
  202. <input
  203. type="search"
  204. placeholder="搜寻文件"
  205. onChange={handleSearch}
  206. className="border-none search-input bg-[#ECEFF6] text-white placeholder:text-theme-settings-input-placeholder focus:outline-primary-button active:outline-primary-button outline-none text-sm rounded-lg pl-9 pr-2.5 py-2 w-[600px] h-[32px] light:border-theme-modal-border light:border"
  207. />
  208. <MagnifyingGlass
  209. size={14}
  210. className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white"
  211. weight="bold"
  212. />
  213. </div>
  214. <button
  215. className="border-none flex items-center gap-x-2 cursor-pointer px-[14px] py-[7px] -mr-[14px] rounded-lg hover:bg-theme-sidebar-subitem-hover z-20 relative"
  216. onClick={openFolderModal}
  217. >
  218. <Plus
  219. size={18}
  220. weight="bold"
  221. className="text-theme-text-primary light:text-[#0ba5ec]"
  222. />
  223. <div className="text-theme-text-primary light:text-[#0ba5ec] text-xs font-bold leading-[18px]">
  224. 新文件夹
  225. </div>
  226. </button>
  227. </div>
  228. <div className="relative w-[100%] h-[500px] bg-[#ECEFF6] rounded-2xl overflow-hidden border ">
  229. <div className="absolute top-0 left-0 right-0 z-10 rounded-t-2xl text-theme-text-primary text-xs grid grid-cols-12 py-2 px-8 border-b border-white/20 shadow-md ">
  230. <p className="col-span-6">Name</p>
  231. </div>
  232. <div className="overflow-y-auto h-full pt-8">
  233. {loading ? (
  234. <div className="w-full h-full flex items-center justify-center flex-col gap-y-5">
  235. <PreLoader />
  236. <p className="text-white text-sm font-semibold animate-pulse text-center w-1/3">
  237. {loadingMessage}
  238. </p>
  239. </div>
  240. ) : dataTo.length > 0 ? (
  241. dataTo.map((item, index) => (
  242. <div key={index} onClick={bindUrl}>
  243. <div className="hover:bg-slate-400 pt-[10px] pb-[10px] pl-[30px] hover:text-[#fff]">{item.title}</div>
  244. </div>
  245. ))
  246. ) : (
  247. <div className="w-full h-full flex items-center justify-center">
  248. <p className="text-white text-opacity-40 text-sm font-medium">
  249. 暂无文件
  250. </p>
  251. </div>
  252. )}
  253. </div>
  254. {show == true && (
  255. <div className="absolute bottom-[12px] left-0 right-0 flex justify-center pointer-events-none">
  256. <div className="mx-auto bg-white/40 light:bg-white rounded-lg py-1 px-2 pointer-events-auto light:shadow-lg">
  257. <div className="flex flex-row items-center gap-x-2">
  258. <button
  259. // onClick={moveToWorkspace}
  260. onMouseEnter={() => setHighlightWorkspace(true)}
  261. onMouseLeave={() => setHighlightWorkspace(false)}
  262. className="border-none text-sm font-semibold bg-white light:bg-[#E0F2FE] h-[30px] px-2.5 rounded-lg hover:bg-neutral-800/80 hover:text-white light:text-[#026AA2] light:hover:bg-[#026AA2] light:hover:text-white"
  263. >
  264. 设为私有
  265. </button>
  266. <button
  267. // onClick={moveToWorkspace}
  268. onMouseEnter={() => setHighlightWorkspace(true)}
  269. onMouseLeave={() => setHighlightWorkspace(false)}
  270. className="border-none text-sm font-semibold bg-white light:bg-[#E0F2FE] h-[30px] px-2.5 rounded-lg hover:bg-neutral-800/80 hover:text-white light:text-[#026AA2] light:hover:bg-[#026AA2] light:hover:text-white"
  271. >
  272. 设为公有
  273. </button>
  274. <button
  275. // onClick={moveToWorkspace}
  276. onMouseEnter={() => setHighlightWorkspace(true)}
  277. onMouseLeave={() => setHighlightWorkspace(false)}
  278. className="border-none text-sm font-semibold bg-white light:bg-[#E0F2FE] h-[30px] px-2.5 rounded-lg hover:bg-neutral-800/80 hover:text-white light:text-[#026AA2] light:hover:bg-[#026AA2] light:hover:text-white"
  279. >
  280. 添加标签
  281. </button>
  282. <div className="relative">
  283. <button
  284. // onClick={() =>
  285. // setShowFolderSelection(!showFolderSelection)
  286. // }
  287. className="border-none text-sm font-semibold bg-white light:bg-[#E0F2FE] h-[32px] w-[32px] rounded-lg text-dark-text hover:bg-neutral-800/80 hover:text-white light:text-[#026AA2] light:hover:bg-[#026AA2] light:hover:text-white flex justify-center items-center group"
  288. >
  289. <MoveToFolderIcon className="text-dark-text light:text-[#026AA2] group-hover:text-white" />
  290. </button>
  291. {showFolderSelection && (
  292. <FolderSelectionPopup
  293. folders={files.items.filter(
  294. (item) => item.type === "folder"
  295. )}
  296. onSelect={moveToFolder}
  297. onClose={() => setShowFolderSelection(false)}
  298. />
  299. )}
  300. </div>
  301. <button
  302. // onClick={deleteFiles}
  303. className="border-none text-sm font-semibold bg-white light:bg-[#E0F2FE] h-[32px] w-[32px] rounded-lg text-dark-text hover:bg-neutral-800/80 hover:text-white light:text-[#026AA2] light:hover:bg-[#026AA2] light:hover:text-white flex justify-center items-center"
  304. >
  305. <Trash size={18} weight="bold" />
  306. </button>
  307. </div>
  308. </div>
  309. </div>
  310. )}
  311. </div>
  312. <UploadFile
  313. workspace={workspace}
  314. fetchKeys={fetchKeys}
  315. setLoading={setLoading}
  316. setLoadingMessage={setLoadingMessage}
  317. />
  318. </div>
  319. {isFolderModalOpen && (
  320. <div className="bg-black/60 backdrop-blur-sm fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center z-30">
  321. <NewFolderModal
  322. closeModal={closeFolderModal}
  323. files={files}
  324. setFiles={setFiles}
  325. />
  326. </div>
  327. )}
  328. {/* <ContextMenu
  329. contextMenu={contextMenu}
  330. closeContextMenu={closeContextMenu}
  331. files={files}
  332. selectedItems={selectedItems}
  333. setSelectedItems={setSelectedItems}
  334. /> */}
  335. </div>
  336. <DirectoryTooltips />
  337. </>
  338. );
  339. }
  340. /**
  341. * Tooltips for the directory components. Renders when the directory is shown
  342. * or updated so that tooltips are attached as the items are changed.
  343. */
  344. function DirectoryTooltips() {
  345. return (
  346. <Tooltip
  347. id="directory-item"
  348. place="bottom"
  349. delayShow={800}
  350. className="tooltip invert light:invert-0 z-99 max-w-[200px]"
  351. render={({ content }) => {
  352. const data = safeJsonParse(content, null);
  353. if (!data) return null;
  354. return (
  355. <div className="text-xs">
  356. <p className="text-white light:invert font-medium">{data.title}</p>
  357. <div className="flex mt-1 gap-x-2">
  358. <p className="">
  359. Date: <b>{data.date}</b>
  360. </p>
  361. <p className="">
  362. Type: <b>{data.extension}</b>
  363. </p>
  364. </div>
  365. </div>
  366. );
  367. }}
  368. />
  369. );
  370. }
  371. export default memo(Directory);