Merge pull request #270 from kvnam/issue-112/responsive-mode

Issue 112 - Responsive mode added
This commit is contained in:
Manoj Vivek 2020-07-04 16:04:16 +05:30 committed by GitHub
commit 70f0bee910
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 270 additions and 32 deletions

View file

@ -37,6 +37,7 @@
"jsx-a11y/anchor-is-valid": "off", "jsx-a11y/anchor-is-valid": "off",
"no-console": "off", "no-console": "off",
"no-use-before-define": "off", "no-use-before-define": "off",
"no-underscore-dangle": "off",
"no-multi-assign": "off", "no-multi-assign": "off",
"promise/param-names": "error", "promise/param-names": "error",
"promise/always-return": "error", "promise/always-return": "error",

View file

@ -87,6 +87,7 @@ export default function AddDevice(props) {
const [capabilities, setCapabilities] = useState({ const [capabilities, setCapabilities] = useState({
[CAPABILITIES.mobile]: false, [CAPABILITIES.mobile]: false,
[CAPABILITIES.touch]: true, [CAPABILITIES.touch]: true,
[CAPABILITIES.responsive]: false,
}); });
const [deviceType, setDeviceType] = useState(DEVICE_TYPE.phone); const [deviceType, setDeviceType] = useState(DEVICE_TYPE.phone);
const [os, setOS] = useState(OS.android); const [os, setOS] = useState(OS.android);
@ -368,6 +369,23 @@ export default function AddDevice(props) {
label="Touchscreen" label="Touchscreen"
/> />
</Grid> </Grid>
<Grid item>
<FormControlLabel
control={
<CustomCheckbox
checked={capabilities[CAPABILITIES.responsive]}
onChange={e =>
setCapabilities({
...capabilities,
[CAPABILITIES.responsive]: e.target.checked,
})
}
value="Responsive"
/>
}
label="Responsive"
/>
</Grid>
</Grid> </Grid>
</FormGroup> </FormGroup>
<TextField <TextField

View file

@ -3,15 +3,20 @@ import React, {useState, useCallback} from 'react';
import WebViewContainer from '../../containers/WebViewContainer'; import WebViewContainer from '../../containers/WebViewContainer';
import cx from 'classnames'; import cx from 'classnames';
import Spinner from '../Spinner'; import Spinner from '../Spinner';
import TickAnimation from '../icons/TickAnimation'; import {CAPABILITIES} from '../../constants/devices';
import styles from './style.module.css'; import styles from './style.module.css';
import {getDeviceIcon} from '../../utils/iconUtils'; import {getDeviceIcon} from '../../utils/iconUtils';
function Renderer(props) { function Renderer(props) {
const { device, hidden, transmitNavigatorStatus } = props;
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isFlip, setFlip] = useState(false); const [isFlip, setFlip] = useState(false);
let dimension = [props.device.width, 'x', props.device.height]; const [finalDimensions, setFinalDimensions] = useState({
width: device.width,
height: device.height
});
const dimension = [finalDimensions.width, 'x', finalDimensions.height];
const sendFlipStatus = useCallback( const sendFlipStatus = useCallback(
status => { status => {
@ -21,10 +26,10 @@ function Renderer(props) {
); );
return ( return (
<div className={cx(styles.container, {[styles.hidden]: props.hidden})}> <div className={cx(styles.container, {[styles.hidden]: hidden})}>
<div className={styles.titleContainer}> <div className={styles.titleContainer}>
{getDeviceIcon(props.device.type)} {getDeviceIcon(device.type)}
<span className={cx(styles.deviceTitle)}>{props.device.name}</span> <span className={cx(styles.deviceTitle)}>{device.name}</span>
<div className={cx(styles.deviceSize)}> <div className={cx(styles.deviceSize)}>
{isFlip ? dimension.reverse().join('') : dimension.join('')} {isFlip ? dimension.reverse().join('') : dimension.join('')}
</div> </div>
@ -38,10 +43,11 @@ function Renderer(props) {
</div> </div>
<div className={cx(styles.deviceWrapper)}> <div className={cx(styles.deviceWrapper)}>
<WebViewContainer <WebViewContainer
device={props.device} device={device}
sendFlipStatus={sendFlipStatus} sendFlipStatus={sendFlipStatus}
transmitNavigatorStatus={props.transmitNavigatorStatus} transmitNavigatorStatus={transmitNavigatorStatus}
onLoadingStateChange={setLoading} onLoadingStateChange={setLoading}
updateResponsiveDimensions={setFinalDimensions}
/> />
</div> </div>
</div> </div>

View file

@ -1,11 +1,14 @@
// @flow // @flow
import React, {Component, createRef} from 'react'; import React, {Component, createRef} from 'react';
import {ipcRenderer, remote} from 'electron'; import { remote } from 'electron';
import cx from 'classnames';
import { Resizable } from 're-resizable';
import {Tooltip} from '@material-ui/core';
import debounce from 'lodash.debounce';
import pubsub from 'pubsub.js'; import pubsub from 'pubsub.js';
import BugIcon from '../icons/Bug'; import BugIcon from '../icons/Bug';
import ScreenshotIcon from '../icons/Screenshot'; import ScreenshotIcon from '../icons/Screenshot';
import DeviceRotateIcon from '../icons/DeviceRotate'; import DeviceRotateIcon from '../icons/DeviceRotate';
import cx from 'classnames';
import {iconsColor} from '../../constants/colors'; import {iconsColor} from '../../constants/colors';
import { import {
SCROLL_DOWN, SCROLL_DOWN,
@ -28,7 +31,6 @@ import styles from './style.module.css';
import commonStyles from '../common.styles.css'; import commonStyles from '../common.styles.css';
import UnplugIcon from '../icons/Unplug'; import UnplugIcon from '../icons/Unplug';
import {captureFullPage} from './screenshotUtil'; import {captureFullPage} from './screenshotUtil';
import {Tooltip} from '@material-ui/core';
import { import {
DEVTOOLS_MODES, DEVTOOLS_MODES,
INDIVIDUAL_LAYOUT, INDIVIDUAL_LAYOUT,
@ -63,6 +65,11 @@ class WebView extends Component {
isUnplugged: false, isUnplugged: false,
errorCode: null, errorCode: null,
errorDesc: null, errorDesc: null,
deviceDimensions : {
width: this.props.device.width,
height: this.props.device.height
},
temporaryDims: null,
address: this.props.browser.address, address: this.props.browser.address,
}; };
this.subscriptions = []; this.subscriptions = [];
@ -401,7 +408,8 @@ class WebView extends Component {
return; return;
case MESSAGE_TYPES.toggleEventMirroring: case MESSAGE_TYPES.toggleEventMirroring:
this._unPlug(); this._unPlug();
return; break;
default: break;
} }
}; };
@ -517,17 +525,92 @@ class WebView extends Component {
return this.props.device.capabilities.indexOf(CAPABILITIES.mobile) > -1; return this.props.device.capabilities.indexOf(CAPABILITIES.mobile) > -1;
} }
_setResizeDimensions = (event, direction, ref, delta) => {
const { temporaryDims } = this.state;
const { updateResponsiveDimensions } = this.props;
if(!temporaryDims) return;
const updatedDeviceDims = {
width: temporaryDims.width + delta.width,
height: temporaryDims.height + delta.height
};
this.setState({
deviceDimensions: updatedDeviceDims
}, () => {
updateResponsiveDimensions(this.state.deviceDimensions);
});
}
_getWebViewTag = (deviceStyles) => {
const {device : { id, useragent, capabilities }, browser : { address } } = this.props;
const { deviceDimensions } = this.state;
if(capabilities.includes(CAPABILITIES.responsive)){
const responsiveStyle = {
width: deviceDimensions.width,
height: deviceDimensions.height
}
return (
<Resizable
className={cx(styles.resizableView)}
size={{width: responsiveStyle.width, height: responsiveStyle.height}}
onResizeStart={() => {
const updatedTempDims = {
width: deviceDimensions.width,
height: deviceDimensions.height
}
this.setState({
temporaryDims: updatedTempDims
});
}}
onResize={debounce(this._setResizeDimensions, 25, { maxWait: 50 })}
onResizeStop={() =>{
this.setState({
temporaryDims: null
});
}}
handleComponent={
{
right: <div className={cx(styles.iconWrapper, styles.iconWrapperE)} {...this.props}><div className={styles.iconHolder} /></div>,
bottom : <div className={cx(styles.iconWrapper, styles.iconWrapperS)} {...this.props}><div className={styles.iconHolder} /></div>,
bottomRight : <div className={cx(styles.iconWrapper, styles.iconWrapperSE)} {...this.props}><div className={styles.iconHolder} /></div>,
}
}
>
<webview
ref={this.webviewRef}
preload="./preload.js"
className={cx(styles.device)}
src={address || 'about:blank'}
useragent={useragent}
style={responsiveStyle}
/>
</Resizable>
)
}
return (
<webview
ref={this.webviewRef}
preload="./preload.js"
className={cx(styles.device)}
src={address || 'about:blank'}
useragent={useragent}
style={deviceStyles}
/>
);
}
render() { render() {
const {device, browser} = this.props; const { browser : { zoomLevel, previewer } } = this.props;
const { isTilted, deviceDimensions, errorCode, errorDesc, screenshotInProgress } = this.state;
const deviceStyles = { const deviceStyles = {
width: width:
this.isMobile && this.state.isTilted ? device.height : device.width, this.isMobile && isTilted ? deviceDimensions.height : deviceDimensions.width,
height: height:
this.isMobile && this.state.isTilted ? device.width : device.height, this.isMobile && isTilted ? deviceDimensions.width : deviceDimensions.height,
transform: `scale(${browser.zoomLevel})`,
}; };
let shouldMaximize = browser.previewer.layout !== INDIVIDUAL_LAYOUT; const shouldMaximize = previewer.layout !== INDIVIDUAL_LAYOUT;
const IconFocus = () => { const IconFocus = () => {
if (shouldMaximize) if (shouldMaximize)
return <Focus height={30} padding={6} color={iconsColor} />; return <Focus height={30} padding={6} color={iconsColor} />;
@ -536,7 +619,10 @@ class WebView extends Component {
return ( return (
<div <div
className={cx(styles.webViewContainer)} className={cx(styles.webViewContainer)}
style={{height: deviceStyles.height * browser.zoomLevel + 40}} //Hack, ref below TODO style={{
width: deviceStyles.width * zoomLevel,
height: deviceStyles.height * zoomLevel + 40
}}
> >
<div className={cx(styles.webViewToolbar)}> <div className={cx(styles.webViewToolbar)}>
<div className={cx(styles.webViewToolbarLeft)}> <div className={cx(styles.webViewToolbarLeft)}>
@ -621,33 +707,26 @@ class WebView extends Component {
[styles.devToolsActive]: this._isDevToolsOpen(), [styles.devToolsActive]: this._isDevToolsOpen(),
})} })}
style={{ style={{
width: deviceStyles.width * browser.zoomLevel + 6, width: deviceStyles.width,
height: deviceStyles.height * browser.zoomLevel + 6, //TODO why is this height not getting set? transform: `scale(${zoomLevel})`,
}} }}
> >
<div <div
className={cx(styles.deviceOverlay, { className={cx(styles.deviceOverlay, {
[styles.overlayEnabled]: this.state.screenshotInProgress, [styles.overlayEnabled]: screenshotInProgress,
})} })}
style={deviceStyles} style={deviceStyles}
/> />
<div <div
className={cx(styles.deviceOverlay, { className={cx(styles.deviceOverlay, {
[styles.overlayEnabled]: this.state.errorCode, [styles.overlayEnabled]: errorCode,
})} })}
style={deviceStyles} style={deviceStyles}
> >
<p>ERROR: {this.state.errorCode}</p> <p>ERROR: {errorCode}</p>
<p className={cx(styles.errorDesc)}>{this.state.errorDesc}</p> <p className={cx(styles.errorDesc)}>{errorDesc}</p>
</div> </div>
<webview {this._getWebViewTag(deviceStyles)}
ref={this.webviewRef}
preload="./preload.js"
className={cx(styles.device)}
src={this.state.address || 'about:blank'}
useragent={device.useragent}
style={deviceStyles}
/>
</div> </div>
</div> </div>
); );

View file

@ -6,6 +6,8 @@
.webViewToolbar { .webViewToolbar {
padding: 5px; padding: 5px;
display: flex; display: flex;
align-items: center;
width: fit-content;
justify-content: space-between; justify-content: space-between;
} }
@ -29,6 +31,8 @@
.deviceContainer { .deviceContainer {
position: relative; position: relative;
display:inline-flex;
transform-origin: top left;
border: 3px solid #687cee00; border: 3px solid #687cee00;
} }
@ -59,3 +63,95 @@
.errorDesc { .errorDesc {
font-size: 20px; font-size: 20px;
} }
.iconWrapper{
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: absolute;
background-position: center;
height: '100%';
width: '100%';
padding: 0;
background-color: #3b3b3b;
}
.iconWrapper:hover{
background-color: #575757;
}
.iconWrapperS{
width: 100%;
height: 15px;
bottom: -10px;
left: 0;
}
.iconWrapperE{
width: 15px;
height: 100%;
transform: rotate(180deg);
top: 0;
right: -10px;
}
.iconWrapperSE {
right: -5px;
bottom: -5px;
height: 15px;
width: 15px;
}
.iconHolder{
position: relative;
display: block;
height: 7px;
cursor: pointer;
}
.iconHolder::before, .iconHolder::after {
content: '';
position: absolute;
width: 15px;
height: 2px;
background-color: #000;
}
.iconWrapperE .iconHolder{
right: 1px;
transform: rotate(90deg);
}
.iconWrapperS .iconHolder{
bottom : 0;
}
.iconWrapperSE .iconHolder{
right: 3px;
top: 2px;
height: 6px;
transform: rotate(-45deg);
}
.iconHolder::before{
left: 0;
}
.iconHolder::after{
bottom: 0;
}
.iconWrapperSE .iconHolder::before{
left: -2px;
width: 11px;
height: 2px;
}
.iconWrapperSE .iconHolder::after{
bottom: 0;
width: 7px;
height: 2px;
}
.resizableView{
margin: 0 1rem;
}

View file

@ -1301,6 +1301,35 @@ export default {
], ],
}, },
}, },
{
id: '34',
type: 'emulated-device',
device: {
'show-by-default': true,
title: 'Responsive Mode',
screen: {
horizontal: {
width: 500,
height: 790,
},
'device-pixel-ratio': 2,
vertical: {
width: 790,
height: 500,
},
},
capabilities: ['responsive'],
'user-agent': '',
type: 'notebook',
modes: [
{
title: 'default',
orientation: 'horizontal',
insets: {left: 0, top: 0, right: 0, bottom: 0},
},
],
},
},
], ],
dependencies: ['emulation'], dependencies: ['emulation'],
scripts: [], scripts: [],

View file

@ -19,6 +19,7 @@ export const DEVICE_TYPE: {[key: string]: DeviceType} = {
export const CAPABILITIES: {[key: string]: Capability} = { export const CAPABILITIES: {[key: string]: Capability} = {
mobile: 'mobile', mobile: 'mobile',
touch: 'touch', touch: 'touch',
responsive: 'responsive',
}; };
export const SOURCE: {[key: string]: Source} = { export const SOURCE: {[key: string]: Source} = {

View file

@ -1,4 +1,6 @@
/* eslint-disable import/first */
require('dotenv').config(); require('dotenv').config();
import React from 'react'; import React from 'react';
import {remote} from 'electron'; import {remote} from 'electron';
import {render} from 'react-dom'; import {render} from 'react-dom';

View file

@ -280,6 +280,7 @@
"flwww": "^2.0.10", "flwww": "^2.0.10",
"history": "^4.7.2", "history": "^4.7.2",
"jimp": "^0.12.1", "jimp": "^0.12.1",
"lodash.debounce": "^4.0.8",
"merge-img": "^2.1.3", "merge-img": "^2.1.3",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"promise-worker": "^2.0.1", "promise-worker": "^2.0.1",

View file

@ -9991,6 +9991,11 @@ locate-path@^5.0.0:
dependencies: dependencies:
p-locate "^4.1.0" p-locate "^4.1.0"
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.escape@^4.0.1: lodash.escape@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"