mirror of
https://github.com/responsively-org/responsively-app
synced 2024-11-10 23:04:20 +00:00
Merge conflicts resolved
This commit is contained in:
commit
cc491aa21a
58 changed files with 5777 additions and 3841 deletions
|
@ -208,6 +208,42 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kirubakarthikeyan",
|
||||
"name": "Kiruba Karan",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/38885946?v=4",
|
||||
"profile": "https://github.com/kirubakarthikeyan",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sebasrodriguez",
|
||||
"name": "Sebastián Rodríguez",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1605931?v=4",
|
||||
"profile": "https://github.com/sebasrodriguez",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "karthick3018",
|
||||
"name": "Karthick Raja",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/47154512?v=4",
|
||||
"profile": "https://github.com/karthick3018",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jzabala",
|
||||
"name": "Johnny Zabala",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1315054?v=4",
|
||||
"profile": "https://github.com/jzabala",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 5,
|
||||
|
|
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 6 * * 2'
|
||||
|
||||
jobs:
|
||||
analyse:
|
||||
name: Analyse
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
16
.github/workflows/lint.yml
vendored
Normal file
16
.github/workflows/lint.yml
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: Lint
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hallee/eslint-action@1.0.3
|
||||
# GITHUB_TOKEN in forked repositories is read-only
|
||||
# https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request
|
||||
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
with:
|
||||
repo-token: ${{secrets.GITHUB_TOKEN}}
|
||||
source-root: desktop-app
|
12
.travis.yml
Normal file
12
.travis.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 12.13.0
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
before_install: cd desktop-app
|
||||
install:
|
||||
- brew install yarn
|
||||
- yarn
|
||||
- sh add-osx-cert.sh
|
||||
script:
|
||||
- yarn run package-mac
|
|
@ -1,6 +1,6 @@
|
|||
# Responsively App [![Twitter Follow](https://img.shields.io/twitter/follow/ResponsivelyApp?style=social)](https://twitter.com/ResponsivelyApp)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors-)
|
||||
[![All Contributors](https://img.shields.io/badge/all_contributors-26-orange.svg?style=flat-square)](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
[![Gitter chat](https://img.shields.io/gitter/room/badges/shields.svg)](https://gitter.im/responsively-app) [![xscode](https://img.shields.io/badge/Available%20on-xs%3Acode-blue?style=?style=plastic&logo=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAAlUlEQVR42uzXSwqAMAwE0Mn9L+3Ggtgkk35QwcnSJo9S+yGwM9DCooCbgn4YrJ4CIPUcQF7/XSBbx2TEz4sAZ2q1RAECBAiYBlCtvwN+KiYAlG7UDGj59MViT9hOwEqAhYCtAsUZvL6I6W8c2wcbd+LIWSCHSTeSAAECngN4xxIDSK9f4B9t377Wd7H5Nt7/Xz8eAgwAvesLRjYYPuUAAAAASUVORK5CYII=)](https://xscode.com/manojvivek/responsively-app) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/manojVivek/responsively-app/issues)
|
||||
|
||||
|
@ -97,6 +97,12 @@ Thanks go to these wonderful people ([emoji key](https://allcontributors.org/doc
|
|||
<tr>
|
||||
<td align="center"><a href="https://github.com/diego-vieira"><img src="https://avatars2.githubusercontent.com/u/930792?v=4" width="100px;" alt=""/><br /><sub><b>Diego Vieira</b></sub></a><br /><a href="https://github.com/manojVivek/responsively-app/commits?author=diego-vieira" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/pajaydev"><img src="https://avatars0.githubusercontent.com/u/21375014?v=4" width="100px;" alt=""/><br /><sub><b>Ajaykumar</b></sub></a><br /><a href="https://github.com/manojVivek/responsively-app/commits?author=pajaydev" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/kirubakarthikeyan"><img src="https://avatars0.githubusercontent.com/u/38885946?v=4" width="100px;" alt=""/><br /><sub><b>Kiruba Karan</b></sub></a><br /><a href="https://github.com/manojVivek/responsively-app/commits?author=kirubakarthikeyan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/sebasrodriguez"><img src="https://avatars1.githubusercontent.com/u/1605931?v=4" width="100px;" alt=""/><br /><sub><b>Sebastián Rodríguez</b></sub></a><br /><a href="https://github.com/manojVivek/responsively-app/commits?author=sebasrodriguez" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/karthick3018"><img src="https://avatars1.githubusercontent.com/u/47154512?v=4" width="100px;" alt=""/><br /><sub><b>Karthick Raja</b></sub></a><br /><a href="https://github.com/manojVivek/responsively-app/commits?author=karthick3018" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/jzabala"><img src="https://avatars0.githubusercontent.com/u/1315054?v=4" width="100px;" alt=""/><br /><sub><b>Johnny Zabala</b></sub></a><br /><a href="https://github.com/manojVivek/responsively-app/commits?author=jzabala" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
|
1
desktop-app/.nvmrc
Normal file
1
desktop-app/.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
12.13.0
|
|
@ -5,7 +5,7 @@ matrix:
|
|||
- os: osx
|
||||
language: node_js
|
||||
node_js:
|
||||
- node
|
||||
- 12.13.0
|
||||
env:
|
||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||
|
|
23
desktop-app/add-osx-cert.sh
Normal file
23
desktop-app/add-osx-cert.sh
Normal file
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
KEY_CHAIN=build.keychain
|
||||
CERTIFICATE_P12=certificate.p12
|
||||
|
||||
# Recreate the certificate from the secure environment variable
|
||||
echo $CERTIFICATE_OSX_P12 | base64 --decode > $CERTIFICATE_P12
|
||||
|
||||
#create a keychain
|
||||
security create-keychain -p travis $KEY_CHAIN
|
||||
|
||||
# Make the keychain the default so identities are found
|
||||
security default-keychain -s $KEY_CHAIN
|
||||
|
||||
# Unlock the keychain
|
||||
security unlock-keychain -p travis $KEY_CHAIN
|
||||
|
||||
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $CERTIFICATE_OSX_PASSWORD -T /usr/bin/codesign;
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k travis $KEY_CHAIN
|
||||
|
||||
# remove certs
|
||||
rm -fr *.p12
|
|
@ -200,12 +200,6 @@ export function onAddressChange(newURL, force) {
|
|||
return;
|
||||
}
|
||||
|
||||
const isHashDiff = isHashOnlyChange(newURL, address);
|
||||
|
||||
if (isHashDiff) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(newAddress(newURL));
|
||||
pubsub.publish(ADDRESS_CHANGE, [{address: newURL, force: false}]);
|
||||
};
|
||||
|
|
55
desktop-app/app/actions/networkConfig.js
Normal file
55
desktop-app/app/actions/networkConfig.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
import pubsub from 'pubsub.js';
|
||||
import type {Dispatch, GetState} from '../reducers/types';
|
||||
import {
|
||||
SET_NETWORK_TROTTLING_PROFILE,
|
||||
CLEAR_NETWORK_CACHE,
|
||||
} from '../constants/pubsubEvents';
|
||||
|
||||
export const CHANGE_ACTIVE_THROTTLING_PROFILE =
|
||||
'CHANGE_ACTIVE_THROTTLING_PROFILE';
|
||||
export const SAVE_THROTTLING_PROFILES = 'SAVE_THROTTLING_PROFILES';
|
||||
|
||||
export function changeActiveThrottlingProfile(title = 'Online') {
|
||||
return {
|
||||
type: CHANGE_ACTIVE_THROTTLING_PROFILE,
|
||||
title,
|
||||
};
|
||||
}
|
||||
|
||||
export function saveThrottlingProfilesList(profiles) {
|
||||
return {
|
||||
type: SAVE_THROTTLING_PROFILES,
|
||||
profiles,
|
||||
};
|
||||
}
|
||||
|
||||
export function onActiveThrottlingProfileChanged(title) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const {
|
||||
browser: {
|
||||
networkConfiguration: {throttling},
|
||||
},
|
||||
} = getState();
|
||||
|
||||
const activeProfile = throttling.find(x => x.title === title);
|
||||
|
||||
if (activeProfile != null) {
|
||||
pubsub.publish(SET_NETWORK_TROTTLING_PROFILE, [activeProfile]);
|
||||
dispatch(changeActiveThrottlingProfile(title));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function onThrottlingProfilesListChanged(profiles) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
dispatch(saveThrottlingProfilesList(profiles));
|
||||
const activeProfile = profiles.find(x => x.type === 'Online');
|
||||
pubsub.publish(SET_NETWORK_TROTTLING_PROFILE, [activeProfile]);
|
||||
};
|
||||
}
|
||||
|
||||
export function onClearNetworkCache() {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
pubsub.publish(CLEAR_NETWORK_CACHE);
|
||||
};
|
||||
}
|
|
@ -9,9 +9,16 @@ import HomePlusIcon from '../icons/HomePlus';
|
|||
import DeleteCookieIcon from '../icons/DeleteCookie';
|
||||
import DeleteStorageIcon from '../icons/DeleteStorage';
|
||||
import {iconsColor, lightIconsColor} from '../../constants/colors';
|
||||
import {
|
||||
getExistingSearchResults,
|
||||
updateExistingUrl,
|
||||
searchUrlUtils,
|
||||
} from '../../services/searchUrlSuggestions';
|
||||
import UrlSearchResults from '../UrlSearchResults';
|
||||
|
||||
import commonStyles from '../common.styles.css';
|
||||
import styles from './style.css';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
type Props = {
|
||||
address: string,
|
||||
|
@ -25,7 +32,24 @@ type State = {
|
|||
class AddressBar extends React.Component<Props> {
|
||||
props: Props;
|
||||
|
||||
state: State;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
userTypedAddress: props.address,
|
||||
previousAddress: props.address,
|
||||
finalUrlResult: null,
|
||||
canShowSuggestions: false,
|
||||
};
|
||||
this.inputRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this._handleClickOutside);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this._handleClickOutside);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
if (props.address !== state.previousAddress) {
|
||||
|
@ -37,45 +61,17 @@ class AddressBar extends React.Component<Props> {
|
|||
return null;
|
||||
}
|
||||
|
||||
_handleKeyDown = e => {
|
||||
if (e.key === 'Enter') {
|
||||
this.inputRef.current.blur();
|
||||
this._onChange();
|
||||
}
|
||||
};
|
||||
|
||||
_normalize = address => {
|
||||
if (address.indexOf('://') === -1) {
|
||||
let protocol = 'https://';
|
||||
if (address.startsWith('localhost') || address.startsWith('127.0.0.1')) {
|
||||
protocol = 'http://';
|
||||
}
|
||||
address = `${protocol}${address}`;
|
||||
}
|
||||
return address;
|
||||
};
|
||||
|
||||
_onChange = () => {
|
||||
if (!this.state.userTypedAddress) {
|
||||
return;
|
||||
}
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(this._normalize(this.state.userTypedAddress), true);
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
userTypedAddress: props.address,
|
||||
previousAddress: props.address,
|
||||
};
|
||||
this.inputRef = React.createRef();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.addressBarContainer}>
|
||||
<div
|
||||
className={`${styles.addressBarContainer} ${
|
||||
this.state.finalUrlResult
|
||||
? this.state.finalUrlResult.length && this.state.canShowSuggestions
|
||||
? styles.active
|
||||
: ''
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
ref={this.inputRef}
|
||||
type="text"
|
||||
|
@ -85,7 +81,7 @@ class AddressBar extends React.Component<Props> {
|
|||
placeholder="https://your-website.com"
|
||||
value={this.state.userTypedAddress}
|
||||
onKeyDown={this._handleKeyDown}
|
||||
onChange={e => this.setState({userTypedAddress: e.target.value})}
|
||||
onChange={this._handleInputChange}
|
||||
/>
|
||||
<div className={cx(styles.floatingOptionsContainer)}>
|
||||
<div
|
||||
|
@ -174,9 +170,87 @@ class AddressBar extends React.Component<Props> {
|
|||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.finalUrlResult?.length && this.state.canShowSuggestions ? (
|
||||
<UrlSearchResults
|
||||
divClassName={cx(styles.searchBarSuggestionsContainer)}
|
||||
listItemUiClassName={cx(styles.searchBarSuggestionsListUl)}
|
||||
listItemsClassName={cx(styles.searchBarSuggestionsListItems)}
|
||||
filteredSearchResults={this.state.finalUrlResult}
|
||||
handleUrlChange={this._onSearchedUrlClick}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_handleInputChange = e => {
|
||||
this.setState(
|
||||
{userTypedAddress: e.target.value, canShowSuggestions: true},
|
||||
() => {
|
||||
this._filterExistingUrl();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
_handleKeyDown = e => {
|
||||
if (e.key === 'Enter') {
|
||||
this.inputRef.current.blur();
|
||||
this.setState(
|
||||
{
|
||||
finalUrlResult: [],
|
||||
canShowSuggestions: false,
|
||||
},
|
||||
() => {
|
||||
this._onChange();
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
_onChange = () => {
|
||||
if (!this.state.userTypedAddress) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
this.props.onChange &&
|
||||
this.props.onChange(this._normalize(this.state.userTypedAddress), true)
|
||||
);
|
||||
};
|
||||
|
||||
_onSearchedUrlClick = (url, index) => {
|
||||
if (url !== this.state.previousAddress) {
|
||||
this.props.onChange(this._normalize(url), true);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
userTypedAddress: url,
|
||||
finalUrlResult: [],
|
||||
});
|
||||
};
|
||||
|
||||
_normalize = address => {
|
||||
if (address.indexOf('://') === -1) {
|
||||
let protocol = 'https://';
|
||||
if (address.startsWith('localhost') || address.startsWith('127.0.0.1')) {
|
||||
protocol = 'http://';
|
||||
}
|
||||
address = `${protocol}${address}`;
|
||||
}
|
||||
return address;
|
||||
};
|
||||
|
||||
_filterExistingUrl = debounce(() => {
|
||||
const finalResult = searchUrlUtils(this.state.userTypedAddress);
|
||||
this.setState({finalUrlResult: finalResult});
|
||||
}, 300);
|
||||
|
||||
_handleClickOutside = () => {
|
||||
this.setState({
|
||||
finalUrlResult: [],
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default AddressBar;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
padding: 14px 10px;
|
||||
border-radius: 20px;
|
||||
background: unset;
|
||||
color: #ffffffcc;
|
||||
color: #fffc;
|
||||
border: solid 1px #ffffff80;
|
||||
outline: none;
|
||||
transition: border 500ms ease-out;
|
||||
|
@ -25,6 +25,10 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.active {
|
||||
border-radius: 14px 14px 0 0;
|
||||
}
|
||||
|
||||
.addressBarContainer:focus-within {
|
||||
color: white;
|
||||
border: solid 1px #7587ec;
|
||||
|
@ -35,3 +39,42 @@
|
|||
display: flex;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.searchBarSuggestionsContainer {
|
||||
width: calc(100% + 2px);
|
||||
max-height: 20em;
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: 1.8em;
|
||||
background: #4b4b4c;
|
||||
border-radius: 0 0 14px 14px;
|
||||
border-right: solid 1px #7587ec;
|
||||
border-bottom: solid 1px #7587ec;
|
||||
border-left: solid 1px #7587ec;
|
||||
}
|
||||
.searchBarSuggestionsListItems {
|
||||
font-size: 1.1em;
|
||||
line-height: 22px;
|
||||
color: #cacaca;
|
||||
padding: 0.4em 1em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.searchBarSuggestionsListUl {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.searchBarSuggestionsListItems:hover {
|
||||
background: gray;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.searchBarSuggestionsListItems.active {
|
||||
background: gray;
|
||||
color: white;
|
||||
}
|
||||
|
|
27
desktop-app/app/components/ClearNetworkCache/index.js
Normal file
27
desktop-app/app/components/ClearNetworkCache/index.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import CachedIcon from '@material-ui/icons/Cached';
|
||||
|
||||
import commonStyles from '../common.styles.css';
|
||||
|
||||
export default function ClearNetworkCache(props) {
|
||||
return (
|
||||
<div className={cx(commonStyles.sidebarContentSection)}>
|
||||
<div className={cx(commonStyles.sidebarContentSectionTitleBar)}>
|
||||
<CachedIcon style={{marginRight: 5}} /> Network Cache
|
||||
</div>
|
||||
<div className={cx(commonStyles.sidebarContentSectionContainer)}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
aria-label="clear network cache"
|
||||
component="span"
|
||||
onClick={() => props.onClearNetworkCache()}
|
||||
>
|
||||
Clear Network Cache
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -5,6 +5,7 @@ import cx from 'classnames';
|
|||
import DeviceDrawerContainer from '../../containers/DeviceDrawerContainer';
|
||||
import UserPreferencesContainer from '../../containers/UserPreferencesContainer';
|
||||
import ExtensionsManagerContainer from '../../containers/ExtensionsManagerContainer';
|
||||
import NetworkConfigurationContainer from '../../containers/NetworkConfigurationContainer';
|
||||
|
||||
import styles from './styles.css';
|
||||
import commonStyles from '../common.styles.css';
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
DEVICE_MANAGER,
|
||||
USER_PREFERENCES,
|
||||
EXTENSIONS_MANAGER,
|
||||
NETWORK_CONFIGURATION,
|
||||
} from '../../constants/DrawerContents';
|
||||
import {iconsColor} from '../../constants/colors';
|
||||
import DoubleLeftArrowIcon from '../icons/DoubleLeftArrow';
|
||||
|
@ -70,6 +72,8 @@ function getDrawerContent(type) {
|
|||
return <UserPreferencesContainer />;
|
||||
case EXTENSIONS_MANAGER:
|
||||
return <ExtensionsManagerContainer />;
|
||||
case NETWORK_CONFIGURATION:
|
||||
return <NetworkConfigurationContainer />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
padding: 20px 0 5px;
|
||||
background: #252526;
|
||||
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.35);
|
||||
z-index: 2;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.darkToast {
|
||||
|
|
|
@ -6,6 +6,7 @@ import DevicesIcon from '@material-ui/icons/Devices';
|
|||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import PhotoLibraryIcon from '@material-ui/icons/PhotoLibraryOutlined';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
import NetworkIcon from '../icons/Network';
|
||||
import cx from 'classnames';
|
||||
import Logo from '../icons/Logo';
|
||||
|
||||
|
@ -17,6 +18,7 @@ import {
|
|||
SCREENSHOT_MANAGER,
|
||||
USER_PREFERENCES,
|
||||
EXTENSIONS_MANAGER,
|
||||
NETWORK_CONFIGURATION,
|
||||
} from '../../constants/DrawerContents';
|
||||
|
||||
const LeftIconsPane = props => {
|
||||
|
@ -81,6 +83,19 @@ const LeftIconsPane = props => {
|
|||
<ExtensionIcon {...iconProps} className="extensionsIcon" />
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
className={cx(commonStyles.icons, styles.icon, commonStyles.enabled, {
|
||||
[commonStyles.selected]:
|
||||
props.drawer.open &&
|
||||
props.drawer.content === NETWORK_CONFIGURATION,
|
||||
})}
|
||||
onClick={() => toggleState(NETWORK_CONFIGURATION)}
|
||||
>
|
||||
<div>
|
||||
<NetworkIcon {...iconProps} color="white" className="networkIcon" />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<div style={{position: 'relative'}}>
|
||||
<div
|
||||
|
|
25
desktop-app/app/components/NetworkConfiguration/index.js
Normal file
25
desktop-app/app/components/NetworkConfiguration/index.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import ClearNetworkCache from '../ClearNetworkCache';
|
||||
import NetworkThrottling from '../NetworkThrottling';
|
||||
|
||||
import styles from './styles.css';
|
||||
import commonStyles from '../common.styles.css';
|
||||
|
||||
export default function DeviceDrawer({
|
||||
throttling,
|
||||
onActiveThrottlingProfileChanged,
|
||||
onThrottlingProfilesListChanged,
|
||||
onClearNetworkCache,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<ClearNetworkCache onClearNetworkCache={onClearNetworkCache} />
|
||||
<NetworkThrottling
|
||||
throttling={throttling}
|
||||
onActiveThrottlingProfileChanged={onActiveThrottlingProfileChanged}
|
||||
onThrottlingProfilesListChanged={onThrottlingProfilesListChanged}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
.label {
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
import React, {useState} from 'react';
|
||||
import cx from 'classnames';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Table from '@material-ui/core/Table';
|
||||
import TableBody from '@material-ui/core/TableBody';
|
||||
import TableCell from '@material-ui/core/TableCell';
|
||||
import TableContainer from '@material-ui/core/TableContainer';
|
||||
import TableHead from '@material-ui/core/TableHead';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
import TableFooter from '@material-ui/core/TableFooter';
|
||||
import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined';
|
||||
import AddCircleOutlineOutlinedIcon from '@material-ui/icons/AddCircleOutlineOutlined';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import NumberFormat from 'react-number-format';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
|
||||
import commonStyles from '../../common.styles.css';
|
||||
import styles from './styles.css';
|
||||
|
||||
function NumberFormatCustom(props) {
|
||||
const {inputRef, onChange, ...other} = props;
|
||||
|
||||
return (
|
||||
<NumberFormat
|
||||
{...other}
|
||||
getInputRef={inputRef}
|
||||
onValueChange={values => {
|
||||
onChange({
|
||||
target: {
|
||||
name: props.name,
|
||||
value: values.floatValue,
|
||||
},
|
||||
});
|
||||
}}
|
||||
allowNegative={false}
|
||||
decimalScale={0}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProfileManager({profiles, onSave}) {
|
||||
const [currentProfiles, updateProfiles] = useState(profiles);
|
||||
const [newElement, setNewElement] = useState({type: 'Custom'});
|
||||
const [editModeRows, toggleEditModeRows] = useState({});
|
||||
|
||||
const newElementIsInvalid =
|
||||
newElement.title != null &&
|
||||
(newElement.title.trim() === '' ||
|
||||
newElement.title.length > 20 ||
|
||||
currentProfiles.filter(x => x.title === newElement.title).length !== 0);
|
||||
|
||||
const addNewElement = () => {
|
||||
if (!newElementIsInvalid && newElement.title != null) {
|
||||
setNewElement({type: 'Custom'});
|
||||
updateProfiles([...currentProfiles, newElement]);
|
||||
}
|
||||
};
|
||||
|
||||
const removeProfile = title => {
|
||||
updateProfiles(currentProfiles.filter(p => p.title !== title));
|
||||
};
|
||||
|
||||
const updateProfile = (title, key, value) => {
|
||||
const profile = currentProfiles.find(x => x.title === title);
|
||||
if (profile != null && profile.type === 'Custom') {
|
||||
profile[key] = value;
|
||||
updateProfiles([...currentProfiles]);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleEditMode = row => {
|
||||
if (row.type !== 'Custom') return;
|
||||
editModeRows[row.title] = !editModeRows[row.title];
|
||||
toggleEditModeRows({...editModeRows});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx(styles.profileManagerContainer)}>
|
||||
<TableContainer className={cx(styles.profilesContainer)}>
|
||||
<Table size="small">
|
||||
<TableHead className={cx(styles.profilesHeader)}>
|
||||
<TableRow>
|
||||
<TableCell style={{width: '32%'}}>Name</TableCell>
|
||||
<TableCell style={{width: '21%'}} align="right">
|
||||
Download
|
||||
</TableCell>
|
||||
<TableCell style={{width: '21%'}} align="right">
|
||||
Upload
|
||||
</TableCell>
|
||||
<TableCell style={{width: '21%'}} align="right">
|
||||
Latency
|
||||
</TableCell>
|
||||
<TableCell style={{width: '5%'}} align="right" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{currentProfiles.map(row => (
|
||||
<TableRow key={row.title} className={cx(styles.profilesRow)}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={cx({
|
||||
[styles.customProfile]: row.type === 'Custom',
|
||||
})}
|
||||
onClick={() => toggleEditMode(row)}
|
||||
>
|
||||
{row.title}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{row.type !== 'Online' && (
|
||||
<TextField
|
||||
className={cx(styles.numericField, {
|
||||
[styles.numericFieldDisabled]:
|
||||
row.type !== 'Custom' || !editModeRows[row.title],
|
||||
})}
|
||||
value={row.downloadKps == null ? '' : row.downloadKps}
|
||||
onChange={e =>
|
||||
updateProfile(row.title, 'downloadKps', e.target.value)
|
||||
}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder={
|
||||
row.type !== 'Custom' || !editModeRows[row.title]
|
||||
? ''
|
||||
: '(optional)'
|
||||
}
|
||||
InputProps={{
|
||||
inputComponent: NumberFormatCustom,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">Kb/s</InputAdornment>
|
||||
),
|
||||
}}
|
||||
disabled={
|
||||
row.type !== 'Custom' || !editModeRows[row.title]
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{row.type !== 'Online' && (
|
||||
<TextField
|
||||
className={cx(styles.numericField, {
|
||||
[styles.numericFieldDisabled]:
|
||||
row.type !== 'Custom' || !editModeRows[row.title],
|
||||
})}
|
||||
value={row.uploadKps == null ? '' : row.uploadKps}
|
||||
onChange={e =>
|
||||
updateProfile(row.title, 'uploadKps', e.target.value)
|
||||
}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder={
|
||||
row.type !== 'Custom' || !editModeRows[row.title]
|
||||
? ''
|
||||
: '(optional)'
|
||||
}
|
||||
InputProps={{
|
||||
inputComponent: NumberFormatCustom,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">Kb/s</InputAdornment>
|
||||
),
|
||||
}}
|
||||
disabled={
|
||||
row.type !== 'Custom' || !editModeRows[row.title]
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{row.type !== 'Online' && (
|
||||
<TextField
|
||||
className={cx(styles.numericField, {
|
||||
[styles.numericFieldDisabled]:
|
||||
row.type !== 'Custom' || !editModeRows[row.title],
|
||||
})}
|
||||
value={row.latencyMs == null ? '' : row.latencyMs}
|
||||
onChange={e =>
|
||||
updateProfile(row.title, 'latencyMs', e.target.value)
|
||||
}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder={
|
||||
row.type !== 'Custom' || !editModeRows[row.title]
|
||||
? ''
|
||||
: '(optional)'
|
||||
}
|
||||
InputProps={{
|
||||
inputComponent: NumberFormatCustom,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">ms</InputAdornment>
|
||||
),
|
||||
}}
|
||||
disabled={
|
||||
row.type !== 'Custom' || !editModeRows[row.title]
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{row.type === 'Custom' && (
|
||||
<CancelOutlinedIcon
|
||||
className={cx(styles.actionIcon)}
|
||||
onClick={() => removeProfile(row.title)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TableFooter component={Table}>
|
||||
<TableHead className={cx(styles.profilesHeader)}>
|
||||
<TableRow>
|
||||
<TableCell style={{width: '32%'}}>
|
||||
<TextField
|
||||
autoFocus
|
||||
value={newElement.title || ''}
|
||||
onChange={e =>
|
||||
setNewElement({...newElement, title: e.target.value})
|
||||
}
|
||||
error={newElementIsInvalid}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="New Profile Name"
|
||||
className={cx(styles.titleField)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell style={{width: '21%'}} align="right">
|
||||
<TextField
|
||||
className={cx(styles.numericField)}
|
||||
value={
|
||||
newElement.downloadKps == null ? '' : newElement.downloadKps
|
||||
}
|
||||
onChange={e =>
|
||||
setNewElement({...newElement, downloadKps: e.target.value})
|
||||
}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="(optional)"
|
||||
InputProps={{
|
||||
inputComponent: NumberFormatCustom,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">Kb/s</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell style={{width: '21%'}} align="right">
|
||||
<TextField
|
||||
className={cx(styles.numericField)}
|
||||
value={newElement.uploadKps == null ? '' : newElement.uploadKps}
|
||||
onChange={e =>
|
||||
setNewElement({...newElement, uploadKps: e.target.value})
|
||||
}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="(optional)"
|
||||
InputProps={{
|
||||
inputComponent: NumberFormatCustom,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">Kb/s</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell style={{width: '21%'}} align="right">
|
||||
<TextField
|
||||
className={cx(styles.numericField)}
|
||||
value={newElement.latencyMs == null ? '' : newElement.latencyMs}
|
||||
onChange={e =>
|
||||
setNewElement({...newElement, latencyMs: e.target.value})
|
||||
}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="(optional)"
|
||||
InputProps={{
|
||||
inputComponent: NumberFormatCustom,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">ms</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell style={{width: '5%'}} align="right">
|
||||
<AddCircleOutlineOutlinedIcon
|
||||
className={cx(styles.actionIcon)}
|
||||
onClick={addNewElement}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
</TableFooter>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
aria-label="clear network cache"
|
||||
component="span"
|
||||
onClick={() => onSave(currentProfiles)}
|
||||
size="large"
|
||||
className={cx(styles.saveButton)}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
.profileManagerContainer {
|
||||
height: 600px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.profilesContainer {
|
||||
max-height: 430px;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.profilesRow > th,
|
||||
td {
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
|
||||
.profilesRow > th {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.profilesHeader > tr > th {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.profilesRow > th.customProfile {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.actionIcon {
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.actionIcon:hover {
|
||||
color: #6075ef;
|
||||
}
|
||||
|
||||
.saveButton {
|
||||
position: absolute !important;
|
||||
bottom: 25px;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.numericField * {
|
||||
text-align: right;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.numericFieldDisabled fieldset {
|
||||
border: 0 !important;
|
||||
}
|
||||
.numericFieldDisabled input {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.titleField * {
|
||||
font-size: 14px !important;
|
||||
}
|
134
desktop-app/app/components/NetworkThrottling/index.js
Normal file
134
desktop-app/app/components/NetworkThrottling/index.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
import React, {useState} from 'react';
|
||||
import cx from 'classnames';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import NetworkCheckIcon from '@material-ui/icons/NetworkCheck';
|
||||
import Select from 'react-select';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import ProfileManager from './ProfileManager';
|
||||
|
||||
import {makeStyles} from '@material-ui/core/styles';
|
||||
import commonStyles from '../common.styles.css';
|
||||
import styles from './styles.css';
|
||||
|
||||
const selectStyles = {
|
||||
control: selectStyles => ({...selectStyles, backgroundColor: '#ffffff10'}),
|
||||
option: (selectStyles, {data, isDisabled, isFocused, isSelected}) => {
|
||||
const color = 'white';
|
||||
return {
|
||||
...selectStyles,
|
||||
backgroundColor: isDisabled
|
||||
? null
|
||||
: isSelected
|
||||
? '#ffffff40'
|
||||
: isFocused
|
||||
? '#ffffff20'
|
||||
: null,
|
||||
color: 'white',
|
||||
|
||||
':active': {
|
||||
...selectStyles[':active'],
|
||||
backgroundColor: !isDisabled && '#ffffff40',
|
||||
},
|
||||
};
|
||||
},
|
||||
input: selectStyles => ({...selectStyles}),
|
||||
placeholder: selectStyles => ({...selectStyles}),
|
||||
singleValue: (selectStyles, {data}) => ({...selectStyles, color: 'white'}),
|
||||
menu: selectStyles => ({...selectStyles, background: '#4b4b4b', zIndex: 100}),
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
appBar: {
|
||||
position: 'relative',
|
||||
},
|
||||
title: {
|
||||
marginLeft: theme.spacing(2),
|
||||
flex: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function NetworkThrottling({
|
||||
throttling,
|
||||
onActiveThrottlingProfileChanged,
|
||||
onThrottlingProfilesListChanged,
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const closeDialog = () => setOpen(false);
|
||||
const classes = useStyles();
|
||||
|
||||
const selectedIdx = throttling.findIndex(p => p.active);
|
||||
const options = throttling.map(p => ({
|
||||
value: p.title,
|
||||
label: p.title,
|
||||
}));
|
||||
const selectedOption = options[selectedIdx];
|
||||
|
||||
const onThrottlingProfileChanged = val => {
|
||||
if (val.value !== selectedOption.value)
|
||||
onActiveThrottlingProfileChanged(val.value);
|
||||
};
|
||||
|
||||
const saveThrottlingProfiles = profiles => {
|
||||
onThrottlingProfilesListChanged(profiles);
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx(commonStyles.sidebarContentSection)}>
|
||||
<div className={cx(commonStyles.sidebarContentSectionTitleBar)}>
|
||||
<NetworkCheckIcon className={cx(styles.networkThrottlingIcon)} />{' '}
|
||||
Network Throttling
|
||||
</div>
|
||||
<div className={cx(commonStyles.sidebarContentSectionContainer)}>
|
||||
<div className={cx(styles.throttlingProfileSelectorContainer)}>
|
||||
<Select
|
||||
options={options}
|
||||
value={selectedOption}
|
||||
onChange={onThrottlingProfileChanged}
|
||||
styles={selectStyles}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
aria-label="clear network cache"
|
||||
component="span"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
Manage Profiles
|
||||
</Button>
|
||||
<Dialog
|
||||
className={cx(styles.profileManagerDialog)}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
open={open}
|
||||
scroll="paper"
|
||||
onClose={closeDialog}
|
||||
>
|
||||
<AppBar className={classes.appBar} color="secondary">
|
||||
<Toolbar>
|
||||
<Typography variant="h6" className={classes.title}>
|
||||
Manage Throttling Profiles
|
||||
</Typography>
|
||||
<Button color="inherit" onClick={closeDialog}>
|
||||
close
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<DialogContent>
|
||||
<ProfileManager
|
||||
profiles={[...throttling]}
|
||||
onSave={saveThrottlingProfiles}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
6
desktop-app/app/components/NetworkThrottling/styles.css
Normal file
6
desktop-app/app/components/NetworkThrottling/styles.css
Normal file
|
@ -0,0 +1,6 @@
|
|||
.networkThrottlingIcon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.throttlingProfileSelectorContainer {
|
||||
margin-bottom: 20px;
|
||||
}
|
53
desktop-app/app/components/ScreenShotSavePreference/index.js
Normal file
53
desktop-app/app/components/ScreenShotSavePreference/index.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import React, {useState} from 'react';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import FolderOpenIcon from '@material-ui/icons/FolderOpenOutlined';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import {ipcRenderer} from 'electron';
|
||||
import {lightIconsColor} from '../../constants/colors';
|
||||
import styles from '../UserPreferences/styles.module.css';
|
||||
import cx from 'classnames';
|
||||
|
||||
export default function ScreenShotSavePreference({
|
||||
onScreenShotSaveLocationChange,
|
||||
screenShotSavePath,
|
||||
}) {
|
||||
const getScreenshotSavePath = async event => {
|
||||
const screenshotSavePathResponseFromIpc = await ipcRenderer.invoke(
|
||||
'get-screen-shot-save-path'
|
||||
);
|
||||
if (screenshotSavePathResponseFromIpc) {
|
||||
onScreenShotSaveLocationChange(
|
||||
'screenShotSavePath',
|
||||
screenshotSavePathResponseFromIpc
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx(styles.screenshotLocationInputContainer)}>
|
||||
<div className={cx(styles.sectionTitle)}>Screenshot Location:</div>
|
||||
<TextField
|
||||
type="text"
|
||||
color="secondary"
|
||||
id="standard-size-small"
|
||||
value={screenShotSavePath}
|
||||
placeholder="Screenshot save path"
|
||||
variant="outlined"
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment>
|
||||
<IconButton
|
||||
onClick={getScreenshotSavePath}
|
||||
size="small"
|
||||
title="Select Screenshots save location"
|
||||
>
|
||||
<FolderOpenIcon fontSize="small" htmlColor={lightIconsColor} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -22,7 +22,7 @@ const Announcement = () => {
|
|||
return (
|
||||
<div className={styles.section}>
|
||||
<div
|
||||
className={styles.link}
|
||||
className={cx(styles.text, styles.link)}
|
||||
onClick={() => shell.openExternal(data.link)}
|
||||
>
|
||||
<span className={cx('featureSuggestionLink', styles.linkText)}>
|
||||
|
|
|
@ -9,7 +9,7 @@ import Twitter from '../icons/Twitter';
|
|||
import RoadMap from '../icons/RoadMap';
|
||||
|
||||
const Spacer = ({width = 10}) => (
|
||||
<div className={styles.link} style={{width}} />
|
||||
<div className={cx(styles.text)} style={{width}} />
|
||||
);
|
||||
|
||||
const AppUpdaterStatusInfoSection = () => {
|
||||
|
@ -59,13 +59,13 @@ const StatusBar = ({visible, zoomLevel}) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const zoomPercent = Math.round(zoomLevel * 100)
|
||||
const zoomPercent = Math.round(zoomLevel * 100);
|
||||
|
||||
return (
|
||||
<div className={styles.statusBar}>
|
||||
<div className={styles.section}>
|
||||
<div
|
||||
className={styles.link}
|
||||
className={cx(styles.text, styles.link)}
|
||||
onClick={() =>
|
||||
shell.openExternal('https://github.com/manojVivek/responsively-app')
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ const StatusBar = ({visible, zoomLevel}) => {
|
|||
<Github width={14} className={styles.linkIcon} />
|
||||
</div>
|
||||
<div
|
||||
className={styles.link}
|
||||
className={cx(styles.text, styles.link)}
|
||||
onClick={() =>
|
||||
shell.openExternal(
|
||||
'https://twitter.com/intent/follow?original_referer=app&ref_src=twsrc%5Etfw®ion=follow_link&screen_name=ResponsivelyApp&tw_p=followbutton'
|
||||
|
@ -84,7 +84,7 @@ const StatusBar = ({visible, zoomLevel}) => {
|
|||
</div>
|
||||
<Spacer />
|
||||
<div
|
||||
className={cx('roadMapLink', styles.link)}
|
||||
className={cx('roadMapLink', styles.text, styles.link)}
|
||||
onClick={() =>
|
||||
shell.openExternal(
|
||||
'https://github.com/manojVivek/responsively-app/projects/12?fullscreen=true'
|
||||
|
@ -96,7 +96,7 @@ const StatusBar = ({visible, zoomLevel}) => {
|
|||
</div>
|
||||
<Spacer />
|
||||
<div
|
||||
className={styles.link}
|
||||
className={cx(styles.text, styles.link)}
|
||||
onClick={() =>
|
||||
shell.openExternal('https://headwayapp.co/responsively-changelog')
|
||||
}
|
||||
|
@ -105,15 +105,16 @@ const StatusBar = ({visible, zoomLevel}) => {
|
|||
Changelog
|
||||
</span>
|
||||
</div>
|
||||
<Spacer />
|
||||
<div className={cx(styles.text)}>
|
||||
<span className={cx('zoomText', styles.linkText)}>
|
||||
Zoom: {zoomPercent}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<AppUpdaterStatusInfoSection />
|
||||
<div className={styles.section}>
|
||||
<Announcement />
|
||||
{zoomPercent !== 100 && <>
|
||||
<div className={cx('roadMapLink', styles.statusText)}>
|
||||
{zoomPercent}%
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
display: flex;
|
||||
background-color: #ffffff15;
|
||||
box-shadow: 0 -3px 5px rgba(0, 0, 0, 0.35);
|
||||
color: #ffffffcc;
|
||||
color: #fffc;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
@ -23,10 +23,10 @@
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.link {
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
cursor: default;
|
||||
color: grey;
|
||||
fill: grey;
|
||||
border-radius: 2px;
|
||||
|
@ -35,6 +35,10 @@
|
|||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: lightgrey;
|
||||
fill: lightgrey;
|
||||
|
|
30
desktop-app/app/components/UrlSearchResults/index.js
Normal file
30
desktop-app/app/components/UrlSearchResults/index.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
|
||||
const UrlSearchResults = ({
|
||||
divClassName,
|
||||
listItemsClassName,
|
||||
filteredSearchResults,
|
||||
handleUrlChange,
|
||||
activeClass,
|
||||
listItemUiClassName,
|
||||
}) => (
|
||||
<div className={divClassName}>
|
||||
<ul className={listItemUiClassName}>
|
||||
{filteredSearchResults?.map(
|
||||
(eachResult, index) =>
|
||||
index < 8 && (
|
||||
<li key={index} className={`${listItemsClassName}`}>
|
||||
<div
|
||||
onKeyDown={e => handleOnKeyDown(e, eachResult.url)}
|
||||
onClick={() => handleUrlChange(eachResult.url, index)}
|
||||
>
|
||||
{eachResult.url}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default UrlSearchResults;
|
|
@ -9,6 +9,8 @@ import SettingsIcon from '@material-ui/icons/Settings';
|
|||
import commonStyles from '../common.styles.css';
|
||||
import styles from './styles.module.css';
|
||||
import {DEVTOOLS_MODES} from '../../constants/previewerLayouts';
|
||||
import ScreenShotSavePreference from '../ScreenShotSavePreference/index';
|
||||
import {userPreferenceSettings} from '../../settings/userPreferenceSettings';
|
||||
|
||||
export default function UserPreference({
|
||||
devToolsConfig,
|
||||
|
@ -106,6 +108,15 @@ export default function UserPreference({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx(commonStyles.sidebarContentSectionContainer)}>
|
||||
<ScreenShotSavePreference
|
||||
screenShotSavePath={
|
||||
userPreferences.screenShotSavePath ||
|
||||
userPreferenceSettings.getDefaultScreenshotpath()
|
||||
}
|
||||
onScreenShotSaveLocationChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,3 +14,10 @@
|
|||
height: 40px;
|
||||
border: transparent;
|
||||
}
|
||||
|
||||
.screenshotLocationInputContainer {
|
||||
border-top: 1px solid #383737;
|
||||
}
|
||||
.sectionTitle {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import {remote, ipcRenderer} from 'electron';
|
|||
import cx from 'classnames';
|
||||
import {Resizable} from 're-resizable';
|
||||
import {Tooltip} from '@material-ui/core';
|
||||
import debounce from 'lodash.debounce';
|
||||
import debounce from 'lodash/debounce';
|
||||
import pubsub from 'pubsub.js';
|
||||
import console from 'electron-timber';
|
||||
import BugIcon from '../icons/Bug';
|
||||
|
@ -28,6 +28,8 @@ import {
|
|||
DELETE_STORAGE,
|
||||
ADDRESS_CHANGE,
|
||||
STOP_LOADING,
|
||||
CLEAR_NETWORK_CACHE,
|
||||
SET_NETWORK_TROTTLING_PROFILE,
|
||||
} from '../../constants/pubsubEvents';
|
||||
import {CAPABILITIES} from '../../constants/devices';
|
||||
import DevToolsService from '../../services/dev-tools';
|
||||
|
@ -44,6 +46,7 @@ import Maximize from '../icons/Maximize';
|
|||
import Minimize from '../icons/Minimize';
|
||||
import Focus from '../icons/Focus';
|
||||
import Unfocus from '../icons/Unfocus';
|
||||
import {getBrowserSyncEmbedScriptURL} from '../../service/browserSync';
|
||||
|
||||
const {BrowserWindow} = remote;
|
||||
|
||||
|
@ -77,6 +80,7 @@ class WebView extends Component {
|
|||
address: this.props.browser.address,
|
||||
};
|
||||
this.subscriptions = [];
|
||||
this.dbg = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -142,8 +146,20 @@ class WebView extends Component {
|
|||
)
|
||||
);
|
||||
|
||||
this.webviewRef.current.addEventListener('dom-ready', async () => {
|
||||
this.subscriptions.push(
|
||||
pubsub.subscribe(
|
||||
SET_NETWORK_TROTTLING_PROFILE,
|
||||
this.setNetworkThrottlingProfile
|
||||
)
|
||||
);
|
||||
this.subscriptions.push(
|
||||
pubsub.subscribe(CLEAR_NETWORK_CACHE, this.clearNetworkCache)
|
||||
);
|
||||
|
||||
this.webviewRef.current.addEventListener('dom-ready', () => {
|
||||
this.initEventTriggers(this.webviewRef.current);
|
||||
this.dbg = this.getWebContents().debugger;
|
||||
if (!this.dbg.isAttached()) this.dbg.attach();
|
||||
});
|
||||
|
||||
if (this.props.transmitNavigatorStatus) {
|
||||
|
@ -249,6 +265,7 @@ class WebView extends Component {
|
|||
|
||||
componentWillUnmount() {
|
||||
this.subscriptions.forEach(pubsub.unsubscribe);
|
||||
if (this.dbg && this.dbg.isAttached()) this.dbg.detach();
|
||||
}
|
||||
|
||||
initDeviceEmulationParams = () => {
|
||||
|
@ -405,6 +422,49 @@ class WebView extends Component {
|
|||
this.webviewRef.current.send('disableInspectorMessage');
|
||||
};
|
||||
|
||||
setNetworkThrottlingProfile = ({type, downloadKps, uploadKps, latencyMs}) => {
|
||||
// TODO : change this when https://github.com/electron/electron/issues/21250 is solved
|
||||
// if (type === 'Online') {
|
||||
// this.getWebContents().session.disableNetworkEmulation();
|
||||
// } else if (type === 'Offline') {
|
||||
// this.getWebContents().session.enableNetworkEmulation({offline: true});
|
||||
// } else if (type === 'Custom') {
|
||||
// const downloadThroughput = downloadKps != null? downloadKps * 128 : undefined;
|
||||
// const uploadThroughput = uploadKps != null? uploadKps * 128 : undefined;
|
||||
// this.getWebContents().session.enableNetworkEmulation({offline: false, latency: latencyMs, downloadThroughput, uploadThroughput });
|
||||
// }
|
||||
|
||||
// WORKAROUND
|
||||
if (type === 'Online') {
|
||||
this.dbg.sendCommand('Network.disable');
|
||||
} else if (type === 'Offline') {
|
||||
this.dbg.sendCommand('Network.enable').then(_ => {
|
||||
this.dbg.sendCommand('Network.emulateNetworkConditions', {
|
||||
offline: true,
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const downloadThroughput = downloadKps != null ? downloadKps * 128 : -1;
|
||||
const uploadThroughput = uploadKps != null ? uploadKps * 128 : -1;
|
||||
const latency = latencyMs || 0;
|
||||
this.dbg.sendCommand('Network.enable').then(_ => {
|
||||
this.dbg.sendCommand('Network.emulateNetworkConditions', {
|
||||
offline: false,
|
||||
latency,
|
||||
downloadThroughput,
|
||||
uploadThroughput,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
clearNetworkCache = () => {
|
||||
this.getWebContents().session.clearCache();
|
||||
};
|
||||
|
||||
messageHandler = ({channel: type, args: [message]}) => {
|
||||
if (type !== MESSAGE_TYPES.toggleEventMirroring && this.state.isUnplugged) {
|
||||
return;
|
||||
|
@ -443,8 +503,19 @@ class WebView extends Component {
|
|||
pubsub.publish(DISABLE_INSPECTOR_ALL_DEVICES, [message]);
|
||||
};
|
||||
|
||||
initEventTriggers = webview => {
|
||||
initBrowserSync = webview => {
|
||||
this.getWebContentForId(webview.getWebContentsId()).executeJavaScript(`
|
||||
var bsScript= document.createElement('script');
|
||||
bsScript.src = '${getBrowserSyncEmbedScriptURL()}';
|
||||
bsScript.async = true;
|
||||
document.body.appendChild(bsScript);
|
||||
`);
|
||||
};
|
||||
|
||||
initEventTriggers = webview => {
|
||||
this.initBrowserSync(webview);
|
||||
this.getWebContentForId(webview.getWebContentsId()).executeJavaScript(`
|
||||
|
||||
responsivelyApp.deviceId = '${this.props.device.id}';
|
||||
document.addEventListener('mouseleave', () => {
|
||||
window.responsivelyApp.mouseOn = false;
|
||||
|
@ -459,18 +530,6 @@ class WebView extends Component {
|
|||
}
|
||||
});
|
||||
|
||||
document.addEventListener('scroll', (e) => {
|
||||
if (!responsivelyApp.mouseOn) {
|
||||
return;
|
||||
}
|
||||
window.responsivelyApp.sendMessageToHost(
|
||||
'${MESSAGE_TYPES.scroll}',
|
||||
{
|
||||
position: {x: window.scrollX, y: window.scrollY},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
document.addEventListener(
|
||||
'click',
|
||||
(e) => {
|
||||
|
@ -494,12 +553,6 @@ class WebView extends Component {
|
|||
return;
|
||||
}
|
||||
e.responsivelyAppProcessed = true;
|
||||
window.responsivelyApp.sendMessageToHost(
|
||||
'${MESSAGE_TYPES.click}',
|
||||
{
|
||||
cssPath: window.responsivelyApp.cssPath(e.target),
|
||||
}
|
||||
);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
@ -522,10 +575,19 @@ class WebView extends Component {
|
|||
};
|
||||
|
||||
_flipOrientation = () => {
|
||||
if (!this.isMobile) return;
|
||||
|
||||
if (this.props.sendFlipStatus) {
|
||||
this.props.sendFlipStatus(!this.state.isTilted);
|
||||
}
|
||||
this.setState({isTilted: !this.state.isTilted});
|
||||
const flippedDeviceDims = {
|
||||
width: this.state.deviceDimensions.height,
|
||||
height: this.state.deviceDimensions.width,
|
||||
};
|
||||
this.setState({
|
||||
isTilted: !this.state.isTilted,
|
||||
deviceDimensions: flippedDeviceDims,
|
||||
});
|
||||
};
|
||||
|
||||
_unPlug = () => {
|
||||
|
@ -552,6 +614,7 @@ class WebView extends Component {
|
|||
this.getWebContents().setAudioMuted(true);
|
||||
this.props.onDeviceMutedChange(this.props.device.id, true);
|
||||
};
|
||||
|
||||
_unmuteDevice = () => {
|
||||
this.getWebContents().setAudioMuted(false);
|
||||
this.props.onDeviceMutedChange(this.props.device.id, false);
|
||||
|
@ -583,17 +646,13 @@ class WebView extends Component {
|
|||
const {
|
||||
device: {id, useragent, capabilities},
|
||||
} = this.props;
|
||||
const {deviceDimensions, address} = this.state;
|
||||
const {deviceDimensions, address, isTilted} = 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}}
|
||||
size={{width: deviceStyles.width, height: deviceStyles.height}}
|
||||
onResizeStart={() => {
|
||||
const updatedTempDims = {
|
||||
width: deviceDimensions.width,
|
||||
|
@ -642,7 +701,7 @@ class WebView extends Component {
|
|||
className={cx(styles.device)}
|
||||
src={address || 'about:blank'}
|
||||
useragent={useragent}
|
||||
style={responsiveStyle}
|
||||
style={deviceStyles}
|
||||
/>
|
||||
<webview id={`dev-tools-${this.props.key}`} src="about:blank" />
|
||||
</Resizable>
|
||||
|
@ -667,26 +726,25 @@ class WebView extends Component {
|
|||
render() {
|
||||
const {
|
||||
browser: {zoomLevel, previewer},
|
||||
device: {capabilities},
|
||||
} = this.props;
|
||||
const {
|
||||
isTilted,
|
||||
deviceDimensions,
|
||||
errorCode,
|
||||
errorDesc,
|
||||
screenshotInProgress,
|
||||
} = this.state;
|
||||
const deviceStyles = {
|
||||
outline: `4px solid ${this.props.browser.userPreferences.deviceOutlineStyle}`,
|
||||
width:
|
||||
this.isMobile && isTilted
|
||||
? deviceDimensions.height
|
||||
: deviceDimensions.width,
|
||||
height:
|
||||
this.isMobile && isTilted
|
||||
? deviceDimensions.width
|
||||
: deviceDimensions.height,
|
||||
outline: `4px solid ${
|
||||
this._isDevToolsOpen()
|
||||
? `#6075ef`
|
||||
: this.props.browser.userPreferences.deviceOutlineStyle
|
||||
}`,
|
||||
width: deviceDimensions.width,
|
||||
height: deviceDimensions.height,
|
||||
};
|
||||
const isMuted = this.props.device.isMuted;
|
||||
const isResponsive = capabilities.includes(CAPABILITIES.responsive);
|
||||
const shouldMaximize = previewer.layout !== INDIVIDUAL_LAYOUT;
|
||||
const IconFocus = () => {
|
||||
if (shouldMaximize)
|
||||
|
@ -695,7 +753,9 @@ class WebView extends Component {
|
|||
};
|
||||
return (
|
||||
<div
|
||||
className={cx(styles.webViewContainer)}
|
||||
className={cx(styles.webViewContainer, {
|
||||
[styles.withMarginRight]: isResponsive,
|
||||
})}
|
||||
style={{
|
||||
width: deviceStyles.width * zoomLevel,
|
||||
height: deviceStyles.height * zoomLevel + 40,
|
||||
|
@ -742,21 +802,6 @@ class WebView extends Component {
|
|||
<DeviceRotateIcon height={17} color={iconsColor} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip title="Disable event mirroring">
|
||||
<div
|
||||
className={cx(
|
||||
styles.webViewToolbarIcons,
|
||||
commonStyles.icons,
|
||||
commonStyles.enabled,
|
||||
{
|
||||
[commonStyles.selected]: this.state.isUnplugged,
|
||||
}
|
||||
)}
|
||||
onClick={this._unPlug}
|
||||
>
|
||||
<UnplugIcon height={30} color={iconsColor} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={isMuted ? 'Unmute' : 'Mute'}
|
||||
disableFocusListener={true}
|
||||
|
@ -799,9 +844,7 @@ class WebView extends Component {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cx(styles.deviceContainer, {
|
||||
[styles.devToolsActive]: this._isDevToolsOpen(),
|
||||
})}
|
||||
className={cx(styles.deviceContainer)}
|
||||
style={{
|
||||
width: deviceStyles.width,
|
||||
transform: `scale(${zoomLevel})`,
|
||||
|
|
|
@ -10,6 +10,7 @@ import path from 'path';
|
|||
import fs from 'fs-extra';
|
||||
import PromiseWorker from 'promise-worker';
|
||||
import NotificationMessage from '../NotificationMessage';
|
||||
import {userPreferenceSettings} from '../../settings/userPreferenceSettings';
|
||||
|
||||
const mergeImg = Promise.promisifyAll(_mergeImg);
|
||||
|
||||
|
@ -174,10 +175,11 @@ function _getScreenshotFileName(
|
|||
.replace(/:/g, '.')
|
||||
.toUpperCase()}`;
|
||||
const directoryPath = createSeparateDir ? `${dateString}/` : '';
|
||||
const userSelectedScreenShotSavePath = userPreferenceSettings.getScreenShotSavePath();
|
||||
return {
|
||||
dir: path.join(
|
||||
os.homedir(),
|
||||
`Desktop/Responsively-Screenshots`,
|
||||
userSelectedScreenShotSavePath ||
|
||||
path.join(os.homedir(), `Desktop/Responsively-Screenshots`),
|
||||
directoryPath
|
||||
),
|
||||
file: `${getWebsiteName(address)} - ${device.name.replace(
|
||||
|
|
|
@ -15,18 +15,6 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.webViewToolbarLeft .webViewToolbarIcons {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.webViewToolbarRight {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.webViewToolbarRight .webViewToolbarIcons {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.webViewToolbarIcons {
|
||||
padding: 0 2px;
|
||||
align-items: center;
|
||||
|
@ -36,15 +24,22 @@
|
|||
width: 30px;
|
||||
}
|
||||
|
||||
.webViewToolbarRight {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.webViewToolbarLeft .webViewToolbarIcons {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.webViewToolbarRight .webViewToolbarIcons {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.deviceContainer {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
transform-origin: top left;
|
||||
border: 3px solid #687cee00;
|
||||
}
|
||||
|
||||
.devToolsActive {
|
||||
border: 3px solid #6075ef;
|
||||
}
|
||||
|
||||
.device {
|
||||
|
@ -161,5 +156,9 @@
|
|||
}
|
||||
|
||||
.resizableView {
|
||||
margin: 0 1rem;
|
||||
margin: 0 1rem 1rem 0;
|
||||
}
|
||||
|
||||
.withMarginRight {
|
||||
margin-right: 5rem;
|
||||
}
|
||||
|
|
43
desktop-app/app/components/icons/Network.js
Normal file
43
desktop-app/app/components/icons/Network.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
import React, {Fragment} from 'react';
|
||||
|
||||
export default ({width, height, color, padding, margin}) => (
|
||||
<Fragment>
|
||||
<svg
|
||||
height={height}
|
||||
width={width}
|
||||
fill={color}
|
||||
style={{padding, margin}}
|
||||
viewBox="0 0 269.393 269.393"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
className="muteIcon"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<path
|
||||
d="M134.696,0C60.424,0,0,60.425,0,134.696s60.424,134.696,134.696,134.696s134.696-60.425,134.696-134.696
|
||||
S208.968,0,134.696,0z M136.869,252.518c-12.91,0-25.84-8.779-36.409-24.721c-4.421-6.669-8.316-14.428-11.614-22.979h38.351v20.879
|
||||
c0,4.143,3.358,7.5,7.5,7.5s7.5-3.357,7.5-7.5v-20.879h42.616C173.729,233.559,156.188,252.518,136.869,252.518z M15.242,142.196
|
||||
H39.23c2.921,11.683,12.5,20.75,24.457,22.928c1.12,8.63,2.689,16.884,4.66,24.693H28.471
|
||||
C20.961,175.403,16.303,159.28,15.242,142.196z M142.196,17.372c13.801,2.521,26.969,14.745,37.083,34.544
|
||||
c-3.816,3.294-6.688,7.649-8.15,12.607h-28.934V17.372z M196.607,83.585c-6.375,0-11.563-5.187-11.563-11.563
|
||||
s5.187-11.563,11.563-11.563s11.563,5.187,11.563,11.563S202.983,83.585,196.607,83.585z M171.13,79.522
|
||||
c3.01,10.206,11.992,17.875,22.887,18.934c1.574,9.312,2.518,18.938,2.841,28.74h-54.661V79.522H171.13z M127.196,127.196H99.257
|
||||
c-2.597-10.386-10.455-18.707-20.568-21.958c1.209-8.956,2.981-17.578,5.238-25.716h43.269V127.196z M69.244,150.634
|
||||
c-8.788,0-15.938-7.149-15.938-15.938s7.149-15.938,15.938-15.938s15.938,7.149,15.938,15.938S78.032,150.634,69.244,150.634z
|
||||
M63.697,104.267c-11.961,2.175-21.545,11.243-24.467,22.93H15.242c1.063-17.104,5.731-33.247,13.257-47.674H68.42
|
||||
C66.401,87.448,64.81,95.734,63.697,104.267z M78.7,164.15c10.108-3.253,17.961-11.572,20.557-21.954h27.939v47.621h-43.29
|
||||
C81.657,181.696,79.906,173.082,78.7,164.15z M142.196,189.817v-47.621h54.667c-0.576,17.011-3.092,33.174-7.111,47.621H142.196z
|
||||
M211.869,142.196h18.328c4.142,0,7.5-3.357,7.5-7.5s-3.358-7.5-7.5-7.5h-18.331c-0.332-10.757-1.371-21.329-3.116-31.561
|
||||
c6.386-3.297,11.269-9.105,13.336-16.113h18.809c8.618,16.521,13.499,35.287,13.499,55.174c0,19.866-4.871,38.614-13.471,55.121
|
||||
h-35.547C209.143,174.965,211.356,158.863,211.869,142.196z M231.602,64.522h-9.517c-3.245-11.005-13.435-19.063-25.477-19.063
|
||||
c-1.242,0-2.463,0.092-3.66,0.258c-4.421-8.751-9.442-16.33-14.927-22.601C199.615,31.531,218.182,46.041,231.602,64.522z
|
||||
M127.196,15.242v49.281H88.854c3.211-8.316,6.994-15.876,11.277-22.426c2.267-3.467,1.294-8.115-2.173-10.382
|
||||
c-3.467-2.268-8.115-1.293-10.382,2.173c-5.761,8.811-10.693,19.19-14.666,30.635H37.791
|
||||
C58.186,36.436,90.473,17.524,127.196,15.242z M37.752,204.817H72.81c6.236,18.028,14.801,32.962,24.934,43.73
|
||||
C73.41,240.63,52.477,225.118,37.752,204.817z M178.108,246.242c4.755-5.473,9.176-11.948,13.18-19.369
|
||||
c3.662-6.787,6.855-14.192,9.587-22.056h30.765C218.235,223.299,199.686,237.815,178.108,246.242z"
|
||||
/>
|
||||
</svg>
|
||||
</Fragment>
|
||||
);
|
|
@ -2,3 +2,4 @@ export const DEVICE_MANAGER = 'DEVICE_MANAGER';
|
|||
export const SCREENSHOT_MANAGER = 'SCREENSHOT_MANAGER';
|
||||
export const USER_PREFERENCES = 'USER_PREFERENCES';
|
||||
export const EXTENSIONS_MANAGER = 'EXTENSIONS_MANAGER';
|
||||
export const NETWORK_CONFIGURATION = 'NETWORK_CONFIGURATION';
|
||||
|
|
1
desktop-app/app/constants/browserSync.js
Normal file
1
desktop-app/app/constants/browserSync.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const BROWSER_SYNC_VERSION = '2.26.7';
|
|
@ -1305,7 +1305,7 @@ export default {
|
|||
id: '34',
|
||||
type: 'emulated-device',
|
||||
device: {
|
||||
'show-by-default': true,
|
||||
'show-by-default': false,
|
||||
title: 'Responsive Mode',
|
||||
screen: {
|
||||
horizontal: {
|
||||
|
@ -1314,13 +1314,14 @@ export default {
|
|||
},
|
||||
'device-pixel-ratio': 2,
|
||||
vertical: {
|
||||
width: 790,
|
||||
height: 500,
|
||||
width: 500,
|
||||
height: 790,
|
||||
},
|
||||
},
|
||||
capabilities: ['responsive'],
|
||||
'user-agent': '',
|
||||
type: 'notebook',
|
||||
capabilities: ['responsive', 'mobile'],
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
|
||||
type: 'phone',
|
||||
modes: [
|
||||
{
|
||||
title: 'default',
|
||||
|
|
|
@ -13,5 +13,8 @@ export const ENABLE_INSPECTOR_ALL_DEVICES = 'ENABLE_INSPECTOR_ALL_DEVICES';
|
|||
export const DISABLE_INSPECTOR_ALL_DEVICES = 'DISABLE_INSPECTOR_ALL_DEVICES';
|
||||
export const STOP_LOADING = 'STOP_LOADING';
|
||||
|
||||
export const SET_NETWORK_TROTTLING_PROFILE = 'SET_NETWORK_TROTTLING_PROFILE';
|
||||
export const CLEAR_NETWORK_CACHE = 'CLEAR_NETWORK_CACHE';
|
||||
|
||||
// status bar events
|
||||
export const STATUS_BAR_VISIBILITY_CHANGE = 'status-bar-visibility-change';
|
||||
|
|
1
desktop-app/app/constants/searchResultSettings.js
Normal file
1
desktop-app/app/constants/searchResultSettings.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const ADD_SEARCH_RESULTS = 'ADD_SEARCH_RESULTS';
|
|
@ -1,5 +1,6 @@
|
|||
export const ACTIVE_DEVICES = 'activeDevices';
|
||||
export const CUSTOM_DEVICES = 'customDevices';
|
||||
export const USER_PREFERENCES = 'userPreferences';
|
||||
export const NETWORK_CONFIGURATION = 'networkConfiguration';
|
||||
export const BOOKMARKS = 'bookmarks';
|
||||
export const STATUS_BAR_VISIBILITY = 'statusBarVisibility';
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import NetworkConfiguration from '../../components/NetworkConfiguration';
|
||||
import * as NetworkConfigActions from '../../actions/networkConfig';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
throttling: state.browser.networkConfiguration.throttling,
|
||||
// proxy: state.browser.networkConfiguration.proxy,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(NetworkConfigActions, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(NetworkConfiguration);
|
|
@ -87,7 +87,11 @@ export default class Root extends Component<Props> {
|
|||
document.addEventListener('wheel', this.onWheel);
|
||||
|
||||
registerShortcut(
|
||||
{id: 'ZoomIn', title: 'Zoom In', accelerators: ['mod+=', 'mod+shift+=']},
|
||||
{
|
||||
id: 'ZoomIn',
|
||||
title: 'Zoom In',
|
||||
accelerators: ['mod+=', 'mod++', 'mod+shift+='],
|
||||
},
|
||||
() => {
|
||||
store.dispatch(onZoomChange(store.getState().browser.zoomLevel + 0.1));
|
||||
},
|
||||
|
@ -224,12 +228,16 @@ export default class Root extends Component<Props> {
|
|||
);
|
||||
};
|
||||
|
||||
onWheel = (e) => {
|
||||
onWheel = e => {
|
||||
if (e.ctrlKey) {
|
||||
const {store} = this.props
|
||||
store.dispatch(onZoomChange(store.getState().browser.zoomLevel + (e.deltaY < 0 ? 0.1 : -0.1)));
|
||||
}
|
||||
const {store} = this.props;
|
||||
store.dispatch(
|
||||
onZoomChange(
|
||||
store.getState().browser.zoomLevel + (e.deltaY < 0 ? 0.1 : -0.1)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {store, history} = this.props;
|
||||
|
|
|
@ -39,6 +39,13 @@ import {initMainShortcutManager} from './shortcut-manager/main-shortcut-manager'
|
|||
import {appUpdater} from './app-updater';
|
||||
import trimStart from 'lodash/trimStart';
|
||||
import isURL from 'validator/lib/isURL';
|
||||
import {
|
||||
initBrowserSync,
|
||||
getBrowserSyncHost,
|
||||
getBrowserSyncEmbedScriptURL,
|
||||
} from './utils/browserSync';
|
||||
import {getHostFromURL} from './utils/urlUtils';
|
||||
import browserSync from 'browser-sync';
|
||||
|
||||
const path = require('path');
|
||||
const chokidar = require('chokidar');
|
||||
|
@ -46,9 +53,9 @@ const URL = require('url').URL;
|
|||
|
||||
migrateDeviceSchema();
|
||||
|
||||
app &&
|
||||
app.commandLine &&
|
||||
if (app && app.commandLine) {
|
||||
app.commandLine.appendSwitch('remote-debugging-port', '31313');
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
Sentry.init({
|
||||
|
@ -108,16 +115,14 @@ app.on('open-url', async (event, url) => {
|
|||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (
|
||||
false &&
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
['win32', 'darwin'].includes(process.platform)
|
||||
) {
|
||||
app.removeAsDefaultProtocolClient(protocol);
|
||||
}
|
||||
hasActiveWindow = false;
|
||||
ipcMain.removeAllListeners();
|
||||
ipcMain.removeHandler('install-extension');
|
||||
ipcMain.removeHandler('get-local-extension-path');
|
||||
ipcMain.removeHandler('get-screen-shot-save-path');
|
||||
ipcMain.removeHandler('request-browser-sync');
|
||||
// Respect the OSX convention of having the application in memory even
|
||||
// after all windows have been closed
|
||||
hasActiveWindow = false;
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
|
@ -126,7 +131,10 @@ app.on('window-all-closed', () => {
|
|||
app.on(
|
||||
'certificate-error',
|
||||
(event, webContents, url, error, certificate, callback) => {
|
||||
if ((settings.get(USER_PREFERENCES) || {}).disableSSLValidation === true) {
|
||||
if (
|
||||
getHostFromURL(url) === getBrowserSyncHost() ||
|
||||
(settings.get(USER_PREFERENCES) || {}).disableSSLValidation === true
|
||||
) {
|
||||
event.preventDefault();
|
||||
callback(true);
|
||||
}
|
||||
|
@ -200,6 +208,7 @@ const openUrl = url => {
|
|||
|
||||
const createWindow = async () => {
|
||||
hasActiveWindow = true;
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
await installExtensions();
|
||||
}
|
||||
|
@ -221,8 +230,6 @@ const createWindow = async () => {
|
|||
icon: iconPath,
|
||||
});
|
||||
|
||||
ipcMain.removeAllListeners();
|
||||
|
||||
mainWindow.loadURL(`file://${__dirname}/app.html`);
|
||||
|
||||
mainWindow.webContents.on('did-finish-load', () => {
|
||||
|
@ -241,6 +248,8 @@ const createWindow = async () => {
|
|||
}
|
||||
});
|
||||
|
||||
await initBrowserSync();
|
||||
|
||||
initMainShortcutManager();
|
||||
|
||||
const onResize = () => {
|
||||
|
@ -363,6 +372,24 @@ const createWindow = async () => {
|
|||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-screen-shot-save-path', async event => {
|
||||
try {
|
||||
const {filePaths = []} = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory'],
|
||||
});
|
||||
return filePaths[0];
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('request-browser-sync', (event, data) => {
|
||||
const browserSyncOptions = {
|
||||
url: getBrowserSyncEmbedScriptURL(),
|
||||
};
|
||||
return browserSyncOptions;
|
||||
});
|
||||
|
||||
ipcMain.on('open-devtools', (event, ...args) => {
|
||||
const {webViewId, bounds, mode} = args[0];
|
||||
if (!webViewId) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
import {appUpdater, AppUpdaterStatus} from './app-updater';
|
||||
import {statusBarSettings} from './settings/statusBarSettings';
|
||||
import {STATUS_BAR_VISIBILITY_CHANGE} from './constants/pubsubEvents';
|
||||
import {userPreferenceSettings} from './settings/userPreferenceSettings';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
|
@ -200,10 +201,10 @@ export default class MenuBuilder {
|
|||
label: 'Open Screenshots folder',
|
||||
click: () => {
|
||||
try {
|
||||
const dir = path.join(
|
||||
os.homedir(),
|
||||
`Desktop/Responsively-Screenshots`
|
||||
);
|
||||
const userSelectedScreenShotSavePath = userPreferenceSettings.getScreenShotSavePath();
|
||||
const dir =
|
||||
userSelectedScreenShotSavePath ||
|
||||
userPreferenceSettings.getDefaultScreenshotpath();
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,10 @@ import {
|
|||
TOGGLE_ALL_DEVICES_MUTED,
|
||||
TOGGLE_DEVICE_MUTED,
|
||||
} from '../actions/browser';
|
||||
import {
|
||||
CHANGE_ACTIVE_THROTTLING_PROFILE,
|
||||
SAVE_THROTTLING_PROFILES,
|
||||
} from '../actions/networkConfig';
|
||||
import type {Action} from './types';
|
||||
import getAllDevices from '../constants/devices';
|
||||
import type {Device} from '../constants/devices';
|
||||
|
@ -40,6 +44,7 @@ import {
|
|||
ACTIVE_DEVICES,
|
||||
USER_PREFERENCES,
|
||||
CUSTOM_DEVICES,
|
||||
NETWORK_CONFIGURATION,
|
||||
} from '../constants/settingKeys';
|
||||
import {
|
||||
getHomepage,
|
||||
|
@ -47,6 +52,7 @@ import {
|
|||
saveHomepage,
|
||||
saveLastOpenedAddress,
|
||||
} from '../utils/navigatorUtils';
|
||||
import {updateExistingUrl} from '../services/searchUrlSuggestions';
|
||||
|
||||
export const FILTER_FIELDS = {
|
||||
OS: 'OS',
|
||||
|
@ -112,12 +118,27 @@ type UserPreferenceType = {
|
|||
drawerState: boolean,
|
||||
devToolsOpenMode: DevToolsOpenModeType,
|
||||
deviceOutlineStyle: string,
|
||||
zoomLevel: number,
|
||||
};
|
||||
|
||||
type FilterFieldType = FILTER_FIELDS.OS | FILTER_FIELDS.DEVICE_TYPE;
|
||||
|
||||
type FilterType = {[key: FilterFieldType]: Array<string>};
|
||||
|
||||
type NetworkThrottlingProfileType = {
|
||||
type: 'Online' | 'Offline' | 'Preset' | 'Custom',
|
||||
title: string,
|
||||
downloadKps: number,
|
||||
uploadKps: number,
|
||||
latencyMs: number,
|
||||
active: boolean,
|
||||
};
|
||||
|
||||
type NetworkConfigurationType = {
|
||||
throttling: NetworkThrottlingProfileType[],
|
||||
// proxy: NetworkProxyProfileType[],
|
||||
};
|
||||
|
||||
export type BrowserStateType = {
|
||||
devices: Array<Device>,
|
||||
homepage: string,
|
||||
|
@ -135,6 +156,7 @@ export type BrowserStateType = {
|
|||
isInspecting: boolean,
|
||||
windowSize: WindowSizeType,
|
||||
allDevicesMuted: boolean,
|
||||
networkConfiguration: NetworkConfigurationType,
|
||||
};
|
||||
|
||||
let _activeDevices = null;
|
||||
|
@ -229,6 +251,57 @@ function _updateFileWatcher(newURL) {
|
|||
else ipcRenderer.send('stop-watcher');
|
||||
}
|
||||
|
||||
function getDefaultNetworkThrottlingProfiles(): NetworkThrottlingProfileType[] {
|
||||
return [
|
||||
{
|
||||
type: 'Online',
|
||||
title: 'Online',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
type: 'Offline',
|
||||
title: 'Offline',
|
||||
downloadKps: 0,
|
||||
uploadKps: 0,
|
||||
latencyMs: 0,
|
||||
},
|
||||
// https://github.com/ChromeDevTools/devtools-frontend/blob/4f404fa8beab837367e49f68e29da427361b1f81/front_end/sdk/NetworkManager.js#L251-L265
|
||||
{
|
||||
type: 'Preset',
|
||||
title: 'Slow 3G',
|
||||
downloadKps: 400,
|
||||
uploadKps: 400,
|
||||
latencyMs: 2000,
|
||||
},
|
||||
{
|
||||
type: 'Preset',
|
||||
title: 'Fast 3G',
|
||||
downloadKps: 1475,
|
||||
uploadKps: 675,
|
||||
latencyMs: 563,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function _getNetworkConfiguration(): NetworkConfigurationType {
|
||||
const ntwrk: NetworkConfigurationType =
|
||||
settings.get(NETWORK_CONFIGURATION) || {};
|
||||
|
||||
if (ntwrk.throttling == null)
|
||||
ntwrk.throttling = getDefaultNetworkThrottlingProfiles();
|
||||
|
||||
// if (ntwrk.proxy == null)
|
||||
// ntwrk.proxy = getDefaultNetworkProxyProfiles();
|
||||
|
||||
return ntwrk;
|
||||
}
|
||||
|
||||
function _setNetworkConfiguration(
|
||||
networkConfiguration: NetworkConfigurationType
|
||||
) {
|
||||
settings.set(NETWORK_CONFIGURATION, networkConfiguration);
|
||||
}
|
||||
|
||||
export default function browser(
|
||||
state: BrowserStateType = {
|
||||
devices: _getActiveDevices(),
|
||||
|
@ -237,7 +310,7 @@ export default function browser(
|
|||
? getLastOpenedAddress()
|
||||
: getHomepage(),
|
||||
currentPageMeta: {},
|
||||
zoomLevel: 0.6,
|
||||
zoomLevel: _getUserPreferences().zoomLevel || 0.6,
|
||||
previousZoomLevel: null,
|
||||
scrollPosition: {x: 0, y: 0},
|
||||
navigatorStatus: {backEnabled: false, forwardEnabled: false},
|
||||
|
@ -269,6 +342,7 @@ export default function browser(
|
|||
isInspecting: false,
|
||||
windowSize: getWindowSize(),
|
||||
allDevicesMuted: false,
|
||||
networkConfiguration: _getNetworkConfiguration(),
|
||||
},
|
||||
action: Action
|
||||
) {
|
||||
|
@ -276,6 +350,7 @@ export default function browser(
|
|||
case NEW_ADDRESS:
|
||||
saveLastOpenedAddress(action.address);
|
||||
_updateFileWatcher(action.address);
|
||||
updateExistingUrl(action.address);
|
||||
return {...state, address: action.address, currentPageMeta: {}};
|
||||
case NEW_PAGE_META_FIELD:
|
||||
return {
|
||||
|
@ -290,6 +365,10 @@ export default function browser(
|
|||
saveHomepage(homepage);
|
||||
return {...state, homepage};
|
||||
case NEW_ZOOM_LEVEL:
|
||||
_setUserPreferences({
|
||||
...state.userPreferences,
|
||||
zoomLevel: action.zoomLevel,
|
||||
});
|
||||
return {...state, zoomLevel: action.zoomLevel};
|
||||
case NEW_SCROLL_POSITION:
|
||||
return {...state, scrollPosition: action.scrollPosition};
|
||||
|
@ -380,6 +459,34 @@ export default function browser(
|
|||
allDevicesMuted: state.devices.every(x => x.isMuted),
|
||||
devices: [...state.devices],
|
||||
};
|
||||
case CHANGE_ACTIVE_THROTTLING_PROFILE:
|
||||
const throttling = state.networkConfiguration.throttling;
|
||||
const activeProfile = throttling.find(x => x.title === action.title);
|
||||
if (activeProfile != null) {
|
||||
throttling.forEach(x => (x.active = false));
|
||||
activeProfile.active = true;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
networkConfiguration: {
|
||||
...state.networkConfiguration,
|
||||
throttling: [...throttling],
|
||||
},
|
||||
};
|
||||
case SAVE_THROTTLING_PROFILES:
|
||||
action.profiles.forEach(x => (x.active = false));
|
||||
action.profiles[0].active = true;
|
||||
_setNetworkConfiguration({
|
||||
...state.networkConfiguration,
|
||||
throttling: action.profiles,
|
||||
});
|
||||
return {
|
||||
...state,
|
||||
networkConfiguration: {
|
||||
...state.networkConfiguration,
|
||||
throttling: action.profiles,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
18
desktop-app/app/service/browserSync.js
Normal file
18
desktop-app/app/service/browserSync.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {ipcRenderer} from 'electron';
|
||||
import browserSync from 'browser-sync';
|
||||
|
||||
let browserSyncOptions;
|
||||
|
||||
initializeBrowserSyncOptions();
|
||||
|
||||
async function initializeBrowserSyncOptions() {
|
||||
if (!browserSyncOptions) {
|
||||
browserSyncOptions = await ipcRenderer.invoke('request-browser-sync');
|
||||
}
|
||||
}
|
||||
|
||||
export function getBrowserSyncEmbedScriptURL() {
|
||||
if (browserSyncOptions) {
|
||||
return browserSyncOptions.url;
|
||||
}
|
||||
}
|
78
desktop-app/app/services/searchUrlSuggestions/index.js
Normal file
78
desktop-app/app/services/searchUrlSuggestions/index.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
import React from 'react';
|
||||
import filter from 'lodash/filter';
|
||||
import settings from 'electron-settings';
|
||||
import {ADD_SEARCH_RESULTS} from '../../constants/searchResultSettings';
|
||||
|
||||
let previousSearchResults = settings.get(ADD_SEARCH_RESULTS);
|
||||
|
||||
export const getExistingSearchResults = () => settings.get(ADD_SEARCH_RESULTS);
|
||||
|
||||
const addUrlToSearchResults = url => settings.set(ADD_SEARCH_RESULTS, url);
|
||||
|
||||
const deleteSearchResults = () => settings.delete(ADD_SEARCH_RESULTS);
|
||||
|
||||
const _sortedExistingUrlSearchResult = filteredData => {
|
||||
// Most visited site should appear first in the list
|
||||
filteredData.sort((a, b) => {
|
||||
if (a.visitedCount > b.visitedCount) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.visitedCount < b.visitedCount) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return filteredData;
|
||||
};
|
||||
|
||||
export const searchUrlUtils = url => {
|
||||
if (url) {
|
||||
const filteredData = filter(previousSearchResults, eachResult =>
|
||||
eachResult.url.toLowerCase().includes(url)
|
||||
);
|
||||
const finalResult = _sortedExistingUrlSearchResult(filteredData);
|
||||
return finalResult;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const normalizeURL = url => {
|
||||
if (url.indexOf('?') === -1 && !url.endsWith('/')) {
|
||||
url = `${url}/`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export const updateExistingUrl = url => {
|
||||
url = normalizeURL(url);
|
||||
if (previousSearchResults?.length) {
|
||||
let updatedSearchResults = [...previousSearchResults];
|
||||
|
||||
const index = updatedSearchResults.findIndex(
|
||||
eachSearchResult => eachSearchResult.url === url
|
||||
);
|
||||
|
||||
if (index !== (undefined || -1 || null)) {
|
||||
updatedSearchResults[index].visitedCount =
|
||||
1 + updatedSearchResults[index].visitedCount;
|
||||
} else {
|
||||
updatedSearchResults = [
|
||||
{url, visitedCount: 1},
|
||||
...updatedSearchResults,
|
||||
].slice(0, 300);
|
||||
}
|
||||
|
||||
addUrlToSearchResults(updatedSearchResults);
|
||||
previousSearchResults = updatedSearchResults;
|
||||
} else {
|
||||
const addNewUrl = [];
|
||||
addNewUrl.push({
|
||||
url,
|
||||
visitedCount: 1,
|
||||
});
|
||||
addUrlToSearchResults(addNewUrl);
|
||||
previousSearchResults = addNewUrl;
|
||||
}
|
||||
return previousSearchResults;
|
||||
};
|
16
desktop-app/app/settings/userPreferenceSettings.js
Normal file
16
desktop-app/app/settings/userPreferenceSettings.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import settings from 'electron-settings';
|
||||
import {USER_PREFERENCES} from '../constants/settingKeys';
|
||||
import * as os from 'os';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
class UserPreferenceSettings {
|
||||
getScreenShotSavePath = () =>
|
||||
settings.get(USER_PREFERENCES).screenShotSavePath;
|
||||
getDefaultScreenshotpath = () =>
|
||||
path.join(os.homedir(), `Desktop/Responsively-Screenshots`);
|
||||
}
|
||||
|
||||
const userPreferenceSettingsInstance = new UserPreferenceSettings();
|
||||
|
||||
export {userPreferenceSettingsInstance as userPreferenceSettings};
|
37
desktop-app/app/utils/browserSync.js
Normal file
37
desktop-app/app/utils/browserSync.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const browserSync = require('browser-sync').create();
|
||||
|
||||
import {BROWSER_SYNC_VERSION} from '../constants/browserSync';
|
||||
|
||||
export async function initBrowserSync() {
|
||||
if (!browserSync.active) {
|
||||
await initInstance();
|
||||
}
|
||||
}
|
||||
|
||||
export function getBrowserSyncHost() {
|
||||
return `localhost:${browserSync.getOption('port')}`;
|
||||
}
|
||||
|
||||
export function getBrowserSyncEmbedScriptURL() {
|
||||
return `https://${getBrowserSyncHost()}/browser-sync/browser-sync-client.js?v=${BROWSER_SYNC_VERSION}`;
|
||||
}
|
||||
|
||||
async function initInstance(): Promise<> {
|
||||
return new Promise((resolve, reject) => {
|
||||
browserSync.init(
|
||||
{
|
||||
open: false,
|
||||
localOnly: true,
|
||||
https: true,
|
||||
notify: false,
|
||||
ui: false,
|
||||
},
|
||||
(err, bs) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(bs);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import settings from 'electron-settings';
|
||||
import path from 'path';
|
||||
|
||||
const HOME_PAGE = 'HOME_PAGE';
|
||||
const LAST_OPENED_ADDRESS = 'LAST_OPENED_ADDRESS';
|
||||
|
|
8
desktop-app/app/utils/urlUtils.js
Normal file
8
desktop-app/app/utils/urlUtils.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export function getHostFromURL(url: String) {
|
||||
let host = '';
|
||||
if (url) {
|
||||
const urlObj = new URL(url);
|
||||
host = urlObj.host;
|
||||
}
|
||||
return host;
|
||||
}
|
|
@ -1563,8 +1563,8 @@ lodash.cond@^4.3.0:
|
|||
resolved "http://registry.npm.taobao.org/lodash.cond/download/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
|
||||
|
||||
lodash@^4.0.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
|
||||
longest@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Responsively-App",
|
||||
"productName": "ResponsivelyApp",
|
||||
"version": "0.6.1",
|
||||
"version": "0.8.0",
|
||||
"description": "A developer-friendly browser for developing responsive web apps",
|
||||
"scripts": {
|
||||
"build": "concurrently \"yarn build-main\" \"yarn build-renderer\"",
|
||||
|
@ -190,21 +190,21 @@
|
|||
"@babel/register": "^7.0.0",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-jest": "^26.1.0",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-plugin-dev-expression": "^0.2.1",
|
||||
"babel-plugin-transform-es2015-classes": "^6.24.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.20",
|
||||
"chalk": "^2.4.1",
|
||||
"concurrently": "^4.1.0",
|
||||
"concurrently": "^5.2.0",
|
||||
"connected-react-router": "^6.5.2",
|
||||
"cross-env": "^5.2.0",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"css-loader": "^1.0.1",
|
||||
"detect-port": "^1.3.0",
|
||||
"electron": "^8.3.0",
|
||||
"electron": "^8.4.0",
|
||||
"electron-builder": "^22.6.1",
|
||||
"electron-devtools-installer": "^3.0.0",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"enzyme": "^3.7.0",
|
||||
"enzyme-adapter-react-16": "^1.7.0",
|
||||
"enzyme-to-json": "^3.3.4",
|
||||
|
@ -216,7 +216,7 @@
|
|||
"eslint-plugin-compat": "^2.6.3",
|
||||
"eslint-plugin-flowtype": "^3.2.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-jest": "^22.0.0",
|
||||
"eslint-plugin-jest": "^23.18.0",
|
||||
"eslint-plugin-jsx-a11y": "6.1.2",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
|
@ -225,10 +225,10 @@
|
|||
"file-loader": "^2.0.0",
|
||||
"flow-bin": "^0.77.0",
|
||||
"flow-runtime": "^0.17.0",
|
||||
"flow-typed": "^2.5.1",
|
||||
"flow-typed": "^3.2.0",
|
||||
"husky": "^1.1.4",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^23.6.0",
|
||||
"jest": "^26.1.0",
|
||||
"lint-staged": "^8.1.0",
|
||||
"mini-css-extract-plugin": "^0.4.4",
|
||||
"node-sass": "^4.10.0",
|
||||
|
@ -240,13 +240,12 @@
|
|||
"rimraf": "^2.6.2",
|
||||
"sass-loader": "^7.1.0",
|
||||
"sinon": "^7.1.1",
|
||||
"spectron": "^5.0.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"stylelint": "^9.8.0",
|
||||
"stylelint": "^13.6.1",
|
||||
"stylelint-config-prettier": "^4.0.0",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"terser-webpack-plugin": "^1.1.0",
|
||||
"testcafe": "^0.23.2",
|
||||
"testcafe": "^1.8.8",
|
||||
"testcafe-browser-provider-electron": "0.0.11",
|
||||
"testcafe-live": "^0.1.4",
|
||||
"testcafe-react-selectors": "^3.0.0",
|
||||
|
@ -265,6 +264,7 @@
|
|||
"@material-ui/lab": "^4.0.0-alpha.26",
|
||||
"@sentry/electron": "^1.3.0",
|
||||
"bluebird": "^3.7.2",
|
||||
"browser-sync": "^2.26.7",
|
||||
"chokidar": "^3.4.0",
|
||||
"chrome-remote-interface": "^0.28.2",
|
||||
"classnames": "^2.2.6",
|
||||
|
@ -281,16 +281,17 @@
|
|||
"flwww": "^2.0.10",
|
||||
"history": "^4.7.2",
|
||||
"jimp": "^0.12.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash": "^4.17.19",
|
||||
"merge-img": "^2.1.3",
|
||||
"mousetrap": "^1.6.5",
|
||||
"promise-worker": "^2.0.1",
|
||||
"pubsub.js": "^1.5.2",
|
||||
"re-resizable": "^6.4.0",
|
||||
"react": "^16.12.0",
|
||||
"react": "^16.13.1",
|
||||
"react-beautiful-dnd": "^11.0.5",
|
||||
"react-dom": "^16.12.0",
|
||||
"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-router": "^5.0.1",
|
||||
|
|
File diff suppressed because it is too large
Load diff
BIN
website/pages/assets/img/responsively-image.png
Normal file
BIN
website/pages/assets/img/responsively-image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 513 KiB |
|
@ -30,9 +30,10 @@
|
|||
<meta property="og:title" content="A Web developer's browser - Responsively App" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://responsively.app" />
|
||||
<meta property="og:description" content="A dev-tool that aids faster and precise responsive web development." />
|
||||
<meta property="og:image" content="https://responsively.app/assets/img/screenshot.png" />
|
||||
<meta name="twitter:card" content="summary"></meta>
|
||||
<meta property="og:description" content="A must-have devtool for web developers that aids faster and precise responsive web development." />
|
||||
<meta property="og:image" content="https://responsively.app/assets/img/responsively-image.png" />
|
||||
<meta name="twitter:image" content="https://responsively.app/assets/img/responsively-image.png"/>
|
||||
<meta name="twitter:card" content="summary_large_image"></meta>
|
||||
<meta name="twitter:site" content="@ResponsivelyApp"></meta>
|
||||
<link
|
||||
href="assets/css/loaders/loader-typing.css"
|
||||
|
|
|
@ -31,9 +31,10 @@
|
|||
<meta property="og:title" content="A Web developer's browser - Responsively App" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://responsively.app" />
|
||||
<meta property="og:description" content="A dev-tool that aids faster and precise responsive web development." />
|
||||
<meta property="og:image" content="https://responsively.app/assets/img/screenshot.png" />
|
||||
<meta name="twitter:card" content="summary"></meta>
|
||||
<meta property="og:description" content="A must-have devtool for web developers that aids faster and precise responsive web development." />
|
||||
<meta property="og:image" content="https://responsively.app/assets/img/responsively-image.png" />
|
||||
<meta name="twitter:image" content="https://responsively.app/assets/img/responsively-image.png"/>
|
||||
<meta name="twitter:card" content="summary_large_image"></meta>
|
||||
<meta name="twitter:site" content="@ResponsivelyApp"></meta>
|
||||
<link
|
||||
href="assets/css/loaders/loader-typing.css"
|
||||
|
|
|
@ -29,9 +29,10 @@
|
|||
<meta property="og:title" content="A Web developer's browser - Responsively App" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://responsively.app" />
|
||||
<meta property="og:description" content="A dev-tool that aids faster and precise responsive web development." />
|
||||
<meta property="og:image" content="https://responsively.app/assets/img/screenshot.png" />
|
||||
<meta name="twitter:card" content="summary"></meta>
|
||||
<meta property="og:description" content="A must-have devtool for web developers that aids faster and precise responsive web development." />
|
||||
<meta property="og:image" content="https://responsively.app/assets/img/responsively-image.png" />
|
||||
<meta name="twitter:image" content="https://responsively.app/assets/img/responsively-image.png"/>
|
||||
<meta name="twitter:card" content="summary_large_image"></meta>
|
||||
<meta name="twitter:site" content="@ResponsivelyApp"></meta>
|
||||
<link
|
||||
href="assets/css/loaders/loader-typing.css"
|
||||
|
|
|
@ -29,9 +29,10 @@
|
|||
<meta property="og:title" content="A Web developer's browser - Responsively App" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://responsively.app" />
|
||||
<meta property="og:description" content="A dev-tool that aids faster and precise responsive web development." />
|
||||
<meta property="og:image" content="https://responsively.app/assets/img/screenshot.png" />
|
||||
<meta name="twitter:card" content="summary"></meta>
|
||||
<meta property="og:description" content="A must-have devtool for web developers that aids faster and precise responsive web development." />
|
||||
<meta property="og:image" content="https://responsively.app/assets/img/responsively-image.png" />
|
||||
<meta name="twitter:image" content="https://responsively.app/assets/img/responsively-image.png"/>
|
||||
<meta name="twitter:card" content="summary_large_image"></meta>
|
||||
<meta name="twitter:site" content="@ResponsivelyApp"></meta>
|
||||
<link
|
||||
href="assets/css/loaders/loader-typing.css"
|
||||
|
|
Loading…
Reference in a new issue