mirror of
https://github.com/pawelmalak/flame.git
synced 2025-07-19 19:49:37 +02:00
App state: refactored reducers and actions for apps, categories and bookmarks
This commit is contained in:
parent
7e89ab0204
commit
adc017c48d
10 changed files with 893 additions and 1 deletions
170
client/src/store/action-creators/app.ts
Normal file
170
client/src/store/action-creators/app.ts
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { ApiResponse, App, Config, NewApp } from '../../interfaces';
|
||||||
|
import {
|
||||||
|
AddAppAction,
|
||||||
|
DeleteAppAction,
|
||||||
|
GetAppsAction,
|
||||||
|
PinAppAction,
|
||||||
|
ReorderAppsAction,
|
||||||
|
SortAppsAction,
|
||||||
|
UpdateAppAction,
|
||||||
|
} from '../actions/app';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { createNotification } from '.';
|
||||||
|
|
||||||
|
export const getApps =
|
||||||
|
() => async (dispatch: Dispatch<GetAppsAction<undefined | App[]>>) => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.getApps,
|
||||||
|
payload: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios.get<ApiResponse<App[]>>('/api/apps');
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.getAppsSuccess,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pinApp =
|
||||||
|
(app: App) => async (dispatch: Dispatch<PinAppAction>) => {
|
||||||
|
try {
|
||||||
|
const { id, isPinned, name } = app;
|
||||||
|
const res = await axios.put<ApiResponse<App>>(`/api/apps/${id}`, {
|
||||||
|
isPinned: !isPinned,
|
||||||
|
});
|
||||||
|
|
||||||
|
const status = isPinned
|
||||||
|
? 'unpinned from Homescreen'
|
||||||
|
: 'pinned to Homescreen';
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `App ${name} ${status}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.pinApp,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addApp =
|
||||||
|
(formData: NewApp | FormData) => async (dispatch: Dispatch<AddAppAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.post<ApiResponse<App>>('/api/apps', formData);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `App added`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await dispatch({
|
||||||
|
type: ActionType.addAppSuccess,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort apps
|
||||||
|
// dispatch<any>(sortApps());
|
||||||
|
sortApps();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteApp =
|
||||||
|
(id: number) => async (dispatch: Dispatch<DeleteAppAction>) => {
|
||||||
|
try {
|
||||||
|
await axios.delete<ApiResponse<{}>>(`/api/apps/${id}`);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: 'App deleted',
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.deleteApp,
|
||||||
|
payload: id,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateApp =
|
||||||
|
(id: number, formData: NewApp | FormData) =>
|
||||||
|
async (dispatch: Dispatch<UpdateAppAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.put<ApiResponse<App>>(
|
||||||
|
`/api/apps/${id}`,
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `App updated`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await dispatch({
|
||||||
|
type: ActionType.updateApp,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort apps
|
||||||
|
dispatch<any>(sortApps());
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reorderApps =
|
||||||
|
(apps: App[]) => async (dispatch: Dispatch<ReorderAppsAction>) => {
|
||||||
|
interface ReorderQuery {
|
||||||
|
apps: {
|
||||||
|
id: number;
|
||||||
|
orderId: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updateQuery: ReorderQuery = { apps: [] };
|
||||||
|
|
||||||
|
apps.forEach((app, index) =>
|
||||||
|
updateQuery.apps.push({
|
||||||
|
id: app.id,
|
||||||
|
orderId: index + 1,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await axios.put<ApiResponse<{}>>('/api/apps/0/reorder', updateQuery);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.reorderApps,
|
||||||
|
payload: apps,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sortApps = () => async (dispatch: Dispatch<SortAppsAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get<ApiResponse<Config>>('/api/config');
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.sortApps,
|
||||||
|
payload: res.data.data.useOrdering,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
289
client/src/store/action-creators/bookmark.ts
Normal file
289
client/src/store/action-creators/bookmark.ts
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { createNotification } from '.';
|
||||||
|
import {
|
||||||
|
ApiResponse,
|
||||||
|
Bookmark,
|
||||||
|
Category,
|
||||||
|
Config,
|
||||||
|
NewBookmark,
|
||||||
|
NewCategory,
|
||||||
|
} from '../../interfaces';
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
|
import {
|
||||||
|
AddBookmarkAction,
|
||||||
|
AddCategoryAction,
|
||||||
|
DeleteBookmarkAction,
|
||||||
|
DeleteCategoryAction,
|
||||||
|
GetCategoriesAction,
|
||||||
|
PinCategoryAction,
|
||||||
|
ReorderCategoriesAction,
|
||||||
|
SortCategoriesAction,
|
||||||
|
UpdateBookmarkAction,
|
||||||
|
UpdateCategoryAction,
|
||||||
|
} from '../actions/bookmark';
|
||||||
|
|
||||||
|
export const getCategories =
|
||||||
|
() =>
|
||||||
|
async (dispatch: Dispatch<GetCategoriesAction<undefined | Category[]>>) => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.getCategories,
|
||||||
|
payload: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios.get<ApiResponse<Category[]>>('/api/categories');
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.getCategoriesSuccess,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addCategory =
|
||||||
|
(formData: NewCategory) => async (dispatch: Dispatch<AddCategoryAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.post<ApiResponse<Category>>(
|
||||||
|
'/api/categories',
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `Category ${formData.name} created`,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.addCategory,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
// dispatch<any>(sortCategories());
|
||||||
|
sortCategories();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addBookmark =
|
||||||
|
(formData: NewBookmark | FormData) =>
|
||||||
|
async (dispatch: Dispatch<AddBookmarkAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.post<ApiResponse<Bookmark>>(
|
||||||
|
'/api/bookmarks',
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `Bookmark created`,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.addBookmark,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pinCategory =
|
||||||
|
(category: Category) => async (dispatch: Dispatch<PinCategoryAction>) => {
|
||||||
|
try {
|
||||||
|
const { id, isPinned, name } = category;
|
||||||
|
const res = await axios.put<ApiResponse<Category>>(
|
||||||
|
`/api/categories/${id}`,
|
||||||
|
{ isPinned: !isPinned }
|
||||||
|
);
|
||||||
|
|
||||||
|
const status = isPinned
|
||||||
|
? 'unpinned from Homescreen'
|
||||||
|
: 'pinned to Homescreen';
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `Category ${name} ${status}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.pinCategory,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteCategory =
|
||||||
|
(id: number) => async (dispatch: Dispatch<DeleteCategoryAction>) => {
|
||||||
|
try {
|
||||||
|
await axios.delete<ApiResponse<{}>>(`/api/categories/${id}`);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `Category deleted`,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.deleteCategory,
|
||||||
|
payload: id,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateCategory =
|
||||||
|
(id: number, formData: NewCategory) =>
|
||||||
|
async (dispatch: Dispatch<UpdateCategoryAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.put<ApiResponse<Category>>(
|
||||||
|
`/api/categories/${id}`,
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `Category ${formData.name} updated`,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.updateCategory,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
// dispatch<any>(sortCategories());
|
||||||
|
sortCategories();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteBookmark =
|
||||||
|
(bookmarkId: number, categoryId: number) =>
|
||||||
|
async (dispatch: Dispatch<DeleteBookmarkAction>) => {
|
||||||
|
try {
|
||||||
|
await axios.delete<ApiResponse<{}>>(`/api/bookmarks/${bookmarkId}`);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: 'Bookmark deleted',
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.deleteBookmark,
|
||||||
|
payload: {
|
||||||
|
bookmarkId,
|
||||||
|
categoryId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateBookmark =
|
||||||
|
(
|
||||||
|
bookmarkId: number,
|
||||||
|
formData: NewBookmark | FormData,
|
||||||
|
category: {
|
||||||
|
prev: number;
|
||||||
|
curr: number;
|
||||||
|
}
|
||||||
|
) =>
|
||||||
|
async (
|
||||||
|
dispatch: Dispatch<
|
||||||
|
DeleteBookmarkAction | AddBookmarkAction | UpdateBookmarkAction
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.put<ApiResponse<Bookmark>>(
|
||||||
|
`/api/bookmarks/${bookmarkId}`,
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
title: 'Success',
|
||||||
|
message: `Bookmark updated`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if category was changed
|
||||||
|
const categoryWasChanged = category.curr !== category.prev;
|
||||||
|
|
||||||
|
if (categoryWasChanged) {
|
||||||
|
// Delete bookmark from old category
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.deleteBookmark,
|
||||||
|
payload: {
|
||||||
|
bookmarkId,
|
||||||
|
categoryId: category.prev,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add bookmark to the new category
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.addBookmark,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Else update only name/url/icon
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.updateBookmark,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sortCategories =
|
||||||
|
() => async (dispatch: Dispatch<SortCategoriesAction>) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get<ApiResponse<Config>>('/api/config');
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.sortCategories,
|
||||||
|
payload: res.data.data.useOrdering,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reorderCategories =
|
||||||
|
(categories: Category[]) =>
|
||||||
|
async (dispatch: Dispatch<ReorderCategoriesAction>) => {
|
||||||
|
interface ReorderQuery {
|
||||||
|
categories: {
|
||||||
|
id: number;
|
||||||
|
orderId: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updateQuery: ReorderQuery = { categories: [] };
|
||||||
|
|
||||||
|
categories.forEach((category, index) =>
|
||||||
|
updateQuery.categories.push({
|
||||||
|
id: category.id,
|
||||||
|
orderId: index + 1,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await axios.put<ApiResponse<{}>>(
|
||||||
|
'/api/categories/0/reorder',
|
||||||
|
updateQuery
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.reorderCategories,
|
||||||
|
payload: categories,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,3 +1,5 @@
|
||||||
export * from './theme';
|
export * from './theme';
|
||||||
export * from './config';
|
export * from './config';
|
||||||
export * from './notification';
|
export * from './notification';
|
||||||
|
export * from './app';
|
||||||
|
export * from './bookmark';
|
||||||
|
|
|
@ -4,6 +4,7 @@ export enum ActionType {
|
||||||
// CONFIG
|
// CONFIG
|
||||||
getConfig = 'GET_CONFIG',
|
getConfig = 'GET_CONFIG',
|
||||||
updateConfig = 'UPDATE_CONFIG',
|
updateConfig = 'UPDATE_CONFIG',
|
||||||
|
// QUERIES
|
||||||
addQuery = 'ADD_QUERY',
|
addQuery = 'ADD_QUERY',
|
||||||
deleteQuery = 'DELETE_QUERY',
|
deleteQuery = 'DELETE_QUERY',
|
||||||
fetchQueries = 'FETCH_QUERIES',
|
fetchQueries = 'FETCH_QUERIES',
|
||||||
|
@ -11,4 +12,29 @@ export enum ActionType {
|
||||||
// NOTIFICATIONS
|
// NOTIFICATIONS
|
||||||
createNotification = 'CREATE_NOTIFICATION',
|
createNotification = 'CREATE_NOTIFICATION',
|
||||||
clearNotification = 'CLEAR_NOTIFICATION',
|
clearNotification = 'CLEAR_NOTIFICATION',
|
||||||
|
// APPS
|
||||||
|
getApps = 'GET_APPS',
|
||||||
|
getAppsSuccess = 'GET_APPS_SUCCESS',
|
||||||
|
getAppsError = 'GET_APPS_ERROR',
|
||||||
|
pinApp = 'PIN_APP',
|
||||||
|
addApp = 'ADD_APP',
|
||||||
|
addAppSuccess = 'ADD_APP_SUCCESS',
|
||||||
|
deleteApp = 'DELETE_APP',
|
||||||
|
updateApp = 'UPDATE_APP',
|
||||||
|
reorderApps = 'REORDER_APPS',
|
||||||
|
sortApps = 'SORT_APPS',
|
||||||
|
// CATEGORES
|
||||||
|
getCategories = 'GET_CATEGORIES',
|
||||||
|
getCategoriesSuccess = 'GET_CATEGORIES_SUCCESS',
|
||||||
|
getCategoriesError = 'GET_CATEGORIES_ERROR',
|
||||||
|
addCategory = 'ADD_CATEGORY',
|
||||||
|
pinCategory = 'PIN_CATEGORY',
|
||||||
|
deleteCategory = 'DELETE_CATEGORY',
|
||||||
|
updateCategory = 'UPDATE_CATEGORY',
|
||||||
|
sortCategories = 'SORT_CATEGORIES',
|
||||||
|
reorderCategories = 'REORDER_CATEGORIES',
|
||||||
|
// BOOKMARKS
|
||||||
|
addBookmark = 'ADD_BOOKMARK',
|
||||||
|
deleteBookmark = 'DELETE_BOOKMARK',
|
||||||
|
updateBookmark = 'UPDATE_BOOKMARK',
|
||||||
}
|
}
|
||||||
|
|
38
client/src/store/actions/app.ts
Normal file
38
client/src/store/actions/app.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
|
import { App } from '../../interfaces';
|
||||||
|
|
||||||
|
export interface GetAppsAction<T> {
|
||||||
|
type:
|
||||||
|
| ActionType.getApps
|
||||||
|
| ActionType.getAppsSuccess
|
||||||
|
| ActionType.getAppsError;
|
||||||
|
payload: T;
|
||||||
|
}
|
||||||
|
export interface PinAppAction {
|
||||||
|
type: ActionType.pinApp;
|
||||||
|
payload: App;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddAppAction {
|
||||||
|
type: ActionType.addAppSuccess;
|
||||||
|
payload: App;
|
||||||
|
}
|
||||||
|
export interface DeleteAppAction {
|
||||||
|
type: ActionType.deleteApp;
|
||||||
|
payload: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateAppAction {
|
||||||
|
type: ActionType.updateApp;
|
||||||
|
payload: App;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReorderAppsAction {
|
||||||
|
type: ActionType.reorderApps;
|
||||||
|
payload: App[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SortAppsAction {
|
||||||
|
type: ActionType.sortApps;
|
||||||
|
payload: string;
|
||||||
|
}
|
58
client/src/store/actions/bookmark.ts
Normal file
58
client/src/store/actions/bookmark.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { Bookmark, Category } from '../../interfaces';
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
|
|
||||||
|
export interface GetCategoriesAction<T> {
|
||||||
|
type:
|
||||||
|
| ActionType.getCategories
|
||||||
|
| ActionType.getCategoriesSuccess
|
||||||
|
| ActionType.getCategoriesError;
|
||||||
|
payload: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddCategoryAction {
|
||||||
|
type: ActionType.addCategory;
|
||||||
|
payload: Category;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddBookmarkAction {
|
||||||
|
type: ActionType.addBookmark;
|
||||||
|
payload: Bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PinCategoryAction {
|
||||||
|
type: ActionType.pinCategory;
|
||||||
|
payload: Category;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteCategoryAction {
|
||||||
|
type: ActionType.deleteCategory;
|
||||||
|
payload: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateCategoryAction {
|
||||||
|
type: ActionType.updateCategory;
|
||||||
|
payload: Category;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteBookmarkAction {
|
||||||
|
type: ActionType.deleteBookmark;
|
||||||
|
payload: {
|
||||||
|
bookmarkId: number;
|
||||||
|
categoryId: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateBookmarkAction {
|
||||||
|
type: ActionType.updateBookmark;
|
||||||
|
payload: Bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SortCategoriesAction {
|
||||||
|
type: ActionType.sortCategories;
|
||||||
|
payload: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReorderCategoriesAction {
|
||||||
|
type: ActionType.reorderCategories;
|
||||||
|
payload: Category[];
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { SetThemeAction } from './theme';
|
import { SetThemeAction } from './theme';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AddQueryAction,
|
AddQueryAction,
|
||||||
DeleteQueryAction,
|
DeleteQueryAction,
|
||||||
|
@ -7,11 +8,37 @@ import {
|
||||||
UpdateConfigAction,
|
UpdateConfigAction,
|
||||||
UpdateQueryAction,
|
UpdateQueryAction,
|
||||||
} from './config';
|
} from './config';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ClearNotificationAction,
|
ClearNotificationAction,
|
||||||
CreateNotificationAction,
|
CreateNotificationAction,
|
||||||
} from './notification';
|
} from './notification';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GetAppsAction,
|
||||||
|
PinAppAction,
|
||||||
|
AddAppAction,
|
||||||
|
DeleteAppAction,
|
||||||
|
UpdateAppAction,
|
||||||
|
ReorderAppsAction,
|
||||||
|
SortAppsAction,
|
||||||
|
} from './app';
|
||||||
|
|
||||||
|
import { App } from '../../interfaces';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GetCategoriesAction,
|
||||||
|
AddCategoryAction,
|
||||||
|
PinCategoryAction,
|
||||||
|
DeleteCategoryAction,
|
||||||
|
UpdateCategoryAction,
|
||||||
|
SortCategoriesAction,
|
||||||
|
ReorderCategoriesAction,
|
||||||
|
AddBookmarkAction,
|
||||||
|
DeleteBookmarkAction,
|
||||||
|
UpdateBookmarkAction,
|
||||||
|
} from './bookmark';
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
// Theme
|
// Theme
|
||||||
| SetThemeAction
|
| SetThemeAction
|
||||||
|
@ -24,4 +51,24 @@ export type Action =
|
||||||
| UpdateQueryAction
|
| UpdateQueryAction
|
||||||
// Notifications
|
// Notifications
|
||||||
| CreateNotificationAction
|
| CreateNotificationAction
|
||||||
| ClearNotificationAction;
|
| ClearNotificationAction
|
||||||
|
// Apps
|
||||||
|
| GetAppsAction<undefined | App[]>
|
||||||
|
| PinAppAction
|
||||||
|
| AddAppAction
|
||||||
|
| DeleteAppAction
|
||||||
|
| UpdateAppAction
|
||||||
|
| ReorderAppsAction
|
||||||
|
| SortAppsAction
|
||||||
|
// Categories
|
||||||
|
| GetCategoriesAction<any>
|
||||||
|
| AddCategoryAction
|
||||||
|
| PinCategoryAction
|
||||||
|
| DeleteCategoryAction
|
||||||
|
| UpdateCategoryAction
|
||||||
|
| SortCategoriesAction
|
||||||
|
| ReorderCategoriesAction
|
||||||
|
// Bookmarks
|
||||||
|
| AddBookmarkAction
|
||||||
|
| DeleteBookmarkAction
|
||||||
|
| UpdateBookmarkAction;
|
||||||
|
|
92
client/src/store/reducers/app.ts
Normal file
92
client/src/store/reducers/app.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
|
import { Action } from '../actions/index';
|
||||||
|
import { App } from '../../interfaces';
|
||||||
|
import { sortData } from '../../utility';
|
||||||
|
|
||||||
|
interface AppsState {
|
||||||
|
loading: boolean;
|
||||||
|
apps: App[];
|
||||||
|
errors: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: AppsState = {
|
||||||
|
loading: true,
|
||||||
|
apps: [],
|
||||||
|
errors: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appsReducer = (
|
||||||
|
state: AppsState = initialState,
|
||||||
|
action: Action
|
||||||
|
): AppsState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionType.getApps:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
errors: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.getAppsSuccess:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
apps: action.payload || [],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.pinApp:
|
||||||
|
const pinnedAppIdx = state.apps.findIndex(
|
||||||
|
(app) => app.id === action.payload.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
apps: [
|
||||||
|
...state.apps.slice(0, pinnedAppIdx),
|
||||||
|
action.payload,
|
||||||
|
...state.apps.slice(pinnedAppIdx + 1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.addAppSuccess:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
apps: [...state.apps, action.payload],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.deleteApp:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
apps: [...state.apps].filter((app) => app.id !== action.payload),
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.updateApp:
|
||||||
|
const updatedAppIdx = state.apps.findIndex(
|
||||||
|
(app) => app.id === action.payload.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
apps: [
|
||||||
|
...state.apps.slice(0, updatedAppIdx),
|
||||||
|
action.payload,
|
||||||
|
...state.apps.slice(updatedAppIdx + 1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.reorderApps:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
apps: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.sortApps:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
apps: sortData<App>(state.apps, action.payload),
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
166
client/src/store/reducers/bookmark.ts
Normal file
166
client/src/store/reducers/bookmark.ts
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
import { Category } from '../../interfaces';
|
||||||
|
import { sortData } from '../../utility';
|
||||||
|
import { ActionType } from '../action-types';
|
||||||
|
import { Action } from '../actions';
|
||||||
|
|
||||||
|
interface BookmarksState {
|
||||||
|
loading: boolean;
|
||||||
|
errors: string | undefined;
|
||||||
|
categories: Category[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: BookmarksState = {
|
||||||
|
loading: true,
|
||||||
|
errors: undefined,
|
||||||
|
categories: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bookmarksReducer = (
|
||||||
|
state: BookmarksState = initialState,
|
||||||
|
action: Action
|
||||||
|
): BookmarksState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionType.getCategories:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
errors: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.getCategoriesSuccess:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
categories: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.addCategory:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categories: [...state.categories, { ...action.payload, bookmarks: [] }],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.addBookmark:
|
||||||
|
const categoryIdx = state.categories.findIndex(
|
||||||
|
(category) => category.id === action.payload.categoryId
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categories: [
|
||||||
|
...state.categories.slice(0, categoryIdx),
|
||||||
|
{
|
||||||
|
...state.categories[categoryIdx],
|
||||||
|
bookmarks: [
|
||||||
|
...state.categories[categoryIdx].bookmarks,
|
||||||
|
action.payload,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...state.categories.slice(categoryIdx + 1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.pinCategory:
|
||||||
|
const pinnedCategoryIdx = state.categories.findIndex(
|
||||||
|
(category) => category.id === action.payload.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categories: [
|
||||||
|
...state.categories.slice(0, pinnedCategoryIdx),
|
||||||
|
action.payload,
|
||||||
|
...state.categories.slice(pinnedCategoryIdx + 1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.deleteCategory:
|
||||||
|
const deletedCategoryIdx = state.categories.findIndex(
|
||||||
|
(category) => category.id === action.payload
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categories: [
|
||||||
|
...state.categories.slice(0, deletedCategoryIdx),
|
||||||
|
...state.categories.slice(deletedCategoryIdx + 1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.updateCategory:
|
||||||
|
const updatedCategoryIdx = state.categories.findIndex(
|
||||||
|
(category) => category.id === action.payload.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categories: [
|
||||||
|
...state.categories.slice(0, updatedCategoryIdx),
|
||||||
|
action.payload,
|
||||||
|
...state.categories.slice(updatedCategoryIdx + 1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.deleteBookmark:
|
||||||
|
const categoryInUpdateIdx = state.categories.findIndex(
|
||||||
|
(category) => category.id === action.payload.categoryId
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categories: [
|
||||||
|
...state.categories.slice(0, categoryInUpdateIdx),
|
||||||
|
{
|
||||||
|
...state.categories[categoryInUpdateIdx],
|
||||||
|
bookmarks: state.categories[categoryInUpdateIdx].bookmarks.filter(
|
||||||
|
(bookmark) => bookmark.id !== action.payload.bookmarkId
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...state.categories.slice(categoryInUpdateIdx + 1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...state.categories.slice(parentCategoryIdx + 1),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.sortCategories:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categories: sortData<Category>(state.categories, action.payload),
|
||||||
|
};
|
||||||
|
|
||||||
|
case ActionType.reorderCategories:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
categories: action.payload,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -3,11 +3,15 @@ import { combineReducers } from 'redux';
|
||||||
import { themeReducer } from './theme';
|
import { themeReducer } from './theme';
|
||||||
import { configReducer } from './config';
|
import { configReducer } from './config';
|
||||||
import { notificationReducer } from './notification';
|
import { notificationReducer } from './notification';
|
||||||
|
import { appsReducer } from './app';
|
||||||
|
import { bookmarksReducer } from './bookmark';
|
||||||
|
|
||||||
export const reducers = combineReducers({
|
export const reducers = combineReducers({
|
||||||
theme: themeReducer,
|
theme: themeReducer,
|
||||||
config: configReducer,
|
config: configReducer,
|
||||||
notification: notificationReducer,
|
notification: notificationReducer,
|
||||||
|
apps: appsReducer,
|
||||||
|
bookmarks: bookmarksReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type State = ReturnType<typeof reducers>;
|
export type State = ReturnType<typeof reducers>;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue