Merge pull request #162 from Grafikart/feat-favorites

Bookmarks
This commit is contained in:
Manoj Vivek 2020-06-19 13:40:11 +05:30 committed by GitHub
commit 975518015a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 318 additions and 8 deletions

View file

@ -0,0 +1,21 @@
export const TOGGLE_BOOKMARK = 'TOGGLE_BOOKMARK';
export const EDIT_BOOKMARK = 'EDIT_BOOKMARK';
// Add or Remove an URL from the bookmark list
export function toggleBookmarkUrl(url, title = null) {
return {
type: TOGGLE_BOOKMARK,
url,
title
};
}
// Updates bookmark title
export function editBookmark(bookmark, {title, url}) {
return {
type: EDIT_BOOKMARK,
title,
url,
bookmark
}
}

View file

@ -33,6 +33,7 @@ export const NEW_CUSTOM_DEVICE = 'NEW_CUSTOM_DEVICE';
export const DELETE_CUSTOM_DEVICE = 'DELETE_CUSTOM_DEVICE';
export const NEW_FILTERS = 'NEW_FILTERS';
export const NEW_USER_PREFERENCES = 'NEW_USER_PREFERENCES';
export const TOGGLE_BOOKMARK = 'TOGGLE_BOOKMARK';
export const NEW_WINDOW_SIZE = 'NEW_WINDOW_SIZE';
export function newAddress(address) {
@ -358,6 +359,20 @@ export function goToHomepage() {
};
}
export function gotoUrl(url) {
return (dispatch: Dispatch, getState: RootStateType) => {
const {
browser: {address},
} = getState();
if (url === address) {
return;
}
dispatch(newAddress(url));
}
}
export function onDevToolsModeChange(newMode) {
return (dispatch: Dispatch, getState: RootStateType) => {
const {
@ -670,4 +685,4 @@ export function reloadCSS() {
return (dispatch: Dispatch, getState: RootStateType) => {
pubsub.publish(RELOAD_CSS);
};
}
}

View file

@ -4,6 +4,8 @@ import cx from 'classnames';
import HomePlusIcon from '../icons/HomePlus';
import DeleteCookieIcon from '../icons/DeleteCookie';
import DeleteStorageIcon from '../icons/DeleteStorage';
import FavIconOff from '@material-ui/icons/StarBorder';
import FavIconOn from '@material-ui/icons/Star';
import {iconsColor} from '../../constants/colors';
import commonStyles from '../common.styles.css';
@ -43,6 +45,7 @@ class AddressBar extends React.Component<Props> {
}
render() {
const FavIcon = this.props.isBookmarked ? FavIconOn : FavIconOff;
return (
<div className={styles.addressBarContainer}>
<input
@ -57,6 +60,20 @@ class AddressBar extends React.Component<Props> {
onChange={e => this.setState({userTypedAddress: e.target.value})}
/>
<div className={cx(styles.floatingOptionsContainer)}>
<div
className={cx(commonStyles.icons, commonStyles.roundIcon, {
[commonStyles.enabled]: true,
})}
>
<Tooltip title="Bookmark">
<div
className={cx(commonStyles.flexAlignVerticalMiddle)}
onClick={() => this.props.toggleBookmark(this.state.userTypedAddress)}
>
<FavIcon fontSize="small"/>
</div>
</Tooltip>
</div>
<div
className={cx(commonStyles.icons, commonStyles.roundIcon, {
[commonStyles.enabled]: true,

View file

@ -0,0 +1,67 @@
import React, { useRef } from 'react';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
export default function BookmarkEditDialog({open, onClose, onSubmit, bookmark}) {
const titleInput = useRef(null)
const urlInput = useRef(null)
const handleSubmit = function (e) {
onSubmit(
titleInput.current.querySelector('input').value,
urlInput.current.querySelector('input').value
)
onClose()
}
const handleKeyPress = function (e) {
if (e.key === 'Enter') {
handleSubmit(e)
} else if (e.key === 'Escape') {
onClose()
}
}
return (
<Dialog open={open} onClose={onClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Bookmark title</DialogTitle>
<DialogContent>
<TextField
autoFocus
ref={titleInput}
margin="dense"
id="title"
label="Title"
type="text"
onKeyPress={handleKeyPress}
defaultValue={bookmark.title}
fullWidth
/>
<TextField
style={{marginTop: '16px'}}
autoFocus
ref={urlInput}
margin="dense"
id="url"
label="URL"
type="text"
onKeyPress={handleKeyPress}
defaultValue={bookmark.url}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>
Cancel
</Button>
<Button onClick={handleSubmit} color="primary">
Update
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -0,0 +1,74 @@
// @flow
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import BookmarkEditDialog from './BookmarkEditDialog'
import styles from './style.css';
export const BookmarksBar = function ({bookmarks, onBookmarkClick, onBookmarkDelete, onBookmarkEdit}) {
return <Grid container direction="row" justify="flex-start" alignItems="center" className={styles.bookmarks} spacing={1}>
{bookmarks.map((bookmark, k) => (
<BookmarkItem bookmark={bookmark} onClick={onBookmarkClick} key={'bookmark' + k} onDelete={onBookmarkDelete} onEdit={onBookmarkEdit}/>
))}
</Grid>
};
const useToggle = function () {
const [value, setValue] = useState(false)
return [
value,
function () { setValue(true) },
function () { setValue(false) }
]
}
function BookmarkItem ({bookmark, onClick, onDelete, onEdit}) {
const [anchorEl, setAnchorEl] = useState(null);
const [renameDialog, openRenameDialog, closeRenameDialog] = useToggle(null)
const handleContextMenu = function (event) {
event.preventDefault();
setAnchorEl(event.currentTarget);
};
const handleClose = function () {
setAnchorEl(null);
};
const handleClick = function () {
onClick(bookmark)
};
const handleDelete = function () {
onDelete(bookmark)
};
const handleRename = function (title, url) {
onEdit(bookmark, {title, url})
setAnchorEl(null)
}
const closeDialog = function () {
closeRenameDialog()
setAnchorEl(null)
}
return <Grid item key={bookmark.url}>
<Button aria-controls="bookmark-menu" aria-haspopup="true" onClick={handleClick} onContextMenu={handleContextMenu}>
{bookmark.title}
</Button>
<Menu
id="bookmark-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={openRenameDialog}>Rename</MenuItem>
<MenuItem onClick={handleDelete}>Delete</MenuItem>
</Menu>
<BookmarkEditDialog open={renameDialog} onSubmit={handleRename} onClose={closeDialog} bookmark={bookmark}/>
</Grid>
}

View file

@ -0,0 +1,3 @@
.bookmarks {
padding: 0 10px;
}

View file

@ -5,11 +5,11 @@ import Grid from '@material-ui/core/Grid';
import {ToastContainer} from 'react-toastify';
import AddressBar from '../../containers/AddressBar';
import ScrollControlsContainer from '../../containers/ScrollControlsContainer';
import ZoomContainer from '../../containers/ZoomContainer';
import HttpAuthDialog from '../HttpAuthDialog';
import styles from './style.module.css';
import NavigationControlsContainer from '../../containers/NavigationControlsContainer';
import BookmarksBar from '../../containers/BookmarksBarContainer';
const Header = function() {
return (
@ -25,6 +25,7 @@ const Header = function() {
<ScrollControlsContainer />
</Grid>
</Grid>
<BookmarksBar />
<HttpAuthDialog />
<ToastContainer
position="top-right"

View file

@ -55,7 +55,7 @@ export const captureFullPage = async (
document.body.classList.add('responsivelyApp__ScreenshotInProgress');
responsivelyApp.screenshotVar = {
previousScrollPosition : {
left: window.scrollX,
left: window.scrollX,
top: window.scrollY,
},
scrollHeight: document.body.scrollHeight,
@ -186,14 +186,14 @@ function _getScreenshotFileName(
`Desktop/Responsively-Screenshots`,
directoryPath
),
file: `${_getWebsiteName(address)} - ${device.name.replace(
file: `${getWebsiteName(address)} - ${device.name.replace(
/\//g,
'-'
)} - ${dateString}.png`,
};
}
const _getWebsiteName = address => {
export const getWebsiteName = address => {
let domain = '';
if (address.startsWith('file://')) {
let fileNameStartingIndex = address.lastIndexOf('/') + 1;

View file

@ -1,3 +1,4 @@
export const ACTIVE_DEVICES = 'activeDevices';
export const CUSTOM_DEVICES = 'customDevices';
export const USER_PREFERENCES = 'userPreferences';
export const BOOKMARKS = 'bookmarks';

View file

@ -6,6 +6,7 @@ import {bindActionCreators} from 'redux';
import AddressInput from '../../components/Addressinput';
import * as BrowserActions from '../../actions/browser';
import {toggleBookmarkUrl} from '../../actions/bookmarks'
const AddressBar = function(props) {
useEffect(() => {
@ -19,6 +20,8 @@ const AddressBar = function(props) {
onChange={props.onAddressChange}
homepage={props.browser.homepage}
setHomepage={props.setCurrentAddressAsHomepage}
isBookmarked={props.isBookmarked}
toggleBookmark={props.toggleBookmarkUrl}
deleteCookies={props.deleteCookies}
deleteStorage={props.deleteStorage}
/>
@ -28,11 +31,12 @@ const AddressBar = function(props) {
function mapStateToProps(state) {
return {
browser: state.browser,
isBookmarked: state.bookmarks.bookmarks.some(b => b.url === state.browser.address)
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(BrowserActions, dispatch);
return bindActionCreators({...BrowserActions, toggleBookmarkUrl}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(AddressBar);

View file

@ -0,0 +1,38 @@
// @flow
import React, { useCallback } from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as BrowserActions from '../../actions/browser';
import * as BookmarksActions from '../../actions/bookmarks';
import {BookmarksBar} from '../../components/BookmarksBar';
const BookmarksBarContainer = function(props) {
const handleBookmarkClick = useCallback(function (bookmark) {
props.onAddressChange(bookmark.url)
}, [])
const handleBookmarkDelete = useCallback(function (bookmark) {
props.toggleBookmarkUrl(bookmark.url)
}, [])
return (
<BookmarksBar bookmarks={props.bookmarks} onBookmarkClick={handleBookmarkClick} onBookmarkDelete={handleBookmarkDelete} onBookmarkEdit={props.editBookmark}/>
);
};
function mapStateToProps(state) {
return {
bookmarks: state.bookmarks.bookmarks
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
onAddressChange: BrowserActions.onAddressChange,
...BookmarksActions
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(BookmarksBarContainer);

View file

@ -29,6 +29,7 @@ import {
deleteCookies,
deleteStorage,
} from '../actions/browser';
import {toggleBookmarkUrl} from '../actions/bookmarks'
type Props = {
store: Store,
@ -78,7 +79,6 @@ export default class Root extends Component<Props> {
registerAllShortcuts = () => {
const {store} = this.props;
registerShortcut(
{id: 'ZoomIn', title: 'Zoom In', accelerators: ['mod+=', 'mod+shift+=']},
() => {
@ -203,6 +203,18 @@ export default class Root extends Component<Props> {
},
true
);
registerShortcut(
{
id: 'AddBookmark',
title: 'Add Bookmark',
accelerators: ['mod+d']
},
() => {
store.dispatch(toggleBookmarkUrl(store.getState().browser.address));
},
true
);
};
componentWillUnmount() {

View file

@ -0,0 +1,46 @@
import settings from 'electron-settings';
import {TOGGLE_BOOKMARK, EDIT_BOOKMARK} from '../actions/bookmarks'
import {BOOKMARKS} from '../constants/settingKeys'
import { getWebsiteName } from '../components/WebView/screenshotUtil';
type BookmarksType = {
title: string,
url: string
}
function fetchBookmarks(): BookmarksType {
return settings.get(BOOKMARKS) || [];
}
function persistBookmarks(bookmarks) {
settings.set(BOOKMARKS, bookmarks);
}
export default function browser(
state: BrowserStateType = {
bookmarks: fetchBookmarks(),
},
action: Action
) {
switch (action.type) {
case TOGGLE_BOOKMARK:
let bookmarks = state.bookmarks
const bookmark = {
title: getWebsiteName(action.url),
url: action.url
}
if (bookmarks.find(b => b.url === action.url)) {
bookmarks = bookmarks.filter(b => b.url !== action.url)
} else {
bookmarks = [...bookmarks, bookmark]
}
persistBookmarks(bookmarks)
return {...state, bookmarks}
case EDIT_BOOKMARK:
const updatedBookmarks = state.bookmarks.map(b => b === action.bookmark ? {...b, title: action.title, url: action.url} : b)
persistBookmarks(updatedBookmarks)
return {...state, bookmarks: updatedBookmarks}
default:
return state;
}
}

View file

@ -12,10 +12,12 @@ import {
NEW_HOMEPAGE,
NEW_USER_PREFERENCES,
DELETE_CUSTOM_DEVICE,
TOGGLE_BOOKMARK,
NEW_DEV_TOOLS_CONFIG,
NEW_INSPECTOR_STATUS,
NEW_WINDOW_SIZE,
} from '../actions/browser';
import type {Action} from './types';
import getAllDevices from '../constants/devices';
import {ipcRenderer, remote} from 'electron';
@ -30,10 +32,11 @@ import {DEVICE_MANAGER} from '../constants/DrawerContents';
import {
ACTIVE_DEVICES,
USER_PREFERENCES,
CUSTOM_DEVICES,
CUSTOM_DEVICES
} from '../constants/settingKeys';
import {isIfStatement} from 'typescript';
import {getHomepage, saveHomepage} from '../utils/navigatorUtils';
import {getWebsiteName} from '../components/WebView/screenshotUtil'
import console from 'electron-timber';
export const FILTER_FIELDS = {
@ -108,6 +111,7 @@ export type BrowserStateType = {
previewer: PreviewerType,
filters: FilterType,
userPreferences: UserPreferenceType,
bookmarks: BookmarksType,
devToolsConfig: DevToolsConfigType,
isInspecting: boolean,
windowSize: WindowSizeType,

View file

@ -2,10 +2,12 @@
import {combineReducers} from 'redux';
import {connectRouter} from 'connected-react-router';
import browser from './browser';
import bookmarks from './bookmarks'
export default function createRootReducer(history: History) {
return combineReducers({
router: connectRouter(history),
browser,
bookmarks,
});
}

View file

@ -10,3 +10,4 @@ export function saveHomepage(url) {
export function getHomepage() {
return settings.get(HOME_PAGE) || 'https://www.google.com/';
}

4
yarn.lock Normal file
View file

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1