BookmarkFolderItem.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import {
  2. FC, useCallback, useEffect, useState,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import { DropdownToggle } from 'reactstrap';
  6. import { toastError, toastSuccess } from '~/client/util/apiNotification';
  7. import { apiv3Delete, apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
  8. import CountBadge from '~/components/Common/CountBadge';
  9. import FolderIcon from '~/components/Icons/FolderIcon';
  10. import TriangleIcon from '~/components/Icons/TriangleIcon';
  11. import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
  12. import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
  13. import BookmarkFolderItemControl from './BookmarkFolderItemControl';
  14. import BookmarkFolderNameInput from './BookmarkFolderNameInput';
  15. import DeleteBookmarkFolderModal from './DeleteBookmarkFolderModal';
  16. type BookmarkFolderItemProps = {
  17. bookmarkFolder: BookmarkFolderItems
  18. isOpen?: boolean
  19. }
  20. const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
  21. const { bookmarkFolder, isOpen: _isOpen = false } = props;
  22. const { t } = useTranslation();
  23. const {
  24. name, _id: folderId, children, parent,
  25. } = bookmarkFolder;
  26. const [currentChildren, setCurrentChildren] = useState<BookmarkFolderItems[]>();
  27. const [targetFolder, setTargetFolder] = useState<string | null>(folderId);
  28. const [isOpen, setIsOpen] = useState(_isOpen);
  29. const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(targetFolder);
  30. const { mutate: mutateParentBookmarkFolder } = useSWRxBookamrkFolderAndChild(parent);
  31. const [isRenameAction, setIsRenameAction] = useState<boolean>(false);
  32. const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
  33. const [isDeleteFolderModalShown, setIsDeleteFolderModalShown] = useState<boolean>(false);
  34. const childCount = useCallback((): number => {
  35. if (currentChildren != null && currentChildren.length > children.length) {
  36. return currentChildren.length;
  37. }
  38. return children.length;
  39. }, [children.length, currentChildren]);
  40. useEffect(() => {
  41. if (childBookmarkFolderData != null) {
  42. mutateChildBookmarkData();
  43. setCurrentChildren(childBookmarkFolderData);
  44. }
  45. }, [childBookmarkFolderData, mutateChildBookmarkData]);
  46. const hasChildren = useCallback((): boolean => {
  47. if (currentChildren != null && currentChildren.length > children.length) {
  48. return currentChildren.length > 0;
  49. }
  50. return children.length > 0;
  51. }, [children.length, currentChildren]);
  52. const loadChildFolder = useCallback(async() => {
  53. setIsOpen(!isOpen);
  54. setTargetFolder(folderId);
  55. }, [folderId, isOpen]);
  56. const loadParent = useCallback(async() => {
  57. mutateParentBookmarkFolder();
  58. }, [mutateParentBookmarkFolder]);
  59. // Rename for bookmark folder handler
  60. const onPressEnterHandlerForRename = useCallback(async(folderName: string) => {
  61. try {
  62. await apiv3Put('/bookmark-folder', { bookmarkFolderId: folderId, name: folderName, parent });
  63. loadParent();
  64. setIsRenameAction(false);
  65. toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder') }));
  66. }
  67. catch (err) {
  68. toastError(err);
  69. }
  70. }, [folderId, loadParent, parent, t]);
  71. // Create new folder / subfolder handler
  72. const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
  73. try {
  74. await apiv3Post('/bookmark-folder', { name: folderName, parent: targetFolder });
  75. setIsOpen(true);
  76. setIsCreateAction(false);
  77. mutateChildBookmarkData();
  78. toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder') }));
  79. }
  80. catch (err) {
  81. toastError(err);
  82. }
  83. }, [mutateChildBookmarkData, t, targetFolder]);
  84. const onClickPlusButton = useCallback(async() => {
  85. if (!isOpen && hasChildren()) {
  86. setIsOpen(true);
  87. }
  88. setIsCreateAction(true);
  89. }, [hasChildren, isOpen]);
  90. const RenderChildFolder = () => {
  91. return isOpen && currentChildren?.map((childFolder) => {
  92. return (
  93. <div key={childFolder._id} className="grw-foldertree-item-children">
  94. <BookmarkFolderItem
  95. key={childFolder._id}
  96. bookmarkFolder={childFolder}
  97. />
  98. </div>
  99. );
  100. });
  101. };
  102. const onClickRenameHandler = useCallback(() => {
  103. setIsRenameAction(true);
  104. }, []);
  105. const onClickDeleteHandler = useCallback(() => {
  106. setIsDeleteFolderModalShown(true);
  107. }, []);
  108. const onDeleteFolderModalClose = useCallback(() => {
  109. setIsDeleteFolderModalShown(false);
  110. }, []);
  111. const onClickDeleteButtonHandler = useCallback(async() => {
  112. try {
  113. await apiv3Delete(`/bookmark-folder/${folderId}`);
  114. setIsDeleteFolderModalShown(false);
  115. loadParent();
  116. toastSuccess(t('toaster.delete_succeeded', { target: t('bookmark_folder.bookmark_folder') }));
  117. }
  118. catch (err) {
  119. toastError(err);
  120. }
  121. }, [folderId, loadParent, t]);
  122. return (
  123. <div id={`bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container"
  124. >
  125. <li className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center">
  126. <div className="grw-triangle-container d-flex justify-content-center">
  127. {hasChildren() && (
  128. <button
  129. type="button"
  130. className={`grw-foldertree-triangle-btn btn ${isOpen ? 'grw-foldertree-open' : ''}`}
  131. onClick={loadChildFolder}
  132. >
  133. <div className="d-flex justify-content-center">
  134. <TriangleIcon />
  135. </div>
  136. </button>
  137. )}
  138. </div>
  139. {
  140. <div>
  141. <FolderIcon isOpen={isOpen} />
  142. </div>
  143. }
  144. { isRenameAction ? (
  145. <BookmarkFolderNameInput
  146. onClickOutside={() => setIsRenameAction(false)}
  147. onPressEnter={onPressEnterHandlerForRename}
  148. value={name}
  149. />
  150. ) : (
  151. <>
  152. <div className='grw-foldertree-title-anchor flex-grow-1 pl-2' onClick={loadChildFolder}>
  153. <p className={'text-truncate m-auto '}>{name}</p>
  154. </div>
  155. {hasChildren() && (
  156. <div className="grw-foldertree-count-wrapper">
  157. <CountBadge count={ childCount() } />
  158. </div>
  159. )}
  160. </>
  161. )
  162. }
  163. <div className="grw-foldertree-control d-flex">
  164. <BookmarkFolderItemControl
  165. onClickRename={onClickRenameHandler}
  166. onClickDelete={onClickDeleteHandler}
  167. >
  168. <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
  169. <i className="icon-options fa fa-rotate-90 p-1"></i>
  170. </DropdownToggle>
  171. </BookmarkFolderItemControl>
  172. <button
  173. type="button"
  174. className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
  175. onClick={onClickPlusButton}
  176. >
  177. <i className="icon-plus d-block p-0" />
  178. </button>
  179. </div>
  180. </li>
  181. {isCreateAction && (
  182. <div className="flex-fill">
  183. <BookmarkFolderNameInput
  184. onClickOutside={() => setIsCreateAction(false)}
  185. onPressEnter={onPressEnterHandlerForCreate}
  186. />
  187. </div>
  188. )}
  189. {
  190. RenderChildFolder()
  191. }
  192. <DeleteBookmarkFolderModal
  193. bookmarkFolder={bookmarkFolder}
  194. isOpen={isDeleteFolderModalShown}
  195. onClickDeleteButton={onClickDeleteButtonHandler}
  196. onModalClose={onDeleteFolderModalClose}/>
  197. </div>
  198. );
  199. };
  200. export default BookmarkFolderItem;