mirror of
https://github.com/responsively-org/responsively-app
synced 2024-11-15 08:57:15 +00:00
commit
975518015a
17 changed files with 318 additions and 8 deletions
21
desktop-app/app/actions/bookmarks.js
Normal file
21
desktop-app/app/actions/bookmarks.js
Normal 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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
74
desktop-app/app/components/BookmarksBar/index.js
Normal file
74
desktop-app/app/components/BookmarksBar/index.js
Normal 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>
|
||||
}
|
3
desktop-app/app/components/BookmarksBar/style.css
Normal file
3
desktop-app/app/components/BookmarksBar/style.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.bookmarks {
|
||||
padding: 0 10px;
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export const ACTIVE_DEVICES = 'activeDevices';
|
||||
export const CUSTOM_DEVICES = 'customDevices';
|
||||
export const USER_PREFERENCES = 'userPreferences';
|
||||
export const BOOKMARKS = 'bookmarks';
|
||||
|
|
|
@ -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);
|
||||
|
|
38
desktop-app/app/containers/BookmarksBarContainer/index.js
Normal file
38
desktop-app/app/containers/BookmarksBarContainer/index.js
Normal 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);
|
|
@ -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() {
|
||||
|
|
46
desktop-app/app/reducers/bookmarks.js
Normal file
46
desktop-app/app/reducers/bookmarks.js
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
4
yarn.lock
Normal file
|
@ -0,0 +1,4 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
Loading…
Reference in a new issue