Add Go to URL dialog menu (#131)

This commit is contained in:
Quang Lam 2020-02-11 04:27:06 -06:00 committed by GitHub
parent 06c9a4db24
commit 3e9ff738a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 247 additions and 8 deletions

View file

@ -11,6 +11,7 @@ const codeInjectionWindow = require('../windows/code-injection');
const customUserAgentWindow = require('../windows/custom-user-agent'); const customUserAgentWindow = require('../windows/custom-user-agent');
const displayMediaWindow = require('../windows/display-media'); const displayMediaWindow = require('../windows/display-media');
const editWorkspaceWindow = require('../windows/edit-workspace'); const editWorkspaceWindow = require('../windows/edit-workspace');
const goToUrlWindow = require('../windows/go-to-url');
const licenseRegistrationWindow = require('../windows/license-registration'); const licenseRegistrationWindow = require('../windows/license-registration');
const mainWindow = require('../windows/main'); const mainWindow = require('../windows/main');
const notificationsWindow = require('../windows/notifications'); const notificationsWindow = require('../windows/notifications');
@ -299,6 +300,19 @@ function createMenu() {
} }
}, },
}, },
{
label: 'Go To URL Window',
click: () => {
const win = goToUrlWindow.get();
if (win != null) {
if (win.webContents.isDevToolsOpened()) {
win.webContents.closeDevTools();
} else {
win.webContents.openDevTools({ mode: 'detach' });
}
}
},
},
{ type: 'separator' }, { type: 'separator' },
], ],
}, },
@ -339,6 +353,15 @@ function createMenu() {
}, },
enabled: hasWorkspaces, enabled: hasWorkspaces,
}, },
{ type: 'separator' },
{
label: 'Go to URL...',
accelerator: 'CmdOrCtrl+Shift+G',
click: () => {
goToUrlWindow.show();
},
enabled: hasWorkspaces,
},
], ],
}, },
{ {

View file

@ -0,0 +1,5 @@
window.mode = 'go-to-url';
const contextMenu = require('electron-context-menu');
contextMenu();

View file

@ -0,0 +1,51 @@
const { BrowserWindow } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
const mainWindow = require('./main');
let win;
const get = () => win;
const create = () => {
const attachToMenubar = getPreference('attachToMenubar');
win = new BrowserWindow({
width: 400,
height: 200,
resizable: false,
maximizable: false,
minimizable: false,
fullscreenable: false,
autoHideMenuBar: true,
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, '..', 'preload', 'go-to-url.js'),
},
parent: attachToMenubar ? null : mainWindow.get(),
});
win.loadURL(REACT_PATH);
win.on('closed', () => {
win = null;
});
};
const show = (url) => {
if (win == null) {
create(url);
} else {
win.close();
create(url);
}
};
module.exports = {
get,
create,
show,
};

View file

@ -122,9 +122,10 @@ const AddWorkspaceCustom = ({
<div> <div>
<TextField <TextField
id="outlined-full-width" id="outlined-full-width"
label={nameError || 'Name'} label="Name"
error={Boolean(nameError)} error={Boolean(nameError)}
placeholder="Example: Singlebox" placeholder="Example: Singlebox"
helperText={nameError}
fullWidth fullWidth
margin="dense" margin="dense"
variant="outlined" variant="outlined"
@ -137,9 +138,10 @@ const AddWorkspaceCustom = ({
/> />
<TextField <TextField
id="outlined-full-width" id="outlined-full-width"
label={homeUrlError || 'Home URL'} label="Home URL"
error={Boolean(homeUrlError)} error={Boolean(homeUrlError)}
placeholder="Example: https://singleboxapp.com" placeholder="Example: https://singleboxapp.com"
helperText={homeUrlError || (isMailApp && 'Email app detected.')}
fullWidth fullWidth
margin="dense" margin="dense"
variant="outlined" variant="outlined"
@ -149,7 +151,6 @@ const AddWorkspaceCustom = ({
}} }}
value={homeUrl} value={homeUrl}
onChange={(e) => onUpdateForm({ homeUrl: e.target.value })} onChange={(e) => onUpdateForm({ homeUrl: e.target.value })}
helperText={!homeUrlError && isMailApp && 'Email app detected.'}
/> />
<div className={classes.avatarFlex}> <div className={classes.avatarFlex}>
<div className={classes.avatarLeft}> <div className={classes.avatarLeft}>

View file

@ -31,6 +31,7 @@ const CodeInjection = ({
<div className={classes.root}> <div className={classes.root}>
<div className={classes.flexGrow}> <div className={classes.flexGrow}>
<TextField <TextField
autoFocus
id="outlined-full-width" id="outlined-full-width"
label="Code" label="Code"
placeholder="" placeholder=""

View file

@ -31,6 +31,7 @@ const CustomUserAgent = ({
<div className={classes.root}> <div className={classes.root}>
<div className={classes.flexGrow}> <div className={classes.flexGrow}>
<TextField <TextField
autoFocus
id="outlined-full-width" id="outlined-full-width"
label="User-Agent" label="User-Agent"
placeholder="" placeholder=""

View file

@ -124,9 +124,10 @@ const EditWorkspace = ({
<div className={classes.flexGrow}> <div className={classes.flexGrow}>
<TextField <TextField
id="outlined-full-width" id="outlined-full-width"
label={nameError || 'Name'} label="Name"
error={Boolean(nameError)} error={Boolean(nameError)}
placeholder="Optional" placeholder="Optional"
helperText={nameError}
fullWidth fullWidth
margin="dense" margin="dense"
variant="outlined" variant="outlined"
@ -139,9 +140,10 @@ const EditWorkspace = ({
/> />
<TextField <TextField
id="outlined-full-width" id="outlined-full-width"
label={homeUrlError || 'Home URL'} label="Home URL"
error={Boolean(homeUrlError)} error={Boolean(homeUrlError)}
placeholder="Optional" placeholder="Optional"
helperText={homeUrlError || (isMailApp && 'Email app detected.')}
fullWidth fullWidth
margin="dense" margin="dense"
variant="outlined" variant="outlined"
@ -151,7 +153,6 @@ const EditWorkspace = ({
}} }}
value={homeUrl} value={homeUrl}
onChange={(e) => onUpdateForm({ homeUrl: e.target.value })} onChange={(e) => onUpdateForm({ homeUrl: e.target.value })}
helperText={!homeUrlError && isMailApp && 'Email app detected.'}
/> />
<div className={classes.avatarFlex}> <div className={classes.avatarFlex}>
<div className={classes.avatarLeft}> <div className={classes.avatarLeft}>

View file

@ -0,0 +1,92 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import connectComponent from '../../helpers/connect-component';
import { updateForm, go } from '../../state/go-to-url/actions';
const styles = (theme) => ({
root: {
background: theme.palette.background.paper,
height: '100vh',
width: '100vw',
padding: theme.spacing.unit * 3,
display: 'flex',
flexDirection: 'column',
},
flexGrow: {
flex: 1,
},
button: {
float: 'right',
},
});
const GoToUrl = ({
classes, url, urlError, onUpdateForm, onGo,
}) => (
<div className={classes.root}>
<div className={classes.flexGrow}>
<TextField
autoFocus
id="outlined-full-width"
label="URL"
error={Boolean(urlError)}
helperText={urlError}
placeholder="Type a URL"
fullWidth
margin="dense"
variant="outlined"
multiline={false}
InputLabelProps={{
shrink: true,
}}
value={url}
onChange={(e) => onUpdateForm({ url: e.target.value })}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onGo();
e.target.blur();
}
}}
/>
</div>
<div>
<Button color="primary" variant="contained" className={classes.button} onClick={onGo}>
Go
</Button>
</div>
</div>
);
GoToUrl.defaultProps = {
urlError: null,
};
GoToUrl.propTypes = {
classes: PropTypes.object.isRequired,
url: PropTypes.string.isRequired,
urlError: PropTypes.string,
onUpdateForm: PropTypes.func.isRequired,
onGo: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({
url: state.goToUrl.form.url,
urlError: state.goToUrl.form.urlError,
});
const actionCreators = {
updateForm,
go,
};
export default connectComponent(
GoToUrl,
mapStateToProps,
actionCreators,
styles,
);

View file

@ -46,16 +46,17 @@ const DialogLicenseRegistration = (props) => {
perpetual license key ($9.99) from our store. perpetual license key ($9.99) from our store.
</DialogContentText> </DialogContentText>
<TextField <TextField
autoFocus
fullWidth fullWidth
id="" id=""
label={licenseKeyError || 'License Key'} label="License Key"
margin="normal" margin="normal"
onChange={(e) => onUpdateForm({ licenseKey: e.target.value })} onChange={(e) => onUpdateForm({ licenseKey: e.target.value })}
value={licenseKey} value={licenseKey}
placeholder="0-0000000000000-00000000-00000000-00000000-00000000" placeholder="0-0000000000000-00000000-00000000-00000000-00000000"
error={Boolean(licenseKeyError)} error={Boolean(licenseKeyError)}
variant="outlined" variant="outlined"
helperText="If you have already purchased Singlebox from our store, you should have received a license key via email to enter above." helperText={licenseKeyError || 'If you have already purchased Singlebox from our store, you should have received a license key via email to enter above.'}
/> />
</DialogContent> </DialogContent>
<DialogActions className={classes.dialogActions}> <DialogActions className={classes.dialogActions}>

View file

@ -37,6 +37,9 @@ export const UPDATE_CODE_INJECTION_FORM = 'UPDATE_CODE_INJECTION_FORM';
// Custom User Agent // Custom User Agent
export const UPDATE_CUSTOM_USER_AGENT_FORM = 'UPDATE_CUSTOM_USER_AGENT_FORM'; export const UPDATE_CUSTOM_USER_AGENT_FORM = 'UPDATE_CUSTOM_USER_AGENT_FORM';
// Go To URL
export const UPDATE_GO_TO_URL_FORM = 'UPDATE_GO_TO_URL_FORM';
// Auth // Auth
export const UPDATE_AUTH_FORM = 'UPDATE_AUTH_FORM'; export const UPDATE_AUTH_FORM = 'UPDATE_AUTH_FORM';

View file

@ -19,6 +19,7 @@ const CodeInjection = React.lazy(() => import('./components/code-injection'));
const CustomUserAgent = React.lazy(() => import('./components/custom-user-agent')); const CustomUserAgent = React.lazy(() => import('./components/custom-user-agent'));
const DisplayMedia = React.lazy(() => import('./components/display-media')); const DisplayMedia = React.lazy(() => import('./components/display-media'));
const EditWorkspace = React.lazy(() => import('./components/edit-workspace')); const EditWorkspace = React.lazy(() => import('./components/edit-workspace'));
const GoToUrl = React.lazy(() => import('./components/go-to-url'));
const LicenseRegistration = React.lazy(() => import('./components/license-registration')); const LicenseRegistration = React.lazy(() => import('./components/license-registration'));
const Main = React.lazy(() => import('./components/main')); const Main = React.lazy(() => import('./components/main'));
const Notifications = React.lazy(() => import('./components/notifications')); const Notifications = React.lazy(() => import('./components/notifications'));
@ -34,6 +35,7 @@ const App = () => {
case 'custom-user-agent': return <CustomUserAgent />; case 'custom-user-agent': return <CustomUserAgent />;
case 'display-media': return <DisplayMedia />; case 'display-media': return <DisplayMedia />;
case 'edit-workspace': return <EditWorkspace />; case 'edit-workspace': return <EditWorkspace />;
case 'go-to-url': return <GoToUrl />;
case 'license-registration': return <LicenseRegistration />; case 'license-registration': return <LicenseRegistration />;
case 'notifications': return <Notifications />; case 'notifications': return <Notifications />;
case 'open-url-with': return <OpenUrlWith />; case 'open-url-with': return <OpenUrlWith />;
@ -82,6 +84,8 @@ const runApp = () => {
document.title = 'Share your Screen'; document.title = 'Share your Screen';
} else if (window.mode === 'custom-user-agent') { } else if (window.mode === 'custom-user-agent') {
document.title = 'Edit Custom User Agent'; document.title = 'Edit Custom User Agent';
} else if (window.mode === 'go-to-url') {
document.title = 'Go to URL';
} else { } else {
document.title = 'Singlebox'; document.title = 'Singlebox';
} }

View file

@ -0,0 +1,38 @@
import { UPDATE_GO_TO_URL_FORM } from '../../constants/actions';
import hasErrors from '../../helpers/has-errors';
import isUrl from '../../helpers/is-url';
import validate from '../../helpers/validate';
import { requestLoadUrl } from '../../senders';
const { remote } = window.require('electron');
const getValidationRules = () => ({
url: {
fieldName: 'URL',
required: true,
lessStrictUrl: true,
},
});
export const updateForm = (changes) => (dispatch) => dispatch({
type: UPDATE_GO_TO_URL_FORM,
changes: validate(changes, getValidationRules()),
});
export const go = () => (dispatch, getState) => {
const { form } = getState().goToUrl;
const validatedChanges = validate(form, getValidationRules());
if (hasErrors(validatedChanges)) {
return dispatch(updateForm(validatedChanges));
}
const { url } = form;
const finalUrl = isUrl(url) ? url : `http://${url}`;
requestLoadUrl(finalUrl);
remote.getCurrentWindow().close();
return null;
};

View file

@ -0,0 +1,16 @@
import { combineReducers } from 'redux';
import { UPDATE_GO_TO_URL_FORM } from '../../constants/actions';
const defaultForm = {
url: '',
};
const form = (state = defaultForm, action) => {
switch (action.type) {
case UPDATE_GO_TO_URL_FORM: return { ...state, ...action.changes };
default: return state;
}
};
export default combineReducers({ form });

View file

@ -12,6 +12,7 @@ import customUserAgent from './custom-user-agent/reducers';
import editWorkspace from './edit-workspace/reducers'; import editWorkspace from './edit-workspace/reducers';
import findInPage from './find-in-page/reducers'; import findInPage from './find-in-page/reducers';
import general from './general/reducers'; import general from './general/reducers';
import goToUrl from './go-to-url/reducers';
import licenseRegistration from './license-registration/reducers'; import licenseRegistration from './license-registration/reducers';
import notifications from './notifications/reducers'; import notifications from './notifications/reducers';
import preferences from './preferences/reducers'; import preferences from './preferences/reducers';
@ -29,6 +30,7 @@ const rootReducer = combineReducers({
editWorkspace, editWorkspace,
findInPage, findInPage,
general, general,
goToUrl,
licenseRegistration, licenseRegistration,
notifications, notifications,
preferences, preferences,