added feat: bookmark add, remove, view, store

This commit is contained in:
themohammadsa 2023-05-31 18:09:57 +05:30
parent 06939a0f1e
commit d1d7ca67c5
10 changed files with 274 additions and 9 deletions

View file

@ -0,0 +1,62 @@
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import Button from 'renderer/components/Button';
import {
IBookmarks,
addBookmark,
removeBookmark,
} from 'renderer/store/features/bookmarks';
export interface Props {
currentBookmark: IBookmarks;
setOpenEditBookmarkFlyout: (bool: boolean) => void;
address: string;
}
const EditBookmarkFlyout = ({
currentBookmark,
setOpenEditBookmarkFlyout,
address,
}: Props) => {
const [name, setName] = useState<string>(currentBookmark.name);
const dispatch = useDispatch();
const handleSave = () => {
dispatch(addBookmark({ name, address }));
setOpenEditBookmarkFlyout(false);
};
const handleRemove = () => {
dispatch(removeBookmark({ name, address }));
setOpenEditBookmarkFlyout(false);
};
return (
<div className="absolute top-[40px] right-[0px] z-50 flex w-80 flex-col gap-2 rounded bg-white p-2 px-6 py-4 text-sm shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40">
<div className="flex flex-col gap-2">
<span className="w-50 mr-6 overflow-hidden text-ellipsis whitespace-nowrap">
Bookmark Name
</span>
<input
type="text"
className="rounded-sm bg-slate-200 p-1 px-1 dark:bg-slate-700"
id="bookmark-name"
name="bookmarkName"
placeholder=""
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="mt-4 flex justify-center">
<Button onClick={handleRemove} className="mr-6 px-6">
Remove
</Button>
<Button onClick={handleSave} className="px-8" isActionButton>
Save
</Button>
</div>
</div>
);
};
export default EditBookmarkFlyout;

View file

@ -14,8 +14,10 @@ import { useDispatch, useSelector } from 'react-redux';
import Button from 'renderer/components/Button';
import { webViewPubSub } from 'renderer/lib/pubsub';
import { selectAddress, setAddress } from 'renderer/store/features/renderer';
import { IBookmarks, selectBookmarks } from 'renderer/store/features/bookmarks';
import AuthModal from './AuthModal';
import SuggestionList from './SuggestionList';
import EditBookmarkFlyout from './EditBookmarkFlyout';
export const ADDRESS_BAR_EVENTS = {
DELETE_COOKIES: 'DELETE_COOKIES',
@ -37,8 +39,15 @@ const AddressBar = () => {
const [deleteCacheLoading, setDeleteCacheLoading] = useState<boolean>(false);
const [permissionRequest, setPermissionRequest] =
useState<PermissionRequestArg | null>(null);
const [currentBookmark, setCurrentBookmark] = useState<IBookmarks>({
name: '',
address: '',
});
const [openEditBookmarkFlyout, setOpenEditBookmarkFlyout] =
useState<boolean>(false);
const [authRequest, setAuthRequest] = useState<AuthRequestArgs | null>(null);
const address = useSelector(selectAddress);
const bookmarks = useSelector(selectBookmarks);
const dispatch = useDispatch();
useEffect(() => {
@ -108,6 +117,16 @@ const AddressBar = () => {
}
}, [homepage]);
useEffect(() => {
const bookmarkFound = bookmarks.find(
(bookmark: IBookmarks) => bookmark.address === address
);
setCurrentBookmark({
name: bookmarkFound?.name || '',
address: bookmarkFound?.address || '',
});
}, [bookmarks, address]);
const permissionReqClickHandler = (allow: boolean) => {
if (!permissionRequest) {
return;
@ -149,6 +168,9 @@ const AddressBar = () => {
};
const isHomepage = address === homepage;
const isPageBookmarked = Boolean(
currentBookmark.name && currentBookmark.address
);
return (
<>
@ -240,10 +262,28 @@ const AddressBar = () => {
>
<Icon icon={isHomepage ? 'mdi:home' : 'mdi:home-outline'} />
</Button>
<Button
className={cx('rounded-full', {
'text-blue-500': isPageBookmarked,
})}
onClick={() => setOpenEditBookmarkFlyout(!openEditBookmarkFlyout)}
title={`${!isPageBookmarked ? 'Add' : 'Remove'} bookmark`}
>
<Icon
icon={`ic:baseline-star${!isPageBookmarked ? '-border' : ''}`}
/>
</Button>
</div>
{isSuggesting ? (
<SuggestionList match={typedAddress} onEnter={onEnter} />
) : null}
{openEditBookmarkFlyout && (
<EditBookmarkFlyout
currentBookmark={currentBookmark}
setOpenEditBookmarkFlyout={setOpenEditBookmarkFlyout}
address={address}
/>
)}
</div>
<AuthModal
isOpen={authRequest != null}

View file

@ -0,0 +1,44 @@
import { useDispatch } from 'react-redux';
import Button from 'renderer/components/Button';
import { IBookmarks } from 'renderer/store/features/bookmarks';
import { setAddress } from 'renderer/store/features/renderer';
export interface Props {
bookmarks: IBookmarks[];
handleBookmarkFlyout: () => void;
}
const ViewAllBookmarks = ({ bookmarks, handleBookmarkFlyout }: Props) => {
const dispatch = useDispatch();
const areBookmarksPresent = bookmarks.length > 0;
const handleBookmarkClick = (address: string) => {
dispatch(setAddress(address));
handleBookmarkFlyout();
};
return (
<div className="absolute top-[179px] right-[322px] z-50 flex flex-col rounded bg-white text-sm shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40">
{bookmarks.map((bookmark) => {
return (
<div className="w-60 overflow-hidden">
<Button
className="w-full cursor-pointer justify-between truncate py-2 px-4"
onClick={() => handleBookmarkClick(bookmark.address)}
>
{bookmark.name}
</Button>
</div>
);
})}
{!areBookmarksPresent && (
<Button className="w-60 py-2" disabled disableHoverEffects>
No bookmarks found{' '}
</Button>
)}
</div>
);
};
export default ViewAllBookmarks;

View file

@ -0,0 +1,52 @@
import { Icon } from '@iconify/react';
import { useEffect, useState } from 'react';
import Button from 'renderer/components/Button';
import { closeMenuFlyout, selectMenuFlyout } from 'renderer/store/features/ui';
import { useDispatch, useSelector } from 'react-redux';
import { selectBookmarks } from 'renderer/store/features/bookmarks';
import ViewAllBookmarks from './ViewAllBookmarks';
const Bookmark = () => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const dispatch = useDispatch();
const menuFlyout = useSelector(selectMenuFlyout);
const bookmarks = useSelector(selectBookmarks);
const handleBookmarkFlyout = () => {
setIsOpen(!isOpen);
dispatch(closeMenuFlyout(!isOpen));
};
useEffect(() => {
if (!menuFlyout) setIsOpen(false);
}, [menuFlyout]);
return (
<div>
<div className="">
<div className="relative right-2 w-80 dark:border-slate-400">
<Button
className="flex w-full items-center justify-between pl-6 pb-2 pt-1.5"
onClick={() => setIsOpen(!isOpen)}
isActive={isOpen}
>
<span>Bookmarks</span>
<Icon
className="mr-3 -rotate-90 transform"
icon="ic:baseline-arrow-drop-down"
height={20}
/>
</Button>
</div>
</div>
{isOpen && (
<ViewAllBookmarks
bookmarks={bookmarks}
handleBookmarkFlyout={handleBookmarkFlyout}
/>
)}
</div>
);
};
export default Bookmark;

View file

@ -8,18 +8,20 @@ import Zoom from './Zoom';
import AllowInSecureSSL from './AllowInSecureSSL';
import ClearHistory from './ClearHistory';
import PreviewLayout from './PreviewLayout';
import Bookmark from './Bookmark';
const MenuFlyout = () => {
const dispatch = useDispatch();
return (
<div className="absolute top-[26px] right-[4px] z-50 flex w-80 flex-col gap-2 rounded bg-white p-2 text-sm shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40">
<div className="absolute top-[26px] right-[4px] z-50 flex w-80 flex-col gap-2 rounded bg-white p-2 pb-0 text-sm shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40">
<Zoom />
<PreviewLayout />
<UITheme />
<Devtools />
<AllowInSecureSSL />
<ClearHistory />
<Bookmark />
</div>
);
};

View file

@ -1,11 +1,15 @@
import { Icon } from '@iconify/react';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useDetectClickOutside } from 'react-detect-click-outside';
import Button from 'renderer/components/Button';
import { useDispatch, useSelector } from 'react-redux';
import { closeMenuFlyout, selectMenuFlyout } from 'renderer/store/features/ui';
import MenuFlyout from './Flyout';
const Menu = () => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const dispatch = useDispatch();
const menuFlyout = useSelector(selectMenuFlyout);
const ref = useDetectClickOutside({
onTriggered: () => {
@ -13,17 +17,22 @@ const Menu = () => {
return;
}
setIsOpen(false);
dispatch(closeMenuFlyout(false));
},
});
const handleFlyout = () => {
setIsOpen(!isOpen);
dispatch(closeMenuFlyout(!isOpen));
};
useEffect(() => {
setIsOpen(menuFlyout);
}, [menuFlyout]);
return (
<div className="relative flex items-center" ref={ref}>
<Button
onClick={() => {
setIsOpen(!isOpen);
}}
isActive={isOpen}
>
<Button onClick={handleFlyout} isActive={isOpen}>
<Icon icon="carbon:overflow-menu-vertical" />
</Button>
<div style={{ visibility: isOpen ? 'visible' : 'hidden' }}>

View file

@ -0,0 +1,41 @@
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from '../..';
export interface IBookmarks {
name: string;
address: string;
}
export interface BookmarksState {
bookmarks: IBookmarks[];
}
const initialState: BookmarksState = {
bookmarks: window.electron.store.get('bookmarks'),
};
export const bookmarksSlice = createSlice({
name: 'bookmarks',
initialState,
reducers: {
addBookmark: (state, action: PayloadAction<IBookmarks>) => {
state.bookmarks = [...state.bookmarks, action.payload];
window.electron.store.set('bookmarks', action.payload);
},
removeBookmark: (state, action: PayloadAction<IBookmarks>) => {
const bookmarks: IBookmarks[] = window.electron.store.get('bookmarks');
const filteredBookmark = bookmarks.filter(
(bookmark) => bookmark.address !== action.payload.address
);
state.bookmarks = filteredBookmark;
window.electron.store.set('bookmarks', filteredBookmark);
},
},
});
// Action creators are generated for each case reducer function
export const { addBookmark, removeBookmark } = bookmarksSlice.actions;
export const selectBookmarks = (state: RootState) => state.bookmarks.bookmarks;
export default bookmarksSlice.reducer;

View file

@ -12,11 +12,13 @@ export type AppView = typeof APP_VIEWS[keyof typeof APP_VIEWS];
export interface UIState {
darkMode: boolean;
appView: AppView;
menuFlyout: boolean;
}
const initialState: UIState = {
darkMode: window.electron.store.get('ui.darkMode'),
appView: APP_VIEWS.BROWSER,
menuFlyout: false,
};
export const uiSlice = createSlice({
@ -30,13 +32,17 @@ export const uiSlice = createSlice({
setAppView: (state, action: PayloadAction<AppView>) => {
state.appView = action.payload;
},
closeMenuFlyout: (state, action: PayloadAction<boolean>) => {
state.menuFlyout = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const { setDarkMode, setAppView } = uiSlice.actions;
export const { setDarkMode, setAppView, closeMenuFlyout } = uiSlice.actions;
export const selectDarkMode = (state: RootState) => state.ui.darkMode;
export const selectAppView = (state: RootState) => state.ui.appView;
export const selectMenuFlyout = (state: RootState) => state.ui.menuFlyout;
export default uiSlice.reducer;

View file

@ -4,6 +4,7 @@ import deviceManagerReducer from './features/device-manager';
import devtoolsReducer from './features/devtools';
import rendererReducer from './features/renderer';
import uiReducer from './features/ui';
import bookmarkReducer from './features/bookmarks';
export const store = configureStore({
reducer: {
@ -11,6 +12,7 @@ export const store = configureStore({
ui: uiReducer,
deviceManager: deviceManagerReducer,
devtools: devtoolsReducer,
bookmarks: bookmarkReducer,
},
});

View file

@ -190,6 +190,13 @@ const schema = {
},
default: [],
},
bookmarks: {
type: 'array',
items: {
type: 'object',
},
default: [],
},
} as const;
const store = new Store({ schema, watch: true, migrations });