Drag and drop bookmark settings

This commit is contained in:
Sean Edmunds
2025-03-26 16:36:14 -05:00
parent 4aacf06b5c
commit ed27956d01
9 changed files with 352 additions and 62 deletions

View File

@@ -50,22 +50,24 @@ function Links({ contentType }: any) {
gridTemplateRows: 'auto',
}}
>
{groups.map((category: any) => {
if (
contentType.includes(category.filterName) ||
contentType.includes('All')
) {
return (
<LinkGroups
key={category.categoryName}
category={category.categoryName}
links={bookmarkData.filter(
(bookmark: any) => bookmark.category === category.categoryName,
)}
/>
);
}
})}
{groups
.sort((a: any, b: any) => (a.categoryIndex > b.categoryIndex ? 1 : -1))
.map((category: any) => {
if (
contentType.includes(category.filterName) ||
contentType.includes('All')
) {
return (
<LinkGroups
key={category.categoryName}
category={category.categoryName}
links={bookmarkData.filter(
(bookmark: any) => bookmark.category === category.categoryName,
)}
/>
);
}
})}
</Stack>
</Stack>
)}

View File

@@ -15,9 +15,11 @@ function LinkGroups({ links, category }: any) {
<Text className={textClasses.linkCategory} classNames={textClasses}>
{category}
</Text>
{links?.map((link: any) => (
<LinkItem key={link.name} link={link} />
))}
{links
?.sort((a: any, b: any) => (a.index > b.index ? 1 : -1))
.map((link: any) => (
<LinkItem key={link.name} link={link} />
))}
</Stack>
);
}

View File

@@ -1,12 +1,16 @@
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import {
ActionIcon,
Box,
Button,
Card,
Divider,
Group,
Highlight,
Modal,
Popover,
Stack,
Switch,
Text,
TextInput,
} from '@mantine/core';
@@ -30,14 +34,43 @@ const BookmarkSettings = () => {
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
endpoint: 'api/categories',
});
const [groups, setGroups] = useState([]);
const [dragging, setDragging] = useState(false);
const [groups, setGroups] = useState<any>([]);
const [newGroup, setNewGroup] = useState<any>({});
const [groupList, setGroupList] = useState<any>([]);
const { theme, isMobile } = useCommonHooks();
const { mutateAsync } = useMutationAPI();
const [searchValue, setSearchValue] = useState('');
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const reorder = (list: any, startIndex: any, endIndex: any) => {
const result = Array.from(list);
const [removed]: any = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
function onDragEnd(result: any) {
if (!result.destination) {
return;
}
if (result.destination.index === result.source.index) {
return;
}
const items = reorder(groups, result.source.index, result.destination.index);
setGroupList(
items.map((item: any, index: number) => {
return {
...item,
index: index,
};
}),
);
}
useEffect(() => {
if (linkData && linksIsFetched) {
setBookmarks(linkData);
@@ -54,6 +87,12 @@ const BookmarkSettings = () => {
}
}, [groupData, groupsIsFetched, debouncedGroupSearch]);
useEffect(() => {
if (groupList.length > 0 && groupList !== groups) {
mutateAsync({ method: 'PATCH', endpoint: 'api/categoryOrder', body: groupList });
}
}, [groupList]);
return (
<Stack>
<Modal.Root
@@ -218,18 +257,117 @@ const BookmarkSettings = () => {
setSearchValue(event.target.value);
}}
/>
<Switch
label={'Reorder'}
labelPosition='left'
pos={'absolute'}
right={isMobile ? '2rem' : '1rem'}
top={isMobile ? '9.5rem' : undefined}
onChange={(e) => {
setDragging(e.target.checked);
}}
color={theme.primaryColor}
styles={{ label: { color: theme.primaryColor } }}
/>
</Group>
<Stack w={'100%'} align='center'>
<Group w={'75%'} align='start'>
{groups?.map((group: any) => (
<DebouncedGroupSettingsItem
key={group.id}
bookmarkGroup={group}
bookmarks={bookmarks.filter((a: any) => a.category === group.categoryName)}
/>
))}
</Group>
</Stack>
{dragging ? (
<DragDropContext onDragEnd={onDragEnd}>
<Stack w={'100%'} align='center'>
<Droppable droppableId={'groupList'}>
{(provided) => (
<Stack
w={'75%'}
align='center'
justify='center'
ref={provided.innerRef}
{...provided.droppableProps}
>
{groups
?.sort((a: any, b: any) =>
a.categoryIndex > b.categoryIndex ? 1 : -1,
)
.map((group: any) => (
<Draggable
key={group.id}
draggableId={`${group.id}`}
index={Number(group.categoryIndex)}
>
{(provided) => (
<Box
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Card
w={'400px'}
style={{
backgroundColor: theme.searchColor,
border: '1px solid var(--color-border)',
borderRadius: '8px',
}}
>
<Group gap={0} align='center' justify='space-between'>
<Stack gap={0}>
<Text c={theme.accentColor} fz={14}>
Name
</Text>
<Text
p={4}
style={{
borderRadius: '4px',
paddingLeft: '8px',
color: theme.primaryColor,
backgroundColor: theme.searchColor,
}}
>
{group.categoryName}
</Text>
</Stack>
<Stack gap={0}>
<Text c={theme.accentColor} fz={14}>
Filter Name
</Text>
<Text
p={4}
style={{
borderRadius: '4px',
paddingLeft: '8px',
color: theme.primaryColor,
backgroundColor: theme.searchColor,
}}
>
{group.filterName}
</Text>
</Stack>
</Group>
</Card>
</Box>
)}
</Draggable>
))}
{provided.placeholder}
</Stack>
)}
</Droppable>
</Stack>
</DragDropContext>
) : (
<Stack w={'100%'} align='center'>
<Group w={'75%'} align='start'>
{groups
?.sort((a: any, b: any) => (a.categoryIndex > b.categoryIndex ? 1 : -1))
.map((group: any) => (
<DebouncedGroupSettingsItem
key={group.id}
bookmarkGroup={group}
bookmarks={bookmarks
.filter((a: any) => a.category === group.categoryName)
.sort((a: any, b: any) => (a.index > b.index ? 1 : -1))}
/>
))}
</Group>
</Stack>
)}
</Stack>
);
};

View File

@@ -1,3 +1,4 @@
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import {
ActionIcon,
Box,
@@ -29,12 +30,41 @@ function DebouncedGroupSettingsItem({ bookmarkGroup, bookmarks }: any) {
const [update, setUpdate] = useState({});
const [debounced] = useDebouncedValue(update, 1000);
const [newBookmark, setNewBookmark] = useState<any>({});
const [bookmarkList, setBookmarkList] = useState<any>(bookmarks);
const handleDeleteBookmark = (application: any) => {
mutateAsync({ method: 'DELETE', endpoint: 'api/links', body: application });
};
const handleDeleteGroup = (application: any) => {
mutateAsync({ method: 'DELETE', endpoint: 'api/categories', body: application });
};
const reorder = (list: any, startIndex: any, endIndex: any) => {
const result = Array.from(list);
const [removed]: any = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
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/categories', body: debounced });
@@ -48,6 +78,12 @@ function DebouncedGroupSettingsItem({ bookmarkGroup, bookmarks }: any) {
}
}, [update]);
useEffect(() => {
if (bookmarkList && bookmarkList !== bookmarks) {
mutateAsync({ method: 'PATCH', endpoint: 'api/linkOrder', body: bookmarkList });
}
}, [bookmarkList]);
return (
<Card w={'400px'} className={cardClasses.settingsItem} classNames={cardClasses}>
<Modal.Root
@@ -194,30 +230,57 @@ function DebouncedGroupSettingsItem({ bookmarkGroup, bookmarks }: any) {
</Card.Section>
<Card.Section mt={16} p={16}>
<Stack>
{bookmarks.map((bookmark: any) => (
<Popover key={bookmark.id} withArrow withOverlay>
<Popover.Target>
<Box>
<BookmarkSettingsItem bookmark={bookmark} />
</Box>
</Popover.Target>
<Popover.Dropdown
style={{
'--color-text-pri': theme.primaryColor,
'--color-text-acc': theme.accentColor,
'--color-background': theme.backgroundColor,
'--color-border': theme.borderColor,
'--color-search-background': theme.searchColor,
'--image-background': `url(${theme.backgroundImage})`,
}}
>
<DebouncedBookmarkItem
bookmarkItem={bookmark}
handleDelete={handleDeleteBookmark}
/>
</Popover.Dropdown>
</Popover>
))}
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId={`${bookmarkGroup.id}`}>
{(provided) => (
<Stack ref={provided.innerRef} {...provided.droppableProps}>
{bookmarks
.sort((a: any, b: any) => (a.index > b.index ? 1 : -1))
.map((bookmark: any) => (
<Draggable
key={bookmark.id}
draggableId={`${bookmark.id}`}
index={bookmark.index}
>
{(provided: any) => (
<Box
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Popover withArrow withOverlay>
<Popover.Target>
<Box>
<BookmarkSettingsItem bookmark={bookmark} />
</Box>
</Popover.Target>
<Popover.Dropdown
style={{
'--color-text-pri': theme.primaryColor,
'--color-text-acc': theme.accentColor,
'--color-background': theme.backgroundColor,
'--color-border': theme.borderColor,
'--color-search-background': theme.searchColor,
'--image-background': `url(${theme.backgroundImage})`,
}}
>
<DebouncedBookmarkItem
bookmarkItem={bookmark}
handleDelete={handleDeleteBookmark}
/>
</Popover.Dropdown>
</Popover>
{provided.placeholder}
</Box>
)}
</Draggable>
))}
{provided.placeholder}
</Stack>
)}
</Droppable>
</DragDropContext>
</Stack>
</Card.Section>
<Card.Section p={8} bg={theme.searchColor} c={theme.backgroundColor}>