Merge pull request #489 from responsively-org/feature/live-css

Feature/live css editor
This commit is contained in:
Manoj Vivek 2020-10-18 21:15:42 +05:30 committed by GitHub
commit a1e4dab2f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 638 additions and 92 deletions

View file

@ -29,6 +29,9 @@ export const NEW_ZOOM_LEVEL = 'NEW_ZOOM_LEVEL';
export const NEW_SCROLL_POSITION = 'NEW_SCROLL_POSITION';
export const NEW_NAVIGATOR_STATUS = 'NEW_NAVIGATOR_STATUS';
export const NEW_INSPECTOR_STATUS = 'NEW_INSPECTOR_STATUS';
export const NEW_CSS_EDITOR_STATUS = 'NEW_CSS_EDITOR_STATUS';
export const NEW_CSS_EDITOR_POSITION = 'NEW_CSS_EDITOR_POSITION';
export const NEW_CSS_EDITOR_CONTENT = 'NEW_CSS_EDITOR_CONTENT';
export const NEW_DRAWER_CONTENT = 'NEW_DRAWER_CONTENT';
export const NEW_PREVIEWER_CONFIG = 'NEW_PREVIEWER_CONFIG';
export const NEW_ACTIVE_DEVICES = 'NEW_ACTIVE_DEVICES';
@ -91,6 +94,27 @@ export function newInspectorState(status) {
};
}
export function newCSSEditorState(status) {
return {
type: NEW_CSS_EDITOR_STATUS,
status,
};
}
export function newCSSEditorPosition(position) {
return {
type: NEW_CSS_EDITOR_POSITION,
position,
};
}
export function newCSSEditorContent(content) {
return {
type: NEW_CSS_EDITOR_CONTENT,
content,
};
}
export function newUserPreferences(userPreferences) {
return {
type: NEW_USER_PREFERENCES,
@ -743,6 +767,50 @@ export function toggleInspector() {
};
}
export function toggleCSSEditor() {
return (dispatch: Dispatch, getState: RootStateType) => {
const {
browser: {
CSSEditor: {isOpen},
},
} = getState();
dispatch(newCSSEditorState(!isOpen));
};
}
export function changeCSSEditorPosition(newPosition) {
return (dispatch: Dispatch, getState: RootStateType) => {
const {
browser: {
CSSEditor: {position},
},
} = getState();
if (position === newPosition) {
return;
}
dispatch(newCSSEditorPosition(newPosition));
};
}
export function onCSSEditorContentChange(newContent) {
return (dispatch: Dispatch, getState: RootStateType) => {
const {
browser: {
CSSEditor: {content},
},
} = getState();
if (content === newContent) {
return;
}
dispatch(newCSSEditorContent(newContent));
};
}
export function deviceLoadingChange(deviceInfo) {
return (dispatch: Dispatch, getState: RootStateType) => {
dispatch(newDeviceLoading(deviceInfo));

View file

@ -115,3 +115,7 @@ Add device form styles */
::-webkit-scrollbar-corner {
background: rgba(0, 0, 0, 0);
}
.ace_mobile-menu {
display: none;
}

View file

@ -9,16 +9,21 @@ import {
FLEXIGRID_LAYOUT,
INDIVIDUAL_LAYOUT,
DEVTOOLS_MODES,
CSS_EDITOR_MODES,
isHorizontallyStacked,
} from '../../constants/previewerLayouts';
import {isDeviceEligible} from '../../utils/filterUtils';
import {getDeviceIcon} from '../../utils/iconUtils';
import useStyes from './useStyles';
import LiveCssEditor from '../LiveCssEditor';
export default function DevicesPreviewer(props) {
const {
browser: {
devices,
devToolsConfig,
address,
CSSEditor,
zoomLevel,
previewer: {layout},
},
@ -61,56 +66,82 @@ export default function DevicesPreviewer(props) {
props.setFocusedDevice(devicesAfterFiltering[newTabIndex].id);
};
const editor = CSSEditor.isOpen && (
<LiveCssEditor
boundaryClass={classes.container}
devToolsConfig={devToolsConfig}
changeCSSEditorPosition={props.changeCSSEditorPosition}
onCSSEditorContentChange={props.onCSSEditorContentChange}
{...CSSEditor}
/>
);
return (
<div className={cx(classes.container)}>
{layout === INDIVIDUAL_LAYOUT && (
<Tabs
className={cx('react-tabs', classes.reactTabs)}
onSelect={onTabClick}
selectedIndex={focusedDeviceIndex}
<div
className={cx(classes.container)}
style={{
flexDirection: isHorizontallyStacked(CSSEditor.position)
? 'column'
: null,
}}
>
{(CSSEditor.position === CSS_EDITOR_MODES.LEFT ||
CSSEditor.position === CSS_EDITOR_MODES.UNDOCKED ||
CSSEditor.position === CSS_EDITOR_MODES.TOP) &&
editor}
<div className={cx(classes.previewer)}>
{layout === INDIVIDUAL_LAYOUT && (
<Tabs
className={cx('react-tabs', classes.reactTabs)}
onSelect={onTabClick}
selectedIndex={focusedDeviceIndex}
>
<TabList
className={cx('react-tabs__tab-list', classes.reactTabs__tabList)}
>
{devicesAfterFiltering.map(device => (
<Tab
className={cx('react-tabs__tab', classes.reactTabs__tab)}
tabId={device.id}
key={device.id}
>
{getDeviceIcon(device.type)}
{device.name}
</Tab>
))}
</TabList>
</Tabs>
)}
<div
className={cx(classes.devicesContainer, {
[classes.flexigrid]: layout === FLEXIGRID_LAYOUT,
[classes.horizontal]: layout === HORIZONTAL_LAYOUT,
})}
>
<TabList
className={cx('react-tabs__tab-list', classes.reactTabs__tabList)}
>
{devicesAfterFiltering.map(device => (
<Tab
className={cx('react-tabs__tab', classes.reactTabs__tab)}
tabId={device.id}
key={device.id}
>
{getDeviceIcon(device.type)}
{device.name}
</Tab>
))}
</TabList>
</Tabs>
)}
<div
className={cx(classes.devicesContainer, {
[classes.flexigrid]: layout === FLEXIGRID_LAYOUT,
[classes.horizontal]: layout === HORIZONTAL_LAYOUT,
})}
>
{devices.map((device, index) => (
<div
key={device.id}
className={cx({
[classes.tab]: layout === INDIVIDUAL_LAYOUT,
[classes.activeTab]:
layout === INDIVIDUAL_LAYOUT && focusedDeviceId === device.id,
})}
>
<Renderer
hidden={!isDeviceEligible(device, props.browser.filters)}
device={device}
src={address}
zoomLevel={zoomLevel}
transmitNavigatorStatus={index === 0}
onDeviceMutedChange={props.onDeviceMutedChange}
/>
</div>
))}
{devices.map((device, index) => (
<div
key={device.id}
className={cx({
[classes.tab]: layout === INDIVIDUAL_LAYOUT,
[classes.activeTab]:
layout === INDIVIDUAL_LAYOUT && focusedDeviceId === device.id,
})}
>
<Renderer
hidden={!isDeviceEligible(device, props.browser.filters)}
device={device}
src={address}
zoomLevel={zoomLevel}
transmitNavigatorStatus={index === 0}
onDeviceMutedChange={props.onDeviceMutedChange}
/>
</div>
))}
</div>
</div>
{(CSSEditor.position === CSS_EDITOR_MODES.RIGHT ||
CSSEditor.position === CSS_EDITOR_MODES.BOTTOM) &&
editor}
</div>
);
}

View file

@ -2,6 +2,10 @@ import {makeStyles} from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
container: {
display: 'flex',
height: 'inherit',
},
previewer: {
display: 'flex',
flex: '1',
height: '100%',
@ -18,6 +22,7 @@ const useStyles = makeStyles(theme => ({
devicesContainer: {
display: 'flex',
paddingBottom: '100px',
paddingLeft: 5,
},
tab: {
display: 'none',

View file

@ -13,6 +13,7 @@ import {
NETWORK_CONFIGURATION,
} from '../constants/DrawerContents';
import DoubleLeftArrowIcon from './icons/DoubleLeftArrow';
import LiveCssEditor from './LiveCssEditor';
function Drawer(props) {
const classes = useStyles();
@ -51,11 +52,12 @@ const useStyles = makeStyles(theme => ({
overflow: 'hidden',
transition: 'margin-left 0.2s',
background: theme.palette.header.main,
margin: '0 10px 0 -310px',
margin: '0 0 0 -310px',
padding: '5px 5px 0 5px',
},
drawerOpen: {
marginLeft: 0,
marginRight: 5,
flexShrink: 0,
boxShadow: `${theme.palette.mode({
light: '0px 2px 4px',

View file

@ -73,7 +73,7 @@ const useStyles = makeStyles(theme => ({
light: '0px',
dark: '3px',
})} 5px rgba(0, 0, 0, 0.35)`,
zIndex: 5,
zIndex: 500,
transform: 'translateY(0)',
transition: 'transform .1s ease-out',
'& .zenButton': {

View file

@ -4,6 +4,15 @@ import {motion} from 'framer-motion';
import {useTheme, makeStyles} from '@material-ui/core/styles';
import Kebab from './icons/Kebab';
export const KebabMenuItem = ({children, ...otherProps}) => {
const classes = useStyles();
return (
<motion.div className={classes.option} {...otherProps}>
{children}
</motion.div>
);
};
const KebabMenu = ({children}) => {
const [mouseOn, setMouseOn] = useState(false);
const [showMenu, setShowMenu] = useState(false);
@ -48,11 +57,7 @@ const KebabMenu = ({children}) => {
className={classes.menu}
onClick={() => setMouseOn(false)}
>
{children.map((child, idx) => (
<motion.div className={classes.option} key={idx}>
{child}
</motion.div>
))}
{children.filter(Boolean)}
</motion.div>
) : null}
</div>
@ -102,4 +107,6 @@ const useStyles = makeStyles(theme => ({
},
}));
KebabMenu.Item = KebabMenuItem;
export default KebabMenu;

View file

@ -0,0 +1,256 @@
import React, {useMemo, useState, useEffect, useRef} from 'react';
import {Rnd} from 'react-rnd';
import {useSelector, useDispatch} from 'react-redux';
import cx from 'classnames';
import pubsub from 'pubsub.js';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Typography from '@material-ui/core/Typography';
import Checkbox from '@material-ui/core/Checkbox';
import Input from '@material-ui/core/Input';
import SettingsIcon from '@material-ui/icons/Settings';
import AceEditor from 'react-ace';
import 'ace-builds/src-noconflict/mode-css';
import 'ace-builds/src-noconflict/theme-github';
import useCommonStyles from '../useCommonStyles';
import useStyles from './useStyles';
import TextAreaWithCopyButton from '../../utils/TextAreaWithCopyButton';
import Button from '@material-ui/core/Button';
import {APPLY_CSS} from '../../constants/pubsubEvents';
import {
CSS_EDITOR_MODES,
DEVTOOLS_MODES,
isHorizontallyStacked,
isVeriticallyStacked,
} from '../../constants/previewerLayouts';
import KebabMenu from '../KebabMenu';
import {Tooltip} from '@material-ui/core';
import DockRight from '../icons/DockRight';
import {debounce} from 'lodash';
import CSSEditor from '../icons/CSSEditor';
const getResizingDirections = position => {
switch (position) {
case CSS_EDITOR_MODES.LEFT:
return {right: true};
case CSS_EDITOR_MODES.RIGHT:
return {left: true};
case CSS_EDITOR_MODES.TOP:
return {bottom: true};
case CSS_EDITOR_MODES.BOTTOM:
return {top: true};
default:
return true;
}
};
const computeHeight = (position, devToolsConfig) => {
if (position === CSS_EDITOR_MODES.UNDOCKED) {
return null;
}
return isVeriticallyStacked(position)
? `calc(100vh - ${10 +
headerHeight +
statusBarHeight +
(devToolsConfig.open && devToolsConfig.mode === DEVTOOLS_MODES.BOTTOM
? devToolsConfig.size.height
: 0)}px)`
: 300;
};
const computeWidth = (position, devToolsConfig) => {
if (position === CSS_EDITOR_MODES.UNDOCKED) {
return null;
}
return isHorizontallyStacked(position) ? 'calc(100vw - 50px)' : 400;
};
const getDefaultSize = isUndocked => ({
width: isUndocked ? 400 : '100%',
height: isUndocked ? 300 : '100%',
});
const getDefaultPosition = isUndocked => ({
x: isUndocked ? 100 : 0,
y: isUndocked ? 100 : 0,
});
const computeIsUndocked = position => position === CSS_EDITOR_MODES.UNDOCKED;
const headerHeight = 70;
const statusBarHeight = 20;
const LiveCssEditor = ({
browser,
isOpen,
position,
content,
boundaryClass,
devToolsConfig,
changeCSSEditorPosition,
onCSSEditorContentChange,
}) => {
const rndRef = useRef();
const classes = useStyles();
const commonClasses = useCommonStyles();
const [autoApply, setAuotApply] = useState(true);
const [prevPosition, setPrevPosition] = useState(null);
const [height, setHeight] = useState(computeHeight(position, devToolsConfig));
const [width, setWidth] = useState(computeWidth(position, devToolsConfig));
useEffect(() => {
setHeight(computeHeight(position, devToolsConfig));
}, [devToolsConfig]);
const onApply = () => {
if (!content) {
return;
}
pubsub.publish(APPLY_CSS, [{css: content}]);
};
useEffect(() => {
if (!autoApply) {
return;
}
onApply();
}, [content]);
useEffect(() => {
refreshHeight();
refreshWidth();
if (prevPosition !== position && rndRef) {
rndRef.current.updateSize(getDefaultSize(computeIsUndocked(position)));
rndRef.current.updatePosition(
getDefaultPosition(computeIsUndocked(position))
);
setPrevPosition(position);
}
}, [position, devToolsConfig, rndRef]);
const refreshHeight = () =>
setHeight(computeHeight(position, devToolsConfig));
const refreshWidth = () => setWidth(computeWidth(position, devToolsConfig));
const isUndocked = useMemo(() => computeIsUndocked(position), [position]);
const enableResizing = useMemo(() => getResizingDirections(position), [
position,
]);
const disableDragging = useMemo(() => !isUndocked, [isUndocked]);
return (
<div className={classes.wrapper} style={{height, width}}>
<Rnd
ref={rndRef}
dragHandleClassName={classes.titleBar}
disableDragging={disableDragging}
enableResizing={enableResizing}
style={{zIndex: 100}}
default={{
...getDefaultPosition(isUndocked),
...getDefaultSize(isUndocked),
}}
bounds={`.${boundaryClass}`}
onResize={(e, dir, ref) => {
if (isUndocked) {
return;
}
const {width: _width, height: _height} = ref.getBoundingClientRect();
if (width !== _width) {
setWidth(_width);
}
if (height !== _height) {
setHeight(_height);
}
}}
>
<div className={classes.container}>
<div
className={cx(
classes.titleBar,
commonClasses.flexContainerSpaceBetween,
{
[classes.dragHandle]: !disableDragging,
}
)}
>
<span className={commonClasses.flexContainer}>
<CSSEditor
color="currentColor"
height={20}
width={25}
margin="0 5px 0 0"
/>
Live CSS Editor
</span>
<KebabMenu>
{position !== CSS_EDITOR_MODES.UNDOCKED && (
<KebabMenu.Item
onClick={() =>
changeCSSEditorPosition(CSS_EDITOR_MODES.UNDOCKED)
}
>
Un-dock Editor
</KebabMenu.Item>
)}
{position !== CSS_EDITOR_MODES.LEFT && (
<KebabMenu.Item
onClick={() => changeCSSEditorPosition(CSS_EDITOR_MODES.LEFT)}
>
Dock to Left
</KebabMenu.Item>
)}
</KebabMenu>
</div>
<div className={classes.mainContent}>
<AceEditor
className={classes.editor}
placeholder="Enter CSS to apply"
mode="css"
theme="twilight"
name="css"
onChange={debounce(onCSSEditorContentChange, 25, {maxWait: 50})}
fontSize={14}
showPrintMargin={true}
showGutter={true}
highlightActiveLine={true}
value={content}
width="100%"
height="100%"
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: false,
showLineNumbers: true,
tabSize: 2,
}}
/>
<FormControlLabel
control={
<Checkbox
checked={autoApply}
onChange={e => setAuotApply(e.target.checked)}
name="Auto apply changes"
color="primary"
size="small"
/>
}
label="Auto apply changes"
/>
{!autoApply && (
<Button
size="small"
color="primary"
variant="contained"
onClick={onApply}
>
Apply
</Button>
)}
</div>
</div>
</Rnd>
</div>
);
};
export default LiveCssEditor;

View file

@ -0,0 +1,41 @@
import {makeStyles} from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
wrapper: {
position: 'relative',
display: 'flex',
flexShrink: 0,
marginBottom: 10,
},
container: {
display: 'flex',
flexDirection: 'column',
height: '100%',
background: theme.palette.background.l2,
margin: 5,
padding: 10,
borderRadius: 2,
boxShadow: `0 ${theme.palette.mode({
light: '0px',
dark: '3px',
})} 5px rgba(0, 0, 0, 0.35)`,
},
titleBar: {
margin: '5px 0 10px',
alignItems: 'center',
},
dragHandle: {
cursor: 'move',
},
mainContent: {
display: 'flex',
flexDirection: 'column',
flex: 1,
},
editor: {
height: '100%',
marginBottom: 10,
},
}));
export default useStyles;

View file

@ -29,6 +29,12 @@ const useStyles = makeStyles(theme => ({
top: theme.spacing(1),
color: theme.palette.grey[500],
},
header: {
display: 'flex',
justifyContent: 'space-between',
margin: '10px',
fontWeight: '500',
},
themeBackground: {
backgroundColor: theme.palette.mode({
light: theme.palette.grey[100],
@ -125,19 +131,18 @@ export default function PermissionPopup() {
[styles.permissionPopupActive]: permissionInfos.length !== 0,
})}
>
<h4 className={styles.permissionPopupTitle}>
Permission Request
<div className={cx(classes.header)}>
<div>Permission Request</div>
<Tooltip classes={tooltipUseStyles()} title="Ignore" placement="left">
<IconButton
aria-label="close"
className={classes.closeButton}
onClick={() => handleClose(null)}
size="small"
>
<CloseIcon />
</IconButton>
</Tooltip>
</h4>
</div>
<DialogContent className={styles.permissionPopupMsgContainer}>
<DialogContentText className={styles.permissionPopupMsg}>
{getMessage(permissionInfos[0])}

View file

@ -15,10 +15,6 @@
height: auto;
}
.permissionPopupTitle {
margin: 14px;
}
.permissionPopupMsg {
margin-bottom: 0 !important;
font-size: 0.9rem !important;

View file

@ -67,14 +67,14 @@ function Renderer(props) {
</div>
</div>
<KebabMenu>
<div
<KebabMenu.Item
onClick={() =>
pubsub.publish(SCREENSHOT_ALL_DEVICES, [{deviceId: device.id}])
}
>
Full Page Screenshot
</div>
<div
</KebabMenu.Item>
<KebabMenu.Item
onClick={() =>
pubsub.publish(FLIP_ORIENTATION_ALL_DEVICES, [
{deviceId: device.id},
@ -82,10 +82,12 @@ function Renderer(props) {
}
>
Tilt Device
</div>
<div onClick={props.device.isMuted ? _unmuteDevice : _muteDevice}>
</KebabMenu.Item>
<KebabMenu.Item
onClick={props.device.isMuted ? _unmuteDevice : _muteDevice}
>
{props.device.isMuted ? 'Unmute Audio' : 'Mute Audio'}
</div>
</KebabMenu.Item>
</KebabMenu>
</div>
<div>

View file

@ -1,5 +1,5 @@
// @flow
import React, {Component} from 'react';
import React, {Component, useState} from 'react';
import cx from 'classnames';
import Grid from '@material-ui/core/Grid';
import Tooltip from '@material-ui/core/Tooltip';
@ -17,6 +17,7 @@ import ZoomContainer from '../../containers/ZoomContainer';
import PrefersColorSchemeSwitch from '../PrefersColorSchemeSwitch';
import ToggleTouch from '../ToggleTouch';
import Muted from '../icons/Muted';
import CSSEditor from '../icons/CSSEditor';
const useStyles = makeStyles({
container: {
@ -31,6 +32,7 @@ const ScrollControls = ({
screenshotAllDevices,
flipOrientationAllDevices,
toggleInspector,
toggleCSSEditor,
onAllDevicesMutedChange,
onToggleAllDeviceDesignMode,
}) => {
@ -49,17 +51,16 @@ const ScrollControls = ({
<Grid item className={commonClasses.icon}>
<PrefersColorSchemeSwitch iconProps={iconProps} />
</Grid>
<Grid item className={commonClasses.icon}>
<Tooltip title="Scroll Down">
<div onClick={triggerScrollDown}>
<ScrollDownIcon {...iconProps} />
</div>
</Tooltip>
</Grid>
<Grid item className={commonClasses.icon}>
<Tooltip title="Scroll Up">
<div onClick={triggerScrollUp}>
<ScrollUpIcon {...iconProps} height={30} width={30} />
<Grid
item
className={cx(commonClasses.icon, {
[commonClasses.iconSelected]: browser.CSSEditor.isOpen,
})}
onClick={toggleCSSEditor}
>
<Tooltip title="Live CSS Editor">
<div>
<CSSEditor {...iconProps} />
</div>
</Tooltip>
</Grid>

View file

@ -27,6 +27,7 @@ import {
SET_NETWORK_TROTTLING_PROFILE,
OPEN_CONSOLE_FOR_DEVICE,
PROXY_AUTH_ERROR,
APPLY_CSS,
TOGGLE_DEVICE_DESIGN_MODE_STATE,
} from '../../constants/pubsubEvents';
import {CAPABILITIES} from '../../constants/devices';
@ -85,6 +86,8 @@ class WebView extends Component {
zoomLevel: null,
};
this.subscriptions = [];
this.domLoaded = false;
this.liveCssKey = null;
this.dbg = null;
}
@ -97,6 +100,9 @@ class WebView extends Component {
this.subscriptions.push(
pubsub.subscribe('scroll', this.processScrollEvent)
);
this.subscriptions.push(
pubsub.subscribe(APPLY_CSS, this.processApplyCssEvent)
);
this.subscriptions.push(pubsub.subscribe('click', this.processClickEvent));
this.subscriptions.push(
pubsub.subscribe(SCROLL_DOWN, this.processScrollDownEvent)
@ -603,6 +609,17 @@ class WebView extends Component {
.catch(captureOnSentry);
};
processApplyCssEvent = async message => {
if (!message.css || !this.domLoaded) {
return;
}
if (this.liveCssKey) {
this.webviewRef.current.removeInsertedCSS(this.liveCssKey);
this.liveCssKey = null;
}
this.liveCssKey = await this.webviewRef.current.insertCSS(message.css);
};
initEventTriggers = async webview => {
await this.initBrowserSync(webview);
this.getWebContentForId(webview.getWebContentsId())
@ -616,6 +633,7 @@ class WebView extends Component {
if (this.state.isUnplugged) {
await this.closeBrowserSyncSocket(webview);
}
this.domLoaded = true;
};
hideScrollbar = () => {

View file

@ -0,0 +1,28 @@
import React from 'react';
const CSSEditor = ({width, height, color, padding, margin}) => (
<svg
width={width}
height={height}
viewBox="0 0 53 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{margin, padding}}
className="cssEditor"
>
<path
d="M12.4033 47.9355C9.94564 47.1217 8.09017 45.7952 6.83691 43.9561C5.59993 42.1169 4.97331 39.6917 4.95703 36.6807V31.627C4.95703 27.7044 3.55729 25.7432 0.757812 25.7432V22.2031C3.55729 22.2031 4.95703 20.2419 4.95703 16.3193V11.29C4.97331 8.29525 5.5918 5.87826 6.8125 4.03906C8.0332 2.18359 9.89681 0.84082 12.4033 0.0107422L13.2822 2.81836C10.6781 3.84375 9.34342 6.56999 9.27832 10.9971V16.2705C9.27832 19.9489 8.08203 22.5205 5.68945 23.9854C8.08203 25.4502 9.27832 28.0299 9.27832 31.7246V37.0713C9.3597 41.4333 10.6862 44.1188 13.2578 45.1279L12.4033 47.9355Z"
fill={color}
/>
<path
d="M39.9521 45.1279C42.5563 44.1025 43.8991 41.4089 43.9805 37.0469V31.6514C43.9805 27.8916 45.2744 25.3363 47.8623 23.9854C45.2744 22.6507 43.9805 20.0628 43.9805 16.2217V11.168C43.9479 6.64323 42.6051 3.86003 39.9521 2.81836L40.8311 0.0107422C43.2725 0.808268 45.1035 2.11035 46.3242 3.91699C47.5612 5.70736 48.2122 8.05111 48.2773 10.9482V16.71C48.3587 20.3721 49.7585 22.2031 52.4766 22.2031V25.7432C49.6771 25.7432 48.2773 27.7044 48.2773 31.627V36.6074C48.2773 42.5319 45.7952 46.3079 40.8311 47.9355L39.9521 45.1279Z"
fill={color}
/>
<path
d="M20.4492 26.4883C20.3711 28.0117 19.9414 29.1771 19.1602 29.9844C18.3854 30.7917 17.2884 31.1953 15.8691 31.1953C14.4434 31.1953 13.3105 30.6549 12.4707 29.5742C11.6309 28.487 11.2109 27.0156 11.2109 25.1602V22.582C11.2109 20.7331 11.6406 19.2715 12.5 18.1973C13.3659 17.123 14.5443 16.5859 16.0352 16.5859C17.4089 16.5859 18.4701 16.9993 19.2188 17.8262C19.974 18.6465 20.3841 19.8216 20.4492 21.3516H18.6426C18.5645 20.1927 18.3203 19.3659 17.9102 18.8711C17.5 18.3763 16.875 18.1289 16.0352 18.1289C15.0651 18.1289 14.3197 18.5098 13.7988 19.2715C13.278 20.0267 13.0176 21.1367 13.0176 22.6016V25.209C13.0176 26.6478 13.2585 27.7513 13.7402 28.5195C14.2285 29.2878 14.9382 29.6719 15.8691 29.6719C16.8001 29.6719 17.4707 29.4408 17.8809 28.9785C18.291 28.5163 18.5449 27.6862 18.6426 26.4883H20.4492ZM29.0918 27.4062C29.0918 26.6966 28.8997 26.153 28.5156 25.7754C28.1315 25.3978 27.4382 25.0299 26.4355 24.6719C25.4329 24.3138 24.6647 23.9395 24.1309 23.5488C23.6035 23.1517 23.2064 22.7025 22.9395 22.2012C22.679 21.6934 22.5488 21.1139 22.5488 20.4629C22.5488 19.3366 22.9232 18.4089 23.6719 17.6797C24.4271 16.9505 25.4134 16.5859 26.6309 16.5859C27.4642 16.5859 28.2064 16.7747 28.8574 17.1523C29.5085 17.5234 30.0098 18.041 30.3613 18.7051C30.7129 19.3691 30.8887 20.0983 30.8887 20.8926H29.0918C29.0918 20.0137 28.8802 19.3333 28.457 18.8516C28.0339 18.3698 27.4251 18.1289 26.6309 18.1289C25.9082 18.1289 25.3483 18.3307 24.9512 18.7344C24.554 19.138 24.3555 19.7044 24.3555 20.4336C24.3555 21.0326 24.5703 21.5339 25 21.9375C25.4297 22.3411 26.0938 22.7025 26.9922 23.0215C28.3919 23.4837 29.3913 24.0534 29.9902 24.7305C30.5957 25.4076 30.8984 26.293 30.8984 27.3867C30.8984 28.5391 30.5241 29.4635 29.7754 30.1602C29.0267 30.8503 28.0078 31.1953 26.7188 31.1953C25.8919 31.1953 25.127 31.0163 24.4238 30.6582C23.7272 30.2936 23.1771 29.7858 22.7734 29.1348C22.3763 28.4772 22.1777 27.7253 22.1777 26.8789H23.9746C23.9746 27.7578 24.2188 28.4414 24.707 28.9297C25.2018 29.418 25.8724 29.6621 26.7188 29.6621C27.5065 29.6621 28.099 29.4603 28.4961 29.0566C28.8932 28.653 29.0918 28.1029 29.0918 27.4062ZM39.4629 27.4062C39.4629 26.6966 39.2708 26.153 38.8867 25.7754C38.5026 25.3978 37.8092 25.0299 36.8066 24.6719C35.804 24.3138 35.0358 23.9395 34.502 23.5488C33.9746 23.1517 33.5775 22.7025 33.3105 22.2012C33.0501 21.6934 32.9199 21.1139 32.9199 20.4629C32.9199 19.3366 33.2943 18.4089 34.043 17.6797C34.7982 16.9505 35.7845 16.5859 37.002 16.5859C37.8353 16.5859 38.5775 16.7747 39.2285 17.1523C39.8796 17.5234 40.3809 18.041 40.7324 18.7051C41.084 19.3691 41.2598 20.0983 41.2598 20.8926H39.4629C39.4629 20.0137 39.2513 19.3333 38.8281 18.8516C38.4049 18.3698 37.7962 18.1289 37.002 18.1289C36.2793 18.1289 35.7194 18.3307 35.3223 18.7344C34.9251 19.138 34.7266 19.7044 34.7266 20.4336C34.7266 21.0326 34.9414 21.5339 35.3711 21.9375C35.8008 22.3411 36.4648 22.7025 37.3633 23.0215C38.763 23.4837 39.7624 24.0534 40.3613 24.7305C40.9668 25.4076 41.2695 26.293 41.2695 27.3867C41.2695 28.5391 40.8952 29.4635 40.1465 30.1602C39.3978 30.8503 38.3789 31.1953 37.0898 31.1953C36.263 31.1953 35.498 31.0163 34.7949 30.6582C34.0983 30.2936 33.5482 29.7858 33.1445 29.1348C32.7474 28.4772 32.5488 27.7253 32.5488 26.8789H34.3457C34.3457 27.7578 34.5898 28.4414 35.0781 28.9297C35.5729 29.418 36.2435 29.6621 37.0898 29.6621C37.8776 29.6621 38.4701 29.4603 38.8672 29.0566C39.2643 28.653 39.4629 28.1029 39.4629 27.4062Z"
fill={color}
/>
</svg>
);
export default CSSEditor;

View file

@ -10,7 +10,10 @@ function useCreateTheme() {
]);
}
const themeProps = {MuiButtonBase: {disableRipple: true}};
const lightTheme = {
props: themeProps,
palette: {
type: 'light',
primary: {
@ -53,6 +56,7 @@ const lightTheme = {
};
const darkTheme = {
props: themeProps,
palette: {
type: 'dark',
primary: {

View file

@ -7,3 +7,17 @@ export const DEVTOOLS_MODES = {
RIGHT: 'RIGHT',
UNDOCKED: 'UNDOCKED',
};
export const CSS_EDITOR_MODES = {
BOTTOM: 'BOTTOM',
LEFT: 'LEFT',
RIGHT: 'RIGHT',
TOP: 'TOP',
UNDOCKED: 'UNDOCKED',
};
export const isVeriticallyStacked = mode =>
mode === CSS_EDITOR_MODES.LEFT || mode === CSS_EDITOR_MODES.RIGHT;
export const isHorizontallyStacked = mode =>
mode === CSS_EDITOR_MODES.TOP || mode === CSS_EDITOR_MODES.BOTTOM;

View file

@ -10,6 +10,7 @@ export const SCREENSHOT_ALL_DEVICES = 'SCREENSHOT_ALL_DEVICES';
export const FLIP_ORIENTATION_ALL_DEVICES = 'FLIP_ORIENTATION_ALL_DEVICES';
export const TOGGLE_DEVICE_MUTED_STATE = 'TOGGLE_DEVICE_MUTED_STATE';
export const STOP_LOADING = 'STOP_LOADING';
export const APPLY_CSS = 'APPLY_CSS';
export const SET_NETWORK_TROTTLING_PROFILE = 'SET_NETWORK_TROTTLING_PROFILE';
export const CLEAR_NETWORK_CACHE = 'CLEAR_NETWORK_CACHE';

View file

@ -23,6 +23,9 @@ import {
TOGGLE_ALL_DEVICES_MUTED,
TOGGLE_DEVICE_MUTED,
NEW_THEME,
NEW_CSS_EDITOR_STATUS,
NEW_CSS_EDITOR_POSITION,
NEW_CSS_EDITOR_CONTENT,
TOGGLE_ALL_DEVICES_DESIGN_MODE,
TOGGLE_DEVICE_DESIGN_MODE,
SET_HEADER_VISIBILITY,
@ -41,6 +44,7 @@ import {
FLEXIGRID_LAYOUT,
INDIVIDUAL_LAYOUT,
DEVTOOLS_MODES,
CSS_EDITOR_MODES,
} from '../constants/previewerLayouts';
import {DEVICE_MANAGER} from '../constants/DrawerContents';
import {
@ -165,6 +169,12 @@ type NetworkConfigurationType = {
proxy: NetworkProxyProfileType,
};
type CSSEditorStateType = {
isOpen: boolean,
position: String,
content: String,
};
export type BrowserStateType = {
devices: Array<Device>,
homepage: string,
@ -179,6 +189,7 @@ export type BrowserStateType = {
userPreferences: UserPreferenceType,
bookmarks: BookmarksType,
devToolsConfig: DevToolsConfigType,
cssEditor: CSSEditorStateType,
isInspecting: boolean,
windowSize: WindowSizeType,
allDevicesMuted: boolean,
@ -337,6 +348,7 @@ export default function browser(
),
},
isInspecting: false,
CSSEditor: {isOpen: true, position: CSS_EDITOR_MODES.LEFT, content: ''},
windowSize: getWindowSize(),
allDevicesMuted: false,
networkConfiguration: _getNetworkConfiguration(),
@ -441,6 +453,18 @@ export default function browser(
newState.userPreferences = newUserPreferences;
}
return newState;
case NEW_CSS_EDITOR_STATUS:
return {...state, CSSEditor: {...state.CSSEditor, isOpen: action.status}};
case NEW_CSS_EDITOR_POSITION:
return {
...state,
CSSEditor: {...state.CSSEditor, position: action.position},
};
case NEW_CSS_EDITOR_CONTENT:
return {
...state,
CSSEditor: {...state.CSSEditor, content: action.content},
};
case NEW_INSPECTOR_STATUS:
return {...state, isInspecting: action.status};
case NEW_WINDOW_SIZE:

View file

@ -1,6 +1,6 @@
declare class WebviewElement extends HTMLElement {
insertCSS: string => Promise<string>,
executeJavaScript: string => Promise<any>,
getWebContentsId: () => number,
removeInsertedCSS: number => Promise<void>,
insertCSS: string => Promise<string>;
executeJavaScript: string => Promise<any>;
getWebContentsId: () => number;
removeInsertedCSS: number => Promise<void>;
}

View file

@ -283,8 +283,7 @@
"webpack-bundle-analyzer": "^3.4.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.8.0",
"webpack-merge": "^4.1.4",
"yarn": "^1.12.3"
"webpack-merge": "^4.1.4"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.5.0",
@ -292,6 +291,7 @@
"@material-ui/icons": "^4.2.1",
"@material-ui/lab": "^4.0.0-alpha.26",
"@sentry/electron": "^1.5.2",
"ace-builds": "^1.4.12",
"bluebird": "^3.7.2",
"browser-sync": "^2.26.7",
"classnames": "^2.2.6",
@ -314,12 +314,14 @@
"pubsub.js": "^1.5.2",
"re-resizable": "^6.4.0",
"react": "^16.13.1",
"react-ace": "^9.1.3",
"react-beautiful-dnd": "^11.0.5",
"react-dom": "^16.13.1",
"react-hot-loader": "^4.8",
"react-number-format": "^4.4.1",
"react-redux": "^7.1.0",
"react-resizable": "^1.10.1",
"react-rnd": "^10.2.2",
"react-select": "^3.1.0",
"react-switch": "^5.0.1",
"react-tabs": "^3.0.0",

View file

@ -2372,6 +2372,11 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
mime-types "~2.1.24"
negotiator "0.6.2"
ace-builds@^1.4.12, ace-builds@^1.4.6:
version "1.4.12"
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.12.tgz#888efa386e36f4345f40b5233fcc4fe4c588fae7"
integrity sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg==
acorn-globals@^4.1.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7"
@ -5960,6 +5965,11 @@ device-specs@^1.0.0:
resolved "https://registry.yarnpkg.com/device-specs/-/device-specs-1.0.0.tgz#47b54577b9b159118bbb0a175177d0aa9c50a9c9"
integrity sha512-fYXbFSeilT7bnKWFi4OERSPHdtaEoDGn4aUhV5Nly6/I+Tp6JZ/6Icmd7LVIF5euyodGpxz2e/bfUmDnIdSIDw==
diff-match-patch@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
diff@^3.2.0, diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@ -13042,6 +13052,13 @@ rc@^1.2.7, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
re-resizable@6.3.2:
version "6.3.2"
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.3.2.tgz#27cc984af6ea5dbafd2b79f64c5224a6e1722fbe"
integrity sha512-ngxe4XBSb46vfwXjAwpURacVDig/pPt1kHRhcKlRRIoGICmo4aQHr725jurezepp1pm5jSC6iQhyLYfx3zOC3w==
dependencies:
fast-memoize "^2.5.1"
re-resizable@^6.4.0:
version "6.5.4"
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.5.4.tgz#909a1e37f9d1a3afd356893a5779a030167be641"
@ -13049,6 +13066,17 @@ re-resizable@^6.4.0:
dependencies:
fast-memoize "^2.5.1"
react-ace@^9.1.3:
version "9.1.3"
resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-9.1.3.tgz#848dc3741d5460f3ac73468b6c39879aab9238bc"
integrity sha512-1TZBs/9hFGgPuzu6DUiBogyhRA5Z1Po2wzPfZslbrTFGQtbNe+JXHuPoJNlUu/uerElzOLLsuJEDTO9FfLnZJA==
dependencies:
ace-builds "^1.4.6"
diff-match-patch "^1.0.4"
lodash.get "^4.4.2"
lodash.isequal "^4.5.0"
prop-types "^15.7.2"
react-beautiful-dnd@^11.0.5:
version "11.0.5"
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-11.0.5.tgz#16b1dbd4d6493de0cb3f842cad57c7e9e1ff5fe7"
@ -13073,7 +13101,7 @@ react-dom@^16.13.1:
prop-types "^15.6.2"
scheduler "^0.19.1"
react-draggable@^4.0.3:
react-draggable@4.4.3, react-draggable@^4.0.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.3.tgz#0727f2cae5813e36b0e4962bf11b2f9ef2b406f3"
integrity sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==
@ -13138,6 +13166,15 @@ react-resizable@^1.10.1:
prop-types "15.x"
react-draggable "^4.0.3"
react-rnd@^10.2.2:
version "10.2.2"
resolved "https://registry.yarnpkg.com/react-rnd/-/react-rnd-10.2.2.tgz#378017178179c9e6cdca57280dee22cf48925b5a"
integrity sha512-UoQnNehseKEspimfPFaO0gN0vSVJ8uCZeG39nCibUVyGTjZ5d+bnY/zHEp9SObHQ9tKW4vFSa9+8Mf7QZrQrUw==
dependencies:
re-resizable "6.3.2"
react-draggable "4.4.3"
tslib "2.0.0"
react-select@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"
@ -15732,6 +15769,11 @@ tsconfig-paths@^3.9.0:
minimist "^1.2.0"
strip-bom "^3.0.0"
tslib@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3"
integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.13.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
@ -16846,11 +16888,6 @@ yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1:
y18n "^4.0.0"
yargs-parser "^18.1.2"
yarn@^1.12.3:
version "1.22.4"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.4.tgz#01c1197ca5b27f21edc8bc472cd4c8ce0e5a470e"
integrity sha512-oYM7hi/lIWm9bCoDMEWgffW8aiNZXCWeZ1/tGy0DWrN6vmzjCXIKu2Y21o8DYVBUtiktwKcNoxyGl/2iKLUNGA==
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"