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",
"no-console": "off",
"no-use-before-define": "off",
"no-underscore-dangle": "off",
"no-multi-assign": "off",
"promise/param-names": "error",
"promise/always-return": "error",

View file

@ -87,6 +87,7 @@ export default function AddDevice(props) {
const [capabilities, setCapabilities] = useState({
[CAPABILITIES.mobile]: false,
[CAPABILITIES.touch]: true,
[CAPABILITIES.responsive]: false,
});
const [deviceType, setDeviceType] = useState(DEVICE_TYPE.phone);
const [os, setOS] = useState(OS.android);
@ -368,6 +369,23 @@ export default function AddDevice(props) {
label="Touchscreen"
/>
</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>
</FormGroup>
<TextField

View file

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

View file

@ -1,11 +1,14 @@
// @flow
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 BugIcon from '../icons/Bug';
import ScreenshotIcon from '../icons/Screenshot';
import DeviceRotateIcon from '../icons/DeviceRotate';
import cx from 'classnames';
import {iconsColor} from '../../constants/colors';
import {
SCROLL_DOWN,
@ -28,7 +31,6 @@ import styles from './style.module.css';
import commonStyles from '../common.styles.css';
import UnplugIcon from '../icons/Unplug';
import {captureFullPage} from './screenshotUtil';
import {Tooltip} from '@material-ui/core';
import {
DEVTOOLS_MODES,
INDIVIDUAL_LAYOUT,
@ -63,6 +65,11 @@ class WebView extends Component {
isUnplugged: false,
errorCode: null,
errorDesc: null,
deviceDimensions : {
width: this.props.device.width,
height: this.props.device.height
},
temporaryDims: null,
address: this.props.browser.address,
};
this.subscriptions = [];
@ -401,7 +408,8 @@ class WebView extends Component {
return;
case MESSAGE_TYPES.toggleEventMirroring:
this._unPlug();
return;
break;
default: break;
}
};
@ -517,17 +525,92 @@ class WebView extends Component {
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() {
const {device, browser} = this.props;
const { browser : { zoomLevel, previewer } } = this.props;
const { isTilted, deviceDimensions, errorCode, errorDesc, screenshotInProgress } = this.state;
const deviceStyles = {
width:
this.isMobile && this.state.isTilted ? device.height : device.width,
this.isMobile && isTilted ? deviceDimensions.height : deviceDimensions.width,
height:
this.isMobile && this.state.isTilted ? device.width : device.height,
transform: `scale(${browser.zoomLevel})`,
this.isMobile && isTilted ? deviceDimensions.width : deviceDimensions.height,
};
let shouldMaximize = browser.previewer.layout !== INDIVIDUAL_LAYOUT;
const shouldMaximize = previewer.layout !== INDIVIDUAL_LAYOUT;
const IconFocus = () => {
if (shouldMaximize)
return <Focus height={30} padding={6} color={iconsColor} />;
@ -536,7 +619,10 @@ class WebView extends Component {
return (
<div
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.webViewToolbarLeft)}>
@ -621,33 +707,26 @@ class WebView extends Component {
[styles.devToolsActive]: this._isDevToolsOpen(),
})}
style={{
width: deviceStyles.width * browser.zoomLevel + 6,
height: deviceStyles.height * browser.zoomLevel + 6, //TODO why is this height not getting set?
}}
width: deviceStyles.width,
transform: `scale(${zoomLevel})`,
}}
>
<div
className={cx(styles.deviceOverlay, {
[styles.overlayEnabled]: this.state.screenshotInProgress,
[styles.overlayEnabled]: screenshotInProgress,
})}
style={deviceStyles}
/>
<div
className={cx(styles.deviceOverlay, {
[styles.overlayEnabled]: this.state.errorCode,
[styles.overlayEnabled]: errorCode,
})}
style={deviceStyles}
>
<p>ERROR: {this.state.errorCode}</p>
<p className={cx(styles.errorDesc)}>{this.state.errorDesc}</p>
<p>ERROR: {errorCode}</p>
<p className={cx(styles.errorDesc)}>{errorDesc}</p>
</div>
<webview
ref={this.webviewRef}
preload="./preload.js"
className={cx(styles.device)}
src={this.state.address || 'about:blank'}
useragent={device.useragent}
style={deviceStyles}
/>
{this._getWebViewTag(deviceStyles)}
</div>
</div>
);

View file

@ -6,6 +6,8 @@
.webViewToolbar {
padding: 5px;
display: flex;
align-items: center;
width: fit-content;
justify-content: space-between;
}
@ -29,6 +31,8 @@
.deviceContainer {
position: relative;
display:inline-flex;
transform-origin: top left;
border: 3px solid #687cee00;
}
@ -59,3 +63,95 @@
.errorDesc {
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'],
scripts: [],

View file

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

View file

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

View file

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

View file

@ -9991,6 +9991,11 @@ locate-path@^5.0.0:
dependencies:
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:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"