diff --git a/client/src/components/Apps/AppGrid/AppGrid.tsx b/client/src/components/Apps/AppGrid/AppGrid.tsx index a29adad..481b901 100644 --- a/client/src/components/Apps/AppGrid/AppGrid.tsx +++ b/client/src/components/Apps/AppGrid/AppGrid.tsx @@ -6,7 +6,7 @@ import classes from './AppGrid.module.css'; interface ComponentProps { categories: Category[]; - apps: App[] + apps: App[]; totalCategories?: number; searching: boolean; } diff --git a/client/src/components/Apps/AppTable/AppTable.tsx b/client/src/components/Apps/AppTable/AppTable.tsx index eb97b84..e93d520 100644 --- a/client/src/components/Apps/AppTable/AppTable.tsx +++ b/client/src/components/Apps/AppTable/AppTable.tsx @@ -29,7 +29,7 @@ interface ComponentProps { reorderAppCategories: (categories: Category[]) => void; updateHandler: (data: Category | App) => void; pinApp: (app: App) => void; - deleteApp: (id: number) => void; + deleteApp: (id: number, categoryId: number) => void; reorderApps: (apps: App[]) => void; updateConfig: (formData: any) => void; createNotification: (notification: NewNotification) => void; @@ -75,7 +75,7 @@ const AppTable = (props: ComponentProps): JSX.Element => { ); if (proceed) { - props.deleteApp(app.id); + props.deleteApp(app.id, app.categoryId); } }; diff --git a/client/src/components/Apps/Apps.tsx b/client/src/components/Apps/Apps.tsx index f2049e4..c7718da 100644 --- a/client/src/components/Apps/Apps.tsx +++ b/client/src/components/Apps/Apps.tsx @@ -14,7 +14,7 @@ import AppGrid from './AppGrid/AppGrid'; import classes from './Apps.module.css'; import AppTable from './AppTable/AppTable'; -interface ComponentProps { +interface ComponentProps { loading: boolean; categories: Category[]; getAppCategories: () => void; @@ -25,23 +25,18 @@ interface ComponentProps { export enum ContentType { category, - app + app, } const Apps = (props: ComponentProps): JSX.Element => { - const { - apps, - getApps, - getAppCategories, - categories, - loading, - searching = false - } = props; + const { apps, getApps, getAppCategories, categories, loading, searching = false } = props; const [modalIsOpen, setModalIsOpen] = useState(false); const [formContentType, setFormContentType] = useState(ContentType.category); const [isInEdit, setIsInEdit] = useState(false); - const [tableContentType, setTableContentType] = useState(ContentType.category); + const [tableContentType, setTableContentType] = useState( + ContentType.category + ); const [isInUpdate, setIsInUpdate] = useState(false); const [categoryInUpdate, setCategoryInUpdate] = useState({ name: "", @@ -49,10 +44,11 @@ const Apps = (props: ComponentProps): JSX.Element => { isPinned: false, orderId: 0, type: "apps", + apps: [], bookmarks: [], createdAt: new Date(), - updatedAt: new Date() - }) + updatedAt: new Date(), + }); const [appInUpdate, setAppInUpdate] = useState({ name: "string", url: "string", @@ -75,18 +71,17 @@ const Apps = (props: ComponentProps): JSX.Element => { if (categories.length === 0) { getAppCategories(); } - }, [getAppCategories]) + }, [getAppCategories]); const toggleModal = (): void => { setModalIsOpen(!modalIsOpen); - // setIsInUpdate(false); - } + }; const addActionHandler = (contentType: ContentType) => { setFormContentType(contentType); setIsInUpdate(false); toggleModal(); - } + }; const editActionHandler = (contentType: ContentType) => { // We"re in the edit mode and the same button was clicked - go back to list @@ -96,11 +91,11 @@ const Apps = (props: ComponentProps): JSX.Element => { setIsInEdit(true); setTableContentType(contentType); } - } + }; const instanceOfCategory = (object: any): object is Category => { return "apps" in object; - } + }; const goToUpdateMode = (data: Category | App): void => { setIsInUpdate(true); @@ -112,7 +107,7 @@ const Apps = (props: ComponentProps): JSX.Element => { setAppInUpdate(data); } toggleModal(); - } + }; return ( @@ -157,7 +152,7 @@ const mapStateToProps = (state: GlobalState) => { loading: state.app.loading, categories: state.app.categories, apps: state.app.apps, - } -} + }; +}; export default connect(mapStateToProps, { getApps, getAppCategories })(Apps); diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx index b332a6f..b9b16e5 100644 --- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx +++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx @@ -1,12 +1,14 @@ -import { Bookmark, Category } from '../../../interfaces'; -import classes from './BookmarkCard.module.css'; - -import Icon from '../../UI/Icons/Icon/Icon'; -import { iconParser, urlParser, searchConfig } from '../../../utility'; import { Fragment } from 'react'; +import { Bookmark, Category } from '../../../interfaces'; +import { iconParser, searchConfig, urlParser } from '../../../utility'; +import Icon from '../../UI/Icons/Icon/Icon'; +import classes from './BookmarkCard.module.css'; + interface ComponentProps { category: Category; + bookmarks: Bookmark[] + pinHandler?: Function; } const BookmarkCard = (props: ComponentProps): JSX.Element => { @@ -14,7 +16,7 @@ const BookmarkCard = (props: ComponentProps): JSX.Element => {

{props.category.name}

- {props.category.bookmarks.map((bookmark: Bookmark) => { + {props.bookmarks.map((bookmark: Bookmark) => { const redirectUrl = urlParser(bookmark.url)[1]; let iconEl: JSX.Element = ; diff --git a/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx b/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx index 8a7db3b..22136b2 100644 --- a/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx +++ b/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx @@ -1,11 +1,12 @@ import { Link } from 'react-router-dom'; -import { Category } from '../../../interfaces'; +import { Bookmark, Category } from '../../../interfaces'; import BookmarkCard from '../BookmarkCard/BookmarkCard'; import classes from './BookmarkGrid.module.css'; interface ComponentProps { categories: Category[]; + bookmarks: Bookmark[]; totalCategories?: number; searching: boolean; } @@ -25,7 +26,7 @@ const BookmarkGrid = (props: ComponentProps): JSX.Element => {
{props.categories.map( (category: Category): JSX.Element => ( - + bookmark.categoryId === category.id)} /> ) )}
diff --git a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx index 577b33f..084f670 100644 --- a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx +++ b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx @@ -8,8 +8,11 @@ import { createNotification, deleteBookmark, deleteBookmarkCategory, + pinBookmark, pinBookmarkCategory, reorderBookmarkCategories, + reorderBookmarks, + updateConfig, } from '../../../store/actions'; import { searchConfig } from '../../../utility'; import Icon from '../../UI/Icons/Icon/Icon'; @@ -20,16 +23,21 @@ import classes from './BookmarkTable.module.css'; interface ComponentProps { contentType: ContentType; categories: Category[]; + bookmarks: Bookmark[]; pinBookmarkCategory: (category: Category) => void; deleteBookmarkCategory: (id: number) => void; - updateHandler: (data: Category | Bookmark) => void; - deleteBookmark: (bookmarkId: number, categoryId: number) => void; - createNotification: (notification: NewNotification) => void; reorderBookmarkCategories: (categories: Category[]) => void; + updateHandler: (data: Category | Bookmark) => void; + pinBookmark: (bookmark: Bookmark) => void; + deleteBookmark: (id: number, categoryId: number) => void; + reorderBookmarks: (bookmarks: Bookmark[]) => void; + updateConfig: (formData: any) => void; + createNotification: (notification: NewNotification) => void; } const BookmarkTable = (props: ComponentProps): JSX.Element => { const [localCategories, setLocalCategories] = useState([]); + const [localBookmarks, setLocalBookmarks] = useState([]); const [isCustomOrder, setIsCustomOrder] = useState(false); // Copy categories array @@ -37,6 +45,11 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => { setLocalCategories([...props.categories]); }, [props.categories]); + // Copy bookmarks array + useEffect(() => { + setLocalBookmarks([...props.bookmarks]); + }, [props.bookmarks]); + // Check ordering useEffect(() => { const order = searchConfig("useOrdering", ""); @@ -66,13 +79,14 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => { } }; + // Support keyboard navigation for actions const keyboardActionHandler = ( e: KeyboardEvent, - category: Category, + object: any, handler: Function ) => { if (e.key === "Enter") { - handler(category); + handler(object); } }; @@ -89,12 +103,21 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => { return; } - const tmpCategories = [...localCategories]; - const [movedCategory] = tmpCategories.splice(result.source.index, 1); - tmpCategories.splice(result.destination.index, 0, movedCategory); + if (props.contentType === ContentType.bookmark) { + const tmpBookmarks = [...localBookmarks]; + const [movedBookmark] = tmpBookmarks.splice(result.source.index, 1); + tmpBookmarks.splice(result.destination.index, 0, movedBookmark); - setLocalCategories(tmpCategories); - props.reorderBookmarkCategories(tmpCategories); + setLocalBookmarks(tmpBookmarks); + props.reorderBookmarks(tmpBookmarks); + } else if (props.contentType === ContentType.category) { + const tmpCategories = [...localCategories]; + const [movedCategory] = tmpCategories.splice(result.source.index, 1); + tmpCategories.splice(result.destination.index, 0, movedCategory); + + setLocalCategories(tmpCategories); + props.reorderBookmarkCategories(tmpCategories); + } }; if (props.contentType === ContentType.category) { @@ -168,7 +191,9 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => {
props.pinBookmarkCategory(category)} + onClick={() => + props.pinBookmarkCategory(category) + } onKeyDown={(e) => keyboardActionHandler( e, @@ -203,47 +228,132 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => { ); } else { - const bookmarks: { bookmark: Bookmark; categoryName: string }[] = []; - props.categories.forEach((category: Category) => { - category.bookmarks.forEach((bookmark: Bookmark) => { - bookmarks.push({ - bookmark, - categoryName: category.name, - }); - }); - }); - return ( - - {bookmarks.map( - (bookmark: { bookmark: Bookmark; categoryName: string }) => { - return ( - - - - - - - - ); - } - )} -
{bookmark.bookmark.name}{bookmark.bookmark.url}{bookmark.bookmark.icon}{bookmark.categoryName} -
deleteBookmarkHandler(bookmark.bookmark)} - tabIndex={0} - > - -
-
props.updateHandler(bookmark.bookmark)} - tabIndex={0} - > - -
-
+ +
+ {isCustomOrder ? ( +

You can drag and drop single rows to reorder bookmarklication

+ ) : ( +

+ Custom order is disabled. You can change it in{" "} + settings +

+ )} +
+ + + {(provided) => ( + + {localBookmarks.map( + (bookmark: Bookmark, index): JSX.Element => { + return ( + + {(provided, snapshot) => { + const style = { + border: snapshot.isDragging + ? "1px solid var(--color-accent)" + : "none", + borderRadius: "4px", + ...provided.draggableProps.style, + }; + + const category = localCategories.find( + (category: Category) => + category.id === bookmark.categoryId + ); + const categoryName = category?.name; + + return ( + + + + + + {!snapshot.isDragging && ( + + )} + + ); + }} + + ); + } + )} +
+ {bookmark.name} + {bookmark.url} + {bookmark.icon} + {categoryName} +
+ deleteBookmarkHandler(bookmark) + } + onKeyDown={(e) => + keyboardActionHandler( + e, + bookmark, + deleteBookmarkHandler + ) + } + tabIndex={0} + > + +
+
+ props.updateHandler(bookmark) + } + onKeyDown={(e) => + keyboardActionHandler( + e, + bookmark, + props.updateHandler + ) + } + tabIndex={0} + > + +
+
props.pinBookmark(bookmark)} + onKeyDown={(e) => + keyboardActionHandler( + e, + bookmark, + props.pinBookmark + ) + } + tabIndex={0} + > + {bookmark.isPinned ? ( + + ) : ( + + )} +
+
+ )} +
+
+
); } }; @@ -251,9 +361,12 @@ const BookmarkTable = (props: ComponentProps): JSX.Element => { const actions = { pinBookmarkCategory, deleteBookmarkCategory, - deleteBookmark, - createNotification, reorderBookmarkCategories, + pinBookmark, + deleteBookmark, + reorderBookmarks, + updateConfig, + createNotification, }; export default connect(null, actions)(BookmarkTable); diff --git a/client/src/components/Bookmarks/Bookmarks.tsx b/client/src/components/Bookmarks/Bookmarks.tsx index af658b7..c332687 100644 --- a/client/src/components/Bookmarks/Bookmarks.tsx +++ b/client/src/components/Bookmarks/Bookmarks.tsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { Bookmark, Category, GlobalState } from '../../interfaces'; -import { getBookmarkCategories } from '../../store/actions'; +import { getBookmarkCategories, getBookmarks } from '../../store/actions'; import ActionButton from '../UI/Buttons/ActionButton/ActionButton'; import Headline from '../UI/Headlines/Headline/Headline'; import { Container } from '../UI/Layout/Layout'; @@ -18,6 +18,8 @@ interface ComponentProps { loading: boolean; categories: Category[]; getBookmarkCategories: () => void; + bookmarks: Bookmark[]; + getBookmarks: () => void; searching: boolean; } @@ -27,7 +29,7 @@ export enum ContentType { } const Bookmarks = (props: ComponentProps): JSX.Element => { - const { getBookmarkCategories, categories, loading, searching = false } = props; + const { bookmarks, getBookmarks, getBookmarkCategories, categories, loading, searching = false } = props; const [modalIsOpen, setModalIsOpen] = useState(false); const [formContentType, setFormContentType] = useState(ContentType.category); @@ -37,25 +39,34 @@ const Bookmarks = (props: ComponentProps): JSX.Element => { ); const [isInUpdate, setIsInUpdate] = useState(false); const [categoryInUpdate, setCategoryInUpdate] = useState({ - name: '', + name: "", id: -1, isPinned: false, orderId: 0, - type: 'bookmarks', + type: "bookmarks", + apps: [], bookmarks: [], createdAt: new Date(), updatedAt: new Date(), }); const [bookmarkInUpdate, setBookmarkInUpdate] = useState({ - name: '', - url: '', + name: "", + url: "", categoryId: -1, - icon: '', - id: -1, + icon: "", + isPinned: false, + orderId: 0, + id: 0, createdAt: new Date(), updatedAt: new Date(), }); + useEffect(() => { + if (bookmarks.length === 0) { + getBookmarks(); + } + }, [getBookmarks]); + useEffect(() => { if (categories.length === 0) { getBookmarkCategories(); @@ -83,7 +94,7 @@ const Bookmarks = (props: ComponentProps): JSX.Element => { }; const instanceOfCategory = (object: any): object is Category => { - return 'bookmarks' in object; + return "bookmarks" in object; }; const goToUpdateMode = (data: Category | Bookmark): void => { @@ -149,11 +160,12 @@ const Bookmarks = (props: ComponentProps): JSX.Element => { {loading ? ( ) : !isInEdit ? ( - + ) : ( )} @@ -168,4 +180,4 @@ const mapStateToProps = (state: GlobalState) => { }; }; -export default connect(mapStateToProps, { getBookmarkCategories })(Bookmarks); +export default connect(mapStateToProps, { getBookmarks, getBookmarkCategories })(Bookmarks); diff --git a/client/src/components/Home/Home.tsx b/client/src/components/Home/Home.tsx index 088e056..5f5033a 100644 --- a/client/src/components/Home/Home.tsx +++ b/client/src/components/Home/Home.tsx @@ -2,9 +2,8 @@ import { Fragment, useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; -import { App, Bookmark, Category } from '../../interfaces'; -import { GlobalState } from '../../interfaces/GlobalState'; -import { getAppCategories, getApps, getBookmarkCategories } from '../../store/actions'; +import { App, Bookmark, Category, GlobalState } from '../../interfaces'; +import { getAppCategories, getApps, getBookmarkCategories, getBookmarks } from '../../store/actions'; import { searchConfig } from '../../utility'; import AppGrid from '../Apps/AppGrid/AppGrid'; import BookmarkGrid from '../Bookmarks/BookmarkGrid/BookmarkGrid'; @@ -21,12 +20,14 @@ import classes from './Home.module.css'; interface ComponentProps { getApps: () => void; getAppCategories: () => void; + getBookmarks: () => void; getBookmarkCategories: () => void; appsLoading: boolean; bookmarkCategoriesLoading: boolean; appCategories: Category[]; apps: App[]; bookmarkCategories: Category[]; + bookmarks: Bookmark[]; } const Home = (props: ComponentProps): JSX.Element => { @@ -34,9 +35,11 @@ const Home = (props: ComponentProps): JSX.Element => { getAppCategories, getApps, getBookmarkCategories, + getBookmarks, appCategories, apps, bookmarkCategories, + bookmarks, appsLoading, bookmarkCategoriesLoading, } = props; @@ -70,12 +73,19 @@ const Home = (props: ComponentProps): JSX.Element => { } }, [getBookmarkCategories]); + // Load bookmarks + useEffect(() => { + if (bookmarks.length === 0) { + getBookmarks(); + } + }, [getBookmarks]); + // Refresh greeter and time useEffect(() => { let interval: any; // Start interval only when hideHeader is false - if (searchConfig('hideHeader', 0) !== 1) { + if (searchConfig("hideHeader", 0) !== 1) { interval = setInterval(() => { setHeader({ dateTime: dateTime(), @@ -87,12 +97,16 @@ const Home = (props: ComponentProps): JSX.Element => { return () => clearInterval(interval); }, []); - // Search bookmarks - const searchBookmarks = (query: string, categoriesToSearch: Category[]): Category[] => { + // Search categories + const searchInCategories = (query: string, categoriesToSearch: Category[]): Category[] => { const category: Category = { name: "Search Results", type: categoriesToSearch[0].type, isPinned: true, + apps: categoriesToSearch + .map((c: Category) => c.apps) + .flat() + .filter((app: App) => new RegExp(query, 'i').test(app.name)), bookmarks: categoriesToSearch .map((c: Category) => c.bookmarks) .flat() @@ -136,7 +150,11 @@ const Home = (props: ComponentProps): JSX.Element => { ) : ( category.isPinned)} + categories={ + !localSearch + ? appCategories.filter((category: Category) => category.isPinned) + : searchInCategories(localSearch, appCategories) + } apps={ !localSearch ? apps.filter((app: App) => app.isPinned) @@ -154,7 +172,7 @@ const Home = (props: ComponentProps): JSX.Element => {
)} - {searchConfig('hideCategories', 0) !== 1 ? ( + {searchConfig('hideBookmarks', 0) !== 1 ? ( {bookmarkCategoriesLoading ? ( @@ -164,7 +182,14 @@ const Home = (props: ComponentProps): JSX.Element => { categories={ !localSearch ? bookmarkCategories.filter((category: Category) => category.isPinned) - : searchBookmarks(localSearch, bookmarkCategories) + : searchInCategories(localSearch, bookmarkCategories) + } + bookmarks={ + !localSearch + ? bookmarks.filter((bookmark: Bookmark) => bookmark.isPinned) + : bookmarks.filter((bookmark: Bookmark) => + new RegExp(localSearch, 'i').test(bookmark.name) + ) } totalCategories={bookmarkCategories.length} searching={!!localSearch} @@ -189,7 +214,8 @@ const mapStateToProps = (state: GlobalState) => { apps: state.app.apps, bookmarkCategoriesLoading: state.bookmark.loading, bookmarkCategories: state.bookmark.categories, + bookmarks: state.bookmark.bookmarks, } } -export default connect(mapStateToProps, { getApps, getAppCategories, getBookmarkCategories })(Home); +export default connect(mapStateToProps, { getApps, getAppCategories, getBookmarks, getBookmarkCategories })(Home); diff --git a/client/src/components/Settings/OtherSettings/OtherSettings.tsx b/client/src/components/Settings/OtherSettings/OtherSettings.tsx index 7d2e83b..a963676 100644 --- a/client/src/components/Settings/OtherSettings/OtherSettings.tsx +++ b/client/src/components/Settings/OtherSettings/OtherSettings.tsx @@ -28,10 +28,11 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { const [formData, setFormData] = useState({ customTitle: document.title, pinAppsByDefault: 1, + pinBookmarksByDefault: 1, pinCategoriesByDefault: 1, hideHeader: 0, hideApps: 0, - hideCategories: 0, + hideBookmarks: 0, useOrdering: 'createdAt', appsSameTab: 0, bookmarksSameTab: 0, @@ -46,10 +47,11 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { setFormData({ customTitle: searchConfig('customTitle', 'Flame'), pinAppsByDefault: searchConfig('pinAppsByDefault', 1), + pinBookmarksByDefault: searchConfig('pinBookmarksByDefault', 1), pinCategoriesByDefault: searchConfig('pinCategoriesByDefault', 1), hideHeader: searchConfig('hideHeader', 0), hideApps: searchConfig('hideApps', 0), - hideCategories: searchConfig('hideCategories', 0), + hideBookmarks: searchConfig('hideBookmarks', 0), useOrdering: searchConfig('useOrdering', 'createdAt'), appsSameTab: searchConfig('appsSameTab', 0), bookmarksSameTab: searchConfig('bookmarksSameTab', 0), @@ -125,6 +127,18 @@ const OtherSettings = (props: ComponentProps): JSX.Element => { + + + + - +