diff --git a/src/hooks/useAPI.ts b/src/hooks/useAPI.ts index 34345f0..609b854 100644 --- a/src/hooks/useAPI.ts +++ b/src/hooks/useAPI.ts @@ -132,6 +132,8 @@ export function useMutationAPI() { queryClient.refetchQueries({ queryKey: ['GET', 'api/links'] }); endpoint === 'api/categoryOrder' && queryClient.refetchQueries({ queryKey: ['GET', 'api/categories'] }); + endpoint === 'api/applicationOrder' && + queryClient.refetchQueries({ queryKey: ['GET', 'api/applicationOrder'] }); }, onSuccess: (data, { method, endpoint }) => { queryClient.refetchQueries({ @@ -141,6 +143,8 @@ export function useMutationAPI() { queryClient.refetchQueries({ queryKey: ['GET', 'api/links'] }); endpoint === 'api/categoryOrder' && queryClient.refetchQueries({ queryKey: ['GET', 'api/categories'] }); + endpoint === 'api/applicationOrder' && + queryClient.refetchQueries({ queryKey: ['GET', 'api/applicationOrder'] }); }, }); } diff --git a/src/hooks/useCommonHooks.ts b/src/hooks/useCommonHooks.ts index 9942925..2e63387 100644 --- a/src/hooks/useCommonHooks.ts +++ b/src/hooks/useCommonHooks.ts @@ -16,6 +16,14 @@ export function useCommonHooks() { key: 'appFilter', defaultValue: ['All'], }); + const [appSort, setAppSort] = useLocalStorage({ + key: 'appSort', + defaultValue: 'A-Z', + }); + const [bookmarkSort, setBookmarkSort] = useLocalStorage({ + key: 'bookmarkSort', + defaultValue: 'A-Z', + }); const [bookmarkFilter, setBookmarkFilter] = useLocalStorage<[any]>({ key: 'bookmarkFilter', defaultValue: ['All'], @@ -114,6 +122,10 @@ export function useCommonHooks() { setThemeName, appFilter, setAppFilter, + appSort, + setAppSort, + bookmarkSort, + setBookmarkSort, bookmarkFilter, setBookmarkFilter, currentTheme, diff --git a/src/pages/homepage/components/Applications/Applications.tsx b/src/pages/homepage/components/Applications/Applications.tsx index 704a763..71c209f 100644 --- a/src/pages/homepage/components/Applications/Applications.tsx +++ b/src/pages/homepage/components/Applications/Applications.tsx @@ -9,17 +9,31 @@ function Applications({ contentType }: any) { const { data: apps, isFetching: isAppsFetching } = useQueryAPI({ endpoint: 'api/apps', }); - const { isMobile } = useCommonHooks(); + const { isMobile, appSort } = useCommonHooks(); const [applicationData, setApplicationData] = useState([]); useEffect(() => { if (apps && !isAppsFetching) { setApplicationData( - apps.sort((a: Application, b: Application) => (a.name > b.name ? 1 : -1)), + apps + .filter((a: Application) => a.enabled === 1) + .sort((a: any, b: any) => { + switch (appSort) { + case 'A>Z': + return a.name > b.name ? 1 : -1; + case 'Z>A': + return a.name < b.name ? 1 : -1; + case 'Newest': + return a.id < b.id ? 1 : -1; + case 'Oldest': + return a.id > b.id ? 1 : -1; + case 'Custom': + return a.index > b.index ? 1 : -1; + } + }), ); } - }, [apps, isAppsFetching]); - + }, [apps, isAppsFetching, appSort]); return ( <> {applicationData.length > 0 && contentType.length > 0 && ( @@ -39,11 +53,13 @@ function Applications({ contentType }: any) { gridTemplateRows: '64px', }} > - {applicationData?.map((application: any) => { - if ( - contentType.includes(application.filterName) || - contentType.includes('All') + {applicationData + ?.filter( + (application: Application) => + contentType.includes(application.filterName) || + contentType.includes('All'), ) + .map((application: any) => { return ( ); - })} + })} )} diff --git a/src/pages/homepage/components/Bookmarks/Bookmarks.tsx b/src/pages/homepage/components/Bookmarks/Bookmarks.tsx index 0c62f09..256ef55 100644 --- a/src/pages/homepage/components/Bookmarks/Bookmarks.tsx +++ b/src/pages/homepage/components/Bookmarks/Bookmarks.tsx @@ -12,7 +12,7 @@ function Links({ contentType }: any) { const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({ endpoint: 'api/categories', }); - const { isMobile } = useCommonHooks(); + const { isMobile, bookmarkSort } = useCommonHooks(); const [bookmarkData, setBookmarkData] = useState([]); const [groups, setGroups] = useState([]); @@ -28,12 +28,23 @@ function Links({ contentType }: any) { return category; }); setGroups( - groupList.sort((a: BookmarkGroup, b: BookmarkGroup) => - a.categoryIndex > b.categoryIndex ? 1 : -1, - ), + groupList.sort((a: any, b: any) => { + switch (bookmarkSort) { + case 'A>Z': + return a.categoryName > b.categoryName ? 1 : -1; + case 'Z>A': + return a.categoryName < b.categoryName ? 1 : -1; + case 'Newest': + return a.id < b.id ? 1 : -1; + case 'Oldest': + return a.id > b.id ? 1 : -1; + case 'Custom': + return a.index > b.index ? 1 : -1; + } + }), ); } - }, [groupData, groupsIsFetched]); + }, [groupData, groupsIsFetched, bookmarkSort]); return ( <> @@ -55,11 +66,14 @@ function Links({ contentType }: any) { gridTemplateRows: 'auto', }} > - {groups.map((category: BookmarkGroup) => { - if ( - contentType.includes(category.filterName) || - contentType.includes('All') - ) { + {groups + ?.filter( + (category: BookmarkGroup) => + contentType.includes(category.filterName) || + contentType.includes('All'), + ) + + .map((category: BookmarkGroup) => { return ( ); - } - })} + })} )} diff --git a/src/pages/homepage/components/SettingsButton.tsx b/src/pages/homepage/components/SettingsButton.tsx index 448e88d..02c1a9a 100644 --- a/src/pages/homepage/components/SettingsButton.tsx +++ b/src/pages/homepage/components/SettingsButton.tsx @@ -53,6 +53,10 @@ function SettingsButton() { theme, appFilter, setAppFilter, + appSort, + setAppSort, + bookmarkSort, + setBookmarkSort, bookmarkFilter, setBookmarkFilter, isMobile, @@ -330,6 +334,57 @@ function SettingsButton() { /> + + + Sorting + + Z', 'Z>A', 'Newest', 'Oldest']} + w={'45%'} + classNames={{ + input: selectClasses.menuSelectInput, + label: selectClasses.menuSelectLabel, + options: selectClasses.menuSelectOptions, + option: selectClasses.menuSelectOption, + }} + comboboxProps={{ withinPortal: false }} + onChange={(_value, option: any) => { + setBookmarkSort(option.value); + }} + /> + + diff --git a/src/pages/settings/components/Applications/AppSettingsItem.tsx b/src/pages/settings/components/Applications/AppSettingsItem.tsx index 97ddc95..9a84b0b 100644 --- a/src/pages/settings/components/Applications/AppSettingsItem.tsx +++ b/src/pages/settings/components/Applications/AppSettingsItem.tsx @@ -1,74 +1,91 @@ import { Icon } from '@iconify/react'; -import { Box, Stack, Text } from '@mantine/core'; +import { ActionIcon, Box, Stack, Text, Tooltip } from '@mantine/core'; import { useCommonHooks } from 'hooks'; import { useState } from 'react'; -import { boxClasses, textClasses } from 'styles'; +import { FiAlertCircle } from 'react-icons/fi'; +import { actionIconClasses, boxClasses, textClasses } from 'styles'; -function AppSettingsItem({ url, icon, name }: any) { +function AppSettingsItem({ url, icon, name, enabled }: any) { const [textDecoration, setTextDecoration] = useState('none'); const [hover, setHover] = useState(false); - const { isMobile } = useCommonHooks(); + const { isMobile, theme } = useCommonHooks(); return ( - { - setTextDecoration('underline'); - setHover(true); - }} - onMouseOut={() => { - setTextDecoration('none'); - setHover(false); - }} - className={boxClasses.appItemBox} - > - {hover && ( - - - + <> + {enabled === 0 && ( + This app is disabled}> + + + + )} - { + setTextDecoration('underline'); + setHover(true); }} - > - - - { + setTextDecoration('none'); + setHover(false); }} + className={boxClasses.appItemBox} > - - {name} - - + + + )} + - {url} - - - + + + + + {name} + + + {url} + + + + ); } diff --git a/src/pages/settings/components/Applications/Applications.tsx b/src/pages/settings/components/Applications/Applications.tsx index 0e40b40..c4b170e 100644 --- a/src/pages/settings/components/Applications/Applications.tsx +++ b/src/pages/settings/components/Applications/Applications.tsx @@ -1,3 +1,4 @@ +import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; import { ActionIcon, Box, @@ -11,6 +12,7 @@ import { Modal, Popover, Stack, + Switch, Text, TextInput, } from '@mantine/core'; @@ -36,19 +38,50 @@ const ApplicationSettings = () => { endpoint: endpoint, }); const [applicationData, setApplicationData] = useState([]); + const [applicationList, setApplicationList] = useState([]); const { theme, isMobile, modalStyle } = useCommonHooks(); const [newApplication, setNewApplication] = useState({ - id: undefined, + id: null, name: '', url: '', icon: '', baseUrl: '', filterName: '', index: 0, + enabled: 1, }); const { mutateAsync } = useMutationAPI(); const [searchValue, setSearchValue] = useState(''); const [debouncedSearch] = useDebouncedValue(searchValue, 500); + const [dragging, setDragging] = useState(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(applicationData, result.source.index, result.destination.index); + setApplicationList( + items.map((item: any, index: number) => { + return { + ...item, + index: index, + }; + }), + ); + } + const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); useEffect(() => { @@ -58,11 +91,21 @@ const ApplicationSettings = () => { .filter((a: Application) => a.name.toLowerCase().includes(debouncedSearch.toLowerCase()), ) - .sort((a: Application, b: Application) => (a.name > b.name ? 1 : -1)), + .sort((a: Application, b: Application) => (a.index > b.index ? 1 : -1)), ); } }, [apps, isAppsFetching, debouncedSearch]); + useEffect(() => { + if (applicationList.length > 0 && applicationList !== applicationData) { + mutateAsync({ + method: 'PATCH', + endpoint: 'api/applicationOrder', + body: applicationList, + }); + } + }, [applicationList]); + return ( { setSearchValue(event.target.value); }} /> + Reorder} + labelPosition='left' + pos={'absolute'} + right={isMobile ? '2rem' : '1rem'} + top={isMobile ? '10rem' : undefined} + onChange={(e) => { + setDragging(e.target.checked); + }} + color={theme.primaryColor} + styles={{ + label: { + color: theme.primaryColor, + top: isMobile ? -22 : 0, + left: isMobile ? -8 : 0, + position: isMobile ? 'absolute' : undefined, + }, + }} + /> - - - {applicationData?.map((application: Application) => ( - - - - { - e.stopPropagation(); - }} - > - - - - - - - - - ))} - - + {dragging ? ( + + + + {(provided) => ( + + {applicationData?.map((app: Application) => ( + + {(provided) => ( + + + + + + Name + + + {app.name} + + + + + Filter Name + + + {app.filterName} + + + + + + )} + + ))} + {provided.placeholder} + + )} + + + + ) : ( + + + {applicationData?.map((application: Application) => ( + + + + { + e.stopPropagation(); + }} + > + + + + + + + + + ))} + + + )} ); }; diff --git a/src/pages/settings/components/Applications/DebouncedApplication.tsx b/src/pages/settings/components/Applications/DebouncedApplication.tsx index 76a5872..819d0a0 100644 --- a/src/pages/settings/components/Applications/DebouncedApplication.tsx +++ b/src/pages/settings/components/Applications/DebouncedApplication.tsx @@ -5,6 +5,7 @@ import { Group, Modal, Stack, + Switch, Text, TextInput, Tooltip, @@ -36,7 +37,6 @@ function DebouncedApplication({ application }: { application: Application }) { setApp(update); } }, [update]); - return ( + + Enabled} + labelPosition='left' + pos={'absolute'} + left={16} + defaultChecked={app.enabled === 1} + defaultValue={app.enabled} + onChange={(event) => { + setUpdate({ + ...app, + enabled: event.target.checked ? 1 : 0, + }); + }} + color={theme.primaryColor} + /> +