import clsx from 'clsx'; import React, { useEffect, useRef, useState } from 'react'; import { MdCheck } from 'react-icons/md'; import { HiOutlineFolder, HiOutlineFolderAdd, HiOutlineFolderRemove } from 'react-icons/hi'; import { Book, BookGroupType } from '@/types/book'; import { isMd5, md5Fingerprint } from '@/utils/md5'; import { useEnv } from '@/context/EnvContext'; import { useTranslation } from '@/hooks/useTranslation'; import { useLibraryStore } from '@/store/libraryStore'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { BOOK_UNGROUPED_ID, BOOK_UNGROUPED_NAME } from '@/services/constants'; import { generateGroupsList } from './BookshelfItem'; interface GroupingModalProps { libraryBooks: Book[]; selectedBooks: string[]; onCancel: () => void; onConfirm: () => void; } const GroupingModal: React.FC = ({ libraryBooks, selectedBooks, onCancel, onConfirm, }) => { const _ = useTranslation(); const { appService } = useEnv(); const { setLibrary } = useLibraryStore(); const groupsList = generateGroupsList(libraryBooks); const [showInput, setShowInput] = useState(false); const [editGroupName, setEditGroupName] = useState(_('Untitled Group')); const [selectedGroup, setSelectedGroup] = useState(null); const [newGroups, setNewGroups] = useState([]); const [allGroups, setAllGroups] = useState(groupsList); const editorRef = useRef(null); const iconSize = useResponsiveSize(16); const isSelectedBooksHasGroup = selectedBooks.some((hash) => !isMd5(hash)) || selectedBooks .map((hash) => libraryBooks.find((book) => book.hash === hash)?.groupId) .some((group) => group && group !== BOOK_UNGROUPED_NAME); const handleCreateGroup = () => { setShowInput(true); }; const handleRemoveFromGroup = () => { selectedBooks.forEach((id) => { for (const book of libraryBooks.filter((book) => book.hash === id || book.groupId === id)) { if ( book && book.groupId && book.groupName && book.groupId !== BOOK_UNGROUPED_ID && book.groupName !== BOOK_UNGROUPED_NAME ) { book.groupId = undefined; book.groupName = undefined; book.updatedAt = Date.now(); } } }); setLibrary(libraryBooks); appService?.saveLibraryBooks(libraryBooks); onConfirm(); }; const handleConfirmCreateGroup = () => { const groupName = editGroupName.trim(); if (groupName) { const newGroup = { id: md5Fingerprint(groupName), name: groupName }; const existingGroupIndex = newGroups.findIndex((group) => group.name === groupName); if (existingGroupIndex > -1) { newGroups.splice(existingGroupIndex, 1); } newGroups.push(newGroup); setSelectedGroup(newGroup); setNewGroups(newGroups); for (const newGroup of newGroups) { const existingGroupIndex = groupsList.findIndex((group) => group.id === newGroup.id); if (existingGroupIndex > -1) { groupsList.splice(existingGroupIndex, 1); } groupsList.unshift(newGroup); } setAllGroups(groupsList); const untitledGroupPattern = new RegExp(`^${_('Untitled Group')}\\s*(\\d+)?$`); const untitledGroupNumbers = groupsList .map((group) => { const match = group.name.match(untitledGroupPattern); return match ? parseInt(match[1] || '0', 10) : null; }) .filter((num) => num !== null); const nextNumber = untitledGroupNumbers.length > 0 ? Math.max(...untitledGroupNumbers) + 1 : 1; setEditGroupName(`${_('Untitled Group')} ${nextNumber}`); setShowInput(false); } }; const handleToggleSelectGroup = (group: BookGroupType) => { setSelectedGroup((prevGroup) => (prevGroup?.id === group.id ? null : group)); }; const handleConfirmGrouping = () => { selectedBooks.forEach((id) => { for (const book of libraryBooks.filter((book) => book.hash === id || book.groupId === id)) { if (book && selectedGroup) { book.groupId = selectedGroup.id; book.groupName = selectedGroup.name; book.updatedAt = Date.now(); } } }); setLibrary(libraryBooks); appService?.saveLibraryBooks(libraryBooks); onConfirm(); }; useEffect(() => { if (editorRef.current) { editorRef.current.select(); } }, [showInput]); useEffect(() => { const groupIds = selectedBooks .map((id) => libraryBooks.find((book) => book.hash === id || book.groupId === id)?.groupId) .filter((groupId) => groupId); if (Array.from(new Set(groupIds)).length === 1) { setSelectedGroup(groupsList.find((group) => group.id === groupIds[0]) || null); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedBooks]); return (

{_('Group Books')}

{isSelectedBooksHasGroup && (
{_('Remove From Group')}
)}
{_('Create New Group')}
{showInput && (
setEditGroupName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleConfirmCreateGroup(); e.stopPropagation(); }} className='input input-ghost w-full border-0 px-2 text-base !outline-none sm:text-sm' />
)}
    {allGroups.map((group, index) => ( ))}
); }; export default GroupingModal;