418 lines
12 KiB
TypeScript
418 lines
12 KiB
TypeScript
import {
|
|
DragDropContext,
|
|
Draggable,
|
|
type DraggableProvided,
|
|
Droppable,
|
|
type DroppableProvided,
|
|
} from '@hello-pangea/dnd';
|
|
import {
|
|
ActionIcon,
|
|
Box,
|
|
Button,
|
|
Card,
|
|
Group,
|
|
Popover,
|
|
Stack,
|
|
Text,
|
|
TextInput,
|
|
Tooltip,
|
|
} from '@mantine/core';
|
|
import { isNotEmpty, useForm } from '@mantine/form';
|
|
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
|
import { useCommonHooks, useMutationAPI } from 'hooks';
|
|
import { useEffect, useState } from 'react';
|
|
import { FaChevronLeft } from 'react-icons/fa';
|
|
import { FiPlus, FiTrash } from 'react-icons/fi';
|
|
import { buttonClasses, cardClasses, popoverClasses, textInputClasses } from 'styles';
|
|
import type { Bookmark, BookmarkGroup } from 'types';
|
|
import DeleteItemModal from '../Shared/DeleteItemModal';
|
|
import BookmarkSettingsItem from './BookmarkSettingsItem';
|
|
import DebouncedBookmarkItem from './DebouncedBookmarkItem';
|
|
|
|
function DebouncedGroupSettingsItem({
|
|
bookmarkGroup,
|
|
bookmarks,
|
|
searchTerm,
|
|
}: {
|
|
bookmarkGroup: BookmarkGroup;
|
|
bookmarks: Bookmark[];
|
|
searchTerm: string;
|
|
}) {
|
|
const { isMobile, handleDelete, reorder, theme } = useCommonHooks();
|
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
|
const [popoverOpened, setPopoverOpened] = useState(false);
|
|
const { mutateAsync } = useMutationAPI();
|
|
const [group, setGroup] = useState<BookmarkGroup>(bookmarkGroup);
|
|
const [update, setUpdate] = useState<any>({});
|
|
const [debounced] = useDebouncedValue(update, 1000);
|
|
const [newBookmark, setNewBookmark] = useState<Bookmark>({
|
|
id: undefined,
|
|
name: '',
|
|
url: '',
|
|
category: group.categoryName,
|
|
index: 0,
|
|
});
|
|
const form = useForm({
|
|
initialValues: newBookmark,
|
|
validate: {
|
|
name: isNotEmpty('Name is required'),
|
|
url: isNotEmpty('URL is required'),
|
|
},
|
|
});
|
|
const [bookmarkList, setBookmarkList] = useState<Bookmark[]>(bookmarks);
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
function onDragEnd(result: any) {
|
|
if (!result.destination) {
|
|
return;
|
|
}
|
|
|
|
if (result.destination.index === result.source.index) {
|
|
return;
|
|
}
|
|
|
|
const items = reorder(bookmarkList, result.source.index, result.destination.index);
|
|
setBookmarkList(
|
|
items.map((item: any, index: number) => {
|
|
return {
|
|
...item,
|
|
index: index,
|
|
};
|
|
}),
|
|
);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (Object.keys(debounced).length) {
|
|
mutateAsync({
|
|
method: 'PATCH',
|
|
endpoint: 'api/bookmarkCategories',
|
|
body: debounced,
|
|
});
|
|
setUpdate({});
|
|
}
|
|
}, [debounced]);
|
|
|
|
useEffect(() => {
|
|
if (update !== group && Object.keys(update).length) {
|
|
setGroup(update);
|
|
}
|
|
}, [update]);
|
|
|
|
useEffect(() => {
|
|
if (bookmarkList && bookmarkList !== bookmarks) {
|
|
mutateAsync({ method: 'PATCH', endpoint: 'api/bookmarkOrder', body: bookmarkList });
|
|
}
|
|
}, [bookmarkList]);
|
|
|
|
return (
|
|
<Card w={'400px'} className={cardClasses.settingsItem} classNames={cardClasses}>
|
|
<DeleteItemModal
|
|
modalOpened={modalOpened}
|
|
handleClose={closeModal}
|
|
handleDelete={() => {
|
|
handleDelete(bookmarks, 'api/bookmarks');
|
|
handleDelete(group, 'api/bookmarkCategories');
|
|
}}
|
|
title='Delete message'
|
|
body={
|
|
<Stack gap={0} w={'100%'} h={'175px'} align='center'>
|
|
<Text>Are you sure you want to delete this bookmark group?</Text>
|
|
<Text
|
|
style={{
|
|
fontWeight: 'bold',
|
|
fontSize: '1.25rem',
|
|
padding: '.5rem',
|
|
margin: '.5rem',
|
|
borderRadius: '10px',
|
|
backgroundColor: 'var(--mantine-color-accent-0)',
|
|
color: 'var(--mantine-color-background-0)',
|
|
}}
|
|
>{`"${group.categoryName}"`}</Text>
|
|
<Text style={{ fontWeight: 'bold', textTransform: 'uppercase' }}>
|
|
This will also delete all bookmarks in this group!
|
|
</Text>
|
|
<Text c='red'>This action cannot be undone!</Text>
|
|
</Stack>
|
|
}
|
|
/>
|
|
<Card.Section
|
|
p={'1rem'}
|
|
pt={0}
|
|
bg={'var(--mantine-color-search-0)'}
|
|
c={'var(--mantine-color-background-0)'}
|
|
>
|
|
<Stack gap={0}>
|
|
<Group gap={0} align='center' justify='space-between'>
|
|
<TextInput
|
|
label={'Name'}
|
|
w={isMobile ? undefined : '58%'}
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
defaultValue={group.categoryName}
|
|
onChange={(event) => {
|
|
setUpdate({
|
|
...group,
|
|
categoryName: event.target.value,
|
|
});
|
|
}}
|
|
styles={
|
|
theme.textShadowsEnabled && {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
}
|
|
/>
|
|
<TextInput
|
|
label={'Filter Name'}
|
|
w={isMobile ? undefined : '28%'}
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
defaultValue={group.filterName}
|
|
onChange={(event) => {
|
|
setUpdate({
|
|
...group,
|
|
filterName: event.target.value,
|
|
});
|
|
}}
|
|
styles={
|
|
theme.textShadowsEnabled && {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
}
|
|
/>
|
|
<Tooltip
|
|
c={'var(--mantine-color-primary-0)'}
|
|
bg={'var(--mantine-color-background-0)'}
|
|
bd={'solid 1px var(--mantine-color-border-0)'}
|
|
label={'Delete'}
|
|
>
|
|
<ActionIcon
|
|
mt={'2.5rem'}
|
|
size={'lg'}
|
|
color='red'
|
|
onClick={() => {
|
|
openModal();
|
|
}}
|
|
>
|
|
<FiTrash color='black' />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
</Group>
|
|
|
|
<Text
|
|
style={{
|
|
width: '30px',
|
|
height: '30px',
|
|
borderRadius: '100%',
|
|
backgroundColor: 'var(--mantine-color-accent-0)',
|
|
position: 'absolute',
|
|
top: isMobile ? 170 : 92,
|
|
right: 18,
|
|
zIndex: 10,
|
|
fontSize: '1.25rem',
|
|
textAlign: 'center',
|
|
textJustify: 'auto',
|
|
}}
|
|
>
|
|
{bookmarks.length}
|
|
</Text>
|
|
</Stack>
|
|
</Card.Section>
|
|
<Card.Section mt={'1rem'} p={'1rem'}>
|
|
<Stack>
|
|
<DragDropContext onDragEnd={onDragEnd}>
|
|
<Droppable droppableId={`${bookmarkGroup.id}`}>
|
|
{(provided: DroppableProvided) => (
|
|
<Stack ref={provided.innerRef} {...provided.droppableProps}>
|
|
{bookmarks.map((bookmark: Bookmark) => (
|
|
<Draggable
|
|
key={bookmark.id}
|
|
draggableId={`${bookmark.id}`}
|
|
index={bookmark.index}
|
|
>
|
|
{(provided: DraggableProvided) => (
|
|
<Box
|
|
ref={provided.innerRef}
|
|
{...provided.draggableProps}
|
|
{...provided.dragHandleProps}
|
|
>
|
|
<Popover
|
|
withArrow
|
|
withOverlay
|
|
trapFocus
|
|
closeOnClickOutside={!isOpen}
|
|
classNames={popoverClasses}
|
|
middlewares={{ shift: { padding: 20 } }}
|
|
position='bottom'
|
|
>
|
|
<Popover.Target>
|
|
<Box pos={'relative'}>
|
|
{(bookmark.name.toLowerCase().includes(searchTerm) ||
|
|
bookmark.url.toLowerCase().includes(searchTerm)) &&
|
|
searchTerm && (
|
|
<ActionIcon
|
|
variant='subtle'
|
|
style={{
|
|
position: 'absolute',
|
|
top: 6,
|
|
left: -12,
|
|
color: 'red',
|
|
backgroundColor: 'transparent',
|
|
}}
|
|
>
|
|
<FaChevronLeft
|
|
style={{ transform: 'rotate(180deg)' }}
|
|
/>
|
|
</ActionIcon>
|
|
)}
|
|
<BookmarkSettingsItem bookmark={bookmark} />
|
|
</Box>
|
|
</Popover.Target>
|
|
<Popover.Dropdown w={isMobile ? '90%' : undefined}>
|
|
<DebouncedBookmarkItem
|
|
bookmarkItem={bookmark}
|
|
setIsOpen={setIsOpen}
|
|
/>
|
|
</Popover.Dropdown>
|
|
</Popover>
|
|
</Box>
|
|
)}
|
|
</Draggable>
|
|
))}
|
|
{provided.placeholder}
|
|
</Stack>
|
|
)}
|
|
</Droppable>
|
|
</DragDropContext>
|
|
</Stack>
|
|
</Card.Section>
|
|
<Card.Section
|
|
p={'.5rem'}
|
|
bg={'var(--mantine-color-search-0)'}
|
|
c={'var(--mantine-color-background-0)'}
|
|
>
|
|
<Group align='center' justify='center'>
|
|
<Popover
|
|
withArrow
|
|
withOverlay
|
|
trapFocus
|
|
position='bottom'
|
|
opened={popoverOpened}
|
|
onChange={setPopoverOpened}
|
|
closeOnEscape
|
|
classNames={popoverClasses}
|
|
>
|
|
<Popover.Target>
|
|
<Button
|
|
className={buttonClasses.newItemButton}
|
|
classNames={buttonClasses}
|
|
w={'min-content'}
|
|
onClick={() => {
|
|
setPopoverOpened((o) => !o);
|
|
setNewBookmark({
|
|
...newBookmark,
|
|
category: group.categoryName,
|
|
});
|
|
}}
|
|
>
|
|
<FiPlus /> Bookmark
|
|
</Button>
|
|
</Popover.Target>
|
|
<Popover.Dropdown w={isMobile ? '90%' : undefined}>
|
|
<Card className={cardClasses.settingsItem} classNames={cardClasses}>
|
|
<form
|
|
onSubmit={form.onSubmit((values) => {
|
|
mutateAsync({
|
|
method: 'POST',
|
|
endpoint: 'api/bookmarks',
|
|
body: { ...values, index: bookmarks.length },
|
|
});
|
|
setPopoverOpened(false);
|
|
form.reset();
|
|
})}
|
|
>
|
|
<Stack w={'100%'} display={'flex'} pos={'relative'}>
|
|
<Group w={'100%'} justify='center'>
|
|
<TextInput
|
|
key={form.key('name')}
|
|
{...form.getInputProps('name')}
|
|
label={'Name'}
|
|
placeholder='eg. Google'
|
|
className={textInputClasses.settingsItem}
|
|
pt={'1rem'}
|
|
classNames={textInputClasses}
|
|
withAsterisk
|
|
styles={
|
|
theme.textShadowsEnabled && {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
}
|
|
/>
|
|
<TextInput
|
|
key={form.key('category')}
|
|
{...form.getInputProps('category')}
|
|
label={'Category'}
|
|
className={textInputClasses.settingsItem}
|
|
pt={'1rem'}
|
|
classNames={textInputClasses}
|
|
disabled
|
|
defaultValue={group.categoryName}
|
|
styles={
|
|
theme.textShadowsEnabled && {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
}
|
|
/>
|
|
</Group>
|
|
<Group w={'100%'} justify='center' mb={60}>
|
|
<TextInput
|
|
key={form.key('url')}
|
|
{...form.getInputProps('url')}
|
|
label={'URL'}
|
|
placeholder='eg. https://google.com'
|
|
className={textInputClasses.settingsItem}
|
|
w={'100%'}
|
|
pt={'1rem'}
|
|
classNames={textInputClasses}
|
|
withAsterisk
|
|
styles={
|
|
theme.textShadowsEnabled && {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
}
|
|
/>
|
|
</Group>
|
|
<Group
|
|
w={'100%'}
|
|
pos={'absolute'}
|
|
bottom={8}
|
|
align='center'
|
|
justify='center'
|
|
>
|
|
<Button type='submit' color={'green'}>
|
|
Save
|
|
</Button>
|
|
<Button
|
|
color={'red'}
|
|
styles={{ label: { color: 'black' } }}
|
|
onClick={() => {
|
|
setPopoverOpened(false);
|
|
form.reset();
|
|
}}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</form>
|
|
</Card>
|
|
</Popover.Dropdown>
|
|
</Popover>
|
|
</Group>
|
|
</Card.Section>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default DebouncedGroupSettingsItem;
|