mirror of
https://github.com/responsively-org/responsively-app
synced 2024-11-10 23:04:20 +00:00
Merge pull request #270 from kvnam/issue-112/responsive-mode
Issue 112 - Responsive mode added
This commit is contained in:
commit
70f0bee910
10 changed files with 270 additions and 32 deletions
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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: [],
|
||||
|
|
|
@ -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} = {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* eslint-disable import/first */
|
||||
require('dotenv').config();
|
||||
|
||||
import React from 'react';
|
||||
import {remote} from 'electron';
|
||||
import {render} from 'react-dom';
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue