mirror of
https://github.com/responsively-org/responsively-app
synced 2024-11-10 14:54:12 +00:00
added feat: bookmark add, remove, view, store
This commit is contained in:
parent
06939a0f1e
commit
d1d7ca67c5
10 changed files with 274 additions and 9 deletions
|
@ -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;
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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' }}>
|
||||
|
|
41
desktop-app/src/renderer/store/features/bookmarks/index.ts
Normal file
41
desktop-app/src/renderer/store/features/bookmarks/index.ts
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -190,6 +190,13 @@ const schema = {
|
|||
},
|
||||
default: [],
|
||||
},
|
||||
bookmarks: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
},
|
||||
default: [],
|
||||
},
|
||||
} as const;
|
||||
|
||||
const store = new Store({ schema, watch: true, migrations });
|
||||
|
|
Loading…
Reference in a new issue