149 lines
4.8 KiB
TypeScript
149 lines
4.8 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Drawer, Text, Stack, NavLink, ThemeIcon, ActionIcon, ScrollArea, Group } from '@repo/ui/components';
|
|
import { useAppEvent } from '@repo/core-events';
|
|
import { useTranslation } from '@repo/core-i18n';
|
|
import { Bookmark, Trash } from 'lucide-react';
|
|
import { LAYOUT_EVENTS } from '../../../../../core/constants/events';
|
|
import { appDatabase, AppDatabaseKey } from '../../../../../core/storage/local';
|
|
import { Link } from 'react-router-dom';
|
|
import dayjs from 'dayjs';
|
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
import type { SavedPageItem } from '../../types/saved-page.types';
|
|
|
|
// Setup relative time formatting
|
|
dayjs.extend(relativeTime);
|
|
|
|
export function BookmarkDrawer() {
|
|
const { t } = useTranslation();
|
|
const [opened, setOpened] = useState(false);
|
|
const [items, setItems] = useState<SavedPageItem[]>([]);
|
|
|
|
// Listen to the global event bus to toggle the drawer
|
|
useAppEvent(LAYOUT_EVENTS.TOGGLE_BOOKMARK_DRAWER, () => {
|
|
setOpened((prev) => !prev);
|
|
});
|
|
|
|
const loadData = async () => {
|
|
try {
|
|
const data = await appDatabase.getItem<SavedPageItem[]>(AppDatabaseKey.BOOKMARK_PAGE);
|
|
setItems(data || []);
|
|
} catch (e) {
|
|
console.error('Failed to load bookmark items', e);
|
|
setItems([]);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (opened) {
|
|
loadData();
|
|
}
|
|
}, [opened]);
|
|
|
|
const handleRemoveItem = async (id: string, e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
try {
|
|
const newItems = items.filter((item) => item.id !== id);
|
|
await appDatabase.setItem(AppDatabaseKey.BOOKMARK_PAGE, newItems);
|
|
setItems(newItems);
|
|
} catch (err) {
|
|
console.error('Failed to remove bookmark item', err);
|
|
}
|
|
};
|
|
|
|
const handleClearAll = async () => {
|
|
try {
|
|
await appDatabase.removeItem(AppDatabaseKey.BOOKMARK_PAGE);
|
|
setItems([]);
|
|
} catch (err) {
|
|
console.error('Failed to clear bookmarks', err);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Drawer
|
|
opened={opened}
|
|
onClose={() => setOpened(false)}
|
|
position="right"
|
|
size="sm"
|
|
title={
|
|
<Group gap="sm">
|
|
<Bookmark size={18} style={{ color: 'var(--mantine-color-text)' }} />
|
|
<Text fw={600} size="lg">
|
|
{t('bookmark:title')}
|
|
</Text>
|
|
</Group>
|
|
}
|
|
styles={{ body: { padding: 0, display: 'flex', flexDirection: 'column', height: 'calc(100vh - 60px)' } }}
|
|
>
|
|
{items.length === 0 ? (
|
|
<Text c="dimmed" size="sm" ta="center" mt="xl" p="md">
|
|
{t('bookmark:empty')}
|
|
</Text>
|
|
) : (
|
|
<>
|
|
<Group justify="flex-end" px="md" py="xs">
|
|
<Text
|
|
size="xs"
|
|
c="var(--mantine-color-error-6)"
|
|
style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '4px' }}
|
|
onClick={handleClearAll}
|
|
>
|
|
<Trash size={12} /> {t('bookmark:clearAll')}
|
|
</Text>
|
|
</Group>
|
|
<ScrollArea flex={1} type="scroll" px="xs" py="xs" pb={'xl'}>
|
|
<Stack gap={2}>
|
|
{items.map((item) => (
|
|
<NavLink
|
|
key={item.id}
|
|
component={Link as any}
|
|
to={item.path}
|
|
onClick={() => setOpened(false)}
|
|
label={
|
|
<Text size="sm" truncate="end" fw={500} lineClamp={1}>
|
|
{item.title}
|
|
</Text>
|
|
}
|
|
description={
|
|
<Stack gap={2}>
|
|
<Text size="xs" c="dimmed" truncate="end" mt={2}>
|
|
{item.path}
|
|
</Text>
|
|
</Stack>
|
|
}
|
|
leftSection={
|
|
<ThemeIcon variant="transparent" c="dimmed" size="sm">
|
|
<Bookmark size={16} />
|
|
</ThemeIcon>
|
|
}
|
|
rightSection={
|
|
<ActionIcon
|
|
variant="subtle"
|
|
color="var(--mantine-color-error-6)"
|
|
size="sm"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
handleRemoveItem(item.id, e);
|
|
}}
|
|
aria-label={t('bookmark:removeItem')}
|
|
>
|
|
<Trash size={14} />
|
|
</ActionIcon>
|
|
}
|
|
styles={{
|
|
root: {
|
|
padding: '8px 12px',
|
|
borderBottom: '1px solid var(--mantine-color-default-border)',
|
|
},
|
|
}}
|
|
/>
|
|
))}
|
|
</Stack>
|
|
</ScrollArea>
|
|
</>
|
|
)}
|
|
</Drawer>
|
|
);
|
|
}
|