Date: Mon, 22 Nov 2021 14:36:00 +0100
Subject: [PATCH 05/11] Moved entityInUpdate to app state. It applies for apps,
categories and bookmarks
---
.../src/components/Actions/TableActions.tsx | 8 +-
.../src/components/Apps/AppForm/AppForm.tsx | 30 ++++---
client/src/components/Apps/Apps.tsx | 25 +++---
.../BookmarkCard/BookmarkCard.module.css | 4 +
.../Bookmarks/BookmarkCard/BookmarkCard.tsx | 21 ++++-
client/src/components/Bookmarks/Bookmarks.tsx | 29 ++++---
client/src/components/Bookmarks/Form/Form.tsx | 22 ++++--
client/src/store/action-creators/app.ts | 9 +++
client/src/store/action-creators/bookmark.ts | 20 +++++
client/src/store/action-types/index.ts | 3 +
client/src/store/actions/app.ts | 5 ++
client/src/store/actions/bookmark.ts | 10 +++
client/src/store/actions/index.ts | 6 ++
client/src/store/reducers/app.ts | 8 ++
client/src/store/reducers/bookmark.ts | 78 ++++++++++++-------
15 files changed, 197 insertions(+), 81 deletions(-)
diff --git a/client/src/components/Actions/TableActions.tsx b/client/src/components/Actions/TableActions.tsx
index 75613ee..6d9460c 100644
--- a/client/src/components/Actions/TableActions.tsx
+++ b/client/src/components/Actions/TableActions.tsx
@@ -4,7 +4,7 @@ import classes from './TableActions.module.css';
interface Entity {
id: number;
name: string;
- isPinned: boolean;
+ isPinned?: boolean;
isPublic: boolean;
}
@@ -12,7 +12,7 @@ interface Props {
entity: Entity;
deleteHandler: (id: number, name: string) => void;
updateHandler: (id: number) => void;
- pinHanlder: (id: number) => void;
+ pinHanlder?: (id: number) => void;
changeVisibilty: (id: number) => void;
showPin?: boolean;
}
@@ -27,6 +27,8 @@ export const TableActions = (props: Props): JSX.Element => {
showPin = true,
} = props;
+ const _pinHandler = pinHanlder || function () {};
+
return (
{/* DELETE */}
@@ -51,7 +53,7 @@ export const TableActions = (props: Props): JSX.Element => {
{showPin && (
pinHanlder(entity.id)}
+ onClick={() => _pinHandler(entity.id)}
tabIndex={0}
>
{entity.isPinned ? (
diff --git a/client/src/components/Apps/AppForm/AppForm.tsx b/client/src/components/Apps/AppForm/AppForm.tsx
index 10bc694..8679f82 100644
--- a/client/src/components/Apps/AppForm/AppForm.tsx
+++ b/client/src/components/Apps/AppForm/AppForm.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect, ChangeEvent, SyntheticEvent } from 'react';
-import { useDispatch } from 'react-redux';
-import { App, NewApp } from '../../../interfaces';
+import { useDispatch, useSelector } from 'react-redux';
+import { NewApp } from '../../../interfaces';
import classes from './AppForm.module.css';
@@ -8,29 +8,34 @@ import { ModalForm, InputGroup, Button } from '../../UI';
import { inputHandler, newAppTemplate } from '../../../utility';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../../store';
+import { State } from '../../../store/reducers';
interface Props {
modalHandler: () => void;
- app?: App;
}
-export const AppForm = ({ app, modalHandler }: Props): JSX.Element => {
+export const AppForm = ({ modalHandler }: Props): JSX.Element => {
+ const { appInUpdate } = useSelector((state: State) => state.apps);
+
const dispatch = useDispatch();
- const { addApp, updateApp } = bindActionCreators(actionCreators, dispatch);
+ const { addApp, updateApp, setEditApp } = bindActionCreators(
+ actionCreators,
+ dispatch
+ );
const [useCustomIcon, toggleUseCustomIcon] = useState (false);
const [customIcon, setCustomIcon] = useState(null);
const [formData, setFormData] = useState(newAppTemplate);
useEffect(() => {
- if (app) {
+ if (appInUpdate) {
setFormData({
- ...app,
+ ...appInUpdate,
});
} else {
setFormData(newAppTemplate);
}
- }, [app]);
+ }, [appInUpdate]);
const inputChangeHandler = (
e: ChangeEvent,
@@ -66,7 +71,7 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => {
return data;
};
- if (!app) {
+ if (!appInUpdate) {
if (customIcon) {
const data = createFormData();
addApp(data);
@@ -76,14 +81,15 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => {
} else {
if (customIcon) {
const data = createFormData();
- updateApp(app.id, data);
+ updateApp(appInUpdate.id, data);
} else {
- updateApp(app.id, formData);
+ updateApp(appInUpdate.id, formData);
modalHandler();
}
}
setFormData(newAppTemplate);
+ setEditApp(null);
};
return (
@@ -182,7 +188,7 @@ export const AppForm = ({ app, modalHandler }: Props): JSX.Element => {
- {!app ? (
+ {!appInUpdate ? (
) : (
diff --git a/client/src/components/Apps/Apps.tsx b/client/src/components/Apps/Apps.tsx
index 7231e18..88db874 100644
--- a/client/src/components/Apps/Apps.tsx
+++ b/client/src/components/Apps/Apps.tsx
@@ -19,7 +19,6 @@ import { AppForm } from './AppForm/AppForm';
import { AppTable } from './AppTable/AppTable';
// Utils
-import { appTemplate } from '../../utility';
import { State } from '../../store/reducers';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../store';
@@ -37,7 +36,7 @@ export const Apps = (props: Props): JSX.Element => {
// Get Redux action creators
const dispatch = useDispatch();
- const { getApps } = bindActionCreators(actionCreators, dispatch);
+ const { getApps, setEditApp } = bindActionCreators(actionCreators, dispatch);
// Load apps if array is empty
useEffect(() => {
@@ -49,8 +48,6 @@ export const Apps = (props: Props): JSX.Element => {
// Form
const [modalIsOpen, setModalIsOpen] = useState(false);
const [showTable, setShowTable] = useState(false);
- const [isInUpdate, setIsInUpdate] = useState(false);
- const [appInUpdate, setAppInUpdate] = useState(appTemplate);
// Observe if user is authenticated -> set default view if not
useEffect(() => {
@@ -63,28 +60,21 @@ export const Apps = (props: Props): JSX.Element => {
// Form actions
const toggleModal = (): void => {
setModalIsOpen(!modalIsOpen);
- setIsInUpdate(false);
};
const toggleEdit = (): void => {
setShowTable(!showTable);
- setIsInUpdate(false);
};
const openFormForUpdating = (app: App): void => {
- setAppInUpdate(app);
- setIsInUpdate(true);
+ setEditApp(app);
setModalIsOpen(true);
};
return (
- {!isInUpdate ? (
-
- ) : (
-
- )}
+
{
{isAuthenticated && (
-
+ {
+ setEditApp(null);
+ toggleModal();
+ }}
+ />
)}
diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
index b840a42..c61217d 100644
--- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
+++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
@@ -10,6 +10,10 @@
text-transform: uppercase;
}
+.BookmarkCard h3:hover {
+ cursor: pointer;
+}
+
.Bookmarks {
display: flex;
flex-direction: column;
diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx
index 146bf67..fe6c97d 100644
--- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx
+++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx
@@ -1,14 +1,17 @@
import { Fragment } from 'react';
-import { useSelector } from 'react-redux';
+// Redux
+import { useDispatch, useSelector } from 'react-redux';
import { State } from '../../../store/reducers';
+import { bindActionCreators } from 'redux';
+import { actionCreators } from '../../../store';
+// Typescript
import { Bookmark, Category } from '../../../interfaces';
+// Other
import classes from './BookmarkCard.module.css';
-
import { Icon } from '../../UI';
-
import { iconParser, isImage, isSvg, isUrl, urlParser } from '../../../utility';
interface Props {
@@ -18,9 +21,19 @@ interface Props {
export const BookmarkCard = (props: Props): JSX.Element => {
const { config } = useSelector((state: State) => state.config);
+ const dispatch = useDispatch();
+ const { setEditCategory } = bindActionCreators(actionCreators, dispatch);
+
return (
- {props.category.name}
+ {
+ setEditCategory(props.category);
+ }}
+ >
+ {props.category.name}
+
+
{props.category.bookmarks.map((bookmark: Bookmark) => {
const redirectUrl = urlParser(bookmark.url)[1];
diff --git a/client/src/components/Bookmarks/Bookmarks.tsx b/client/src/components/Bookmarks/Bookmarks.tsx
index ba5570e..f461d48 100644
--- a/client/src/components/Bookmarks/Bookmarks.tsx
+++ b/client/src/components/Bookmarks/Bookmarks.tsx
@@ -19,9 +19,6 @@ import { Container, Headline, ActionButton, Spinner, Modal } from '../UI';
// Components
import { BookmarkGrid } from './BookmarkGrid/BookmarkGrid';
import { Form } from './Form/Form';
-
-// Utils
-import { bookmarkTemplate, categoryTemplate } from '../../utility';
import { Table } from './Table/Table';
interface Props {
@@ -36,13 +33,14 @@ export enum ContentType {
export const Bookmarks = (props: Props): JSX.Element => {
// Get Redux state
const {
- bookmarks: { loading, categories },
+ bookmarks: { loading, categories, categoryInEdit },
auth: { isAuthenticated },
} = useSelector((state: State) => state);
// Get Redux action creators
const dispatch = useDispatch();
- const { getCategories } = bindActionCreators(actionCreators, dispatch);
+ const { getCategories, setEditCategory, setEditBookmark } =
+ bindActionCreators(actionCreators, dispatch);
// Load categories if array is empty
useEffect(() => {
@@ -55,10 +53,6 @@ export const Bookmarks = (props: Props): JSX.Element => {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [formContentType, setFormContentType] = useState(ContentType.category);
const [isInUpdate, setIsInUpdate] = useState(false);
- const [categoryInUpdate, setCategoryInUpdate] =
- useState (categoryTemplate);
- const [bookmarkInUpdate, setBookmarkInUpdate] =
- useState(bookmarkTemplate);
// Table
const [showTable, setShowTable] = useState(false);
@@ -74,6 +68,13 @@ export const Bookmarks = (props: Props): JSX.Element => {
}
}, [isAuthenticated]);
+ useEffect(() => {
+ if (categoryInEdit && !showTable) {
+ setTableContentType(ContentType.bookmark);
+ setShowTable(true);
+ }
+ }, [categoryInEdit]);
+
// Form actions
const toggleModal = (): void => {
setModalIsOpen(!modalIsOpen);
@@ -94,10 +95,10 @@ export const Bookmarks = (props: Props): JSX.Element => {
if (instanceOfCategory(data)) {
setFormContentType(ContentType.category);
- setCategoryInUpdate(data);
+ setEditCategory(data);
} else {
setFormContentType(ContentType.bookmark);
- setBookmarkInUpdate(data);
+ setEditBookmark(data);
}
toggleModal();
@@ -121,8 +122,6 @@ export const Bookmarks = (props: Props): JSX.Element => {
modalHandler={toggleModal}
contentType={formContentType}
inUpdate={isInUpdate}
- category={categoryInUpdate}
- bookmark={bookmarkInUpdate}
/>
@@ -145,11 +144,11 @@ export const Bookmarks = (props: Props): JSX.Element => {
icon="mdiPencil"
handler={() => showTableForEditing(ContentType.category)}
/>
- {/* showTableForEditing(ContentType.bookmark)}
- /> */}
+ />
)}
diff --git a/client/src/components/Bookmarks/Form/Form.tsx b/client/src/components/Bookmarks/Form/Form.tsx
index 41ed1bb..960e8cf 100644
--- a/client/src/components/Bookmarks/Form/Form.tsx
+++ b/client/src/components/Bookmarks/Form/Form.tsx
@@ -1,22 +1,26 @@
// Typescript
-import { Bookmark, Category } from '../../../interfaces';
import { ContentType } from '../Bookmarks';
// Utils
import { CategoryForm } from './CategoryForm';
import { BookmarksForm } from './BookmarksForm';
import { Fragment } from 'react';
+import { useSelector } from 'react-redux';
+import { State } from '../../../store/reducers';
+import { bookmarkTemplate, categoryTemplate } from '../../../utility';
interface Props {
modalHandler: () => void;
contentType: ContentType;
inUpdate?: boolean;
- category?: Category;
- bookmark?: Bookmark;
}
export const Form = (props: Props): JSX.Element => {
- const { modalHandler, contentType, inUpdate, category, bookmark } = props;
+ const { categoryInEdit, bookmarkInEdit } = useSelector(
+ (state: State) => state.bookmarks
+ );
+
+ const { modalHandler, contentType, inUpdate } = props;
return (
@@ -33,9 +37,15 @@ export const Form = (props: Props): JSX.Element => {
// form: update
{contentType === ContentType.category ? (
-
+
) : (
-
+
)}
)}
diff --git a/client/src/store/action-creators/app.ts b/client/src/store/action-creators/app.ts
index ddd88fa..7e285b6 100644
--- a/client/src/store/action-creators/app.ts
+++ b/client/src/store/action-creators/app.ts
@@ -7,6 +7,7 @@ import {
GetAppsAction,
PinAppAction,
ReorderAppsAction,
+ SetEditAppAction,
SortAppsAction,
UpdateAppAction,
} from '../actions/app';
@@ -196,3 +197,11 @@ export const sortApps = () => async (dispatch: Dispatch) => {
console.log(err);
}
};
+
+export const setEditApp =
+ (app: App | null) => (dispatch: Dispatch) => {
+ dispatch({
+ type: ActionType.setEditApp,
+ payload: app,
+ });
+ };
diff --git a/client/src/store/action-creators/bookmark.ts b/client/src/store/action-creators/bookmark.ts
index 5010bd3..4b1b0d2 100644
--- a/client/src/store/action-creators/bookmark.ts
+++ b/client/src/store/action-creators/bookmark.ts
@@ -18,6 +18,8 @@ import {
GetCategoriesAction,
PinCategoryAction,
ReorderCategoriesAction,
+ SetEditBookmarkAction,
+ SetEditCategoryAction,
SortCategoriesAction,
UpdateBookmarkAction,
UpdateCategoryAction,
@@ -319,3 +321,21 @@ export const reorderCategories =
console.log(err);
}
};
+
+export const setEditCategory =
+ (category: Category | null) =>
+ (dispatch: Dispatch) => {
+ dispatch({
+ type: ActionType.setEditCategory,
+ payload: category,
+ });
+ };
+
+export const setEditBookmark =
+ (bookmark: Bookmark | null) =>
+ (dispatch: Dispatch) => {
+ dispatch({
+ type: ActionType.setEditBookmark,
+ payload: bookmark,
+ });
+ };
diff --git a/client/src/store/action-types/index.ts b/client/src/store/action-types/index.ts
index 58ca529..81e1a84 100644
--- a/client/src/store/action-types/index.ts
+++ b/client/src/store/action-types/index.ts
@@ -23,6 +23,7 @@ export enum ActionType {
updateApp = 'UPDATE_APP',
reorderApps = 'REORDER_APPS',
sortApps = 'SORT_APPS',
+ setEditApp = 'SET_EDIT_APP',
// CATEGORES
getCategories = 'GET_CATEGORIES',
getCategoriesSuccess = 'GET_CATEGORIES_SUCCESS',
@@ -33,10 +34,12 @@ export enum ActionType {
updateCategory = 'UPDATE_CATEGORY',
sortCategories = 'SORT_CATEGORIES',
reorderCategories = 'REORDER_CATEGORIES',
+ setEditCategory = 'SET_EDIT_CATEGORY',
// BOOKMARKS
addBookmark = 'ADD_BOOKMARK',
deleteBookmark = 'DELETE_BOOKMARK',
updateBookmark = 'UPDATE_BOOKMARK',
+ setEditBookmark = 'SET_EDIT_BOOKMARK',
// AUTH
login = 'LOGIN',
logout = 'LOGOUT',
diff --git a/client/src/store/actions/app.ts b/client/src/store/actions/app.ts
index 37f5419..689014a 100644
--- a/client/src/store/actions/app.ts
+++ b/client/src/store/actions/app.ts
@@ -36,3 +36,8 @@ export interface SortAppsAction {
type: ActionType.sortApps;
payload: string;
}
+
+export interface SetEditAppAction {
+ type: ActionType.setEditApp;
+ payload: App | null;
+}
diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts
index e4cfcfd..60e7c9f 100644
--- a/client/src/store/actions/bookmark.ts
+++ b/client/src/store/actions/bookmark.ts
@@ -56,3 +56,13 @@ export interface ReorderCategoriesAction {
type: ActionType.reorderCategories;
payload: Category[];
}
+
+export interface SetEditCategoryAction {
+ type: ActionType.setEditCategory;
+ payload: Category | null;
+}
+
+export interface SetEditBookmarkAction {
+ type: ActionType.setEditBookmark;
+ payload: Bookmark | null;
+}
diff --git a/client/src/store/actions/index.ts b/client/src/store/actions/index.ts
index 02862b6..3193873 100644
--- a/client/src/store/actions/index.ts
+++ b/client/src/store/actions/index.ts
@@ -24,6 +24,7 @@ import {
UpdateAppAction,
ReorderAppsAction,
SortAppsAction,
+ SetEditAppAction,
} from './app';
import {
@@ -37,6 +38,8 @@ import {
AddBookmarkAction,
DeleteBookmarkAction,
UpdateBookmarkAction,
+ SetEditCategoryAction,
+ SetEditBookmarkAction,
} from './bookmark';
import {
@@ -67,6 +70,7 @@ export type Action =
| UpdateAppAction
| ReorderAppsAction
| SortAppsAction
+ | SetEditAppAction
// Categories
| GetCategoriesAction
| AddCategoryAction
@@ -75,10 +79,12 @@ export type Action =
| UpdateCategoryAction
| SortCategoriesAction
| ReorderCategoriesAction
+ | SetEditCategoryAction
// Bookmarks
| AddBookmarkAction
| DeleteBookmarkAction
| UpdateBookmarkAction
+ | SetEditBookmarkAction
// Auth
| LoginAction
| LogoutAction
diff --git a/client/src/store/reducers/app.ts b/client/src/store/reducers/app.ts
index e6da902..7f10793 100644
--- a/client/src/store/reducers/app.ts
+++ b/client/src/store/reducers/app.ts
@@ -7,12 +7,14 @@ interface AppsState {
loading: boolean;
apps: App[];
errors: string | undefined;
+ appInUpdate: App | null;
}
const initialState: AppsState = {
loading: true,
apps: [],
errors: undefined,
+ appInUpdate: null,
};
export const appsReducer = (
@@ -86,6 +88,12 @@ export const appsReducer = (
apps: sortData(state.apps, action.payload),
};
+ case ActionType.setEditApp:
+ return {
+ ...state,
+ appInUpdate: action.payload,
+ };
+
default:
return state;
}
diff --git a/client/src/store/reducers/bookmark.ts b/client/src/store/reducers/bookmark.ts
index b8e7e61..d1fc380 100644
--- a/client/src/store/reducers/bookmark.ts
+++ b/client/src/store/reducers/bookmark.ts
@@ -1,4 +1,4 @@
-import { Category } from '../../interfaces';
+import { Bookmark, Category } from '../../interfaces';
import { sortData } from '../../utility';
import { ActionType } from '../action-types';
import { Action } from '../actions';
@@ -7,12 +7,16 @@ interface BookmarksState {
loading: boolean;
errors: string | undefined;
categories: Category[];
+ categoryInEdit: Category | null;
+ bookmarkInEdit: Bookmark | null;
}
const initialState: BookmarksState = {
loading: true,
errors: undefined,
categories: [],
+ categoryInEdit: null,
+ bookmarkInEdit: null,
};
export const bookmarksReducer = (
@@ -45,19 +49,19 @@ export const bookmarksReducer = (
(category) => category.id === action.payload.categoryId
);
+ const categoryWithNewBookmark = {
+ ...state.categories[categoryIdx],
+ bookmarks: [...state.categories[categoryIdx].bookmarks, action.payload],
+ };
+
return {
...state,
categories: [
...state.categories.slice(0, categoryIdx),
- {
- ...state.categories[categoryIdx],
- bookmarks: [
- ...state.categories[categoryIdx].bookmarks,
- action.payload,
- ],
- },
+ categoryWithNewBookmark,
...state.categories.slice(categoryIdx + 1),
],
+ categoryInEdit: categoryWithNewBookmark,
};
case ActionType.pinCategory:
@@ -112,47 +116,54 @@ export const bookmarksReducer = (
(category) => category.id === action.payload.categoryId
);
+ const targetCategory = {
+ ...state.categories[categoryInUpdateIdx],
+ bookmarks: state.categories[categoryInUpdateIdx].bookmarks.filter(
+ (bookmark) => bookmark.id !== action.payload.bookmarkId
+ ),
+ };
+
return {
...state,
categories: [
...state.categories.slice(0, categoryInUpdateIdx),
- {
- ...state.categories[categoryInUpdateIdx],
- bookmarks: state.categories[categoryInUpdateIdx].bookmarks.filter(
- (bookmark) => bookmark.id !== action.payload.bookmarkId
- ),
- },
+ targetCategory,
...state.categories.slice(categoryInUpdateIdx + 1),
],
+ categoryInEdit: targetCategory,
};
case ActionType.updateBookmark:
const parentCategoryIdx = state.categories.findIndex(
(category) => category.id === action.payload.categoryId
);
+
const updatedBookmarkIdx = state.categories[
parentCategoryIdx
].bookmarks.findIndex((bookmark) => bookmark.id === action.payload.id);
+ const categoryWithUpdatedBookmark = {
+ ...state.categories[parentCategoryIdx],
+ bookmarks: [
+ ...state.categories[parentCategoryIdx].bookmarks.slice(
+ 0,
+ updatedBookmarkIdx
+ ),
+ action.payload,
+ ...state.categories[parentCategoryIdx].bookmarks.slice(
+ updatedBookmarkIdx + 1
+ ),
+ ],
+ };
+
return {
...state,
categories: [
...state.categories.slice(0, parentCategoryIdx),
- {
- ...state.categories[parentCategoryIdx],
- bookmarks: [
- ...state.categories[parentCategoryIdx].bookmarks.slice(
- 0,
- updatedBookmarkIdx
- ),
- action.payload,
- ...state.categories[parentCategoryIdx].bookmarks.slice(
- updatedBookmarkIdx + 1
- ),
- ],
- },
+ categoryWithUpdatedBookmark,
...state.categories.slice(parentCategoryIdx + 1),
],
+ categoryInEdit: categoryWithUpdatedBookmark,
};
case ActionType.sortCategories:
@@ -166,6 +177,19 @@ export const bookmarksReducer = (
...state,
categories: action.payload,
};
+
+ case ActionType.setEditCategory:
+ return {
+ ...state,
+ categoryInEdit: action.payload,
+ };
+
+ case ActionType.setEditBookmark:
+ return {
+ ...state,
+ bookmarkInEdit: action.payload,
+ };
+
default:
return state;
}
From f1f7b698f8f6ab6755bbc63e66a14a60c5fe852a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Malak?=
Date: Mon, 22 Nov 2021 16:45:59 +0100
Subject: [PATCH 06/11] Db migration to support custom order of bookmarks.
Created route to reorder bookmarks. Added more sorting options to bookmark
controllers. Simplified ordering in getAllApps controller
---
controllers/apps/getAllApps.js | 20 +++++------
controllers/bookmarks/getAllBookmarks.js | 10 +++++-
controllers/bookmarks/index.js | 1 +
controllers/bookmarks/reorderBookmarks.js | 23 +++++++++++++
controllers/categories/getAllCategories.js | 38 ++++++++-------------
controllers/categories/getSingleCategory.js | 10 ++++++
controllers/categories/reorderCategories.js | 1 +
db/migrations/04_bookmarks-order.js | 19 +++++++++++
models/Bookmark.js | 5 +++
routes/bookmark.js | 3 ++
10 files changed, 95 insertions(+), 35 deletions(-)
create mode 100644 controllers/bookmarks/reorderBookmarks.js
create mode 100644 db/migrations/04_bookmarks-order.js
diff --git a/controllers/apps/getAllApps.js b/controllers/apps/getAllApps.js
index 04a7585..36f4eb1 100644
--- a/controllers/apps/getAllApps.js
+++ b/controllers/apps/getAllApps.js
@@ -28,17 +28,15 @@ const getAllApps = asyncWrapper(async (req, res, next) => {
// apps visibility
const where = req.isAuthenticated ? {} : { isPublic: true };
- if (orderType == 'name') {
- apps = await App.findAll({
- order: [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']],
- where,
- });
- } else {
- apps = await App.findAll({
- order: [[orderType, 'ASC']],
- where,
- });
- }
+ const order =
+ orderType == 'name'
+ ? [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']]
+ : [[orderType, 'ASC']];
+
+ apps = await App.findAll({
+ order,
+ where,
+ });
if (process.env.NODE_ENV === 'production') {
// Set header to fetch containers info every time
diff --git a/controllers/bookmarks/getAllBookmarks.js b/controllers/bookmarks/getAllBookmarks.js
index aece14b..25af9d8 100644
--- a/controllers/bookmarks/getAllBookmarks.js
+++ b/controllers/bookmarks/getAllBookmarks.js
@@ -1,16 +1,24 @@
const asyncWrapper = require('../../middleware/asyncWrapper');
const Bookmark = require('../../models/Bookmark');
const { Sequelize } = require('sequelize');
+const loadConfig = require('../../utils/loadConfig');
// @desc Get all bookmarks
// @route GET /api/bookmarks
// @access Public
const getAllBookmarks = asyncWrapper(async (req, res, next) => {
+ const { useOrdering: orderType } = await loadConfig();
+
// bookmarks visibility
const where = req.isAuthenticated ? {} : { isPublic: true };
+ const order =
+ orderType == 'name'
+ ? [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']]
+ : [[orderType, 'ASC']];
+
const bookmarks = await Bookmark.findAll({
- order: [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']],
+ order,
where,
});
diff --git a/controllers/bookmarks/index.js b/controllers/bookmarks/index.js
index f1ef588..5c1bd86 100644
--- a/controllers/bookmarks/index.js
+++ b/controllers/bookmarks/index.js
@@ -4,4 +4,5 @@ module.exports = {
getSingleBookmark: require('./getSingleBookmark'),
updateBookmark: require('./updateBookmark'),
deleteBookmark: require('./deleteBookmark'),
+ reorderBookmarks: require('./reorderBookmarks'),
};
diff --git a/controllers/bookmarks/reorderBookmarks.js b/controllers/bookmarks/reorderBookmarks.js
new file mode 100644
index 0000000..3ea1c35
--- /dev/null
+++ b/controllers/bookmarks/reorderBookmarks.js
@@ -0,0 +1,23 @@
+const asyncWrapper = require('../../middleware/asyncWrapper');
+const Bookmark = require('../../models/Bookmark');
+
+// @desc Reorder bookmarks
+// @route PUT /api/bookmarks/0/reorder
+// @access Public
+const reorderBookmarks = asyncWrapper(async (req, res, next) => {
+ req.body.bookmarks.forEach(async ({ id, orderId }) => {
+ await Bookmark.update(
+ { orderId },
+ {
+ where: { id },
+ }
+ );
+ });
+
+ res.status(200).json({
+ success: true,
+ data: {},
+ });
+});
+
+module.exports = reorderBookmarks;
diff --git a/controllers/categories/getAllCategories.js b/controllers/categories/getAllCategories.js
index c42db2d..eb0eb24 100644
--- a/controllers/categories/getAllCategories.js
+++ b/controllers/categories/getAllCategories.js
@@ -16,29 +16,21 @@ const getAllCategories = asyncWrapper(async (req, res, next) => {
// categories visibility
const where = req.isAuthenticated ? {} : { isPublic: true };
- if (orderType == 'name') {
- categories = await Category.findAll({
- include: [
- {
- model: Bookmark,
- as: 'bookmarks',
- },
- ],
- order: [[Sequelize.fn('lower', Sequelize.col('Category.name')), 'ASC']],
- where,
- });
- } else {
- categories = await Category.findAll({
- include: [
- {
- model: Bookmark,
- as: 'bookmarks',
- },
- ],
- order: [[orderType, 'ASC']],
- where,
- });
- }
+ const order =
+ orderType == 'name'
+ ? [[Sequelize.fn('lower', Sequelize.col('bookmarks.name')), 'ASC']]
+ : [[{ model: Bookmark, as: 'bookmarks' }, orderType, 'ASC']];
+
+ categories = categories = await Category.findAll({
+ include: [
+ {
+ model: Bookmark,
+ as: 'bookmarks',
+ },
+ ],
+ order,
+ where,
+ });
if (req.isAuthenticated) {
output = categories;
diff --git a/controllers/categories/getSingleCategory.js b/controllers/categories/getSingleCategory.js
index 8eb5fb2..c854627 100644
--- a/controllers/categories/getSingleCategory.js
+++ b/controllers/categories/getSingleCategory.js
@@ -2,13 +2,22 @@ const asyncWrapper = require('../../middleware/asyncWrapper');
const ErrorResponse = require('../../utils/ErrorResponse');
const Category = require('../../models/Category');
const Bookmark = require('../../models/Bookmark');
+const { Sequelize } = require('sequelize');
+const loadConfig = require('../../utils/loadConfig');
// @desc Get single category
// @route GET /api/categories/:id
// @access Public
const getSingleCategory = asyncWrapper(async (req, res, next) => {
+ const { useOrdering: orderType } = await loadConfig();
+
const visibility = req.isAuthenticated ? {} : { isPublic: true };
+ const order =
+ orderType == 'name'
+ ? [[Sequelize.fn('lower', Sequelize.col('bookmarks.name')), 'ASC']]
+ : [[{ model: Bookmark, as: 'bookmarks' }, orderType, 'ASC']];
+
const category = await Category.findOne({
where: { id: req.params.id, ...visibility },
include: [
@@ -18,6 +27,7 @@ const getSingleCategory = asyncWrapper(async (req, res, next) => {
where: visibility,
},
],
+ order,
});
if (!category) {
diff --git a/controllers/categories/reorderCategories.js b/controllers/categories/reorderCategories.js
index 492675b..8922125 100644
--- a/controllers/categories/reorderCategories.js
+++ b/controllers/categories/reorderCategories.js
@@ -1,5 +1,6 @@
const asyncWrapper = require('../../middleware/asyncWrapper');
const Category = require('../../models/Category');
+
// @desc Reorder categories
// @route PUT /api/categories/0/reorder
// @access Public
diff --git a/db/migrations/04_bookmarks-order.js b/db/migrations/04_bookmarks-order.js
new file mode 100644
index 0000000..26ddd69
--- /dev/null
+++ b/db/migrations/04_bookmarks-order.js
@@ -0,0 +1,19 @@
+const { DataTypes } = require('sequelize');
+const { INTEGER } = DataTypes;
+
+const up = async (query) => {
+ await query.addColumn('bookmarks', 'orderId', {
+ type: INTEGER,
+ allowNull: true,
+ defaultValue: null,
+ });
+};
+
+const down = async (query) => {
+ await query.removeColumn('bookmarks', 'orderId');
+};
+
+module.exports = {
+ up,
+ down,
+};
diff --git a/models/Bookmark.js b/models/Bookmark.js
index 159ea28..197a632 100644
--- a/models/Bookmark.js
+++ b/models/Bookmark.js
@@ -25,6 +25,11 @@ const Bookmark = sequelize.define(
allowNull: true,
defaultValue: 1,
},
+ orderId: {
+ type: DataTypes.INTEGER,
+ allowNull: true,
+ defaultValue: null,
+ },
},
{
tableName: 'bookmarks',
diff --git a/routes/bookmark.js b/routes/bookmark.js
index ea1a344..6bc96ad 100644
--- a/routes/bookmark.js
+++ b/routes/bookmark.js
@@ -10,6 +10,7 @@ const {
getSingleBookmark,
updateBookmark,
deleteBookmark,
+ reorderBookmarks,
} = require('../controllers/bookmarks');
router
@@ -23,4 +24,6 @@ router
.put(auth, requireAuth, upload, updateBookmark)
.delete(auth, requireAuth, deleteBookmark);
+router.route('/0/reorder').put(auth, requireAuth, reorderBookmarks);
+
module.exports = router;
From e15c2a2f070dac2a5bc525390449679cb98146a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Malak?=
Date: Mon, 22 Nov 2021 17:15:01 +0100
Subject: [PATCH 07/11] Reorder bookmarks action. Small refactor of state
reducers
---
client/src/store/action-creators/bookmark.ts | 42 ++++++-
client/src/store/action-types/index.ts | 1 +
client/src/store/actions/bookmark.ts | 8 ++
client/src/store/actions/index.ts | 2 +
client/src/store/reducers/bookmark.ts | 124 +++++++++++--------
5 files changed, 126 insertions(+), 51 deletions(-)
diff --git a/client/src/store/action-creators/bookmark.ts b/client/src/store/action-creators/bookmark.ts
index 4b1b0d2..f2a17e5 100644
--- a/client/src/store/action-creators/bookmark.ts
+++ b/client/src/store/action-creators/bookmark.ts
@@ -1,5 +1,8 @@
import axios from 'axios';
import { Dispatch } from 'redux';
+import { applyAuth } from '../../utility';
+import { ActionType } from '../action-types';
+
import {
ApiResponse,
Bookmark,
@@ -8,8 +11,7 @@ import {
NewBookmark,
NewCategory,
} from '../../interfaces';
-import { applyAuth } from '../../utility';
-import { ActionType } from '../action-types';
+
import {
AddBookmarkAction,
AddCategoryAction,
@@ -17,6 +19,7 @@ import {
DeleteCategoryAction,
GetCategoriesAction,
PinCategoryAction,
+ ReorderBookmarksAction,
ReorderCategoriesAction,
SetEditBookmarkAction,
SetEditCategoryAction,
@@ -339,3 +342,38 @@ export const setEditBookmark =
payload: bookmark,
});
};
+
+export const reorderBookmarks =
+ (bookmarks: Bookmark[], categoryId: number) =>
+ async (dispatch: Dispatch) => {
+ interface ReorderQuery {
+ bookmarks: {
+ id: number;
+ orderId: number;
+ }[];
+ }
+
+ try {
+ const updateQuery: ReorderQuery = { bookmarks: [] };
+
+ bookmarks.forEach((bookmark, index) =>
+ updateQuery.bookmarks.push({
+ id: bookmark.id,
+ orderId: index + 1,
+ })
+ );
+
+ await axios.put>(
+ '/api/bookmarks/0/reorder',
+ updateQuery,
+ { headers: applyAuth() }
+ );
+
+ dispatch({
+ type: ActionType.reorderBookmarks,
+ payload: { bookmarks, categoryId },
+ });
+ } catch (err) {
+ console.log(err);
+ }
+ };
diff --git a/client/src/store/action-types/index.ts b/client/src/store/action-types/index.ts
index 81e1a84..80853c4 100644
--- a/client/src/store/action-types/index.ts
+++ b/client/src/store/action-types/index.ts
@@ -40,6 +40,7 @@ export enum ActionType {
deleteBookmark = 'DELETE_BOOKMARK',
updateBookmark = 'UPDATE_BOOKMARK',
setEditBookmark = 'SET_EDIT_BOOKMARK',
+ reorderBookmarks = 'REORDER_BOOKMARKS',
// AUTH
login = 'LOGIN',
logout = 'LOGOUT',
diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts
index 60e7c9f..d9c89e8 100644
--- a/client/src/store/actions/bookmark.ts
+++ b/client/src/store/actions/bookmark.ts
@@ -66,3 +66,11 @@ export interface SetEditBookmarkAction {
type: ActionType.setEditBookmark;
payload: Bookmark | null;
}
+
+export interface ReorderBookmarksAction {
+ type: ActionType.reorderBookmarks;
+ payload: {
+ bookmarks: Bookmark[];
+ categoryId: number;
+ };
+}
diff --git a/client/src/store/actions/index.ts b/client/src/store/actions/index.ts
index 3193873..22d33d3 100644
--- a/client/src/store/actions/index.ts
+++ b/client/src/store/actions/index.ts
@@ -40,6 +40,7 @@ import {
UpdateBookmarkAction,
SetEditCategoryAction,
SetEditBookmarkAction,
+ ReorderBookmarksAction,
} from './bookmark';
import {
@@ -85,6 +86,7 @@ export type Action =
| DeleteBookmarkAction
| UpdateBookmarkAction
| SetEditBookmarkAction
+ | ReorderBookmarksAction
// Auth
| LoginAction
| LogoutAction
diff --git a/client/src/store/reducers/bookmark.ts b/client/src/store/reducers/bookmark.ts
index d1fc380..3c069f0 100644
--- a/client/src/store/reducers/bookmark.ts
+++ b/client/src/store/reducers/bookmark.ts
@@ -24,32 +24,35 @@ export const bookmarksReducer = (
action: Action
): BookmarksState => {
switch (action.type) {
- case ActionType.getCategories:
+ case ActionType.getCategories: {
return {
...state,
loading: true,
errors: undefined,
};
+ }
- case ActionType.getCategoriesSuccess:
+ case ActionType.getCategoriesSuccess: {
return {
...state,
loading: false,
categories: action.payload,
};
+ }
- case ActionType.addCategory:
+ case ActionType.addCategory: {
return {
...state,
categories: [...state.categories, { ...action.payload, bookmarks: [] }],
};
+ }
- case ActionType.addBookmark:
+ case ActionType.addBookmark: {
const categoryIdx = state.categories.findIndex(
(category) => category.id === action.payload.categoryId
);
- const categoryWithNewBookmark = {
+ const targetCategory = {
...state.categories[categoryIdx],
bookmarks: [...state.categories[categoryIdx].bookmarks, action.payload],
};
@@ -58,67 +61,71 @@ export const bookmarksReducer = (
...state,
categories: [
...state.categories.slice(0, categoryIdx),
- categoryWithNewBookmark,
+ targetCategory,
...state.categories.slice(categoryIdx + 1),
],
- categoryInEdit: categoryWithNewBookmark,
+ categoryInEdit: targetCategory,
};
+ }
- case ActionType.pinCategory:
- const pinnedCategoryIdx = state.categories.findIndex(
+ case ActionType.pinCategory: {
+ const categoryIdx = state.categories.findIndex(
(category) => category.id === action.payload.id
);
return {
...state,
categories: [
- ...state.categories.slice(0, pinnedCategoryIdx),
+ ...state.categories.slice(0, categoryIdx),
{
...action.payload,
- bookmarks: [...state.categories[pinnedCategoryIdx].bookmarks],
+ bookmarks: [...state.categories[categoryIdx].bookmarks],
},
- ...state.categories.slice(pinnedCategoryIdx + 1),
+ ...state.categories.slice(categoryIdx + 1),
],
};
+ }
- case ActionType.deleteCategory:
- const deletedCategoryIdx = state.categories.findIndex(
+ case ActionType.deleteCategory: {
+ const categoryIdx = state.categories.findIndex(
(category) => category.id === action.payload
);
return {
...state,
categories: [
- ...state.categories.slice(0, deletedCategoryIdx),
- ...state.categories.slice(deletedCategoryIdx + 1),
+ ...state.categories.slice(0, categoryIdx),
+ ...state.categories.slice(categoryIdx + 1),
],
};
+ }
- case ActionType.updateCategory:
- const updatedCategoryIdx = state.categories.findIndex(
+ case ActionType.updateCategory: {
+ const categoryIdx = state.categories.findIndex(
(category) => category.id === action.payload.id
);
return {
...state,
categories: [
- ...state.categories.slice(0, updatedCategoryIdx),
+ ...state.categories.slice(0, categoryIdx),
{
...action.payload,
- bookmarks: [...state.categories[updatedCategoryIdx].bookmarks],
+ bookmarks: [...state.categories[categoryIdx].bookmarks],
},
- ...state.categories.slice(updatedCategoryIdx + 1),
+ ...state.categories.slice(categoryIdx + 1),
],
};
+ }
- case ActionType.deleteBookmark:
- const categoryInUpdateIdx = state.categories.findIndex(
+ case ActionType.deleteBookmark: {
+ const categoryIdx = state.categories.findIndex(
(category) => category.id === action.payload.categoryId
);
const targetCategory = {
- ...state.categories[categoryInUpdateIdx],
- bookmarks: state.categories[categoryInUpdateIdx].bookmarks.filter(
+ ...state.categories[categoryIdx],
+ bookmarks: state.categories[categoryIdx].bookmarks.filter(
(bookmark) => bookmark.id !== action.payload.bookmarkId
),
};
@@ -126,69 +133,88 @@ export const bookmarksReducer = (
return {
...state,
categories: [
- ...state.categories.slice(0, categoryInUpdateIdx),
+ ...state.categories.slice(0, categoryIdx),
targetCategory,
- ...state.categories.slice(categoryInUpdateIdx + 1),
+ ...state.categories.slice(categoryIdx + 1),
],
categoryInEdit: targetCategory,
};
+ }
- case ActionType.updateBookmark:
- const parentCategoryIdx = state.categories.findIndex(
+ case ActionType.updateBookmark: {
+ const categoryIdx = state.categories.findIndex(
(category) => category.id === action.payload.categoryId
);
- const updatedBookmarkIdx = state.categories[
- parentCategoryIdx
- ].bookmarks.findIndex((bookmark) => bookmark.id === action.payload.id);
+ const bookmarkIdx = state.categories[categoryIdx].bookmarks.findIndex(
+ (bookmark) => bookmark.id === action.payload.id
+ );
- const categoryWithUpdatedBookmark = {
- ...state.categories[parentCategoryIdx],
+ const targetCategory = {
+ ...state.categories[categoryIdx],
bookmarks: [
- ...state.categories[parentCategoryIdx].bookmarks.slice(
- 0,
- updatedBookmarkIdx
- ),
+ ...state.categories[categoryIdx].bookmarks.slice(0, bookmarkIdx),
action.payload,
- ...state.categories[parentCategoryIdx].bookmarks.slice(
- updatedBookmarkIdx + 1
- ),
+ ...state.categories[categoryIdx].bookmarks.slice(bookmarkIdx + 1),
],
};
return {
...state,
categories: [
- ...state.categories.slice(0, parentCategoryIdx),
- categoryWithUpdatedBookmark,
- ...state.categories.slice(parentCategoryIdx + 1),
+ ...state.categories.slice(0, categoryIdx),
+ targetCategory,
+ ...state.categories.slice(categoryIdx + 1),
],
- categoryInEdit: categoryWithUpdatedBookmark,
+ categoryInEdit: targetCategory,
};
+ }
- case ActionType.sortCategories:
+ case ActionType.sortCategories: {
return {
...state,
categories: sortData(state.categories, action.payload),
};
+ }
- case ActionType.reorderCategories:
+ case ActionType.reorderCategories: {
return {
...state,
categories: action.payload,
};
+ }
- case ActionType.setEditCategory:
+ case ActionType.setEditCategory: {
return {
...state,
categoryInEdit: action.payload,
};
+ }
- case ActionType.setEditBookmark:
+ case ActionType.setEditBookmark: {
return {
...state,
bookmarkInEdit: action.payload,
};
+ }
+
+ case ActionType.reorderBookmarks: {
+ const categoryIdx = state.categories.findIndex(
+ (category) => category.id === action.payload.categoryId
+ );
+
+ return {
+ ...state,
+ categories: [
+ ...state.categories.slice(0, categoryIdx),
+ {
+ ...state.categories[categoryIdx],
+ bookmarks: action.payload.bookmarks,
+ },
+ ...state.categories.slice(categoryIdx + 1),
+ ],
+ };
+ }
default:
return state;
From a02814aa025591a8a137d6e31eeb9ed77fd83f84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Malak?=
Date: Thu, 25 Nov 2021 16:44:24 +0100
Subject: [PATCH 08/11] Split BookmarksTable into separate components. Minor
changes to reducers
---
.../Bookmarks/BookmarkTable/BookmarkTable.tsx | 272 ------------------
.../Bookmarks/Table/BookmarksTable.tsx | 195 +++++++++++++
.../Bookmarks/Table/CategoryTable.tsx | 169 +++++++++++
.../Table.module.css} | 18 +-
.../src/components/Bookmarks/Table/Table.tsx | 20 ++
client/src/store/reducers/app.ts | 39 ++-
client/src/store/reducers/auth.ts | 4 +
client/src/store/reducers/config.ts | 5 +
client/src/store/reducers/notification.ts | 1 +
client/src/store/reducers/theme.ts | 1 +
10 files changed, 422 insertions(+), 302 deletions(-)
delete mode 100644 client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx
create mode 100644 client/src/components/Bookmarks/Table/BookmarksTable.tsx
create mode 100644 client/src/components/Bookmarks/Table/CategoryTable.tsx
rename client/src/components/Bookmarks/{BookmarkTable/BookmarkTable.module.css => Table/Table.module.css} (60%)
create mode 100644 client/src/components/Bookmarks/Table/Table.tsx
diff --git a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx b/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx
deleted file mode 100644
index 2cc4878..0000000
--- a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.tsx
+++ /dev/null
@@ -1,272 +0,0 @@
-import { KeyboardEvent, useState, useEffect, Fragment } from 'react';
-import {
- DragDropContext,
- Droppable,
- Draggable,
- DropResult,
-} from 'react-beautiful-dnd';
-import { Link } from 'react-router-dom';
-
-// Redux
-import { useDispatch, useSelector } from 'react-redux';
-import { State } from '../../../store/reducers';
-import { bindActionCreators } from 'redux';
-import { actionCreators } from '../../../store';
-
-// Typescript
-import { Bookmark, Category } from '../../../interfaces';
-import { ContentType } from '../Bookmarks';
-
-// CSS
-import classes from './BookmarkTable.module.css';
-
-// UI
-import { Table, Icon } from '../../UI';
-
-interface Props {
- contentType: ContentType;
- categories: Category[];
- updateHandler: (data: Category | Bookmark) => void;
-}
-
-export const BookmarkTable = (props: Props): JSX.Element => {
- const { config } = useSelector((state: State) => state.config);
-
- const dispatch = useDispatch();
- const {
- pinCategory,
- deleteCategory,
- deleteBookmark,
- createNotification,
- reorderCategories,
- } = bindActionCreators(actionCreators, dispatch);
-
- const [localCategories, setLocalCategories] = useState([]);
- const [isCustomOrder, setIsCustomOrder] = useState(false);
-
- // Copy categories array
- useEffect(() => {
- setLocalCategories([...props.categories]);
- }, [props.categories]);
-
- // Check ordering
- useEffect(() => {
- const order = config.useOrdering;
-
- if (order === 'orderId') {
- setIsCustomOrder(true);
- }
- });
-
- const deleteCategoryHandler = (category: Category): void => {
- const proceed = window.confirm(
- `Are you sure you want to delete ${category.name}? It will delete ALL assigned bookmarks`
- );
-
- if (proceed) {
- deleteCategory(category.id);
- }
- };
-
- const deleteBookmarkHandler = (bookmark: Bookmark): void => {
- const proceed = window.confirm(
- `Are you sure you want to delete ${bookmark.name}?`
- );
-
- if (proceed) {
- deleteBookmark(bookmark.id, bookmark.categoryId);
- }
- };
-
- const keyboardActionHandler = (
- e: KeyboardEvent,
- category: Category,
- handler: Function
- ) => {
- if (e.key === 'Enter') {
- handler(category);
- }
- };
-
- const dragEndHanlder = (result: DropResult): void => {
- if (!isCustomOrder) {
- createNotification({
- title: 'Error',
- message: 'Custom order is disabled',
- });
- return;
- }
-
- if (!result.destination) {
- return;
- }
-
- const tmpCategories = [...localCategories];
- const [movedApp] = tmpCategories.splice(result.source.index, 1);
- tmpCategories.splice(result.destination.index, 0, movedApp);
-
- setLocalCategories(tmpCategories);
- reorderCategories(tmpCategories);
- };
-
- if (props.contentType === ContentType.category) {
- return (
-
-
- {isCustomOrder ? (
- You can drag and drop single rows to reorder categories
- ) : (
-
- Custom order is disabled. You can change it in{' '}
- settings
-
- )}
-
-
-
- {(provided) => (
-
- {localCategories.map(
- (category: Category, index): JSX.Element => {
- return (
-
- {(provided, snapshot) => {
- const style = {
- border: snapshot.isDragging
- ? '1px solid var(--color-accent)'
- : 'none',
- borderRadius: '4px',
- ...provided.draggableProps.style,
- };
-
- return (
-
-
- {category.name}
- |
-
- {category.isPublic ? 'Visible' : 'Hidden'}
- |
- {!snapshot.isDragging && (
-
-
- deleteCategoryHandler(category)
- }
- onKeyDown={(e) =>
- keyboardActionHandler(
- e,
- category,
- deleteCategoryHandler
- )
- }
- tabIndex={0}
- >
-
-
-
- props.updateHandler(category)
- }
- tabIndex={0}
- >
-
-
- pinCategory(category)}
- onKeyDown={(e) =>
- keyboardActionHandler(
- e,
- category,
- pinCategory
- )
- }
- tabIndex={0}
- >
- {category.isPinned ? (
-
- ) : (
-
- )}
-
- |
- )}
-
- );
- }}
-
- );
- }
- )}
-
- )}
-
-
-
- );
- } 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.bookmark.isPublic ? 'Visible' : 'Hidden'} |
- {bookmark.categoryName} |
-
- deleteBookmarkHandler(bookmark.bookmark)}
- tabIndex={0}
- >
-
-
- props.updateHandler(bookmark.bookmark)}
- tabIndex={0}
- >
-
-
- |
-
- );
- }
- )}
-
- );
- }
-};
diff --git a/client/src/components/Bookmarks/Table/BookmarksTable.tsx b/client/src/components/Bookmarks/Table/BookmarksTable.tsx
new file mode 100644
index 0000000..dd0f447
--- /dev/null
+++ b/client/src/components/Bookmarks/Table/BookmarksTable.tsx
@@ -0,0 +1,195 @@
+import { useState, useEffect, Fragment } from 'react';
+import {
+ DragDropContext,
+ Droppable,
+ Draggable,
+ DropResult,
+} from 'react-beautiful-dnd';
+
+// Redux
+import { useDispatch, useSelector } from 'react-redux';
+import { State } from '../../../store/reducers';
+import { bindActionCreators } from 'redux';
+import { actionCreators } from '../../../store';
+
+// Typescript
+import { Bookmark, Category } from '../../../interfaces';
+
+// CSS
+import classes from './Table.module.css';
+
+// UI
+import { Table } from '../../UI';
+import { TableActions } from '../../Actions/TableActions';
+import { bookmarkTemplate } from '../../../utility';
+
+interface Props {
+ openFormForUpdating: (data: Category | Bookmark) => void;
+}
+
+export const BookmarksTable = ({ openFormForUpdating }: Props): JSX.Element => {
+ const {
+ bookmarks: { categoryInEdit },
+ config: { config },
+ } = useSelector((state: State) => state);
+
+ const dispatch = useDispatch();
+ const {
+ deleteBookmark,
+ updateBookmark,
+ createNotification,
+ reorderBookmarks,
+ } = bindActionCreators(actionCreators, dispatch);
+
+ const [localBookmarks, setLocalBookmarks] = useState([]);
+
+ // Copy bookmarks array
+ useEffect(() => {
+ if (categoryInEdit) {
+ setLocalBookmarks([...categoryInEdit.bookmarks]);
+ }
+ }, [categoryInEdit]);
+
+ // Drag and drop handler
+ const dragEndHanlder = (result: DropResult): void => {
+ if (config.useOrdering !== 'orderId') {
+ createNotification({
+ title: 'Error',
+ message: 'Custom order is disabled',
+ });
+ return;
+ }
+
+ if (!result.destination) {
+ return;
+ }
+
+ const tmpBookmarks = [...localBookmarks];
+ const [movedBookmark] = tmpBookmarks.splice(result.source.index, 1);
+ tmpBookmarks.splice(result.destination.index, 0, movedBookmark);
+
+ setLocalBookmarks(tmpBookmarks);
+
+ const categoryId = categoryInEdit?.id || -1;
+ reorderBookmarks(tmpBookmarks, categoryId);
+ };
+
+ // Action hanlders
+ const deleteBookmarkHandler = (id: number, name: string) => {
+ const categoryId = categoryInEdit?.id || -1;
+
+ const proceed = window.confirm(`Are you sure you want to delete ${name}?`);
+ if (proceed) {
+ deleteBookmark(id, categoryId);
+ }
+ };
+
+ const updateBookmarkHandler = (id: number) => {
+ const bookmark =
+ categoryInEdit?.bookmarks.find((b) => b.id === id) || bookmarkTemplate;
+
+ openFormForUpdating(bookmark);
+ };
+
+ const changeBookmarkVisibiltyHandler = (id: number) => {
+ const bookmark =
+ categoryInEdit?.bookmarks.find((b) => b.id === id) || bookmarkTemplate;
+
+ const categoryId = categoryInEdit?.id || -1;
+ const [prev, curr] = [categoryId, categoryId];
+
+ updateBookmark(
+ id,
+ { ...bookmark, isPublic: !bookmark.isPublic },
+ { prev, curr }
+ );
+ };
+
+ return (
+
+ {!categoryInEdit ? (
+
+
+ Switch to grid view and click on the name of category you want to
+ edit
+
+
+ ) : (
+
+
+ Editing bookmarks from {categoryInEdit.name} category
+
+
+ )}
+
+ {categoryInEdit && (
+
+
+ {(provided) => (
+
+ {localBookmarks.map((bookmark, index): JSX.Element => {
+ return (
+
+ {(provided, snapshot) => {
+ const style = {
+ border: snapshot.isDragging
+ ? '1px solid var(--color-accent)'
+ : 'none',
+ borderRadius: '4px',
+ ...provided.draggableProps.style,
+ };
+
+ return (
+
+ {bookmark.name} |
+ {bookmark.url} |
+ {bookmark.icon} |
+
+ {bookmark.isPublic ? 'Visible' : 'Hidden'}
+ |
+
+ {categoryInEdit.name}
+ |
+
+ {!snapshot.isDragging && (
+
+ )}
+
+ );
+ }}
+
+ );
+ })}
+
+ )}
+
+
+ )}
+
+ );
+};
diff --git a/client/src/components/Bookmarks/Table/CategoryTable.tsx b/client/src/components/Bookmarks/Table/CategoryTable.tsx
new file mode 100644
index 0000000..124bd35
--- /dev/null
+++ b/client/src/components/Bookmarks/Table/CategoryTable.tsx
@@ -0,0 +1,169 @@
+import { useState, useEffect, Fragment } from 'react';
+import {
+ DragDropContext,
+ Droppable,
+ Draggable,
+ DropResult,
+} from 'react-beautiful-dnd';
+import { Link } from 'react-router-dom';
+
+// Redux
+import { useDispatch, useSelector } from 'react-redux';
+import { State } from '../../../store/reducers';
+import { bindActionCreators } from 'redux';
+import { actionCreators } from '../../../store';
+
+// Typescript
+import { Bookmark, Category } from '../../../interfaces';
+
+// CSS
+import classes from './Table.module.css';
+
+// UI
+import { Table } from '../../UI';
+import { TableActions } from '../../Actions/TableActions';
+
+interface Props {
+ openFormForUpdating: (data: Category | Bookmark) => void;
+}
+
+export const CategoryTable = ({ openFormForUpdating }: Props): JSX.Element => {
+ const {
+ config: { config },
+ bookmarks: { categories },
+ } = useSelector((state: State) => state);
+
+ const dispatch = useDispatch();
+ const {
+ pinCategory,
+ deleteCategory,
+ createNotification,
+ reorderCategories,
+ updateCategory,
+ } = bindActionCreators(actionCreators, dispatch);
+
+ const [localCategories, setLocalCategories] = useState([]);
+
+ // Copy categories array
+ useEffect(() => {
+ setLocalCategories([...categories]);
+ }, [categories]);
+
+ // Drag and drop handler
+ const dragEndHanlder = (result: DropResult): void => {
+ if (config.useOrdering !== 'orderId') {
+ createNotification({
+ title: 'Error',
+ message: 'Custom order is disabled',
+ });
+ return;
+ }
+
+ if (!result.destination) {
+ return;
+ }
+
+ const tmpCategories = [...localCategories];
+ const [movedCategory] = tmpCategories.splice(result.source.index, 1);
+ tmpCategories.splice(result.destination.index, 0, movedCategory);
+
+ setLocalCategories(tmpCategories);
+ reorderCategories(tmpCategories);
+ };
+
+ // Action handlers
+ const deleteCategoryHandler = (id: number, name: string) => {
+ const proceed = window.confirm(
+ `Are you sure you want to delete ${name}? It will delete ALL assigned bookmarks`
+ );
+
+ if (proceed) {
+ deleteCategory(id);
+ }
+ };
+
+ const updateCategoryHandler = (id: number) => {
+ const category = categories.find((c) => c.id === id) as Category;
+ openFormForUpdating(category);
+ };
+
+ const pinCategoryHandler = (id: number) => {
+ const category = categories.find((c) => c.id === id) as Category;
+ pinCategory(category);
+ };
+
+ const changeCategoryVisibiltyHandler = (id: number) => {
+ const category = categories.find((c) => c.id === id) as Category;
+ updateCategory(id, { ...category, isPublic: !category.isPublic });
+ };
+
+ return (
+
+
+ {config.useOrdering === 'orderId' ? (
+ You can drag and drop single rows to reorder categories
+ ) : (
+
+ Custom order is disabled. You can change it in the{' '}
+ settings
+
+ )}
+
+
+
+
+ {(provided) => (
+
+ {localCategories.map((category, index): JSX.Element => {
+ return (
+
+ {(provided, snapshot) => {
+ const style = {
+ border: snapshot.isDragging
+ ? '1px solid var(--color-accent)'
+ : 'none',
+ borderRadius: '4px',
+ ...provided.draggableProps.style,
+ };
+
+ return (
+
+ {category.name} |
+
+ {category.isPublic ? 'Visible' : 'Hidden'}
+ |
+
+ {!snapshot.isDragging && (
+
+ )}
+
+ );
+ }}
+
+ );
+ })}
+
+ )}
+
+
+
+ );
+};
diff --git a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.module.css b/client/src/components/Bookmarks/Table/Table.module.css
similarity index 60%
rename from client/src/components/Bookmarks/BookmarkTable/BookmarkTable.module.css
rename to client/src/components/Bookmarks/Table/Table.module.css
index 8b1e0ed..89ff6ca 100644
--- a/client/src/components/Bookmarks/BookmarkTable/BookmarkTable.module.css
+++ b/client/src/components/Bookmarks/Table/Table.module.css
@@ -1,16 +1,3 @@
-.TableActions {
- display: flex;
- align-items: center;
-}
-
-.TableAction {
- width: 22px;
-}
-
-.TableAction:hover {
- cursor: pointer;
-}
-
.Message {
width: 100%;
display: flex;
@@ -20,10 +7,11 @@
margin-bottom: 20px;
}
-.Message a {
+.Message a,
+.Message span {
color: var(--color-accent);
}
.Message a:hover {
cursor: pointer;
-}
\ No newline at end of file
+}
diff --git a/client/src/components/Bookmarks/Table/Table.tsx b/client/src/components/Bookmarks/Table/Table.tsx
new file mode 100644
index 0000000..8704fdb
--- /dev/null
+++ b/client/src/components/Bookmarks/Table/Table.tsx
@@ -0,0 +1,20 @@
+import { Category, Bookmark } from '../../../interfaces';
+import { ContentType } from '../Bookmarks';
+import { BookmarksTable } from './BookmarksTable';
+import { CategoryTable } from './CategoryTable';
+
+interface Props {
+ contentType: ContentType;
+ openFormForUpdating: (data: Category | Bookmark) => void;
+}
+
+export const Table = (props: Props): JSX.Element => {
+ const tableEl =
+ props.contentType === ContentType.category ? (
+
+ ) : (
+
+ );
+
+ return tableEl;
+};
diff --git a/client/src/store/reducers/app.ts b/client/src/store/reducers/app.ts
index 7f10793..3d08727 100644
--- a/client/src/store/reducers/app.ts
+++ b/client/src/store/reducers/app.ts
@@ -22,77 +22,86 @@ export const appsReducer = (
action: Action
): AppsState => {
switch (action.type) {
- case ActionType.getApps:
+ case ActionType.getApps: {
return {
...state,
loading: true,
errors: undefined,
};
+ }
- case ActionType.getAppsSuccess:
+ case ActionType.getAppsSuccess: {
return {
...state,
loading: false,
apps: action.payload || [],
};
+ }
- case ActionType.pinApp:
- const pinnedAppIdx = state.apps.findIndex(
+ case ActionType.pinApp: {
+ const appIdx = state.apps.findIndex(
(app) => app.id === action.payload.id
);
return {
...state,
apps: [
- ...state.apps.slice(0, pinnedAppIdx),
+ ...state.apps.slice(0, appIdx),
action.payload,
- ...state.apps.slice(pinnedAppIdx + 1),
+ ...state.apps.slice(appIdx + 1),
],
};
+ }
- case ActionType.addAppSuccess:
+ case ActionType.addAppSuccess: {
return {
...state,
apps: [...state.apps, action.payload],
};
+ }
- case ActionType.deleteApp:
+ case ActionType.deleteApp: {
return {
...state,
apps: [...state.apps].filter((app) => app.id !== action.payload),
};
+ }
- case ActionType.updateApp:
- const updatedAppIdx = state.apps.findIndex(
+ case ActionType.updateApp: {
+ const appIdx = state.apps.findIndex(
(app) => app.id === action.payload.id
);
return {
...state,
apps: [
- ...state.apps.slice(0, updatedAppIdx),
+ ...state.apps.slice(0, appIdx),
action.payload,
- ...state.apps.slice(updatedAppIdx + 1),
+ ...state.apps.slice(appIdx + 1),
],
};
+ }
- case ActionType.reorderApps:
+ case ActionType.reorderApps: {
return {
...state,
apps: action.payload,
};
+ }
- case ActionType.sortApps:
+ case ActionType.sortApps: {
return {
...state,
apps: sortData(state.apps, action.payload),
};
+ }
- case ActionType.setEditApp:
+ case ActionType.setEditApp: {
return {
...state,
appInUpdate: action.payload,
};
+ }
default:
return state;
diff --git a/client/src/store/reducers/auth.ts b/client/src/store/reducers/auth.ts
index 4105a0f..2281a86 100644
--- a/client/src/store/reducers/auth.ts
+++ b/client/src/store/reducers/auth.ts
@@ -22,24 +22,28 @@ export const authReducer = (
token: action.payload,
isAuthenticated: true,
};
+
case ActionType.logout:
return {
...state,
token: null,
isAuthenticated: false,
};
+
case ActionType.autoLogin:
return {
...state,
token: action.payload,
isAuthenticated: true,
};
+
case ActionType.authError:
return {
...state,
token: null,
isAuthenticated: false,
};
+
default:
return state;
}
diff --git a/client/src/store/reducers/config.ts b/client/src/store/reducers/config.ts
index 41c57d1..7976919 100644
--- a/client/src/store/reducers/config.ts
+++ b/client/src/store/reducers/config.ts
@@ -26,26 +26,31 @@ export const configReducer = (
loading: false,
config: action.payload,
};
+
case ActionType.updateConfig:
return {
...state,
config: action.payload,
};
+
case ActionType.fetchQueries:
return {
...state,
customQueries: action.payload,
};
+
case ActionType.addQuery:
return {
...state,
customQueries: [...state.customQueries, action.payload],
};
+
case ActionType.deleteQuery:
return {
...state,
customQueries: action.payload,
};
+
case ActionType.updateQuery:
return {
...state,
diff --git a/client/src/store/reducers/notification.ts b/client/src/store/reducers/notification.ts
index 544402f..23d8769 100644
--- a/client/src/store/reducers/notification.ts
+++ b/client/src/store/reducers/notification.ts
@@ -29,6 +29,7 @@ export const notificationReducer = (
],
idCounter: state.idCounter + 1,
};
+
case ActionType.clearNotification:
return {
...state,
diff --git a/client/src/store/reducers/theme.ts b/client/src/store/reducers/theme.ts
index ef32495..6db29fe 100644
--- a/client/src/store/reducers/theme.ts
+++ b/client/src/store/reducers/theme.ts
@@ -24,6 +24,7 @@ export const themeReducer = (
switch (action.type) {
case ActionType.setTheme:
return { theme: action.payload };
+
default:
return state;
}
From ec5f50aba49712e6cc6d1b0687c9a0b788d20cc4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Malak?=
Date: Thu, 25 Nov 2021 16:54:27 +0100
Subject: [PATCH 09/11] Added option to reorder bookmarks
---
.../BookmarkCard/BookmarkCard.module.css | 2 +-
.../Bookmarks/BookmarkCard/BookmarkCard.tsx | 12 +++++++---
.../Bookmarks/BookmarkGrid/BookmarkGrid.tsx | 22 ++++++++++++++-----
client/src/components/Bookmarks/Bookmarks.tsx | 8 ++++++-
client/src/components/Home/Home.tsx | 1 +
5 files changed, 35 insertions(+), 10 deletions(-)
diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
index c61217d..2fd52f0 100644
--- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
+++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.module.css
@@ -10,7 +10,7 @@
text-transform: uppercase;
}
-.BookmarkCard h3:hover {
+.BookmarkHeader:hover {
cursor: pointer;
}
diff --git a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx
index fe6c97d..8891460 100644
--- a/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx
+++ b/client/src/components/Bookmarks/BookmarkCard/BookmarkCard.tsx
@@ -16,9 +16,12 @@ import { iconParser, isImage, isSvg, isUrl, urlParser } from '../../../utility';
interface Props {
category: Category;
+ fromHomepage?: boolean;
}
export const BookmarkCard = (props: Props): JSX.Element => {
+ const { category, fromHomepage = false } = props;
+
const { config } = useSelector((state: State) => state.config);
const dispatch = useDispatch();
@@ -27,15 +30,18 @@ export const BookmarkCard = (props: Props): JSX.Element => {
return (
{
- setEditCategory(props.category);
+ if (!fromHomepage) {
+ setEditCategory(category);
+ }
}}
>
- {props.category.name}
+ {category.name}
- {props.category.bookmarks.map((bookmark: Bookmark) => {
+ {category.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 01a5ed1..7e26c32 100644
--- a/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx
+++ b/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx
@@ -11,27 +11,39 @@ interface Props {
categories: Category[];
totalCategories?: number;
searching: boolean;
+ fromHomepage?: boolean;
}
export const BookmarkGrid = (props: Props): JSX.Element => {
+ const {
+ categories,
+ totalCategories,
+ searching,
+ fromHomepage = false,
+ } = props;
+
let bookmarks: JSX.Element;
- if (props.categories.length) {
- if (props.searching && !props.categories[0].bookmarks.length) {
+ if (categories.length) {
+ if (searching && !categories[0].bookmarks.length) {
bookmarks = No bookmarks match your search criteria;
} else {
bookmarks = (
- {props.categories.map(
+ {categories.map(
(category: Category): JSX.Element => (
-
+
)
)}
);
}
} else {
- if (props.totalCategories) {
+ if (totalCategories) {
bookmarks = (
There are no pinned categories. You can pin them from the{' '}
diff --git a/client/src/components/Bookmarks/Bookmarks.tsx b/client/src/components/Bookmarks/Bookmarks.tsx
index f461d48..5bbe324 100644
--- a/client/src/components/Bookmarks/Bookmarks.tsx
+++ b/client/src/components/Bookmarks/Bookmarks.tsx
@@ -69,12 +69,17 @@ export const Bookmarks = (props: Props): JSX.Element => {
}, [isAuthenticated]);
useEffect(() => {
- if (categoryInEdit && !showTable) {
+ if (categoryInEdit) {
setTableContentType(ContentType.bookmark);
setShowTable(true);
}
}, [categoryInEdit]);
+ useEffect(() => {
+ setShowTable(false);
+ setEditCategory(null);
+ }, []);
+
// Form actions
const toggleModal = (): void => {
setModalIsOpen(!modalIsOpen);
@@ -108,6 +113,7 @@ export const Bookmarks = (props: Props): JSX.Element => {
const showTableForEditing = (contentType: ContentType) => {
// We're in the edit mode and the same button was clicked - go back to list
if (showTable && contentType === tableContentType) {
+ setEditCategory(null);
setShowTable(false);
} else {
setShowTable(true);
diff --git a/client/src/components/Home/Home.tsx b/client/src/components/Home/Home.tsx
index d369aaa..e24e5c0 100644
--- a/client/src/components/Home/Home.tsx
+++ b/client/src/components/Home/Home.tsx
@@ -151,6 +151,7 @@ export const Home = (): JSX.Element => {
}
totalCategories={categories.length}
searching={!!localSearch}
+ fromHomepage={true}
/>
)}
From d5610ad6be4317708f5ff3c4c84879a53804cdc5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Malak?=
Date: Fri, 26 Nov 2021 14:04:46 +0100
Subject: [PATCH 10/11] Fixed bug with alphabetical order not working for
bookmarks. Minor changes related to bookmarks form
---
client/src/components/Bookmarks/Bookmarks.tsx | 2 +-
.../Bookmarks/Form/BookmarksForm.tsx | 8 +++----
.../Bookmarks/Form/CategoryForm.tsx | 2 +-
.../Settings/UISettings/UISettings.tsx | 23 ++++++++++++-------
client/src/interfaces/Bookmark.ts | 4 +++-
client/src/store/action-creators/bookmark.ts | 22 ++++++++++++++++++
client/src/store/action-types/index.ts | 1 +
client/src/store/actions/bookmark.ts | 8 +++++++
client/src/store/actions/index.ts | 2 ++
client/src/store/reducers/bookmark.ts | 23 +++++++++++++++++++
.../templateObjects/bookmarkTemplate.ts | 1 +
controllers/categories/getAllCategories.js | 10 ++++++--
12 files changed, 89 insertions(+), 17 deletions(-)
diff --git a/client/src/components/Bookmarks/Bookmarks.tsx b/client/src/components/Bookmarks/Bookmarks.tsx
index 5bbe324..a708995 100644
--- a/client/src/components/Bookmarks/Bookmarks.tsx
+++ b/client/src/components/Bookmarks/Bookmarks.tsx
@@ -69,7 +69,7 @@ export const Bookmarks = (props: Props): JSX.Element => {
}, [isAuthenticated]);
useEffect(() => {
- if (categoryInEdit) {
+ if (categoryInEdit && !modalIsOpen) {
setTableContentType(ContentType.bookmark);
setShowTable(true);
}
diff --git a/client/src/components/Bookmarks/Form/BookmarksForm.tsx b/client/src/components/Bookmarks/Form/BookmarksForm.tsx
index 60cfb5c..65f658d 100644
--- a/client/src/components/Bookmarks/Form/BookmarksForm.tsx
+++ b/client/src/components/Bookmarks/Form/BookmarksForm.tsx
@@ -137,11 +137,11 @@ export const BookmarksForm = ({
}
modalHandler();
-
- setFormData(newBookmarkTemplate);
-
- setCustomIcon(null);
}
+
+ setFormData(newBookmarkTemplate);
+
+ setCustomIcon(null);
};
return (
diff --git a/client/src/components/Bookmarks/Form/CategoryForm.tsx b/client/src/components/Bookmarks/Form/CategoryForm.tsx
index c7e0105..b7ffb58 100644
--- a/client/src/components/Bookmarks/Form/CategoryForm.tsx
+++ b/client/src/components/Bookmarks/Form/CategoryForm.tsx
@@ -60,10 +60,10 @@ export const CategoryForm = ({
addCategory(formData);
} else {
updateCategory(category.id, formData);
+ modalHandler();
}
setFormData(newCategoryTemplate);
- modalHandler();
};
return (
diff --git a/client/src/components/Settings/UISettings/UISettings.tsx b/client/src/components/Settings/UISettings/UISettings.tsx
index 1e2b42b..075a427 100644
--- a/client/src/components/Settings/UISettings/UISettings.tsx
+++ b/client/src/components/Settings/UISettings/UISettings.tsx
@@ -16,13 +16,14 @@ import { InputGroup, Button, SettingsHeadline } from '../../UI';
import { otherSettingsTemplate, inputHandler } from '../../../utility';
export const UISettings = (): JSX.Element => {
- const { loading, config } = useSelector((state: State) => state.config);
+ const {
+ config: { loading, config },
+ bookmarks: { categories },
+ } = useSelector((state: State) => state);
const dispatch = useDispatch();
- const { updateConfig, sortApps, sortCategories } = bindActionCreators(
- actionCreators,
- dispatch
- );
+ const { updateConfig, sortApps, sortCategories, sortBookmarks } =
+ bindActionCreators(actionCreators, dispatch);
// Initial state
const [formData, setFormData] = useState(
@@ -46,9 +47,15 @@ export const UISettings = (): JSX.Element => {
// Update local page title
document.title = formData.customTitle;
- // Sort apps and categories with new settings
- sortApps();
- sortCategories();
+ // Sort entities with new settings
+ if (formData.useOrdering !== config.useOrdering) {
+ sortApps();
+ sortCategories();
+
+ for (let { id } of categories) {
+ sortBookmarks(id);
+ }
+ }
};
// Input handler
diff --git a/client/src/interfaces/Bookmark.ts b/client/src/interfaces/Bookmark.ts
index db10380..858101c 100644
--- a/client/src/interfaces/Bookmark.ts
+++ b/client/src/interfaces/Bookmark.ts
@@ -8,4 +8,6 @@ export interface NewBookmark {
isPublic: boolean;
}
-export interface Bookmark extends Model, NewBookmark {}
+export interface Bookmark extends Model, NewBookmark {
+ orderId: number;
+}
diff --git a/client/src/store/action-creators/bookmark.ts b/client/src/store/action-creators/bookmark.ts
index f2a17e5..5f077a6 100644
--- a/client/src/store/action-creators/bookmark.ts
+++ b/client/src/store/action-creators/bookmark.ts
@@ -23,6 +23,7 @@ import {
ReorderCategoriesAction,
SetEditBookmarkAction,
SetEditCategoryAction,
+ SortBookmarksAction,
SortCategoriesAction,
UpdateBookmarkAction,
UpdateCategoryAction,
@@ -100,6 +101,8 @@ export const addBookmark =
type: ActionType.addBookmark,
payload: res.data.data,
});
+
+ dispatch(sortBookmarks(res.data.data.categoryId));
} catch (err) {
console.log(err);
}
@@ -271,6 +274,8 @@ export const updateBookmark =
payload: res.data.data,
});
}
+
+ dispatch(sortBookmarks(res.data.data.categoryId));
} catch (err) {
console.log(err);
}
@@ -377,3 +382,20 @@ export const reorderBookmarks =
console.log(err);
}
};
+
+export const sortBookmarks =
+ (categoryId: number) => async (dispatch: Dispatch) => {
+ try {
+ const res = await axios.get>('/api/config');
+
+ dispatch({
+ type: ActionType.sortBookmarks,
+ payload: {
+ orderType: res.data.data.useOrdering,
+ categoryId,
+ },
+ });
+ } catch (err) {
+ console.log(err);
+ }
+ };
diff --git a/client/src/store/action-types/index.ts b/client/src/store/action-types/index.ts
index 80853c4..4be159f 100644
--- a/client/src/store/action-types/index.ts
+++ b/client/src/store/action-types/index.ts
@@ -41,6 +41,7 @@ export enum ActionType {
updateBookmark = 'UPDATE_BOOKMARK',
setEditBookmark = 'SET_EDIT_BOOKMARK',
reorderBookmarks = 'REORDER_BOOKMARKS',
+ sortBookmarks = 'SORT_BOOKMARKS',
// AUTH
login = 'LOGIN',
logout = 'LOGOUT',
diff --git a/client/src/store/actions/bookmark.ts b/client/src/store/actions/bookmark.ts
index d9c89e8..7c9e1f2 100644
--- a/client/src/store/actions/bookmark.ts
+++ b/client/src/store/actions/bookmark.ts
@@ -74,3 +74,11 @@ export interface ReorderBookmarksAction {
categoryId: number;
};
}
+
+export interface SortBookmarksAction {
+ type: ActionType.sortBookmarks;
+ payload: {
+ orderType: string;
+ categoryId: number;
+ };
+}
diff --git a/client/src/store/actions/index.ts b/client/src/store/actions/index.ts
index 22d33d3..bd0b360 100644
--- a/client/src/store/actions/index.ts
+++ b/client/src/store/actions/index.ts
@@ -41,6 +41,7 @@ import {
SetEditCategoryAction,
SetEditBookmarkAction,
ReorderBookmarksAction,
+ SortBookmarksAction,
} from './bookmark';
import {
@@ -87,6 +88,7 @@ export type Action =
| UpdateBookmarkAction
| SetEditBookmarkAction
| ReorderBookmarksAction
+ | SortBookmarksAction
// Auth
| LoginAction
| LogoutAction
diff --git a/client/src/store/reducers/bookmark.ts b/client/src/store/reducers/bookmark.ts
index 3c069f0..7f2510c 100644
--- a/client/src/store/reducers/bookmark.ts
+++ b/client/src/store/reducers/bookmark.ts
@@ -216,6 +216,29 @@ export const bookmarksReducer = (
};
}
+ case ActionType.sortBookmarks: {
+ const categoryIdx = state.categories.findIndex(
+ (category) => category.id === action.payload.categoryId
+ );
+
+ const sortedBookmarks = sortData(
+ state.categories[categoryIdx].bookmarks,
+ action.payload.orderType
+ );
+
+ return {
+ ...state,
+ categories: [
+ ...state.categories.slice(0, categoryIdx),
+ {
+ ...state.categories[categoryIdx],
+ bookmarks: sortedBookmarks,
+ },
+ ...state.categories.slice(categoryIdx + 1),
+ ],
+ };
+ }
+
default:
return state;
}
diff --git a/client/src/utility/templateObjects/bookmarkTemplate.ts b/client/src/utility/templateObjects/bookmarkTemplate.ts
index d2b1749..a359392 100644
--- a/client/src/utility/templateObjects/bookmarkTemplate.ts
+++ b/client/src/utility/templateObjects/bookmarkTemplate.ts
@@ -13,4 +13,5 @@ export const bookmarkTemplate: Bookmark = {
id: -1,
createdAt: new Date(),
updatedAt: new Date(),
+ orderId: 0,
};
diff --git a/controllers/categories/getAllCategories.js b/controllers/categories/getAllCategories.js
index eb0eb24..7bde6ba 100644
--- a/controllers/categories/getAllCategories.js
+++ b/controllers/categories/getAllCategories.js
@@ -18,8 +18,14 @@ const getAllCategories = asyncWrapper(async (req, res, next) => {
const order =
orderType == 'name'
- ? [[Sequelize.fn('lower', Sequelize.col('bookmarks.name')), 'ASC']]
- : [[{ model: Bookmark, as: 'bookmarks' }, orderType, 'ASC']];
+ ? [
+ [Sequelize.fn('lower', Sequelize.col('Category.name')), 'ASC'],
+ [Sequelize.fn('lower', Sequelize.col('bookmarks.name')), 'ASC'],
+ ]
+ : [
+ [orderType, 'ASC'],
+ [{ model: Bookmark, as: 'bookmarks' }, orderType, 'ASC'],
+ ];
categories = categories = await Category.findAll({
include: [
From fcf2b87d1c100e8b159f3e26d39677010e9551a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Malak?=
Date: Fri, 26 Nov 2021 14:40:52 +0100
Subject: [PATCH 11/11] Pushed version 2.1.0
---
.env | 2 +-
CHANGELOG.md | 6 ++++--
client/.env | 2 +-
client/src/components/Bookmarks/Form/BookmarksForm.tsx | 3 +--
4 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/.env b/.env
index 9f1bd80..db3637d 100644
--- a/.env
+++ b/.env
@@ -1,5 +1,5 @@
PORT=5005
NODE_ENV=development
-VERSION=2.0.1
+VERSION=2.1.0
PASSWORD=flame_password
SECRET=e02eb43d69953658c6d07311d6313f2d4467672cb881f96b29368ba1f3f4da4b
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 27a7641..329b2dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,10 @@
-### v2.0.2 (TBA)
+### v2.1.0 (2021-11-26)
+- Added option to set custom order for bookmarks ([#43](https://github.com/pawelmalak/flame/issues/43)) and ([#187](https://github.com/pawelmalak/flame/issues/187))
- Added support for .ico files for custom icons ([#209](https://github.com/pawelmalak/flame/issues/209))
- Empty apps and categories sections will now be hidden from guests ([#210](https://github.com/pawelmalak/flame/issues/210))
- Fixed bug with fahrenheit degrees being displayed as float ([#221](https://github.com/pawelmalak/flame/issues/221))
-- Added option to change visibilty of apps and categories directly from table view
+- Fixed bug with alphabetical order not working for bookmarks until the page was refreshed ([#224](https://github.com/pawelmalak/flame/issues/224))
+- Added option to change visibilty of apps, categories and bookmarks directly from table view
- Password input will now autofocus when visiting /settings/app
### v2.0.1 (2021-11-19)
diff --git a/client/.env b/client/.env
index aac1ed1..d6fe0e5 100644
--- a/client/.env
+++ b/client/.env
@@ -1 +1 @@
-REACT_APP_VERSION=2.0.1
\ No newline at end of file
+REACT_APP_VERSION=2.1.0
\ No newline at end of file
diff --git a/client/src/components/Bookmarks/Form/BookmarksForm.tsx b/client/src/components/Bookmarks/Form/BookmarksForm.tsx
index 65f658d..893b334 100644
--- a/client/src/components/Bookmarks/Form/BookmarksForm.tsx
+++ b/client/src/components/Bookmarks/Form/BookmarksForm.tsx
@@ -139,8 +139,7 @@ export const BookmarksForm = ({
modalHandler();
}
- setFormData(newBookmarkTemplate);
-
+ setFormData({ ...newBookmarkTemplate, categoryId: formData.categoryId });
setCustomIcon(null);
};
|