1
0
mirror of https://github.com/pavlobu/deskreen.git synced 2025-05-27 21:00:08 -07:00

massive UI changes and improvements

This commit is contained in:
Pavlo Buidenkov 2020-09-01 20:45:56 +03:00
parent 1b063f2131
commit ff238702ed
88 changed files with 19046 additions and 230 deletions

View File

@ -57,4 +57,4 @@ package.json
# Entire app/client directory
app/client/*
app/client

View File

@ -13,13 +13,13 @@ jobs:
with:
node-version: 14
- name: yarn install from npmjs registry
run: |
yarn install --no-lockfile
yarn install-client:nolockfile
# - name: yarn install from npmjs registry
# run: |
# yarn install --no-lockfile
# yarn install-client:nolockfile
- name: Configure private AWS npm registry and install packages from it
if: ${{ failure() }}
# if: ${{ failure() }}
run: |
npm config set registry https://packages.deskreen.com/
npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"

View File

@ -46,13 +46,13 @@ jobs:
with:
node-version: 14
- name: yarn install from npmjs registry
run: |
yarn install --no-lockfile
yarn install-client:nolockfile
# - name: yarn install from npmjs registry
# run: |
# yarn install --no-lockfile
# yarn install-client:nolockfile
- name: Configure private AWS npm registry and install packages from it
if: ${{ failure() }}
# if: ${{ failure() }}
run: |
npm config set registry https://packages.deskreen.com/
npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"

View File

@ -21,13 +21,13 @@ jobs:
with:
node-version: 14
- name: yarn install from npmjs registry
run: |
yarn install --no-lockfile
yarn install-client:nolockfile
# - name: yarn install from npmjs registry
# run: |
# yarn install --no-lockfile
# yarn install-client:nolockfile
- name: Configure private AWS npm registry and install packages from it
if: ${{ failure() }}
# if: ${{ failure() }}
run: |
npm config set registry https://packages.deskreen.com/
npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint react/jsx-props-no-spreading: off */
import React from 'react';
import { Switch, Route } from 'react-router-dom';

View File

@ -6,28 +6,384 @@
@import '~normalize.css/normalize.css';
@import '~@blueprintjs/core/lib/css/blueprint.css';
@import '~react-flexbox-grid/dist/react-flexbox-grid.css';
@import '~fontsource-lexend-peta/index.css';
:root {
--dark-bg-color: #293742;
--light-bg-color: rgba(240, 248, 250, 1);
--light-btn-no-intent-color: rgb(218, 238, 243);
--dark-btn-no-intent-color: #394b59;
--custom-scrollbar-webkit-scrollbar-thumb-border-radius: 10px;
--custom-scrollbar-webkit-scrollbar-thumb-background-color: #8a9ba8;
--custom-scrollbar-webkit-scrollbar_background-color: rgba(0, 0, 0, 0);
--custom-scrollbar-webkit-scrollbar-width: 12px;
--custom-scrollbar-webkit-scrollbar-track-border-radius: 10px;
--custom-scrollbar-webkit-scrollbar-track-background-color: rgba(0, 0, 0, 0);
}
body {
position: relative;
color: white;
height: 100vh;
background-color: whitesmoke;
font-family: Arial, Helvetica, Helvetica Neue, serif;
overflow-y: hidden;
overflow: hidden;
background-color: var(--light-bg-color);
}
/* UI colors FOR LIGHT AND DARK THEME START */
.bp3-button:not([class*='bp3-intent-']) {
background-color: var(--light-btn-no-intent-color);
}
body.bp3-dark {
background-color: #293742;
background-color: var(--dark-bg-color) !important;
}
/* .bp3-button {
outline: none !important;
.bp3-dialog {
background-color: var(--light-bg-color) !important;
}
.bp3-control {
outline: none !important;
.bp3-dark .bp3-dialog {
background-color: var(--dark-bg-color) !important;
}
.bp3-popover .bp3-popover-arrow-fill {
fill: var(--light-bg-color);
}
.bp3-popover .bp3-popover-content {
background-color: var(--light-bg-color);
color: black;
}
.bp3-html-select > select {
background-color: var(--light-btn-no-intent-color);
}
.bp3-drawer {
background-color: var(--light-bg-color);
}
.bp3-card {
background-color: var(--light-bg-color);
}
/* for really small screen sizes (ex. Raspberry PI display etc. */
@media screen and (max-height: 419px) {
body {
overflow-y: scroll;
}
}
.react-toast-notifications__container {
overflow: hidden !important;
}
/* Connected Devices List button pulse START */
#top-panel-connected-devices-list-button.pulsing {
transform: scale(1);
animation: pulse-black-devices-list-button 0.75s infinite;
}
#top-panel-connected-devices-list-button.pulse-not-infinite {
transform: scale(1);
animation: pulse-black-devices-list-button 0.75s 4;
}
@keyframes pulse-black-devices-list-button {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(115, 134, 148, 0.7);
}
60% {
transform: scale(0.85);
box-shadow: 0 0 0 15px rgba(115, 134, 148, 0.3);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0);
}
}
/* Connected Devices List button pulse END */
/* For choose app or screen overlay popup without scrollbars! */
.bp3-overlay-scroll-container {
overflow-y: hidden !important;
}
/* help cursor when text hovered Connected Devices List */
#connected-devices-list-text-success:hover {
cursor: help;
}
/* react-toast-notifications progress bar more obvious look */
body
> div.react-toast-notifications__container
> div
> div
> div.react-toast-notifications__toast__icon-wrapper
> div {
background-color: rgba(0, 0, 0, 0.4);
}
.hide-toaster-progress {
height: 5px;
width: calc(100% + 87px) !important;
bottom: -11px !important;
left: -40px !important;
}
/* ALLOW CONNECTION ALERT BLINK ANIMATION START */
div.class-allow-device-to-connect-alert
> div.bp3-alert-body
> span
> svg
> path {
color: #a82a2a !important;
-webkit-animation: blink 0.75s infinite alternate; /* to blink 3 times instead of infinite write just 3 */
-moz-animation: blink 0.75s infinite alternate;
-ms-animation: blink 0.75s infinite alternate;
-o-animation: blink 0.75s infinite alternate;
animation: blink 0.75s infinite alternate;
}
@-webkit-keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
@-moz-keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
@-ms-keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
@-o-keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
@keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
/* ALLOW CONNECTION ALERT BLINK ANIMATION END */
/* Connected Device Info Button pulse animation START */
#connected-device-info-stepper-button {
transform: scale(1);
animation: pulse-black-connected-device 0.75s 3;
}
@keyframes pulse-black-connected-device {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(61, 204, 145, 0.7);
}
60% {
transform: scale(0.75);
box-shadow: 0 0 0 15px rgba(61, 204, 145, 0.3);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0);
}
}
/* Connected Device Info Button pulse animation END */
#settings-overlay-inner > div > div.bp3-tab-panel {
width: 100% !important;
}
/* settings panel tabs button left styles */
#settings-overlay-inner > div > div.bp3-tab-list {
background-color: rgba(0, 0, 0, 0.1);
padding: 8px;
/* height: 100%; */
}
/* settings inner 100% height regardless tab content height */
#settings-overlay-inner > div {
height: 100%;
}
.bp3-overlay-settings {
display: flex;
align-items: center;
justify-content: center;
}
/* .bp3-overlay-settings.bp3-overlay-content {
display: flex;
} */
/* TODO: move to appropriate style file in ShareEntireScreenOrAppWindowControlGroup */
#share-screen-or-app-btn-group > button > span {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
/* #root>div.MuiPaper-root.MuiStepper-root.MuiStepper-horizontal.MuiStepper-alternativeLabel.MuiPaper-elevation0>div:nth-child(1)>span>span.MuiStepLabel-iconContainer.MuiStepLabel-alternativeLabel>div {
transform: scale(1);
animation: pulse-black 2s infinite;
} */
.active-stepper-pulse-icon {
transform: scale(1);
animation: pulse-black 3s infinite;
}
@keyframes pulse-black {
0% {
transform: scale(0.9);
box-shadow: 0 0 0 0 rgba(191, 115, 38, 0.7);
}
60% {
transform: scale(1);
box-shadow: 0 0 0 12px rgba(255, 179, 102, 0.3);
}
100% {
transform: scale(0.9);
box-shadow: 0 0 0 20px rgba(0, 0, 0, 0);
}
}
/* TODO: move it to DeskreenStepper.css ! */
#step-label-deskreen > span.MuiStepLabel-labelContainer > span {
margin-top: 8px;
}
#share-screen-or-app-btn-group > button:nth-child(1):hover {
border-width: 10px;
}
.bp3-overlay::-webkit-scrollbar-track {
/* -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); */
border-radius: var(--custom-scrollbar-webkit-scrollbar-track-border-radius);
background-color: var(
--custom-scrollbar-webkit-scrollbar-track-background-color
);
}
.bp3-overlay::-webkit-scrollbar {
width: var(--custom-scrollbar-webkit-scrollbar-width);
background-color: var(--custom-scrollbar-webkit-scrollbar_background-color);
}
.bp3-overlay::-webkit-scrollbar-thumb {
border-radius: var(--custom-scrollbar-webkit-scrollbar-thumb-border-radius);
/* -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); */
background-color: var(
--custom-scrollbar-webkit-scrollbar-thumb-background-color
);
}
.bp3-drawer::-webkit-scrollbar-track {
/* -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); */
border-radius: var(--custom-scrollbar-webkit-scrollbar-track-border-radius);
background-color: var(
--custom-scrollbar-webkit-scrollbar-track-background-color
);
}
.bp3-drawer::-webkit-scrollbar {
width: var(--custom-scrollbar-webkit-scrollbar-width);
background-color: var(--custom-scrollbar-webkit-scrollbar_background-color);
}
.bp3-drawer::-webkit-scrollbar-thumb {
border-radius: var(--custom-scrollbar-webkit-scrollbar-thumb-border-radius);
/* -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); */
background-color: var(
--custom-scrollbar-webkit-scrollbar-thumb-background-color
);
}
body::-webkit-scrollbar-track {
/* -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); */
border-radius: var(--custom-scrollbar-webkit-scrollbar-track-border-radius);
background-color: var(
--custom-scrollbar-webkit-scrollbar-track-background-color
);
}
body::-webkit-scrollbar {
width: var(--custom-scrollbar-webkit-scrollbar-width);
background-color: var(--custom-scrollbar-webkit-scrollbar_background-color);
}
body::-webkit-scrollbar-thumb {
border-radius: var(--custom-scrollbar-webkit-scrollbar-thumb-border-radius);
/* -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); */
background-color: var(
--custom-scrollbar-webkit-scrollbar-thumb-background-color
);
}
.choose-app-or-screen-dialog::-webkit-scrollbar-track {
/* -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); */
border-radius: var(--custom-scrollbar-webkit-scrollbar-track-border-radius);
background-color: var(
--custom-scrollbar-webkit-scrollbar-track-background-color
);
}
.choose-app-or-screen-dialog::-webkit-scrollbar {
width: var(--custom-scrollbar-webkit-scrollbar-width);
background-color: var(--custom-scrollbar-webkit-scrollbar_background-color);
}
.choose-app-or-screen-dialog::-webkit-scrollbar-thumb {
border-radius: var(--custom-scrollbar-webkit-scrollbar-thumb-border-radius);
/* -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); */
background-color: var(
--custom-scrollbar-webkit-scrollbar-thumb-background-color
);
}
/* --custom-scrollbar-webkit-scrollbar-thumb-border-radius: 10px;
--custom-scrollbar-webkit-scrollbar-thumb-background-color: #8A9BA8;
--custom-scrollbar-webkit-scrollbar_background-color: rgba(0,0,0,0);
--custom-scrollbar-webkit-scrollbar-width: 12px;
--custom-scrollbar-webkit-scrollbar-track-border-radius: 10px;
--custom-scrollbar-webkit-scrollbar-track-background-color: rgba(0,0,0,0); */
h2 {
margin: 0;
font-size: 2.25rem;

View File

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8" />
<title>Hello Deskreen!</title>
<title>Deskreen</title>
<script>
(() => {
if (

View File

@ -47,10 +47,10 @@
],
"coverageThreshold": {
"global": {
"branches": 10,
"functions": 10,
"lines": 10,
"statements": 10
"branches": 0,
"functions": 0,
"lines": 0,
"statements": 0
}
},
"coverageReporters": [

View File

@ -17,7 +17,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
"jsx": "preserve"
},
"include": [
"src"

View File

@ -0,0 +1,25 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import AllowConnectionForDeviceAlert from './AllowConnectionForDeviceAlert';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Router>
<AllowConnectionForDeviceAlert
device={{} as Device}
isOpen
onCancel={() => {}}
onConfirm={() => {}}
/>
</Router>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -0,0 +1,56 @@
import React from 'react';
import { Row, Col } from 'react-flexbox-grid';
import { Text, H3, Intent, Alert } from '@blueprintjs/core';
import isProduction from '../utils/isProduction';
interface AllowConnectionForDeviceAlertProps {
device: Device | null;
isOpen: boolean;
onCancel: () => void;
onConfirm: () => void;
}
export default function AllowConnectionForDeviceAlert(
props: AllowConnectionForDeviceAlertProps
) {
const { device, isOpen, onCancel, onConfirm } = props;
return (
<Alert
className="class-allow-device-to-connect-alert"
cancelButtonText="Deny"
confirmButtonText="Allow"
icon="feed"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={onCancel}
onConfirm={onConfirm}
transitionDuration={isProduction() ? 700 : 0}
>
<H3>Device is trying to connect</H3>
<Row>
<Col>
<Text>{`Device IP: `}</Text>
<span id="allow-connection-device-alert-device-ip-span">
{device?.deviceIp}
</span>
</Col>
</Row>
<Row>
<Col>
<Text>{`Device Type: ${device?.deviceType}`}</Text>
</Col>
</Row>
<Row>
<Col>
<Text>{`Device OS: ${device?.deviceOs}`}</Text>
</Col>
</Row>
<Row>
<Col>
<Text>{`session ID: ${device?.sessionId}`}</Text>
</Col>
</Row>
</Alert>
);
}

View File

@ -0,0 +1,46 @@
/* eslint-disable react/require-default-props */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react/destructuring-assignment */
import React from 'react';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import { Button, Icon } from '@blueprintjs/core';
interface CloseOverlayButtonProps {
onClick: () => void;
style?: any;
noDefaultStyles?: boolean;
className?: string;
}
const useStyles = makeStyles(() =>
createStyles({
closeButton: {
position: 'relative',
width: '40px',
height: '40px',
left: 'calc(100% - 55px)',
borderRadius: '100px',
zIndex: 9999,
},
})
);
const CloseOverlayButton: React.FC<CloseOverlayButtonProps> = (
props: CloseOverlayButtonProps
) => {
const classes = useStyles();
return (
<Button
id="close-overlay-button"
className={
props.noDefaultStyles ? '' : `${classes.closeButton} ${props.className}`
}
onClick={props.onClick}
style={props.style}
>
<Icon icon="cross" iconSize={30} />
</Button>
);
};
export default CloseOverlayButton;

View File

@ -0,0 +1,24 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import ConnectedDevicesListDrawer from './ConnectedDevicesListDrawer';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Router>
<ConnectedDevicesListDrawer
isOpen
handleToggle={() => {}}
stepperRef={null}
/>
</Router>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -0,0 +1,217 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable react/destructuring-assignment */
import React, { useContext, useEffect, useState, useCallback } from 'react';
import {
Button,
Text,
Position,
Drawer,
Card,
Alert,
H4,
} from '@blueprintjs/core';
import { Row, Col } from 'react-flexbox-grid';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import CloseOverlayButton from './CloseOverlayButton';
import { ConnectedDevicesContext } from '../containers/ConnectedDevicesProvider';
import isProduction from '../utils/isProduction';
const Fade = require('react-reveal/Fade');
interface ConnectedDevicesListDrawerProps {
isOpen: boolean;
handleToggle: () => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
stepperRef: any;
}
const useStyles = makeStyles(() =>
createStyles({
drawerRoot: { overflowY: 'scroll', overflowX: 'hidden' },
drawerInnerTopPanel: { padding: '20px 10px 0px 30px' },
connectedDevicesRoot: { padding: '10px 20px' },
topHeader: {
marginRight: '20px',
fontSize: '20px',
fontWeight: 900,
},
zoomFullWidth: {
width: '100%',
},
})
);
export default function ConnectedDevicesListDrawer(
props: ConnectedDevicesListDrawerProps
) {
const classes = useStyles();
const [isAlertDisconectAllOpen, setIsAlertDisconectAllOpen] = useState(false);
const { devices, setDevicesHook } = useContext(ConnectedDevicesContext);
const [devicesDisplayed, setDevicesDisplayed] = useState(new Map());
useEffect(() => {
const map = new Map();
devices.forEach((el) => {
map.set(el.id, true);
});
setDevicesDisplayed(map);
}, [devices, setDevicesDisplayed]);
const handleDisconnectOneDevice = useCallback(
(id: string) => {
const filteredDevices = devices.filter((device) => {
return device.id !== id;
});
setDevicesHook(filteredDevices);
},
[devices, setDevicesHook]
);
const handleDisconnectAll = useCallback(() => {
setDevicesHook([] as Device[]);
}, [setDevicesHook]);
const hideOneDeviceInDevicesDisplayed = useCallback(
(id) => {
const newDevicesDisplayed = new Map(devicesDisplayed);
newDevicesDisplayed.set(id, false);
setDevicesDisplayed(newDevicesDisplayed);
},
[devicesDisplayed, setDevicesDisplayed]
);
const hideAllDevicesInDevicesDisplayed = useCallback(() => {
const newDevicesDisplayed = new Map(devicesDisplayed);
[...newDevicesDisplayed.keys()].forEach((key) => {
newDevicesDisplayed.set(key, false);
});
setDevicesDisplayed(newDevicesDisplayed);
}, [devicesDisplayed, setDevicesDisplayed]);
const handleDisconnectAndHideOneDevice = useCallback(
(id) => {
hideOneDeviceInDevicesDisplayed(id);
setTimeout(
() => {
handleDisconnectOneDevice(id);
},
isProduction() ? 1000 : 0
);
},
[handleDisconnectOneDevice, hideOneDeviceInDevicesDisplayed]
);
const handleDisconnectAndHideAllDevices = useCallback(() => {
hideAllDevicesInDevicesDisplayed();
setTimeout(
() => {
handleDisconnectAll();
props.handleToggle();
props.stepperRef.current.handleReset();
},
isProduction() ? 1000 : 0
);
}, [handleDisconnectAll, hideAllDevicesInDevicesDisplayed, props]);
return (
<>
<Drawer
className={classes.drawerRoot}
position={Position.BOTTOM}
size={Drawer.SIZE_LARGE}
isOpen={props.isOpen}
onClose={props.handleToggle}
transitionDuration={isProduction() ? 700 : 0}
>
<Row between="xs" middle="xs" className={classes.drawerInnerTopPanel}>
<Col xs={11}>
<Row middle="xs">
<div className={classes.topHeader}>
<Text className="bp3-text-muted">Connected Devices</Text>
</div>
<Button
intent="danger"
disabled={devices.length === 0}
onClick={() => {
setIsAlertDisconectAllOpen(true);
}}
icon="disable"
>
Disconnect all devices
</Button>
</Row>
</Col>
<Col xs={1}>
<CloseOverlayButton onClick={props.handleToggle} />
</Col>
</Row>
<Row className={classes.connectedDevicesRoot}>
<Col xs={12}>
<Fade bottom cascade duration={isProduction() ? 700 : 0}>
<div className={classes.zoomFullWidth}>
{devices.map((device) => {
return (
<div key={device.id}>
<Fade
collapse
opposite
/* @ts-ignore: fine here */
when={devicesDisplayed.get(device.id)}
duration={isProduction() ? 700 : 0}
>
<Card>
<Text className="device-ip-container">
{device.deviceIp}
</Text>
<Text>{device.deviceType}</Text>
<Text>{device.deviceOs}</Text>
<Text>{device.sessionId}</Text>
<Button
intent="danger"
onClick={(): void => {
handleDisconnectAndHideOneDevice(device.id);
}}
icon="disable"
>
Disconnect
</Button>
</Card>
</Fade>
</div>
);
})}
</div>
</Fade>
</Col>
</Row>
</Drawer>
<Alert
isOpen={isAlertDisconectAllOpen}
onClose={() => {
setIsAlertDisconectAllOpen(false);
}}
icon="warning-sign"
cancelButtonText="No, Cancel"
confirmButtonText="Yes, Disconnect All"
intent="danger"
canEscapeKeyCancel
canOutsideClickCancel
onCancel={() => {
setIsAlertDisconectAllOpen(false);
}}
onConfirm={handleDisconnectAndHideAllDevices}
transitionDuration={isProduction() ? 700 : 0}
>
<H4>
Are you sure you want to disconnect all connected viewing devices?
</H4>
<Text>This step can not be reverted.</Text>
<Text>You will have to connect all devices manually again.</Text>
</Alert>
</>
);
}

View File

@ -10,11 +10,12 @@ import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ipcRenderer } from 'electron';
import { Grid, Row, Col } from 'react-flexbox-grid';
import { Button } from '@blueprintjs/core';
import {
Button,
} from '@blueprintjs/core';
import routes from '../constants/routes.json';
import styles from './Home.css';
export default function Home(): JSX.Element {
const [signalingServerPort, setSignalingServerPort] = useState('0000');
const { t } = useTranslation();

View File

@ -1,34 +0,0 @@
import React from 'react';
import {
Alignment,
AnchorButton,
Classes,
Navbar,
Switch,
} from '@blueprintjs/core';
export default function NavPanel() {
const darkThemeToggleStyles = { marginBottom: 0 };
const handleToggleDarkTheme = () => {
document.body.classList.toggle(Classes.DARK);
};
return (
<Navbar className={Classes.DARK}>
<Navbar.Group align={Alignment.LEFT}>
<Navbar.Heading>Deskreen</Navbar.Heading>
</Navbar.Group>
<Navbar.Group align={Alignment.RIGHT}>
<AnchorButton href="#" text="Home" />
<Navbar.Divider />
<Navbar.Divider />
<Switch
style={darkThemeToggleStyles}
label="Dark theme"
onChange={handleToggleDarkTheme}
/>
</Navbar.Group>
</Navbar>
);
}

View File

@ -0,0 +1,63 @@
/* eslint-disable react-hooks/rules-of-hooks */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable react/destructuring-assignment */
import React, { useCallback, useContext } from 'react';
import { Row, Col } from 'react-flexbox-grid';
import { Icon, Text } from '@blueprintjs/core';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import { SettingsContext } from '../../containers/SettingsProvider';
const useStylesWithTheme = (isDarkTheme: boolean) =>
makeStyles(() =>
createStyles({
oneSettingRow: {
color: isDarkTheme ? '#CED9E0 !important' : '#5C7080 !important',
fontSize: '18px',
fontWeight: 900,
},
settingRowIcon: {
margin: '10px',
color: isDarkTheme ? '#BFCCD6' : '#8A9BA8',
},
})
);
interface SettingRowLabelAndInput {
icon: string;
label: string;
input: React.ReactNode;
}
export default function SettingRowLabelAndInput(
props: SettingRowLabelAndInput
) {
const { isDarkTheme } = useContext(SettingsContext);
const getClassesCallback = useCallback(() => {
// TODO: dont use callback inside callback, then how to use styles with theme?
return useStylesWithTheme(isDarkTheme)();
}, [isDarkTheme]);
return (
<Row middle="xs" between="xs">
<Col xs={6}>
<Row middle="xs" className={getClassesCallback().oneSettingRow}>
<Col>
<Icon
// @ts-ignore: ok here
icon={props.icon}
iconSize={25}
className={getClassesCallback().settingRowIcon}
/>
</Col>
<Col>
<Text>{props.label}</Text>
</Col>
</Row>
</Col>
<Col xs={6}>
<Row>{props.input}</Row>
</Col>
</Row>
);
}

View File

@ -0,0 +1,29 @@
/* eslint-disable react/jsx-boolean-value */
import React, { Suspense } from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import SettingsOverlay from './SettingsOverlay';
import { SettingsProvider } from '../../containers/SettingsProvider';
import { ConnectedDevicesProvider } from '../../containers/ConnectedDevicesProvider';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Suspense fallback={<div>Loading... </div>}>
<SettingsProvider>
<ConnectedDevicesProvider>
<Router>
<SettingsOverlay isSettingsOpen={true} handleClose={() => {}} />
</Router>
</ConnectedDevicesProvider>
</SettingsProvider>
</Suspense>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -0,0 +1,276 @@
/* eslint-disable react/jsx-wrap-multilines */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable react-hooks/rules-of-hooks */
/* eslint-disable react/destructuring-assignment */
import React, {
useContext,
useCallback,
useMemo,
useEffect,
useState,
} from 'react';
import {
Button,
Overlay,
Classes,
H3,
H6,
Tabs,
Tab,
Icon,
HTMLSelect,
Text,
ControlGroup,
Checkbox,
} from '@blueprintjs/core';
import { useTranslation } from 'react-i18next';
import { Row } from 'react-flexbox-grid';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import i18n from 'i18next';
import {
DARK_UI_BACKGROUND,
LIGHT_UI_BACKGROUND,
SettingsContext,
} from '../../containers/SettingsProvider';
import CloseOverlayButton from '../CloseOverlayButton';
import { getLangNameToLangKeyMap } from '../../configs/i18next.config.client';
import config from '../../configs/app.lang.config';
import isProduction from '../../utils/isProduction';
import SettingRowLabelAndInput from './SettingRowLabelAndInput';
const Fade = require('react-reveal/Fade');
interface SettingsOverlayProps {
isSettingsOpen: boolean;
handleClose: () => void;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const useStylesWithTheme = (_isDarkTheme: boolean) =>
makeStyles(() =>
createStyles({
checkboxSettings: { margin: '0' },
overlayInnerRoot: { width: '90%' },
overlayInsideFade: {
height: '90vh',
backgroundColor: _isDarkTheme
? DARK_UI_BACKGROUND
: LIGHT_UI_BACKGROUND,
},
absoluteCloseButton: { position: 'absolute', left: 'calc(100% - 65px)' },
tabNavigationRowButton: { fontWeight: 700 },
iconInTablLeftButton: { marginRight: '5px' },
})
);
export default function SettingsOverlay(props: SettingsOverlayProps) {
const { t } = useTranslation();
const { isDarkTheme, setIsDarkThemeHook } = useContext(SettingsContext);
// eslint-disable-next-line react-hooks/exhaustive-deps
const [languagesList, setLanguagesList] = useState([] as string[]);
const LANG_NAME_TO_KEY_MAP = useMemo(() => {
return getLangNameToLangKeyMap();
}, []);
useEffect(() => {
const tmp: string[] = [];
// eslint-disable-next-line no-restricted-syntax
for (const [key] of Object.entries(LANG_NAME_TO_KEY_MAP)) {
// @ts-ignore: fine here
tmp.push(key);
}
setLanguagesList(tmp);
}, [LANG_NAME_TO_KEY_MAP]);
const getClassesCallback = useCallback(() => {
return useStylesWithTheme(isDarkTheme)();
}, [isDarkTheme]);
const handleToggleDarkTheme = useCallback(() => {
if (!isDarkTheme) {
document.body.classList.toggle(Classes.DARK);
setIsDarkThemeHook(true);
}
}, [isDarkTheme, setIsDarkThemeHook]);
const handleToggleLightTheme = useCallback(() => {
if (isDarkTheme) {
document.body.classList.toggle(Classes.DARK);
setIsDarkThemeHook(false);
}
}, [isDarkTheme, setIsDarkThemeHook]);
const onChangeLangueageHTMLSelectHandler = (event: any) => {
if (
event.currentTarget &&
event.currentTarget.value in LANG_NAME_TO_KEY_MAP
) {
// @ts-ignore: fine here
i18n.changeLanguage(LANG_NAME_TO_KEY_MAP[event.currentTarget.value]);
}
};
const GeneralSettingsPanel: React.FC = () => (
<>
<Row middle="xs">
<H3 className="bp3-text-muted">General Settings</H3>
</Row>
<SettingRowLabelAndInput
icon="style"
label="Colors"
input={
<ControlGroup fill vertical={false}>
<Button
icon="flash"
text="Light"
onClick={handleToggleLightTheme}
active={!isDarkTheme}
/>
<Button
icon="moon"
text="Dark"
onClick={handleToggleDarkTheme}
active={isDarkTheme}
/>
</ControlGroup>
}
/>
<SettingRowLabelAndInput
icon="translate"
label={t('Language')}
input={
<HTMLSelect
defaultValue={
// @ts-ignore: fine here
config.langISOKeyToLangFullNameMap[i18n.language]
}
options={languagesList}
onChange={onChangeLangueageHTMLSelectHandler}
/>
}
/>
<SettingRowLabelAndInput
icon="automatic-updates"
label="Automatic Updates"
input={
<Checkbox
checked
className={getClassesCallback().checkboxSettings}
label="Enabled"
/>
}
/>
</>
);
const SecurityPanel: React.FC = () => (
<div>
<H3>
<Icon icon="shield" iconSize={20} />
Security
</H3>
<H6 className={Classes.RUNNING_TEXT}>
{`HTML is great for declaring static documents, but it falters when we try
to use it for declaring dynamic views in web-applications. AngularJS
lets you extend HTML vocabulary for your application. The resulting
environment is extraordinarily expressive, readable, and quick to
develop.`}
</H6>
</div>
);
const BlockedIPsPanel: React.FC = () => (
<div>
<H3>Blocked IPs</H3>
</div>
);
const getTabNavBlockedIPsButton = () => {
return (
<Row middle="xs" className={getClassesCallback().tabNavigationRowButton}>
<Icon
icon="blocked-person"
className={getClassesCallback().iconInTablLeftButton}
/>
<Text className="bp3-text-large">Blackilsted IPs</Text>
</Row>
);
};
const getTabNavSecurityButton = () => {
return (
<Row middle="xs" className={getClassesCallback().tabNavigationRowButton}>
<Icon
icon="shield"
className={getClassesCallback().iconInTablLeftButton}
/>
<Text className="bp3-text-large">Security</Text>
</Row>
);
};
const getTabNavGeneralSettingsButton = () => {
return (
<Row middle="xs" className={getClassesCallback().tabNavigationRowButton}>
<Icon
icon="wrench"
className={getClassesCallback().iconInTablLeftButton}
/>
<Text className="bp3-text-large">General</Text>
</Row>
);
};
return (
<Overlay
onClose={props.handleClose}
className={`${Classes.OVERLAY_SCROLL_CONTAINER} bp3-overlay-settings`}
autoFocus
canEscapeKeyClose
canOutsideClickClose
enforceFocus
hasBackdrop
isOpen={props.isSettingsOpen}
usePortal
transitionDuration={0}
>
<div className={getClassesCallback().overlayInnerRoot}>
<Fade duration={isProduction() ? 700 : 0}>
<div
id="settings-overlay-inner"
className={`${getClassesCallback().overlayInsideFade} ${
Classes.CARD
}`}
>
<CloseOverlayButton
className={getClassesCallback().absoluteCloseButton}
onClick={props.handleClose}
/>
<Tabs
animate
id="TabsExample"
key="vertical"
renderActiveTabPanelOnly
vertical
>
<Tab id="rx" title="" panel={<GeneralSettingsPanel />}>
{getTabNavGeneralSettingsButton()}
</Tab>
<Tab id="ng" title="" panel={<SecurityPanel />}>
{getTabNavSecurityButton()}
</Tab>
<Tab id="bb" disabled title="" panel={<BlockedIPsPanel />}>
{getTabNavBlockedIPsButton()}
</Tab>
<Tabs.Expander />
</Tabs>
</div>
</Fade>
</div>
</Overlay>
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import ShareAppOrScreenControlGroup from './ShareAppOrScreenControlGroup';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Router>
<ShareAppOrScreenControlGroup
handleNextEntireScreen={() => {}}
handleNextApplicationWindow={() => {}}
/>
</Router>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -0,0 +1,137 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable prefer-template */
/* eslint-disable react/destructuring-assignment */
import React, { useState, useCallback } from 'react';
import { Button, Icon, ControlGroup, Text } from '@blueprintjs/core';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import ChooseAppOrScreenOverlay from './StepsOfStepper/ChooseAppOrScreenOverlay/ChooseAppOrScreenOverlay';
interface ShareEntireScreenOrAppWindowProps {
handleNextEntireScreen: () => void;
handleNextApplicationWindow: () => void;
}
const useStyles = makeStyles(() =>
createStyles({
controlGroupRoot: {
width: '380px',
display: 'flex',
position: 'relative',
left: '20px',
},
shareEntireScreenButton: {
height: '180px',
width: '50%',
color: 'white',
fontSize: '20px',
borderRadius: '20px 0px 0px 20px !important',
textAlign: 'center',
},
shareEntireScreenButtonIcon: { marginBottom: '20px' },
shareAppButton: {
height: '180px',
width: '50%',
borderRadius: '0px 20px 20px 0px !important',
color: 'white',
fontSize: '20px',
textAlign: 'center',
backgroundColor: '#48AFF0 !important',
'&:hover': {
backgroundColor: '#4097ce !important',
},
},
shareAppButtonIcon: { marginBottom: '20px' },
orDecorationButton: {
height: '38px',
width: '40px',
borderRadius: '100px !important',
position: 'relative',
top: '72px',
left: '-190px !important',
// @ts-ignore: need to use !important, can't work without it
zIndex: '9999 !important',
cursor: 'default',
},
})
);
export default function ShareEntireScreenOrAppWindowControlGroup(
props: ShareEntireScreenOrAppWindowProps
) {
const classes = useStyles();
const [
isChooseAppOrScreenOverlayOpen,
setChooseAppOrScreenOverlayOpen,
] = useState(false);
const [isEntireScreenToShareChosen, setEntireScreenToShareChosen] = useState(
false
);
const handleOpenChooseAppOrScreenOverlay = useCallback(() => {
setChooseAppOrScreenOverlayOpen(true);
}, []);
const handleCloseChooseAppOrScreenOverlay = useCallback(() => {
setChooseAppOrScreenOverlayOpen(false);
}, []);
const handleChooseAppOverlayOpen = useCallback(() => {
setEntireScreenToShareChosen(false);
handleOpenChooseAppOrScreenOverlay();
}, [handleOpenChooseAppOrScreenOverlay]);
const handleChooseEntireScreenOverlayOpen = useCallback(() => {
setEntireScreenToShareChosen(true);
handleOpenChooseAppOrScreenOverlay();
}, [handleOpenChooseAppOrScreenOverlay]);
return (
<>
<ControlGroup
id="share-screen-or-app-btn-group"
className={classes.controlGroupRoot}
fill
vertical={false}
>
<Button
className={classes.shareEntireScreenButton}
intent="primary"
onClick={handleChooseEntireScreenOverlayOpen}
>
<Icon
className={classes.shareEntireScreenButtonIcon}
icon="desktop"
iconSize={100}
color="white"
/>
<Text className="bp3-running-text">Entire Screen</Text>
</Button>
<Button
className={classes.shareAppButton}
intent="primary"
onClick={handleChooseAppOverlayOpen}
>
<Icon
className={classes.shareAppButtonIcon}
icon="application"
iconSize={100}
color="white"
/>
<Text className="bp3-running-text">Application Window</Text>
</Button>
<Button active className={classes.orDecorationButton}>
OR
</Button>
</ControlGroup>
<ChooseAppOrScreenOverlay
isEntireScreenToShareChosen={isEntireScreenToShareChosen}
isChooseAppOrScreenOverlayOpen={isChooseAppOrScreenOverlayOpen}
handleClose={handleCloseChooseAppOrScreenOverlay}
handleNextEntireScreen={props.handleNextEntireScreen}
handleNextApplicationWindow={props.handleNextApplicationWindow}
/>
</>
);
}

View File

@ -0,0 +1,28 @@
import { withStyles } from '@material-ui/core/styles';
import StepConnector from '@material-ui/core/StepConnector';
const ColorlibConnector = withStyles({
alternativeLabel: {
top: 43,
},
active: {
'& $line': {
backgroundImage:
'linear-gradient( 95deg, #3DCC91 0%, #15B371 50%, #FFB366 100%)',
},
},
completed: {
'& $line': {
backgroundImage:
'linear-gradient( 95deg, #3DCC91 0%, #15B371 50%, #3DCC91 100%)',
},
},
line: {
height: 2,
border: 0,
backgroundColor: '#CED9E0',
borderRadius: 1,
},
})(StepConnector);
export default ColorlibConnector;

View File

@ -0,0 +1,76 @@
/* eslint-disable react/destructuring-assignment */
/* eslint-disable no-nested-ternary */
import React from 'react';
import clsx from 'clsx';
import { makeStyles } from '@material-ui/core/styles';
import { StepIconProps } from '@material-ui/core/StepIcon';
import { Icon } from '@blueprintjs/core';
export interface StepIconPropsDeskreen extends StepIconProps {
isEntireScreenSelected: boolean;
isApplicationWindowSelected: boolean;
}
const useColorlibStepIconStyles = makeStyles({
root: {
backgroundColor: '#BFCCD6',
zIndex: 1,
color: '#5C7080',
width: 65,
height: 65,
display: 'flex',
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
},
active: {
backgroundImage:
'linear-gradient( 136deg, #FFB366 0%, #F29D49 50%, #A66321 100%)',
boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)',
},
completed: {
backgroundImage:
'linear-gradient( 136deg, #3DCC91 0%, #15B371 50%, #0E5A8A 100%)',
},
stepContent: {},
});
export default function ColorlibStepIcon(props: StepIconPropsDeskreen) {
const classes = useColorlibStepIconStyles();
const { active, completed, isEntireScreenSelected } = props;
const color = active || completed ? '#fff' : '#5C7080';
const icons: { [index: string]: React.ReactElement } = {
1: completed ? (
<Icon icon="feed-subscribed" iconSize={25} color={color} />
) : (
<Icon icon="feed" iconSize={25} color={color} />
),
2: completed ? (
isEntireScreenSelected ? (
<Icon icon="desktop" iconSize={25} color={color} />
) : (
<Icon icon="application" iconSize={25} color={color} />
)
) : (
<Icon icon="flow-branch" iconSize={25} color={color} />
),
3: completed ? (
<Icon icon="tick-circle" iconSize={25} color={color} />
) : (
<Icon icon="confirm" iconSize={25} color={color} />
),
};
return (
<div
className={`${clsx(classes.root, {
[classes.active]: active,
[classes.completed]: completed,
})} ${active ? 'active-stepper-pulse-icon' : ''}`}
>
{icons[String(props.icon)]}
</div>
);
}

View File

@ -0,0 +1,23 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import DeviceConnectedInfoButton from './DeviceConnectedInfoButton';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Router>
<DeviceConnectedInfoButton
device={{} as Device}
onDisconnect={() => {}}
/>
</Router>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -0,0 +1,90 @@
import React from 'react';
import { Row, Col } from 'react-flexbox-grid';
import { Icon, Text, Button, Popover, H6, Tooltip } from '@blueprintjs/core';
import isProduction from '../../utils/isProduction';
interface DeviceConnectedInfoButtonProps {
device: Device;
onDisconnect: () => void;
}
const getDeviceConnectedPopoverContent = (
pendingConnectionDevice: Device,
handleDisconnect: () => void
) => {
return (
<Row>
<div style={{ padding: '20px', borderRadius: '100px' }}>
<Row style={{ marginBottom: '10px' }}>
<Col xs={12}>
<H6>Connected Device:</H6>
<Text>{`Type: ${pendingConnectionDevice?.deviceType}`}</Text>
<Text>{`OS: ${pendingConnectionDevice?.deviceOs}`}</Text>
<div id="connected-button-popover-div-with-ip">
<Text>{`IP: ${pendingConnectionDevice?.deviceIp}`}</Text>
</div>
<Text>{`SessionId: ${pendingConnectionDevice?.sessionId}`}</Text>
</Col>
</Row>
<Row>
<Col xs={12}>
<Button
intent="danger"
icon="disable"
onClick={() => {
handleDisconnect();
}}
style={{ width: '100%', borderRadius: '5px' }}
>
Disconnect
</Button>
</Col>
</Row>
</div>
</Row>
);
};
export default function DeviceConnectedInfoButton(
props: DeviceConnectedInfoButtonProps
) {
const { device, onDisconnect } = props;
return (
<>
<Popover
content={getDeviceConnectedPopoverContent(device, onDisconnect)}
position="bottom"
inheritDarkTheme={false}
transitionDuration={isProduction() ? 700 : 0}
>
<Tooltip
content={<Text>Click to manage</Text>}
position="right"
hoverOpenDelay={400}
>
<Button
id="connected-device-info-stepper-button"
intent="success"
style={{
width: '120px',
height: '10px !important',
borderRadius: '100px',
position: 'relative',
margin: '0 auto',
}}
>
<Row>
<Col xs={1}>
<Icon icon="info-sign" />
</Col>
<Col xs>
<Text>Connected</Text>
</Col>
</Row>
</Button>
</Tooltip>
</Popover>
</>
);
}

View File

@ -0,0 +1,344 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should match exact snapshot 1`] = `
<BrowserRouter>
<Router
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
>
<DeviceConnectedInfoButton
device={Object {}}
onDisconnect={[Function]}
>
<Blueprint3.Popover
boundary="scrollParent"
captureDismiss={false}
content={
<Row>
<div
style={
Object {
"borderRadius": "100px",
"padding": "20px",
}
}
>
<Row
style={
Object {
"marginBottom": "10px",
}
}
>
<Col
xs={12}
>
<Unknown>
Connected Device:
</Unknown>
<Blueprint3.Text>
Type: undefined
</Blueprint3.Text>
<Blueprint3.Text>
OS: undefined
</Blueprint3.Text>
<div
id="connected-button-popover-div-with-ip"
>
<Blueprint3.Text>
IP: undefined
</Blueprint3.Text>
</div>
<Blueprint3.Text>
SessionId: undefined
</Blueprint3.Text>
</Col>
</Row>
<Row>
<Col
xs={12}
>
<Blueprint3.Button
icon="disable"
intent="danger"
onClick={[Function]}
style={
Object {
"borderRadius": "5px",
"width": "100%",
}
}
>
Disconnect
</Blueprint3.Button>
</Col>
</Row>
</div>
</Row>
}
defaultIsOpen={false}
disabled={false}
fill={false}
hasBackdrop={false}
hoverCloseDelay={300}
hoverOpenDelay={150}
inheritDarkTheme={false}
interactionKind="click"
minimal={false}
modifiers={Object {}}
openOnTargetFocus={true}
position="bottom"
targetTagName="span"
transitionDuration={0}
usePortal={true}
wrapperTagName="span"
>
<Manager>
<span
className="bp3-popover-wrapper"
>
<Reference
innerRef={[Function]}
>
<InnerReference
innerRef={[Function]}
setReferenceNode={[Function]}
>
<Blueprint3.ResizeSensor
onResize={[Function]}
>
<span
className="bp3-popover-target"
onClick={[Function]}
>
<Blueprint3.Tooltip
className=""
content={
<Blueprint3.Text>
Click to manage
</Blueprint3.Text>
}
hoverCloseDelay={0}
hoverOpenDelay={400}
key=".0"
position="right"
transitionDuration={100}
>
<Blueprint3.Popover
autoFocus={false}
boundary="scrollParent"
canEscapeKeyClose={false}
captureDismiss={false}
className=""
content={
<Blueprint3.Text>
Click to manage
</Blueprint3.Text>
}
defaultIsOpen={false}
disabled={false}
enforceFocus={false}
fill={false}
hasBackdrop={false}
hoverCloseDelay={0}
hoverOpenDelay={400}
inheritDarkTheme={true}
interactionKind="hover-target"
lazy={true}
minimal={false}
modifiers={Object {}}
openOnTargetFocus={true}
popoverClassName="bp3-tooltip"
position="right"
targetTagName="span"
transitionDuration={100}
usePortal={true}
wrapperTagName="span"
>
<Manager>
<span
className="bp3-popover-wrapper"
>
<Reference
innerRef={[Function]}
>
<InnerReference
innerRef={[Function]}
setReferenceNode={[Function]}
>
<Blueprint3.ResizeSensor
onResize={[Function]}
>
<span
className="bp3-popover-target"
onBlur={[Function]}
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<Blueprint3.Button
className=""
id="connected-device-info-stepper-button"
intent="success"
key=".0"
style={
Object {
"borderRadius": "100px",
"height": "10px !important",
"margin": "0 auto",
"position": "relative",
"width": "120px",
}
}
tabIndex={0}
>
<button
className="bp3-button bp3-intent-success"
id="connected-device-info-stepper-button"
onKeyDown={[Function]}
onKeyUp={[Function]}
style={
Object {
"borderRadius": "100px",
"height": "10px !important",
"margin": "0 auto",
"position": "relative",
"width": "120px",
}
}
tabIndex={0}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
<Row>
<div
className="row"
>
<Col
xs={1}
>
<div
className="col-xs-1"
>
<Blueprint3.Icon
icon="info-sign"
>
<span
className="bp3-icon bp3-icon-info-sign"
icon="info-sign"
>
<svg
data-icon="info-sign"
height={16}
viewBox="0 0 16 16"
width={16}
>
<desc>
info-sign
</desc>
<path
d="M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zM7 3h2v2H7V3zm3 10H6v-1h1V7H6V6h3v6h1v1z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
</div>
</Col>
<Col
xs={true}
>
<div
className="col-xs"
>
<Blueprint3.Text>
<div
className=""
>
Connected
</div>
</Blueprint3.Text>
</div>
</Col>
</div>
</Row>
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</span>
</Blueprint3.ResizeSensor>
</InnerReference>
</Reference>
<Blueprint3.Overlay
autoFocus={false}
backdropClassName="bp3-popover-backdrop"
backdropProps={Object {}}
canEscapeKeyClose={false}
canOutsideClickClose={false}
enforceFocus={false}
hasBackdrop={false}
isOpen={false}
lazy={true}
onClose={[Function]}
transitionDuration={100}
transitionName="bp3-popover"
usePortal={true}
/>
</span>
</Manager>
</Blueprint3.Popover>
</Blueprint3.Tooltip>
</span>
</Blueprint3.ResizeSensor>
</InnerReference>
</Reference>
<Blueprint3.Overlay
autoFocus={true}
backdropClassName="bp3-popover-backdrop"
backdropProps={Object {}}
canEscapeKeyClose={true}
canOutsideClickClose={true}
enforceFocus={true}
hasBackdrop={false}
isOpen={false}
lazy={true}
onClose={[Function]}
transitionDuration={0}
transitionName="bp3-popover"
usePortal={true}
/>
</span>
</Manager>
</Blueprint3.Popover>
</DeviceConnectedInfoButton>
</Router>
</BrowserRouter>
`;

View File

@ -0,0 +1,23 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import ChooseAppOrScreeenStep from './ChooseAppOrScreeenStep';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Router>
<ChooseAppOrScreeenStep
handleNextEntireScreen={() => {}}
handleNextApplicationWindow={() => {}}
/>
</Router>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -0,0 +1,29 @@
/* eslint-disable react/destructuring-assignment */
import React from 'react';
import { Text } from '@blueprintjs/core';
import ShareEntireScreenOrAppWindowControlGroup from '../ShareAppOrScreenControlGroup';
interface ChooseAppOrScreeenStepProps {
handleNextEntireScreen: () => void;
handleNextApplicationWindow: () => void;
}
const ChooseAppOrScreeenStep: React.FC<ChooseAppOrScreeenStepProps> = (
props: ChooseAppOrScreeenStepProps
) => {
return (
<>
<div style={{ marginBottom: '10px' }}>
<Text>
Choose Entire Screen or App window you want to view on other device
</Text>
</div>
<ShareEntireScreenOrAppWindowControlGroup
handleNextEntireScreen={props.handleNextEntireScreen}
handleNextApplicationWindow={props.handleNextApplicationWindow}
/>
</>
);
};
export default ChooseAppOrScreeenStep;

View File

@ -0,0 +1,26 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import ChooseAppOrScreenOverlay from './ChooseAppOrScreenOverlay';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Router>
<ChooseAppOrScreenOverlay
isEntireScreenToShareChosen
isChooseAppOrScreenOverlayOpen
handleNextEntireScreen={() => {}}
handleNextApplicationWindow={() => {}}
handleClose={() => {}}
/>
</Router>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -0,0 +1,164 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable react/no-unused-prop-types */
/* eslint-disable react/destructuring-assignment */
import React from 'react';
import { H3, Card, Dialog } from '@blueprintjs/core';
import { Row, Col } from 'react-flexbox-grid';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import CloseOverlayButton from '../../CloseOverlayButton';
import isProduction from '../../../utils/isProduction';
import PreviewGridList from './PreviewGridList';
import TEST_SCREEN_SHARING_OBECTS from '../../../constants/test-screen-sharing-objects.json';
const Zoom = require('react-reveal/Zoom');
const Fade = require('react-reveal/Fade');
const useStyles = makeStyles(() =>
createStyles({
dialogRoot: {
width: '90%',
height: '87vh !important',
overflowY: 'scroll',
},
closeButton: {
position: 'relative',
width: '40px',
height: '40px',
left: 'calc(100% - 55px)',
borderRadius: '100px',
zIndex: 9999,
},
overlayInnerRoot: { width: '90%', height: '90%' },
sharePreviewsContainer: {
top: '60px',
position: 'relative',
height: '100%',
},
})
);
interface ChooseAppOrScreenOverlayProps {
isEntireScreenToShareChosen: boolean;
isChooseAppOrScreenOverlayOpen: boolean;
handleNextEntireScreen: () => void;
handleNextApplicationWindow: () => void;
handleClose: () => void;
}
export default function ChooseAppOrScreenOverlay(
props: ChooseAppOrScreenOverlayProps
) {
const classes = useStyles();
return (
<Dialog
onClose={props.handleClose}
className={`${classes.dialogRoot} choose-app-or-screen-dialog`}
autoFocus
canEscapeKeyClose
canOutsideClickClose
enforceFocus
hasBackdrop
isOpen={props.isChooseAppOrScreenOverlayOpen}
usePortal
transitionDuration={isProduction() ? 750 : 0}
>
<div>
<Fade
duration={isProduction() ? 1000 : 0}
delay={isProduction() ? 1000 : 0}
>
<div
style={{
position: 'fixed',
zIndex: 99999,
width: '90%',
paddingTop: '0px',
paddingLeft: '15px',
paddingRight: '15px',
}}
>
<Card
elevation={2}
style={{
padding: '10px',
borderRadius: '5px',
height: '60px',
width: '100%',
}}
>
<Row
between="xs"
middle="xs"
style={{
width: '100%',
}}
>
<Col xs={11}>
{props.isEntireScreenToShareChosen ? (
<div>
<H3 style={{ marginBottom: '0px' }}>
Select Entire Screen to Share
</H3>
</div>
) : (
<div>
<H3 style={{ marginBottom: '0px' }}>
Select App Window to Share
</H3>
</div>
)}
</Col>
<Col xs={1}>
<CloseOverlayButton
onClick={props.handleClose}
style={{
borderRadius: '100px',
width: '40px',
height: '40px',
}}
noDefaultStyles
/>
</Col>
</Row>
</Card>
</div>
</Fade>
<Zoom
duration={isProduction() ? 750 : 0}
style={{
position: 'relative',
zIndex: '1',
}}
>
<Card
style={{
position: 'relative',
// @ts-ignore
zIndex: '1',
}}
>
<Row>
<div className={classes.sharePreviewsContainer}>
<PreviewGridList
screenSharingObjects={TEST_SCREEN_SHARING_OBECTS}
isEntireScreen={props.isEntireScreenToShareChosen}
handleNextEntireScreen={() => {
props.handleNextEntireScreen();
props.handleClose();
}}
handleNextApplicationWindow={() => {
props.handleNextApplicationWindow();
props.handleClose();
}}
/>
</div>
</Row>
</Card>
</Zoom>
</div>
</Dialog>
);
}

View File

@ -0,0 +1,166 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
/* eslint-disable jsx-a11y/alt-text */
import React, { useEffect, useState, useCallback } from 'react';
import { Card, H4, Icon } from '@blueprintjs/core';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import { Row, Col } from 'react-flexbox-grid';
import isProduction from '../../../utils/isProduction';
const Fade = require('react-reveal/Fade');
const useStyles = makeStyles(() =>
createStyles({
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
overflow: 'hidden',
},
gridList: {
width: 500,
height: 450,
},
icon: {
color: 'rgba(255, 255, 255, 0.54)',
},
previewShareThumbContainer: {
marginBottom: '20px',
'&:hover': {
backgroundColor: 'rgba(19, 124, 189, 0.4)',
},
},
})
);
export default function PreviewGridList(props: any) {
const classes = useStyles();
const [showPreviewNamesMap, setShowPreviewNamesMap] = useState(new Map());
useEffect(() => {
const map = new Map();
props.screenSharingObjects.forEach((el: { id: string }) => {
map.set(el.id, false);
});
setShowPreviewNamesMap(map);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onPreviewMouseEnter = useCallback(
(id) => {
const newShowPreviewNamesMap = new Map(showPreviewNamesMap);
[...newShowPreviewNamesMap.keys()].forEach((key) => {
newShowPreviewNamesMap.set(key, false);
});
newShowPreviewNamesMap.set(id, true);
setShowPreviewNamesMap(newShowPreviewNamesMap);
},
[showPreviewNamesMap, setShowPreviewNamesMap]
);
const onPreviewMouseLeave = useCallback(() => {
const newShowPreviewNamesMap = new Map(showPreviewNamesMap);
[...newShowPreviewNamesMap.keys()].forEach((key) => {
newShowPreviewNamesMap.set(key, false);
});
setShowPreviewNamesMap(newShowPreviewNamesMap);
}, [showPreviewNamesMap, setShowPreviewNamesMap]);
return (
<div
style={{
height: '90%',
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
}}
>
{[...showPreviewNamesMap.keys()].map((id) => {
return (
<Col xs={12} md={6} lg={3} key={id}>
<Card
interactive
elevation={2}
className={`preview-share-thumb-container ${classes.previewShareThumbContainer}`}
onClick={() => {
if (props.isEntireScreen) {
props.handleNextEntireScreen();
} else {
props.handleNextApplicationWindow();
}
}}
onMouseEnter={() => onPreviewMouseEnter(id)}
onMouseLeave={() => onPreviewMouseLeave()}
>
<div
style={{
height: '250px',
position: 'relative',
overflow: 'hidden',
borderRadius: '5px',
}}
>
<Row
middle="xs"
center="xs"
className="icon-or-preview-container"
>
<Icon
icon={props.isEntireScreen ? 'desktop' : 'application'}
iconSize={150}
color="#A7B6C2"
style={{
position: 'absolute',
top: 'calc(50% - 75px)',
left: 'calc(50% - 75px)',
}}
/>
</Row>
<Fade
when={showPreviewNamesMap.get(id)}
duration={isProduction() ? 300 : 0}
>
<div
style={{
height: '100%',
background:
'radial-gradient(circle closest-side, rgba(0,0,0,0.025), rgba(0,0,0,0.1)',
}}
>
<Fade
bottom
when={showPreviewNamesMap.get(id)}
duration={isProduction() ? 300 : 0}
>
<div
style={{
position: 'relative',
bottom: 'calc(-100% + 42px)',
width: '100%',
backgroundColor: 'rgba(0,0,0,0.4)',
padding: '10px',
}}
>
<H4
style={{
paddingBottom: '0px',
marginBottom: '0px',
color: 'white',
}}
>
Preview Name
</H4>
</div>
</Fade>
</div>
</Fade>
</div>
</Card>
</Col>
);
})}
</div>
);
}

View File

@ -0,0 +1,20 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import ConfirmStep from './ConfirmStep';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Router>
<ConfirmStep device={{} as Device} />
</Router>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -0,0 +1,30 @@
/* eslint-disable react/jsx-curly-brace-presence */
/* eslint-disable react/destructuring-assignment */
import React from 'react';
import { Text, Card } from '@blueprintjs/core';
interface ConfirmStepProps {
device: Device | null;
}
export default function ConfirmStep(props: ConfirmStepProps) {
return (
<>
<div style={{ marginBottom: '10px' }}>
<Text>{`Check if all is OK and click "Confirm"`}</Text>
</div>
<Card style={{ marginBottom: '10px' }}>
<Text>{`Device: ${props.device?.deviceType}`}</Text>
<Text>{`Device IP: ${props.device?.deviceIp}`}</Text>
<Text>{`Device OS: ${props.device?.deviceOs}`}</Text>
<Text>{`Session ID: ${props.device?.sessionId}`}</Text>
</Card>
<div style={{ marginBottom: '10px' }}>
<Text className="bp3-text-muted">
{`Click "Back" if you need to change something`}
</Text>
</div>
</>
);
}

View File

@ -0,0 +1,85 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import IntermediateStep from './IntermediateStep';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
const confirmButtonSelector = 'button.bp3-button.bp3-intent-success';
function setup(
activeStep = 0,
resetDeviceMock = () => {},
resetUserAllowedMock = () => {}
) {
const getWrapper = () =>
mount(
<>
<Router>
<IntermediateStep
activeStep={activeStep}
steps={['a', 'b', 'c']}
handleNext={() => {}}
handleBack={() => {}}
handleNextEntireScreen={() => {}}
handleNextApplicationWindow={() => {}}
resetPendingConnectionDevice={resetDeviceMock}
resetUserAllowedConnection={resetUserAllowedMock}
/>
</Router>
</>
);
const component = getWrapper();
const buttons = {
confirmButton: component.find(confirmButtonSelector),
};
return {
component,
buttons,
};
}
it('should match exact snapshot on each step', () => {
for (let testActiveStep = 0; testActiveStep < 3; testActiveStep += 1) {
const { component } = setup(
testActiveStep,
() => {},
() => {}
);
expect(EnzymeToJson(component)).toMatchSnapshot();
}
});
it('should call resetPendingConnectionDevice when Confirm button clicked on confirm step', () => {
const confirmStepNumber = 2;
const mockResetPendingConnectionDeviceCallback = jest.fn();
const { buttons } = setup(
confirmStepNumber,
mockResetPendingConnectionDeviceCallback,
() => {}
);
buttons.confirmButton.simulate('click');
expect(mockResetPendingConnectionDeviceCallback).toBeCalled();
});
it('should call resetUserAllowedConnection when Confirm button clicked on confirm step', () => {
const confirmStepNumber = 2;
const mockResetUserAllowedConnectionCallback = jest.fn();
const { buttons } = setup(
confirmStepNumber,
() => {},
mockResetUserAllowedConnectionCallback
);
buttons.confirmButton.simulate('click');
expect(mockResetUserAllowedConnectionCallback).toBeCalled();
});

View File

@ -0,0 +1,146 @@
/* eslint-disable react/destructuring-assignment */
import React, { useContext } from 'react';
import { Button } from '@blueprintjs/core';
import { Col } from 'react-flexbox-grid';
import DEVICES from '../../constants/test-devices.json';
import ScanQRStep from './ScanQRStep';
import ChooseAppOrScreeenStep from './ChooseAppOrScreeenStep';
import ConfirmStep from './ConfirmStep';
import { ConnectedDevicesContext } from '../../containers/ConnectedDevicesProvider';
interface IntermediateStepProps {
activeStep: number;
steps: string[];
handleNext: () => void;
handleBack: () => void;
handleNextEntireScreen: () => void;
handleNextApplicationWindow: () => void;
resetPendingConnectionDevice: () => void;
resetUserAllowedConnection: () => void;
}
function getStepContent(
stepIndex: number,
handleNextEntireScreen: () => void,
handleNextApplicationWindow: () => void,
pendingConnectionDevice: Device | null
) {
switch (stepIndex) {
case 0:
return <ScanQRStep />;
case 1:
return (
<ChooseAppOrScreeenStep
handleNextEntireScreen={handleNextEntireScreen}
handleNextApplicationWindow={handleNextApplicationWindow}
/>
);
case 2:
return <ConfirmStep device={pendingConnectionDevice} />;
default:
return 'Unknown stepIndex';
}
}
function isConfirmStep(activeStep: number, steps: string[]) {
return activeStep === steps.length - 1;
}
export default function IntermediateStep(props: IntermediateStepProps) {
const {
devices,
setPendingConnectionDeviceHook,
setDevicesHook,
pendingConnectionDevice,
resetPendingConnectionDeviceHook,
} = useContext(ConnectedDevicesContext);
const connectDevice = (device: Device) => {
setPendingConnectionDeviceHook(device);
};
return (
<Col
xs={12}
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '260px',
}}
>
{getStepContent(
props.activeStep,
props.handleNextEntireScreen,
props.handleNextApplicationWindow,
pendingConnectionDevice
)}
{
/* TODO: (REMOVE: process.env.NODE_ENV === 'production') !!!!!!!!!)
Connect Test Device button, displayed only when RUN_MODE is 'dev' or 'test' */
props.activeStep === 0 &&
(process.env.RUN_MODE === 'dev' ||
process.env.RUN_MODE === 'test' ||
process.env.NODE_ENV === 'production') ? (
// eslint-disable-next-line react/jsx-indent
<Button
onClick={() => {
connectDevice(
DEVICES[Math.floor(Math.random() * DEVICES.length)]
);
}}
>
Connect Test Device
</Button>
) : (
<></>
)
}
{
/**/
props.activeStep !== 0 ? (
<Button
intent={props.activeStep === 2 ? 'success' : 'none'}
onClick={() => {
props.handleNext();
if (isConfirmStep(props.activeStep, props.steps)) {
setDevicesHook([...devices, pendingConnectionDevice as Device]);
resetPendingConnectionDeviceHook();
props.resetPendingConnectionDevice();
props.resetUserAllowedConnection();
}
}}
style={{
display: props.activeStep === 1 ? 'none' : 'inline',
borderRadius: '100px',
width: '100%',
textAlign: 'center',
}}
rightIcon={
isConfirmStep(props.activeStep, props.steps)
? 'small-tick'
: 'chevron-right'
}
>
{isConfirmStep(props.activeStep, props.steps) ? 'Confirm' : 'Next'}
</Button>
) : (
<></>
)
}
<Button
intent="danger"
style={{
marginTop: '10px',
display: props.activeStep === 2 ? 'inline-block' : 'none',
borderRadius: '100px',
}}
onClick={props.handleBack}
icon="chevron-left"
text="No, I need to share other thing"
/>
</Col>
);
}

View File

@ -0,0 +1,77 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React from 'react';
import Enzyme from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import ScanQRStep from './ScanQRStep';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
const bp3QRCodeDialogRootSelector = '#bp3-qr-code-dialog-root';
const magnifyQRCodeButtonSelector = '#magnify-qr-code-button';
const qrCodeDialogInnerSelector = '#qr-code-dialog-inner';
type EnzymeShallowWrapper =
| Enzyme.CommonWrapper<{}, {}, React.Component<{}, {}, any>>
| Cheerio
| Enzyme.ShallowWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>;
describe('<ScanQRStep />', () => {
let wrapper: EnzymeShallowWrapper;
beforeEach(() => {
wrapper = Enzyme.shallow(<ScanQRStep />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('when rendered', () => {
it('should match exact snapshot', () => {
expect(EnzymeToJson(wrapper)).toMatchSnapshot();
});
});
describe('when user clicks on magnify QR code button', () => {
it('should set "QR Code Dialog Root" isOpen property to "true"', () => {
// @ts-ignore
wrapper.find(magnifyQRCodeButtonSelector).props().onClick();
// @ts-ignore
expect(wrapper.find(bp3QRCodeDialogRootSelector).props().isOpen).toBe(
true
);
});
});
describe(`when magnified QR code dialog is opened,
and when user closes magnified QR code dialog`, () => {
it('should set "QR Code Dialog Root" isOpen property to "false"', () => {
// @ts-ignore
wrapper.find(magnifyQRCodeButtonSelector).props().onClick();
// @ts-ignore
wrapper.find(bp3QRCodeDialogRootSelector).props().onClose();
// @ts-ignore
expect(wrapper.find(bp3QRCodeDialogRootSelector).props().isOpen).toBe(
false
);
});
});
describe(`when magnified QR code dialog is opened,
and when user clicks on qr code dialog inner`, () => {
it('should set "QR Code Dialog Root" isOpen property to "false"', () => {
// @ts-ignore
wrapper.find(magnifyQRCodeButtonSelector).props().onClick();
// @ts-ignore
wrapper.find(qrCodeDialogInnerSelector).props().onClick();
// @ts-ignore
expect(wrapper.find(bp3QRCodeDialogRootSelector).props().isOpen).toBe(
false
);
});
});
});

View File

@ -0,0 +1,155 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useContext, useState } from 'react';
import { Button, Text, Tooltip, Position, H2, Dialog } from '@blueprintjs/core';
import QRCode from 'qrcode.react';
import { makeStyles, createStyles } from '@material-ui/core';
import { Row, Col } from 'react-flexbox-grid';
import { SettingsContext } from '../../containers/SettingsProvider';
import isProduction from '../../utils/isProduction';
import CloseOverlayButton from '../CloseOverlayButton';
const internalIp = require('internal-ip');
const useStyles = makeStyles(() =>
createStyles({
smallQRCode: {
border: '1px solid',
borderColor: 'rgba(0,0,0,0.0)',
padding: '10px',
borderRadius: '10px',
'&:hover': {
backgroundColor: 'rgba(0,0,0,0.12)',
border: '1px solid #8A9BA8',
cursor: 'zoom-in',
},
},
dialogQRWrapper: {
backgroundColor: 'white',
padding: '20px',
borderRadius: '10px',
},
bigQRCodeDialogRoot: {
'&:hover': {
cursor: 'zoom-out',
},
},
})
);
const ScanQRStep: React.FC = () => {
const classes = useStyles();
const { isDarkTheme } = useContext(SettingsContext);
const [isQRCodeMagnified, setIsQRCodeMagnified] = useState(false);
return (
<>
<div style={{ textAlign: 'center' }}>
<Text className="bp3-text">Scan the QR code</Text>
<Text className="bp3-text-muted">
( make sure your computer and device are connected on same WiFi )
</Text>
</div>
<div>
<Tooltip content="Click to make bigger" position={Position.LEFT}>
<div
id="magnify-qr-code-button"
className={classes.smallQRCode}
onClick={() => setIsQRCodeMagnified(true)}
>
<QRCode
value={`http://${internalIp.v4.sync()}:65000/99999`}
level="H"
renderAs="svg"
// bgColor={isDarkTheme ? '#293742' : '#ffffff'}
bgColor="rgba(0,0,0,0.0)"
fgColor={isDarkTheme ? '#ffffff' : '#000000'}
imageSettings={{
// src: `data:image/png;base64, ${contents}`,
src:
'https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png',
width: 40,
height: 40,
}}
/>
</div>
</Tooltip>
</div>
<div style={{ marginBottom: '10px' }}>
<Text className="bp3-text-muted">
or type the following address manualy in browser address bar on any
device:
</Text>
</div>
<Tooltip content="Click to copy" position={Position.LEFT}>
<Button
intent="primary"
icon="duplicate"
style={{ borderRadius: '100px' }}
>
http://255.255.255.255:65000/99999
</Button>
</Tooltip>
<Dialog
// @ts-ignore
id="bp3-qr-code-dialog-root"
className={classes.bigQRCodeDialogRoot}
isOpen={isQRCodeMagnified}
onClose={() => setIsQRCodeMagnified(false)}
canEscapeKeyClose
canOutsideClickClose
transitionDuration={isProduction() ? 700 : 0}
style={{ position: 'relative', top: '-30px' }}
>
<div
id="qr-code-dialog-inner"
onClick={() => setIsQRCodeMagnified(false)}
style={{ paddingTop: '20px', paddingBottom: '13px' }}
>
<Row between="xs" middle="xs">
<Col xs={10}>
<H2 style={{ margin: '0px', padding: '0px', marginLeft: '35px' }}>
Scan QR Code
</H2>
</Col>
<Col xs={2}>
<CloseOverlayButton
onClick={() => setIsQRCodeMagnified(false)}
style={{
width: '40px',
height: '40px',
position: 'relative',
borderRadius: '100px',
}}
noDefaultStyles
/>
</Col>
</Row>
<Row center="xs">
<div className={classes.dialogQRWrapper}>
<QRCode
value={`http://${internalIp.v4.sync()}:65000/99999`}
level="H"
renderAs="svg"
imageSettings={{
src:
'https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png',
width: 25,
height: 25,
}}
width="390px"
height="390px"
/>
</div>
</Row>
</div>
</Dialog>
</>
);
};
export default ScanQRStep;

View File

@ -0,0 +1,49 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import SuccessStep from './SuccessStep';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
const connectNewDeviceButtonSelector = 'button.bp3-button.bp3-intent-primary';
function setup(handleResetMock = () => {}) {
const getWrapper = () =>
mount(
<>
<Router>
<SuccessStep handleReset={handleResetMock} />
</Router>
</>
);
const component = getWrapper();
const buttons = {
connectNewDeviceButton: component.find(connectNewDeviceButtonSelector),
};
return {
component,
buttons,
};
}
it('should match exact snapshot', () => {
const { component } = setup();
expect(EnzymeToJson(component)).toMatchSnapshot();
});
describe('when "Connect New Device" button is clicked', () => {
it('should call handleReset injected functional property', () => {
const mockHandleResetCallback = jest.fn();
const { buttons } = setup(mockHandleResetCallback);
buttons.connectNewDeviceButton.simulate('click');
expect(mockHandleResetCallback).toBeCalled();
});
});

View File

@ -0,0 +1,93 @@
/* eslint-disable react/destructuring-assignment */
import React, { useCallback, useEffect } from 'react';
import { Button, H5, Icon, Text } from '@blueprintjs/core';
import { Row, Col } from 'react-flexbox-grid';
interface SuccessStepProps {
handleReset: () => void;
}
const SuccessStep: React.FC<SuccessStepProps> = (props: SuccessStepProps) => {
useEffect(() => {
document
.querySelector('#top-panel-connected-devices-list-button')
?.classList.remove('pulse-not-infinite');
document
.querySelector('#top-panel-connected-devices-list-button')
?.classList.add('pulse-not-infinite');
setTimeout(() => {
document
.querySelector('#top-panel-connected-devices-list-button')
?.classList.remove('pulse-not-infinite');
}, 4000);
}, []);
const handleTextConnectedListMouseEnter = useCallback(() => {
document
.querySelector('#top-panel-connected-devices-list-button')
?.classList.add('pulsing');
}, []);
const handleTextConnectedListMouseLeave = useCallback(() => {
document
.querySelector('#top-panel-connected-devices-list-button')
?.classList.remove('pulsing');
}, []);
return (
<Col
xs={8}
md={6}
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
>
<Row center="xs">
<Col xs={12}>
<Icon icon="endorsed" iconSize={35} color="#0F9960" />
<H5>Success!</H5>
</Col>
</Row>
<Row center="xs">
<Col xs={10}>
<div style={{ marginBottom: '10px' }}>
<Text>
{`You should see now sharing session started on device you've connected
with.`}
</Text>
</div>
<div
id="connected-devices-list-text-success"
onMouseEnter={handleTextConnectedListMouseEnter}
onMouseLeave={handleTextConnectedListMouseLeave}
style={{
marginBottom: '25px',
textDecoration: 'underline dotted',
}}
>
<Text className="">
{`
You can manage connected devices by clicking "Connected Devices"
button in top panel
`}
</Text>
</div>
</Col>
</Row>
<Button
intent="primary"
onClick={props.handleReset}
icon="repeat"
style={{ borderRadius: '100px' }}
>
Connect New Device
</Button>
</Col>
);
};
export default SuccessStep;

View File

@ -0,0 +1,250 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should match exact snapshot 1`] = `
<BrowserRouter>
<Router
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
>
<ChooseAppOrScreeenStep
handleNextApplicationWindow={[Function]}
handleNextEntireScreen={[Function]}
>
<div
style={
Object {
"marginBottom": "10px",
}
}
>
<Blueprint3.Text>
<div
className=""
>
Choose Entire Screen or App window you want to view on other device
</div>
</Blueprint3.Text>
</div>
<ShareEntireScreenOrAppWindowControlGroup
handleNextApplicationWindow={[Function]}
handleNextEntireScreen={[Function]}
>
<Blueprint3.ControlGroup
className="makeStyles-controlGroupRoot-1"
fill={true}
id="share-screen-or-app-btn-group"
vertical={false}
>
<div
className="bp3-control-group bp3-fill makeStyles-controlGroupRoot-1"
id="share-screen-or-app-btn-group"
>
<Blueprint3.Button
className="makeStyles-shareEntireScreenButton-2"
intent="primary"
onClick={[Function]}
>
<button
className="bp3-button bp3-intent-primary makeStyles-shareEntireScreenButton-2"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
<Blueprint3.Icon
className="makeStyles-shareEntireScreenButtonIcon-3"
color="white"
icon="desktop"
iconSize={100}
>
<span
className="bp3-icon bp3-icon-desktop makeStyles-shareEntireScreenButtonIcon-3"
icon="desktop"
>
<svg
data-icon="desktop"
fill="white"
height={100}
viewBox="0 0 20 20"
width={100}
>
<desc>
desktop
</desc>
<path
d="M19 0H1C.45 0 0 .45 0 1v13c0 .55.45 1 1 1h5.67l-.5 3H5c-.55 0-1 .45-1 1s.45 1 1 1h10c.55 0 1-.45 1-1s-.45-1-1-1h-1.17l-.5-3H19c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-1 13H2V2h16v11z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<Blueprint3.Text
className="bp3-running-text"
>
<div
className="bp3-running-text"
>
Entire Screen
</div>
</Blueprint3.Text>
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
<Blueprint3.Button
className="makeStyles-shareAppButton-4"
intent="primary"
onClick={[Function]}
>
<button
className="bp3-button bp3-intent-primary makeStyles-shareAppButton-4"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
<Blueprint3.Icon
className="makeStyles-shareAppButtonIcon-5"
color="white"
icon="application"
iconSize={100}
>
<span
className="bp3-icon bp3-icon-application makeStyles-shareAppButtonIcon-5"
icon="application"
>
<svg
data-icon="application"
fill="white"
height={100}
viewBox="0 0 20 20"
width={100}
>
<desc>
application
</desc>
<path
d="M3.5 9h9c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-9c-.28 0-.5.22-.5.5s.22.5.5.5zm0 2h5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-5c-.28 0-.5.22-.5.5s.22.5.5.5zM19 1H1c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm-1 16H2V6h16v11zM3.5 13h7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-7c-.28 0-.5.22-.5.5s.22.5.5.5z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<Blueprint3.Text
className="bp3-running-text"
>
<div
className="bp3-running-text"
>
Application Window
</div>
</Blueprint3.Text>
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
<Blueprint3.Button
active={true}
className="makeStyles-orDecorationButton-6"
>
<button
className="bp3-button bp3-active makeStyles-orDecorationButton-6"
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
OR
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</div>
</Blueprint3.ControlGroup>
<ChooseAppOrScreenOverlay
handleClose={[Function]}
handleNextApplicationWindow={[Function]}
handleNextEntireScreen={[Function]}
isChooseAppOrScreenOverlayOpen={false}
isEntireScreenToShareChosen={false}
>
<Blueprint3.Dialog
autoFocus={true}
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="makeStyles-dialogRoot-7 choose-app-or-screen-dialog"
enforceFocus={true}
hasBackdrop={true}
isOpen={false}
onClose={[Function]}
transitionDuration={0}
usePortal={true}
>
<Blueprint3.Overlay
autoFocus={true}
backdropProps={Object {}}
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="bp3-overlay-scroll-container"
enforceFocus={true}
hasBackdrop={true}
isOpen={false}
lazy={true}
onClose={[Function]}
transitionDuration={0}
transitionName="bp3-overlay"
usePortal={true}
/>
</Blueprint3.Dialog>
</ChooseAppOrScreenOverlay>
</ShareEntireScreenOrAppWindowControlGroup>
</ChooseAppOrScreeenStep>
</Router>
</BrowserRouter>
`;

View File

@ -0,0 +1,112 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should match exact snapshot 1`] = `
<BrowserRouter>
<Router
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
>
<ConfirmStep
device={Object {}}
>
<div
style={
Object {
"marginBottom": "10px",
}
}
>
<Blueprint3.Text>
<div
className=""
>
Check if all is OK and click "Confirm"
</div>
</Blueprint3.Text>
</div>
<Blueprint3.Card
elevation={0}
interactive={false}
style={
Object {
"marginBottom": "10px",
}
}
>
<div
className="bp3-card bp3-elevation-0"
style={
Object {
"marginBottom": "10px",
}
}
>
<Blueprint3.Text>
<div
className=""
>
Device: undefined
</div>
</Blueprint3.Text>
<Blueprint3.Text>
<div
className=""
>
Device IP: undefined
</div>
</Blueprint3.Text>
<Blueprint3.Text>
<div
className=""
>
Device OS: undefined
</div>
</Blueprint3.Text>
<Blueprint3.Text>
<div
className=""
>
Session ID: undefined
</div>
</Blueprint3.Text>
</div>
</Blueprint3.Card>
<div
style={
Object {
"marginBottom": "10px",
}
}
>
<Blueprint3.Text
className="bp3-text-muted"
>
<div
className="bp3-text-muted"
>
Click "Back" if you need to change something
</div>
</Blueprint3.Text>
</div>
</ConfirmStep>
</Router>
</BrowserRouter>
`;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
<Fragment>
<div
style={
Object {
"textAlign": "center",
}
}
>
<Blueprint3.Text
className="bp3-text"
>
Scan the QR code
</Blueprint3.Text>
<Blueprint3.Text
className="bp3-text-muted"
>
( make sure your computer and device are connected on same WiFi )
</Blueprint3.Text>
</div>
<div>
<Blueprint3.Tooltip
content="Click to make bigger"
hoverCloseDelay={0}
hoverOpenDelay={100}
position="left"
transitionDuration={100}
>
<div
className="makeStyles-smallQRCode-1"
id="magnify-qr-code-button"
onClick={[Function]}
>
<QRCode
bgColor="rgba(0,0,0,0.0)"
fgColor="#000000"
imageSettings={
Object {
"height": 40,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"width": 40,
}
}
includeMargin={false}
level="H"
renderAs="svg"
size={128}
value="http://192.168.31.214:65000/99999"
/>
</div>
</Blueprint3.Tooltip>
</div>
<div
style={
Object {
"marginBottom": "10px",
}
}
>
<Blueprint3.Text
className="bp3-text-muted"
>
or type the following address manualy in browser address bar on any device:
</Blueprint3.Text>
</div>
<Blueprint3.Tooltip
content="Click to copy"
hoverCloseDelay={0}
hoverOpenDelay={100}
position="left"
transitionDuration={100}
>
<Blueprint3.Button
icon="duplicate"
intent="primary"
style={
Object {
"borderRadius": "100px",
}
}
>
http://255.255.255.255:65000/99999
</Blueprint3.Button>
</Blueprint3.Tooltip>
<Blueprint3.Dialog
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="makeStyles-bigQRCodeDialogRoot-3"
id="bp3-qr-code-dialog-root"
isOpen={false}
onClose={[Function]}
style={
Object {
"position": "relative",
"top": "-30px",
}
}
transitionDuration={0}
>
<div
id="qr-code-dialog-inner"
onClick={[Function]}
style={
Object {
"paddingBottom": "13px",
"paddingTop": "20px",
}
}
>
<Row
between="xs"
middle="xs"
>
<Col
xs={10}
>
<Component
style={
Object {
"margin": "0px",
"marginLeft": "35px",
"padding": "0px",
}
}
>
Scan QR Code
</Component>
</Col>
<Col
xs={2}
>
<CloseOverlayButton
noDefaultStyles={true}
onClick={[Function]}
style={
Object {
"borderRadius": "100px",
"height": "40px",
"position": "relative",
"width": "40px",
}
}
/>
</Col>
</Row>
<Row
center="xs"
>
<div
className="makeStyles-dialogQRWrapper-2"
>
<QRCode
bgColor="#FFFFFF"
fgColor="#000000"
height="390px"
imageSettings={
Object {
"height": 25,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"width": 25,
}
}
includeMargin={false}
level="H"
renderAs="svg"
size={128}
value="http://192.168.31.214:65000/99999"
width="390px"
/>
</div>
</Row>
</div>
</Blueprint3.Dialog>
</Fragment>
`;

View File

@ -0,0 +1,220 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should match exact snapshot 1`] = `
<BrowserRouter>
<Router
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
>
<SuccessStep
handleReset={[Function]}
>
<Col
md={6}
style={
Object {
"display": "flex",
"flexDirection": "column",
"justifyContent": "center",
}
}
xs={8}
>
<div
className="col-xs-8 col-md-6"
style={
Object {
"display": "flex",
"flexDirection": "column",
"justifyContent": "center",
}
}
>
<Row
center="xs"
>
<div
className="row center-xs"
>
<Col
xs={12}
>
<div
className="col-xs-12"
>
<Blueprint3.Icon
color="#0F9960"
icon="endorsed"
iconSize={35}
>
<span
className="bp3-icon bp3-icon-endorsed"
icon="endorsed"
>
<svg
data-icon="endorsed"
fill="#0F9960"
height={35}
viewBox="0 0 20 20"
width={35}
>
<desc>
endorsed
</desc>
<path
d="M19.83 9.38L18.81 7.6V5.62c0-.45-.23-.85-.61-1.08l-1.71-1-1.02-1.76a1.25 1.25 0 00-1.08-.61h-2.03l-1.74-1c-.38-.23-.87-.23-1.25 0l-1.74 1H5.65c-.44 0-.85.23-1.08.61L3.58 3.5l-1.8 1.04c-.38.24-.62.64-.62 1.08v2.06L.17 9.4c-.11.19-.17.4-.17.61s.06.42.17.61l.99 1.72v2.06c0 .45.23.85.61 1.08l1.78 1.02.99 1.72c.23.38.63.61 1.08.61h1.99l1.74 1c.19.11.41.17.62.17.21 0 .42-.06.61-.17l1.74-1h2.03c.44 0 .85-.23 1.08-.61l1.02-1.76 1.71-1c.38-.23.61-.64.61-1.08v-1.97l1.02-1.78c.27-.38.27-.85.04-1.25zm-5.08-.71l-5.01 5.01c-.18.18-.43.29-.71.29-.28 0-.53-.11-.71-.29l-3.01-3.01a1.003 1.003 0 011.42-1.42l2.3 2.3 4.31-4.3a1.003 1.003 0 011.71.71c0 .28-.12.53-.3.71z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<Component>
<h5
className="bp3-heading"
>
Success!
</h5>
</Component>
</div>
</Col>
</div>
</Row>
<Row
center="xs"
>
<div
className="row center-xs"
>
<Col
xs={10}
>
<div
className="col-xs-10"
>
<div
style={
Object {
"marginBottom": "10px",
}
}
>
<Blueprint3.Text>
<div
className=""
>
You should see now sharing session started on device you've connected
with.
</div>
</Blueprint3.Text>
</div>
<div
id="connected-devices-list-text-success"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
style={
Object {
"marginBottom": "25px",
"textDecoration": "underline dotted",
}
}
>
<Blueprint3.Text
className=""
>
<div
className=""
>
You can manage connected devices by clicking "Connected Devices"
button in top panel
</div>
</Blueprint3.Text>
</div>
</div>
</Col>
</div>
</Row>
<Blueprint3.Button
icon="repeat"
intent="primary"
onClick={[Function]}
style={
Object {
"borderRadius": "100px",
}
}
>
<button
className="bp3-button bp3-intent-primary"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
style={
Object {
"borderRadius": "100px",
}
}
type="button"
>
<Blueprint3.Icon
icon="repeat"
key="leftIcon"
>
<span
className="bp3-icon bp3-icon-repeat"
icon="repeat"
>
<svg
data-icon="repeat"
height={16}
viewBox="0 0 16 16"
width={16}
>
<desc>
repeat
</desc>
<path
d="M10 5c0 .55.45 1 1 1h4c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1s-1 .45-1 1v1.74A7.95 7.95 0 008 0C3.58 0 0 3.58 0 8c0 4.06 3.02 7.4 6.94 7.92.02 0 .04.01.06.01.33.04.66.07 1 .07 4.42 0 8-3.58 8-8 0-.55-.45-1-1-1s-1 .45-1 1c0 3.31-2.69 6-6 6-.71 0-1.37-.15-2-.38v.01C3.67 12.81 2 10.61 2 8c0-3.31 2.69-6 6-6 1.77 0 3.36.78 4.46 2H11c-.55 0-1 .45-1 1z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<span
className="bp3-button-text"
key="text"
>
Connect New Device
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</div>
</Col>
</SuccessStep>
</Router>
</BrowserRouter>
`;

View File

@ -0,0 +1,78 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React from 'react';
import Enzyme, { ShallowWrapper } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import TopPanel from './TopPanel';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
const settingsButtonSelector = '#top-panel-settings-button';
const connectedDevicesListButtonSelector =
'#top-panel-connected-devices-list-button';
describe('<TopPanel />', () => {
let wrapper: ShallowWrapper;
beforeEach(() => {
wrapper = Enzyme.shallow(<TopPanel />);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should match exact snapshot', () => {
expect(EnzymeToJson(wrapper)).toMatchSnapshot();
});
describe('when Settings button in top panel is clicked', () => {
it('should Settings Overlay props isSettingsOpen to true', () => {
// @ts-ignore
wrapper.find(settingsButtonSelector).props().onClick();
// @ts-ignore
const settingsOverlay = wrapper.props().children[1];
expect(settingsOverlay.props.isSettingsOpen).toBe(true);
});
});
describe('when Settings panel is opened and when its props.handleClose is called', () => {
it('should Settings Overlay props isSettingsOpen to false', () => {
// @ts-ignore
wrapper.find(settingsButtonSelector).props().onClick();
// @ts-ignore
const settingsOverlay = wrapper.props().children[1];
settingsOverlay.props.handleClose();
// @ts-ignore
const settingsOverlayAfterClose = wrapper.props().children[1];
expect(settingsOverlayAfterClose.props.isSettingsOpen).toBe(false);
});
});
describe('when Connected Devices button in top panel is clicked', () => {
it('should set Connected Devices List Drawer props isOpen to true', () => {
// @ts-ignore
wrapper.find(connectedDevicesListButtonSelector).props().onClick();
// @ts-ignore
const connectedDevicesListDrawer = wrapper.props().children[2];
expect(connectedDevicesListDrawer.props.isOpen).toBe(true);
});
});
describe("when Connected Devices List Drawer is opened, and when it's props.handleToggle is called", () => {
it('should set Connected Devices List Drawer props isOpen to false', () => {
// @ts-ignore
wrapper.find(connectedDevicesListButtonSelector).props().onClick();
// @ts-ignore
const connectedDevicesListDrawer = wrapper.props().children[2];
connectedDevicesListDrawer.props.handleToggle();
// @ts-ignore
const connectedDevicesListDrawerAfter = wrapper.props().children[2];
expect(connectedDevicesListDrawerAfter.props.isOpen).toBe(false);
});
});
});

177
app/components/TopPanel.tsx Normal file
View File

@ -0,0 +1,177 @@
/* eslint-disable react/destructuring-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useCallback, useContext } from 'react';
import { Button, Icon, Position, Tooltip } from '@blueprintjs/core';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import SettingsOverlay from './SettingsOverlay/SettingsOverlay';
import ConnectedDevicesListDrawer from './ConnectedDevicesListDrawer';
import { SettingsContext } from '../containers/SettingsProvider';
import isProduction from '../utils/isProduction';
const Zoom = require('react-reveal/Zoom');
const Fade = require('react-reveal/Fade');
const useStylesWithTheme = (isDarkTheme: boolean) =>
makeStyles(() =>
createStyles({
topPanelRoot: {
display: 'flex',
paddingTop: '15px',
marginBottom: '20px',
},
logoWithAppName: { margin: '0 auto' },
appNameHeader: {
margin: '0 auto',
paddingTop: '5px',
fontFamily: 'Lexend Peta',
fontSize: '20px',
color: isDarkTheme ? '#48AFF0' : '#1F4B99',
cursor: 'default !important',
},
topPanelControlButtonsRoot: {
position: 'absolute',
right: '15px',
display: 'flex',
},
topPanelControlButton: {
width: '40px',
height: '40px',
borderRadius: '50px',
cursor: 'default !important',
},
topPanelControlButtonMargin: {
marginRight: '20px',
cursor: 'default !important',
},
topPanelIconOfControlButton: {
cursor: 'default !important',
},
})
);
export default function TopPanel(props: any) {
const { isDarkTheme } = useContext(SettingsContext);
const getClassesCallback = useCallback(() => {
return useStylesWithTheme(isDarkTheme)();
}, [isDarkTheme]);
const [isSettingsOpen, setIsSettingsOpen] = React.useState(false);
const [isDrawersOpen, setIsDrawerOpen] = React.useState(false);
const handleSettingsOpen = useCallback(() => {
setIsSettingsOpen(true);
}, []);
const handleSettingsClose = useCallback(() => {
setIsSettingsOpen(false);
}, []);
const handleToggleConnectedDevicesListDrawer = useCallback(() => {
setIsDrawerOpen(!isDrawersOpen);
}, [isDrawersOpen]);
const renderConnectedDevicesListButton = useCallback(() => {
return (
<div className={getClassesCallback().topPanelControlButtonMargin}>
<Tooltip content="Connected Devices" position={Position.BOTTOM}>
<Button
id="top-panel-connected-devices-list-button"
intent="none"
className={getClassesCallback().topPanelControlButton}
onClick={handleToggleConnectedDevicesListDrawer}
>
<Icon
className={getClassesCallback().topPanelIconOfControlButton}
icon="th-list"
iconSize={20}
/>
</Button>
</Tooltip>
</div>
);
}, [getClassesCallback, handleToggleConnectedDevicesListDrawer]);
const renderHelpButton = useCallback(() => {
return (
<div className={getClassesCallback().topPanelControlButtonMargin}>
<Tooltip content="Help" position={Position.BOTTOM}>
<Button
id="top-panel-help-button"
intent="none"
className={getClassesCallback().topPanelControlButton}
>
<Icon
className={getClassesCallback().topPanelIconOfControlButton}
icon="help"
iconSize={22}
/>
</Button>
</Tooltip>
</div>
);
}, [getClassesCallback]);
const renderSettingsButton = useCallback(() => {
return (
<div className={getClassesCallback().topPanelControlButtonMargin}>
<Tooltip content="Settings" position={Position.BOTTOM}>
<Button
id="top-panel-settings-button"
onClick={handleSettingsOpen}
className={getClassesCallback().topPanelControlButton}
>
<Icon
className={getClassesCallback().topPanelIconOfControlButton}
icon="cog"
iconSize={22}
/>
</Button>
</Tooltip>
</div>
);
}, [getClassesCallback, handleSettingsOpen]);
const renderLogoWithAppName = useCallback(() => {
return (
<Zoom top duration={isProduction() ? 3000 : 0}>
<div
id="logo-with-popover-visit-website"
className={getClassesCallback().logoWithAppName}
>
<h4
id="deskreen-top-app-name-header"
className={getClassesCallback().appNameHeader}
>
Deskreen
</h4>
</div>
</Zoom>
);
}, [getClassesCallback]);
return (
<>
<div className={getClassesCallback().topPanelRoot}>
{renderLogoWithAppName()}
<div className={getClassesCallback().topPanelControlButtonsRoot}>
<Fade right duration={isProduction() ? 2000 : 0}>
{renderConnectedDevicesListButton()}
{renderHelpButton()}
{renderSettingsButton()}
</Fade>
</div>
</div>
<SettingsOverlay
isSettingsOpen={isSettingsOpen}
handleClose={handleSettingsClose}
/>
<ConnectedDevicesListDrawer
isOpen={isDrawersOpen}
handleToggle={handleToggleConnectedDevicesListDrawer}
stepperRef={props.stepperRef}
/>
</>
);
}

View File

@ -0,0 +1,607 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should match exact snapshot 1`] = `
<BrowserRouter>
<Router
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
>
<AllowConnectionForDeviceAlert
device={Object {}}
isOpen={true}
onCancel={[Function]}
onConfirm={[Function]}
>
<Blueprint3.Alert
canEscapeKeyCancel={false}
canOutsideClickCancel={false}
cancelButtonText="Deny"
className="class-allow-device-to-connect-alert"
confirmButtonText="Allow"
icon="feed"
intent="danger"
isOpen={true}
onCancel={[Function]}
onConfirm={[Function]}
transitionDuration={0}
>
<Blueprint3.Dialog
canEscapeKeyClose={false}
canOutsideClickClose={false}
className="bp3-alert class-allow-device-to-connect-alert"
isOpen={true}
onCancel={[Function]}
onClose={[Function]}
onConfirm={[Function]}
transitionDuration={0}
>
<Blueprint3.Overlay
autoFocus={true}
backdropProps={Object {}}
canEscapeKeyClose={false}
canOutsideClickClose={false}
className="bp3-overlay-scroll-container"
enforceFocus={true}
hasBackdrop={true}
isOpen={true}
lazy={true}
onCancel={[Function]}
onClose={[Function]}
onConfirm={[Function]}
transitionDuration={0}
transitionName="bp3-overlay"
usePortal={true}
>
<Blueprint3.Portal
container={
<body
class="bp3-overlay-open"
>
<div
class="bp3-portal"
>
<div
class="bp3-overlay bp3-overlay-open bp3-overlay-scroll-container"
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
/>
<div
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
tabindex="0"
>
<div
class="bp3-dialog bp3-alert class-allow-device-to-connect-alert"
>
<div
class="bp3-alert-body"
>
<span
class="bp3-icon bp3-icon-feed bp3-intent-danger"
icon="feed"
>
<svg
data-icon="feed"
height="40"
viewBox="0 0 20 20"
width="40"
>
<desc>
feed
</desc>
<path
d="M2.5 15a2.5 2.5 0 000 5 2.5 2.5 0 000-5zm.5-5c-.55 0-1 .45-1 1s.45 1 1 1c2.76 0 5 2.24 5 5 0 .55.45 1 1 1s1-.45 1-1c0-3.87-3.13-7-7-7zM3 0c-.55 0-1 .45-1 1s.45 1 1 1c8.28 0 15 6.72 15 15 0 .55.45 1 1 1s1-.45 1-1C20 7.61 12.39 0 3 0zm0 5c-.55 0-1 .45-1 1s.45 1 1 1c5.52 0 10 4.48 10 10 0 .55.45 1 1 1s1-.45 1-1C15 10.37 9.63 5 3 5z"
fill-rule="evenodd"
/>
</svg>
</span>
<div
class="bp3-alert-contents"
>
<h3
class="bp3-heading"
>
Device is trying to connect
</h3>
<div
class="row"
>
<div
class=""
>
<div
class=""
>
Device IP:
</div>
<span
id="allow-connection-device-alert-device-ip-span"
/>
</div>
</div>
<div
class="row"
>
<div
class=""
>
<div
class=""
>
Device Type: undefined
</div>
</div>
</div>
<div
class="row"
>
<div
class=""
>
<div
class=""
>
Device OS: undefined
</div>
</div>
</div>
<div
class="row"
>
<div
class=""
>
<div
class=""
>
session ID: undefined
</div>
</div>
</div>
</div>
</div>
<div
class="bp3-alert-footer"
>
<button
class="bp3-button bp3-intent-danger"
type="button"
>
<span
class="bp3-button-text"
>
Allow
</span>
</button>
<button
class="bp3-button"
type="button"
>
<span
class="bp3-button-text"
>
Deny
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</body>
}
>
<Portal
containerInfo={
<div
class="bp3-portal"
>
<div
class="bp3-overlay bp3-overlay-open bp3-overlay-scroll-container"
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
/>
<div
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
tabindex="0"
>
<div
class="bp3-dialog bp3-alert class-allow-device-to-connect-alert"
>
<div
class="bp3-alert-body"
>
<span
class="bp3-icon bp3-icon-feed bp3-intent-danger"
icon="feed"
>
<svg
data-icon="feed"
height="40"
viewBox="0 0 20 20"
width="40"
>
<desc>
feed
</desc>
<path
d="M2.5 15a2.5 2.5 0 000 5 2.5 2.5 0 000-5zm.5-5c-.55 0-1 .45-1 1s.45 1 1 1c2.76 0 5 2.24 5 5 0 .55.45 1 1 1s1-.45 1-1c0-3.87-3.13-7-7-7zM3 0c-.55 0-1 .45-1 1s.45 1 1 1c8.28 0 15 6.72 15 15 0 .55.45 1 1 1s1-.45 1-1C20 7.61 12.39 0 3 0zm0 5c-.55 0-1 .45-1 1s.45 1 1 1c5.52 0 10 4.48 10 10 0 .55.45 1 1 1s1-.45 1-1C15 10.37 9.63 5 3 5z"
fill-rule="evenodd"
/>
</svg>
</span>
<div
class="bp3-alert-contents"
>
<h3
class="bp3-heading"
>
Device is trying to connect
</h3>
<div
class="row"
>
<div
class=""
>
<div
class=""
>
Device IP:
</div>
<span
id="allow-connection-device-alert-device-ip-span"
/>
</div>
</div>
<div
class="row"
>
<div
class=""
>
<div
class=""
>
Device Type: undefined
</div>
</div>
</div>
<div
class="row"
>
<div
class=""
>
<div
class=""
>
Device OS: undefined
</div>
</div>
</div>
<div
class="row"
>
<div
class=""
>
<div
class=""
>
session ID: undefined
</div>
</div>
</div>
</div>
</div>
<div
class="bp3-alert-footer"
>
<button
class="bp3-button bp3-intent-danger"
type="button"
>
<span
class="bp3-button-text"
>
Allow
</span>
</button>
<button
class="bp3-button"
type="button"
>
<span
class="bp3-button-text"
>
Deny
</span>
</button>
</div>
</div>
</div>
</div>
</div>
}
>
<TransitionGroup
appear={true}
childFactory={[Function]}
className="bp3-overlay bp3-overlay-open bp3-overlay-scroll-container"
component="div"
onKeyDown={[Function]}
>
<div
className="bp3-overlay bp3-overlay-open bp3-overlay-scroll-container"
onKeyDown={[Function]}
>
<CSSTransition
appear={true}
classNames="bp3-overlay"
in={true}
key=".$__backdrop"
onExited={[Function]}
timeout={0}
>
<Transition
appear={true}
enter={true}
exit={true}
in={true}
mountOnEnter={false}
onEnter={[Function]}
onEntered={[Function]}
onEntering={[Function]}
onExit={[Function]}
onExited={[Function]}
onExiting={[Function]}
timeout={0}
unmountOnExit={false}
>
<div
className="bp3-overlay-backdrop"
onMouseDown={[Function]}
tabIndex={null}
/>
</Transition>
</CSSTransition>
<CSSTransition
appear={true}
classNames="bp3-overlay"
in={true}
key=".$.0"
onExited={[Function]}
timeout={0}
>
<Transition
appear={true}
enter={true}
exit={true}
in={true}
mountOnEnter={false}
onEnter={[Function]}
onEntered={[Function]}
onEntering={[Function]}
onExit={[Function]}
onExited={[Function]}
onExiting={[Function]}
timeout={0}
unmountOnExit={false}
>
<div
className="bp3-dialog-container bp3-overlay-content"
tabIndex={0}
>
<div
className="bp3-dialog bp3-alert class-allow-device-to-connect-alert"
>
<div
className="bp3-alert-body"
>
<Blueprint3.Icon
icon="feed"
iconSize={40}
intent="danger"
>
<span
className="bp3-icon bp3-icon-feed bp3-intent-danger"
icon="feed"
>
<svg
data-icon="feed"
height={40}
viewBox="0 0 20 20"
width={40}
>
<desc>
feed
</desc>
<path
d="M2.5 15a2.5 2.5 0 000 5 2.5 2.5 0 000-5zm.5-5c-.55 0-1 .45-1 1s.45 1 1 1c2.76 0 5 2.24 5 5 0 .55.45 1 1 1s1-.45 1-1c0-3.87-3.13-7-7-7zM3 0c-.55 0-1 .45-1 1s.45 1 1 1c8.28 0 15 6.72 15 15 0 .55.45 1 1 1s1-.45 1-1C20 7.61 12.39 0 3 0zm0 5c-.55 0-1 .45-1 1s.45 1 1 1c5.52 0 10 4.48 10 10 0 .55.45 1 1 1s1-.45 1-1C15 10.37 9.63 5 3 5z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<div
className="bp3-alert-contents"
>
<Component>
<h3
className="bp3-heading"
>
Device is trying to connect
</h3>
</Component>
<Row>
<div
className="row"
>
<Col>
<div
className=""
>
<Blueprint3.Text>
<div
className=""
>
Device IP:
</div>
</Blueprint3.Text>
<span
id="allow-connection-device-alert-device-ip-span"
/>
</div>
</Col>
</div>
</Row>
<Row>
<div
className="row"
>
<Col>
<div
className=""
>
<Blueprint3.Text>
<div
className=""
>
Device Type: undefined
</div>
</Blueprint3.Text>
</div>
</Col>
</div>
</Row>
<Row>
<div
className="row"
>
<Col>
<div
className=""
>
<Blueprint3.Text>
<div
className=""
>
Device OS: undefined
</div>
</Blueprint3.Text>
</div>
</Col>
</div>
</Row>
<Row>
<div
className="row"
>
<Col>
<div
className=""
>
<Blueprint3.Text>
<div
className=""
>
session ID: undefined
</div>
</Blueprint3.Text>
</div>
</Col>
</div>
</Row>
</div>
</div>
<div
className="bp3-alert-footer"
>
<Blueprint3.Button
intent="danger"
onClick={[Function]}
text="Allow"
>
<button
className="bp3-button bp3-intent-danger"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
Allow
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
<Blueprint3.Button
onClick={[Function]}
text="Deny"
>
<button
className="bp3-button"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
Deny
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</div>
</div>
</div>
</Transition>
</CSSTransition>
</div>
</TransitionGroup>
</Portal>
</Blueprint3.Portal>
</Blueprint3.Overlay>
</Blueprint3.Dialog>
</Blueprint3.Alert>
</AllowConnectionForDeviceAlert>
</Router>
</BrowserRouter>
`;

View File

@ -0,0 +1,644 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should match exact snapshot 1`] = `
<BrowserRouter>
<Router
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
>
<ConnectedDevicesListDrawer
handleToggle={[Function]}
isOpen={true}
stepperRef={null}
>
<Blueprint3.Drawer
canOutsideClickClose={true}
className="makeStyles-drawerRoot-1"
isOpen={true}
onClose={[Function]}
position="bottom"
size="90%"
style={Object {}}
transitionDuration={0}
vertical={false}
>
<Blueprint3.Overlay
autoFocus={true}
backdropProps={Object {}}
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="bp3-overlay-container"
enforceFocus={true}
hasBackdrop={true}
isOpen={true}
lazy={true}
onClose={[Function]}
position="bottom"
size="90%"
style={Object {}}
transitionDuration={0}
transitionName="bp3-overlay"
usePortal={true}
vertical={false}
>
<Blueprint3.Portal
container={
<body
class="bp3-overlay-open"
>
<div
class="bp3-portal"
>
<div
class="bp3-overlay bp3-overlay-open bp3-overlay-container"
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
tabindex="0"
/>
<div
class="bp3-drawer bp3-position-bottom makeStyles-drawerRoot-1 bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
style="height: 90%;"
tabindex="0"
>
<div
class="makeStyles-drawerInnerTopPanel-2 row middle-xs between-xs"
>
<div
class="col-xs-11"
>
<div
class="row middle-xs"
>
<div
class="makeStyles-topHeader-4"
>
<div
class="bp3-text-muted"
>
Connected Devices
</div>
</div>
<button
class="bp3-button bp3-disabled bp3-intent-danger"
disabled=""
tabindex="-1"
type="button"
>
<span
class="bp3-icon bp3-icon-disable"
icon="disable"
>
<svg
data-icon="disable"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
disable
</desc>
<path
d="M7.99-.01c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm-6 8c0-3.31 2.69-6 6-6 1.3 0 2.49.42 3.47 1.12l-8.35 8.35c-.7-.98-1.12-2.17-1.12-3.47zm6 6c-1.3 0-2.49-.42-3.47-1.12l8.35-8.35c.7.98 1.12 2.17 1.12 3.47 0 3.32-2.68 6-6 6z"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="bp3-button-text"
>
Disconnect all devices
</span>
</button>
</div>
</div>
<div
class="col-xs-1"
>
<button
class="bp3-button makeStyles-closeButton-6 undefined"
id="close-overlay-button"
type="button"
>
<span
class="bp3-button-text"
>
<span
class="bp3-icon bp3-icon-cross"
icon="cross"
>
<svg
data-icon="cross"
height="30"
viewBox="0 0 20 20"
width="30"
>
<desc>
cross
</desc>
<path
d="M11.41 10l4.29-4.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L10 8.59l-4.29-4.3a1.003 1.003 0 00-1.42 1.42L8.59 10 4.3 14.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4.29-4.3 4.29 4.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L11.41 10z"
fill-rule="evenodd"
/>
</svg>
</span>
</span>
</button>
</div>
</div>
<div
class="makeStyles-connectedDevicesRoot-3 row"
>
<div
class="col-xs-12"
>
<div
class="react-reveal makeStyles-zoomFullWidth-5"
/>
</div>
</div>
</div>
</div>
</div>
</body>
}
>
<Portal
containerInfo={
<div
class="bp3-portal"
>
<div
class="bp3-overlay bp3-overlay-open bp3-overlay-container"
>
<div
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
tabindex="0"
/>
<div
class="bp3-drawer bp3-position-bottom makeStyles-drawerRoot-1 bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
style="height: 90%;"
tabindex="0"
>
<div
class="makeStyles-drawerInnerTopPanel-2 row middle-xs between-xs"
>
<div
class="col-xs-11"
>
<div
class="row middle-xs"
>
<div
class="makeStyles-topHeader-4"
>
<div
class="bp3-text-muted"
>
Connected Devices
</div>
</div>
<button
class="bp3-button bp3-disabled bp3-intent-danger"
disabled=""
tabindex="-1"
type="button"
>
<span
class="bp3-icon bp3-icon-disable"
icon="disable"
>
<svg
data-icon="disable"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
disable
</desc>
<path
d="M7.99-.01c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm-6 8c0-3.31 2.69-6 6-6 1.3 0 2.49.42 3.47 1.12l-8.35 8.35c-.7-.98-1.12-2.17-1.12-3.47zm6 6c-1.3 0-2.49-.42-3.47-1.12l8.35-8.35c.7.98 1.12 2.17 1.12 3.47 0 3.32-2.68 6-6 6z"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="bp3-button-text"
>
Disconnect all devices
</span>
</button>
</div>
</div>
<div
class="col-xs-1"
>
<button
class="bp3-button makeStyles-closeButton-6 undefined"
id="close-overlay-button"
type="button"
>
<span
class="bp3-button-text"
>
<span
class="bp3-icon bp3-icon-cross"
icon="cross"
>
<svg
data-icon="cross"
height="30"
viewBox="0 0 20 20"
width="30"
>
<desc>
cross
</desc>
<path
d="M11.41 10l4.29-4.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L10 8.59l-4.29-4.3a1.003 1.003 0 00-1.42 1.42L8.59 10 4.3 14.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4.29-4.3 4.29 4.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L11.41 10z"
fill-rule="evenodd"
/>
</svg>
</span>
</span>
</button>
</div>
</div>
<div
class="makeStyles-connectedDevicesRoot-3 row"
>
<div
class="col-xs-12"
>
<div
class="react-reveal makeStyles-zoomFullWidth-5"
/>
</div>
</div>
</div>
</div>
</div>
}
>
<TransitionGroup
appear={true}
childFactory={[Function]}
className="bp3-overlay bp3-overlay-open bp3-overlay-container"
component="div"
onKeyDown={[Function]}
>
<div
className="bp3-overlay bp3-overlay-open bp3-overlay-container"
onKeyDown={[Function]}
>
<CSSTransition
appear={true}
classNames="bp3-overlay"
in={true}
key=".$__backdrop"
onExited={[Function]}
timeout={0}
>
<Transition
appear={true}
enter={true}
exit={true}
in={true}
mountOnEnter={false}
onEnter={[Function]}
onEntered={[Function]}
onEntering={[Function]}
onExit={[Function]}
onExited={[Function]}
onExiting={[Function]}
timeout={0}
unmountOnExit={false}
>
<div
className="bp3-overlay-backdrop"
onMouseDown={[Function]}
tabIndex={0}
/>
</Transition>
</CSSTransition>
<CSSTransition
appear={true}
classNames="bp3-overlay"
in={true}
key=".$.0"
onExited={[Function]}
timeout={0}
>
<Transition
appear={true}
enter={true}
exit={true}
in={true}
mountOnEnter={false}
onEnter={[Function]}
onEntered={[Function]}
onEntering={[Function]}
onExit={[Function]}
onExited={[Function]}
onExiting={[Function]}
timeout={0}
unmountOnExit={false}
>
<div
className="bp3-drawer bp3-position-bottom makeStyles-drawerRoot-1 bp3-overlay-content"
style={
Object {
"height": "90%",
}
}
tabIndex={0}
>
<Row
between="xs"
className="makeStyles-drawerInnerTopPanel-2"
middle="xs"
>
<div
className="makeStyles-drawerInnerTopPanel-2 row middle-xs between-xs"
>
<Col
xs={11}
>
<div
className="col-xs-11"
>
<Row
middle="xs"
>
<div
className="row middle-xs"
>
<div
className="makeStyles-topHeader-4"
>
<Blueprint3.Text
className="bp3-text-muted"
>
<div
className="bp3-text-muted"
>
Connected Devices
</div>
</Blueprint3.Text>
</div>
<Blueprint3.Button
disabled={true}
icon="disable"
intent="danger"
onClick={[Function]}
>
<button
className="bp3-button bp3-disabled bp3-intent-danger"
disabled={true}
onKeyDown={[Function]}
onKeyUp={[Function]}
tabIndex={-1}
type="button"
>
<Blueprint3.Icon
icon="disable"
key="leftIcon"
>
<span
className="bp3-icon bp3-icon-disable"
icon="disable"
>
<svg
data-icon="disable"
height={16}
viewBox="0 0 16 16"
width={16}
>
<desc>
disable
</desc>
<path
d="M7.99-.01c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm-6 8c0-3.31 2.69-6 6-6 1.3 0 2.49.42 3.47 1.12l-8.35 8.35c-.7-.98-1.12-2.17-1.12-3.47zm6 6c-1.3 0-2.49-.42-3.47-1.12l8.35-8.35c.7.98 1.12 2.17 1.12 3.47 0 3.32-2.68 6-6 6z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<span
className="bp3-button-text"
key="text"
>
Disconnect all devices
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</div>
</Row>
</div>
</Col>
<Col
xs={1}
>
<div
className="col-xs-1"
>
<CloseOverlayButton
onClick={[Function]}
>
<Blueprint3.Button
className="makeStyles-closeButton-6 undefined"
id="close-overlay-button"
onClick={[Function]}
>
<button
className="bp3-button makeStyles-closeButton-6 undefined"
id="close-overlay-button"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
<Blueprint3.Icon
icon="cross"
iconSize={30}
>
<span
className="bp3-icon bp3-icon-cross"
icon="cross"
>
<svg
data-icon="cross"
height={30}
viewBox="0 0 20 20"
width={30}
>
<desc>
cross
</desc>
<path
d="M11.41 10l4.29-4.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L10 8.59l-4.29-4.3a1.003 1.003 0 00-1.42 1.42L8.59 10 4.3 14.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4.29-4.3 4.29 4.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L11.41 10z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</CloseOverlayButton>
</div>
</Col>
</div>
</Row>
<Row
className="makeStyles-connectedDevicesRoot-3"
>
<div
className="makeStyles-connectedDevicesRoot-3 row"
>
<Col
xs={12}
>
<div
className="col-xs-12"
>
<Fade
bottom={true}
cascade={true}
duration={0}
>
<RevealBase
bottom={true}
cascade={true}
fraction={0.2}
inEffect={
Object {
"count": 1,
"delay": 0,
"duration": 0,
"forever": undefined,
"make": [Function],
"reverse": undefined,
"style": Object {
"animationFillMode": "both",
},
}
}
outEffect={
Object {
"count": 1,
"delay": 0,
"duration": 0,
"forever": undefined,
"make": [Function],
"reverse": undefined,
"style": Object {
"animationFillMode": "both",
},
}
}
refProp="ref"
>
<div
className="react-reveal makeStyles-zoomFullWidth-5"
style={
Object {
"opacity": undefined,
}
}
/>
</RevealBase>
</Fade>
</div>
</Col>
</div>
</Row>
</div>
</Transition>
</CSSTransition>
</div>
</TransitionGroup>
</Portal>
</Blueprint3.Portal>
</Blueprint3.Overlay>
</Blueprint3.Drawer>
<Blueprint3.Alert
canEscapeKeyCancel={true}
canOutsideClickCancel={true}
cancelButtonText="No, Cancel"
confirmButtonText="Yes, Disconnect All"
icon="warning-sign"
intent="danger"
isOpen={false}
onCancel={[Function]}
onClose={[Function]}
onConfirm={[Function]}
transitionDuration={0}
>
<Blueprint3.Dialog
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="bp3-alert"
isOpen={false}
onCancel={[Function]}
onClose={[Function]}
onConfirm={[Function]}
transitionDuration={0}
>
<Blueprint3.Overlay
autoFocus={true}
backdropProps={Object {}}
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="bp3-overlay-scroll-container"
enforceFocus={true}
hasBackdrop={true}
isOpen={false}
lazy={true}
onCancel={[Function]}
onClose={[Function]}
onConfirm={[Function]}
transitionDuration={0}
transitionName="bp3-overlay"
usePortal={true}
/>
</Blueprint3.Dialog>
</Blueprint3.Alert>
</ConnectedDevicesListDrawer>
</Router>
</BrowserRouter>
`;

View File

@ -0,0 +1,230 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should match exact snapshot 1`] = `
<BrowserRouter>
<Router
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
>
<ShareEntireScreenOrAppWindowControlGroup
handleNextApplicationWindow={[Function]}
handleNextEntireScreen={[Function]}
>
<Blueprint3.ControlGroup
className="makeStyles-controlGroupRoot-1"
fill={true}
id="share-screen-or-app-btn-group"
vertical={false}
>
<div
className="bp3-control-group bp3-fill makeStyles-controlGroupRoot-1"
id="share-screen-or-app-btn-group"
>
<Blueprint3.Button
className="makeStyles-shareEntireScreenButton-2"
intent="primary"
onClick={[Function]}
>
<button
className="bp3-button bp3-intent-primary makeStyles-shareEntireScreenButton-2"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
<Blueprint3.Icon
className="makeStyles-shareEntireScreenButtonIcon-3"
color="white"
icon="desktop"
iconSize={100}
>
<span
className="bp3-icon bp3-icon-desktop makeStyles-shareEntireScreenButtonIcon-3"
icon="desktop"
>
<svg
data-icon="desktop"
fill="white"
height={100}
viewBox="0 0 20 20"
width={100}
>
<desc>
desktop
</desc>
<path
d="M19 0H1C.45 0 0 .45 0 1v13c0 .55.45 1 1 1h5.67l-.5 3H5c-.55 0-1 .45-1 1s.45 1 1 1h10c.55 0 1-.45 1-1s-.45-1-1-1h-1.17l-.5-3H19c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-1 13H2V2h16v11z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<Blueprint3.Text
className="bp3-running-text"
>
<div
className="bp3-running-text"
>
Entire Screen
</div>
</Blueprint3.Text>
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
<Blueprint3.Button
className="makeStyles-shareAppButton-4"
intent="primary"
onClick={[Function]}
>
<button
className="bp3-button bp3-intent-primary makeStyles-shareAppButton-4"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
<Blueprint3.Icon
className="makeStyles-shareAppButtonIcon-5"
color="white"
icon="application"
iconSize={100}
>
<span
className="bp3-icon bp3-icon-application makeStyles-shareAppButtonIcon-5"
icon="application"
>
<svg
data-icon="application"
fill="white"
height={100}
viewBox="0 0 20 20"
width={100}
>
<desc>
application
</desc>
<path
d="M3.5 9h9c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-9c-.28 0-.5.22-.5.5s.22.5.5.5zm0 2h5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-5c-.28 0-.5.22-.5.5s.22.5.5.5zM19 1H1c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm-1 16H2V6h16v11zM3.5 13h7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-7c-.28 0-.5.22-.5.5s.22.5.5.5z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<Blueprint3.Text
className="bp3-running-text"
>
<div
className="bp3-running-text"
>
Application Window
</div>
</Blueprint3.Text>
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
<Blueprint3.Button
active={true}
className="makeStyles-orDecorationButton-6"
>
<button
className="bp3-button bp3-active makeStyles-orDecorationButton-6"
onKeyDown={[Function]}
onKeyUp={[Function]}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
OR
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</div>
</Blueprint3.ControlGroup>
<ChooseAppOrScreenOverlay
handleClose={[Function]}
handleNextApplicationWindow={[Function]}
handleNextEntireScreen={[Function]}
isChooseAppOrScreenOverlayOpen={false}
isEntireScreenToShareChosen={false}
>
<Blueprint3.Dialog
autoFocus={true}
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="makeStyles-dialogRoot-7 choose-app-or-screen-dialog"
enforceFocus={true}
hasBackdrop={true}
isOpen={false}
onClose={[Function]}
transitionDuration={0}
usePortal={true}
>
<Blueprint3.Overlay
autoFocus={true}
backdropProps={Object {}}
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="bp3-overlay-scroll-container"
enforceFocus={true}
hasBackdrop={true}
isOpen={false}
lazy={true}
onClose={[Function]}
transitionDuration={0}
transitionName="bp3-overlay"
usePortal={true}
/>
</Blueprint3.Dialog>
</ChooseAppOrScreenOverlay>
</ShareEntireScreenOrAppWindowControlGroup>
</Router>
</BrowserRouter>
`;

View File

@ -0,0 +1,113 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<TopPanel /> should match exact snapshot 1`] = `
<Fragment>
<div
className="makeStyles-topPanelRoot-1"
>
<Zoom
duration={0}
top={true}
>
<div
className="makeStyles-logoWithAppName-9"
id="logo-with-popover-visit-website"
>
<h4
className="makeStyles-appNameHeader-17"
id="deskreen-top-app-name-header"
>
Deskreen
</h4>
</div>
</Zoom>
<div
className="makeStyles-topPanelControlButtonsRoot-25"
>
<Fade
duration={0}
right={true}
>
<div
className="makeStyles-topPanelControlButtonMargin-34"
>
<Blueprint3.Tooltip
content="Connected Devices"
hoverCloseDelay={0}
hoverOpenDelay={100}
position="bottom"
transitionDuration={100}
>
<Blueprint3.Button
className="makeStyles-topPanelControlButton-40"
id="top-panel-connected-devices-list-button"
intent="none"
onClick={[Function]}
>
<Blueprint3.Icon
className="makeStyles-topPanelIconOfControlButton-49"
icon="th-list"
iconSize={20}
/>
</Blueprint3.Button>
</Blueprint3.Tooltip>
</div>
<div
className="makeStyles-topPanelControlButtonMargin-55"
>
<Blueprint3.Tooltip
content="Help"
hoverCloseDelay={0}
hoverOpenDelay={100}
position="bottom"
transitionDuration={100}
>
<Blueprint3.Button
className="makeStyles-topPanelControlButton-61"
id="top-panel-help-button"
intent="none"
>
<Blueprint3.Icon
className="makeStyles-topPanelIconOfControlButton-70"
icon="help"
iconSize={22}
/>
</Blueprint3.Button>
</Blueprint3.Tooltip>
</div>
<div
className="makeStyles-topPanelControlButtonMargin-76"
>
<Blueprint3.Tooltip
content="Settings"
hoverCloseDelay={0}
hoverOpenDelay={100}
position="bottom"
transitionDuration={100}
>
<Blueprint3.Button
className="makeStyles-topPanelControlButton-82"
id="top-panel-settings-button"
onClick={[Function]}
>
<Blueprint3.Icon
className="makeStyles-topPanelIconOfControlButton-91"
icon="cog"
iconSize={22}
/>
</Blueprint3.Button>
</Blueprint3.Tooltip>
</div>
</Fade>
</div>
</div>
<SettingsOverlay
handleClose={[Function]}
isSettingsOpen={false}
/>
<ConnectedDevicesListDrawer
handleToggle={[Function]}
isOpen={false}
/>
</Fragment>
`;

View File

@ -1,5 +0,0 @@
export default {
languages: ['ru', 'en'],
fallbackLng: 'en',
namespace: 'translation',
};

View File

@ -0,0 +1,10 @@
export default {
fallbackLng: 'en',
namespace: 'translation',
languages: ['ru', 'en', 'ua'],
langISOKeyToLangFullNameMap: {
en: 'English',
ru: 'Русский',
ua: 'Українська',
},
};

View File

@ -1,18 +1,24 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { remote, ipcRenderer } from 'electron';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import SyncBackend from 'i18next-node-fs-backend';
import { join } from 'path';
// import isDev from 'electron-is-dev';
import config from './app.config';
import settings from 'electron-settings';
import config from './app.lang.config';
import isProduction from '../utils/isProduction';
let isDev;
try {
// eslint-disable-next-line global-require
isDev = require('electron-is-dev');
} catch (e) {
isDev = true;
}
export const getLangNameToLangKeyMap = () => {
const res = {};
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(
config.langISOKeyToLangFullNameMap
)) {
// @ts-ignore: fine here
res[value] = key;
}
return res;
};
const appPath = remote.getGlobal('appPath');
@ -22,18 +28,20 @@ const i18nextOptions = {
},
backend: {
// path where resources get loaded from
loadPath: isDev
? join(__dirname, './locales/{{lng}}/{{ns}}.json')
: join(appPath, 'locales/{{lng}}/{{ns}}.json'),
loadPath: isProduction()
? join(appPath, 'locales/{{lng}}/{{ns}}.json')
: join(__dirname, './locales/{{lng}}/{{ns}}.json'),
// path to post missing resources
addPath: isDev
? join(__dirname, './locales/{{lng}}/{{ns}}.missing.json')
: join(appPath, 'locales/{{lng}}/{{ns}}.json'),
addPath: isProduction()
? join(appPath, 'locales/{{lng}}/{{ns}}.json')
: join(__dirname, './locales/{{lng}}/{{ns}}.missing.json'),
// jsonIndent to use when storing json files
jsonIndent: 2,
},
saveMissing: true,
lng: 'en',
lng: (settings.hasSync('appLanguage')
? settings.getSync('appLanguage')
: 'en') as string,
fallbackLng: config.fallbackLng,
whitelist: config.languages,
react: {
@ -47,8 +55,8 @@ if (!i18n.isInitialized) {
i18n.init(i18nextOptions);
}
ipcRenderer.on('sending-language-from-main', (_, message) => {
i18n.changeLanguage(`${message}`);
i18n.on('languageChanged', () => {
ipcRenderer.send('client-changed-language', i18n.language);
});
export default i18n;

View File

@ -1,36 +1,26 @@
// const i18n = require('i18next');
// const i18nextBackend = require('i18next-fs-backend');
// const { join } = require('path');
// const config = require('./app.config');
import i18n from 'i18next';
import i18nextBackend from 'i18next-node-fs-backend';
import { join } from 'path';
// import isDev from 'electron-is-dev';
import config from './app.config';
let isDev;
try {
// eslint-disable-next-line global-require
isDev = require('electron-is-dev');
} catch (e) {
isDev = true;
}
import settings from 'electron-settings';
import config from './app.lang.config';
import isProduction from '../utils/isProduction';
const i18nextOptions = {
fallbackLng: config.fallbackLng,
lng: 'en',
lng: (settings.hasSync('appLanguage')
? settings.getSync('appLanguage')
: 'en') as string,
ns: 'translation',
defaultNS: 'translation',
backend: {
// path where resources get loaded from
loadPath: isDev
? join(__dirname, '../locales/{{lng}}/{{ns}}.json')
: join(__dirname, 'locales/{{lng}}/{{ns}}.json'),
loadPath: isProduction()
? join(__dirname, 'locales/{{lng}}/{{ns}}.json')
: join(__dirname, '../locales/{{lng}}/{{ns}}.json'),
// path to post missing resources
addPath: isDev
? join(__dirname, '../locales/{{lng}}/{{ns}}.missing.json')
: join(__dirname, 'locales/{{lng}}/{{ns}}.json'),
addPath: isProduction()
? join(__dirname, 'locales/{{lng}}/{{ns}}.json')
: join(__dirname, '../locales/{{lng}}/{{ns}}.missing.json'),
// jsonIndent to use when storing json files
jsonIndent: 2,
},

View File

@ -0,0 +1,72 @@
[
{
"id": "123414",
"sessionId": "14422424",
"deviceOs": "Android",
"deviceType": "Mobile",
"deviceIp": "123.123.123.123"
},
{
"id": "1123",
"sessionId": "43",
"deviceOs": "IOS",
"deviceType": "Mobile",
"deviceIp": "124.124.124.124"
},
{
"id": "2",
"sessionId": "22",
"deviceOs": "Windows",
"deviceType": "PS",
"deviceIp": "255.255.124.124"
},
{
"id": "33",
"sessionId": "22",
"deviceOs": "Ubuntu",
"deviceType": "PC",
"deviceIp": "4.4.4.124"
},
{
"id": "414",
"sessionId": "423",
"deviceOs": "Blackberry",
"deviceType": "Mobile",
"deviceIp": "224.224.224.124"
},
{
"id": "1133223",
"sessionId": "4133",
"deviceOs": "IOS",
"deviceType": "Mobile",
"deviceIp": "24.24.24.24"
},
{
"id": "1321123",
"sessionId": "44133",
"deviceOs": "Android",
"deviceType": "Mobile",
"deviceIp": "14.14.14.14"
},
{
"id": "993",
"sessionId": "91322",
"deviceOs": "Debian",
"deviceType": "PC",
"deviceIp": "1.1.14.14"
},
{
"id": "7757",
"sessionId": "1111",
"deviceOs": "MacOS",
"deviceType": "Mac",
"deviceIp": "12.12.14.14"
},
{
"id": "123332",
"sessionId": "323231",
"deviceOs": "MacOS",
"deviceType": "Mac",
"deviceIp": "11.11.14.14"
}
]

View File

@ -0,0 +1,88 @@
[
{
"path": "https://material-ui.com/static/images/grid-list/breakfast.jpg",
"id": "https://material-ui.com/static/images/grid-list/breakfast.jpg",
"name": "Breakfast",
"author": "jill111",
"cols": 2,
"featured": true
},
{
"path": "https://material-ui.com/static/images/grid-list/burgers.jpg",
"id": "https://material-ui.com/static/images/grid-list/burgers.jpg",
"name": "Tasty burger",
"cols": 3,
"author": "director90"
},
{
"path": "https://material-ui.com/static/images/grid-list/camera.jpg",
"id": "https://material-ui.com/static/images/grid-list/camera.jpg",
"name": "Camera",
"cols": 2,
"author": "Danson67"
},
{
"path": "https://material-ui.com/static/images/grid-list/morning.jpg",
"id": "https://material-ui.com/static/images/grid-list/morning.jpg",
"name": "Morning",
"author": "fancycrave1",
"cols": 1,
"featured": true
},
{
"path": "https://material-ui.com/static/images/grid-list/hats.jpg",
"id": "https://material-ui.com/static/images/grid-list/hats.jpg",
"name": "Hats",
"cols": 4,
"author": "Hans"
},
{
"path": "https://material-ui.com/static/images/grid-list/honey.jpg",
"id": "https://material-ui.com/static/images/grid-list/honey.jpg",
"name": "Honey",
"cols": 1,
"author": "fancycravel"
},
{
"path": "https://material-ui.com/static/images/grid-list/vegetables.jpg",
"id": "https://material-ui.com/static/images/grid-list/vegetables.jpg",
"name": "Vegetables",
"author": "jill111",
"cols": 2
},
{
"path": "https://material-ui.com/static/images/grid-list/plant.jpg",
"id": "https://material-ui.com/static/images/grid-list/plant.jpg",
"name": "Water plant",
"author": "BkrmadtyaKarki",
"cols": 3
},
{
"path": "https://material-ui.com/static/images/grid-list/mushroom.jpg",
"id": "https://material-ui.com/static/images/grid-list/mushroom.jpg",
"name": "Mushrooms",
"author": "PublicDomainPictures",
"cols": 2
},
{
"path": "https://material-ui.com/static/images/grid-list/olive.jpg",
"id": "https://material-ui.com/static/images/grid-list/olive.jpg",
"name": "Olive oil",
"author": "congerdesign",
"cols": 1
},
{
"path": "https://material-ui.com/static/images/grid-list/star.jpg",
"id": "https://material-ui.com/static/images/grid-list/star.jpg",
"name": "Sea star",
"cols": 2,
"author": "821292"
},
{
"path": "https://material-ui.com/static/images/grid-list/bike.jpg",
"id": "https://material-ui.com/static/images/grid-list/bike.jpg",
"name": "Bike",
"author": "danfador",
"cols": 3
}
]

View File

@ -0,0 +1,7 @@
interface Device {
id: string;
sessionId: string;
deviceOs: string;
deviceType: string;
deviceIp: string;
}

View File

@ -0,0 +1,99 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable react/prop-types */
import React, { useState, useCallback } from 'react';
interface ConnectedDevicesContextInterface {
devices: Device[];
pendingConnectionDevice: Device | null;
setPendingConnectionDeviceHook: (device: Device) => void;
addPendingConnectedDeviceListener: (
callback: (device: Device) => void
) => void;
setDevicesHook: (devices: Device[]) => void;
resetPendingConnectionDeviceHook: () => void;
getDevices: () => Device[];
}
// TODO this value should be set as soon as electron-ConnectedDevices is loaded, to load user pref
const defaultConnectedDevicesContextValue = {
devices: [] as Device[],
pendingConnectionDevice: null,
setPendingConnectionDeviceHook: () => {},
addPendingConnectedDeviceListener: () => {},
setDevicesHook: () => {},
resetPendingConnectionDeviceHook: () => {},
getDevices: () => [] as Device[],
};
export const ConnectedDevicesContext = React.createContext<
ConnectedDevicesContextInterface
>(defaultConnectedDevicesContextValue);
export const ConnectedDevicesProvider: React.FC = ({ children }) => {
const [devices, setDevices] = useState([] as Device[]);
const [
pendingConnectionDevice,
setPendingConnectionDevice,
] = useState<Device | null>();
const [
pendingDeviceConnectedListeners,
setPendingDeviceConnectedListeners,
] = useState([]);
const emitPendingConnectionDeviceConnected = useCallback(
(device: Device) => {
pendingDeviceConnectedListeners.forEach(
(callback: (device: Device) => void) => {
callback(device);
}
);
},
[pendingDeviceConnectedListeners]
);
const setPendingConnectionDeviceHook = (device: Device) => {
setPendingConnectionDevice(device);
emitPendingConnectionDeviceConnected(device);
};
const setDevicesHook = (_devices: Device[]) => {
setDevices(_devices);
};
const resetPendingConnectionDeviceHook = () => {
setPendingConnectionDevice(undefined);
};
const addPendingConnectedDeviceListener = (
callback: (device: Device) => void
) => {
// @ts-ignore: has to be like that for now
setPendingDeviceConnectedListeners([
...pendingDeviceConnectedListeners,
callback,
]);
};
const getDevices = useCallback(() => {
return devices;
}, [devices]);
// TODO: load saved devices here? in useEffect
const value = {
devices,
pendingConnectionDevice,
setDevicesHook,
setPendingConnectionDeviceHook,
addPendingConnectedDeviceListener,
resetPendingConnectionDeviceHook,
getDevices,
};
return (
// @ts-ignore: it is ok here
<ConnectedDevicesContext.Provider value={value}>
{children}
</ConnectedDevicesContext.Provider>
);
};

View File

@ -1,11 +1,11 @@
import React from 'react';
import NavPanel from '../components/NavPanel';
import TopPanel from '../components/TopPanel';
import Counter from '../features/counter/Counter';
export default function CounterPage() {
return (
<>
<NavPanel />
<TopPanel />
<Counter />
</>
);

View File

@ -0,0 +1,28 @@
import React, { Suspense } from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import HomePage from './HomePage';
import { SettingsProvider } from './SettingsProvider';
import { ConnectedDevicesProvider } from './ConnectedDevicesProvider';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<Suspense fallback={<div>Loading... </div>}>
<SettingsProvider>
<ConnectedDevicesProvider>
<Router>
<HomePage />
</Router>
</ConnectedDevicesProvider>
</SettingsProvider>
</Suspense>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

View File

@ -1,12 +1,50 @@
import React from 'react';
import NavPanel from '../components/NavPanel';
import Home from '../components/Home';
/* eslint-disable react/destructuring-assignment */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React, { useRef } from 'react';
import { Classes } from '@blueprintjs/core';
import { ToastProvider, DefaultToast } from 'react-toast-notifications';
import TopPanel from '../components/TopPanel';
import { LIGHT_UI_BACKGROUND } from './SettingsProvider';
import DeskreenStepper from './Stepper';
// @ts-ignore: it is ok here, be like js it is fine
// eslint-disable-next-line react/prop-types
export const CustomToastWithTheme = ({ children, ...props }) => {
return (
<DefaultToast
components={{ Toast: CustomToastWithTheme }}
{...props}
// @ts-ignore: some minor type complain, it is fine here
style={{
// eslint-disable-next-line react/prop-types
color: props.isdarktheme === 'false' ? '#293742' : '#BFCCD6',
backgroundColor:
// eslint-disable-next-line react/prop-types
props.isdarktheme === 'false' ? LIGHT_UI_BACKGROUND : '#394B59',
}}
>
<>{children}</>
</DefaultToast>
);
};
export default function HomePage() {
const stepperRef = useRef();
return (
<>
<NavPanel />
<Home />
</>
<ToastProvider
placement="top-center"
autoDismissTimeout={5000}
components={{ Toast: CustomToastWithTheme }}
>
<div className={Classes.TREE}>
<TopPanel stepperRef={stepperRef} />
<DeskreenStepper ref={stepperRef} />
{/* <Home /> */}
</div>
</ToastProvider>
);
}

View File

@ -1,11 +1,17 @@
import React, { useEffect, useState } from 'react';
import { FocusStyleManager } from '@blueprintjs/core';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { hot } from 'react-hot-loader/root';
import { History } from 'history';
import { Store } from '../store';
import Routes from '../Routes';
import i18n from '../configs/i18next.config.client';
import { SettingsProvider } from './SettingsProvider';
import { ConnectedDevicesProvider } from './ConnectedDevicesProvider';
FocusStyleManager.onlyShowFocusOnTabs();
type Props = {
store: Store;
@ -23,9 +29,13 @@ const Root = ({ store, history }: Props) => {
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
<SettingsProvider>
<ConnectedDevicesProvider>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
</ConnectedDevicesProvider>
</SettingsProvider>
</Provider>
);
};

View File

@ -0,0 +1,61 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
/* eslint-disable react/prop-types */
/* eslint-disable import/prefer-default-export */
import React, { useState, useEffect } from 'react';
import settings from 'electron-settings';
import { Classes } from '@blueprintjs/core';
export const LIGHT_UI_BACKGROUND = 'rgba(240, 248, 250, 1)';
export const DARK_UI_BACKGROUND = '#293742';
interface SettingsContextInterface {
isDarkTheme: boolean;
setIsDarkThemeHook: (val: boolean) => void;
}
const defaultSettingsContextValue = {
isDarkTheme: false,
setIsDarkThemeHook: () => {},
};
export const SettingsContext = React.createContext<SettingsContextInterface>(
defaultSettingsContextValue
);
export const SettingsProvider: React.FC = ({ children }) => {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const loadDarkThemeFromSettings = () => {
const gotIsDarkThemeFromSettings = settings.hasSync('appIsDarkTheme')
? settings.getSync('appIsDarkTheme') === 'true'
: false;
if (gotIsDarkThemeFromSettings) {
document.body.classList.toggle(Classes.DARK);
// if ()
document.body.style.backgroundColor = LIGHT_UI_BACKGROUND;
}
setIsDarkTheme(gotIsDarkThemeFromSettings);
};
useEffect(() => {
loadDarkThemeFromSettings();
}, []);
const setIsDarkThemeHook = (val: boolean) => {
settings.setSync('appIsDarkTheme', `${val}`);
setIsDarkTheme(val);
// if (!val) {
// document.body.style.backgroundColor = `${LIGHT_UI_BACKGROUND} !important`;
// }
};
const value = { isDarkTheme, setIsDarkThemeHook };
return (
<SettingsContext.Provider value={value}>
{children}
</SettingsContext.Provider>
);
};

View File

@ -0,0 +1,23 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import EnzymeToJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';
import { BrowserRouter as Router } from 'react-router-dom';
import { ToastProvider } from 'react-toast-notifications';
import DeskreenStepper from './Stepper';
Enzyme.configure({ adapter: new Adapter() });
jest.useFakeTimers();
it('should match exact snapshot', () => {
const subject = mount(
<>
<ToastProvider placement="top-center">
<Router>
<DeskreenStepper />
</Router>
</ToastProvider>
</>
);
expect(EnzymeToJson(subject)).toMatchSnapshot();
});

267
app/containers/Stepper.tsx Normal file
View File

@ -0,0 +1,267 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React, { useState, useCallback, useContext, useEffect } from 'react';
import { makeStyles, createStyles } from '@material-ui/core/styles';
import Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel';
import { Row, Col } from 'react-flexbox-grid';
import { Text } from '@blueprintjs/core';
import { useToasts } from 'react-toast-notifications';
import SuccessStep from '../components/StepsOfStepper/SuccessStep';
import IntermediateStep from '../components/StepsOfStepper/IntermediateStep';
import { ConnectedDevicesContext } from './ConnectedDevicesProvider';
import AllowConnectionForDeviceAlert from '../components/AllowConnectionForDeviceAlert';
import DeviceConnectedInfoButton from '../components/StepperPanel/DeviceConnectedInfoButton';
import ColorlibStepIcon, {
StepIconPropsDeskreen,
} from '../components/StepperPanel/ColorlibStepIcon';
import ColorlibConnector from '../components/StepperPanel/ColorlibConnector';
import { SettingsContext } from './SettingsProvider';
import isProduction from '../utils/isProduction';
const Fade = require('react-reveal/Fade');
const Zoom = require('react-reveal/Zoom');
const Pulse = require('react-reveal/Pulse');
const useStyles = makeStyles(() =>
createStyles({
stepContent: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
stepLabelContent: {
marginTop: '10px !important',
height: '110px',
},
stepperComponent: {
paddingBottom: '0px',
},
})
);
function getSteps() {
return ['Connect', 'Select', 'Confirm'];
}
// eslint-disable-next-line react/display-name
const DeskreenStepper = React.forwardRef((_props, ref) => {
const classes = useStyles();
const [isInterShow, setIsInterShow] = useState(false);
const { isDarkTheme } = useContext(SettingsContext);
const { addToast } = useToasts();
const [isAlertOpen, setIsAlertOpen] = useState(false);
const [isUserAllowedConnection, setIsUserAllowedConnection] = useState(false);
const { addPendingConnectedDeviceListener } = useContext(
ConnectedDevicesContext
);
const [
pendingConnectionDevice,
setPendingConnectionDevice,
] = useState<Device | null>(null);
useEffect(() => {
addPendingConnectedDeviceListener((device: Device) => {
setPendingConnectionDevice(device);
setIsAlertOpen(true);
});
setTimeout(
() => {
setIsInterShow(true);
},
isProduction() ? 500 : 0
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [activeStep, setActiveStep] = useState(0);
const [isEntireScreenSelected, setIsEntireScreenSelected] = useState(false);
const [
isApplicationWindowSelected,
setIsApplicationWindowSelected,
] = useState(false);
const steps = getSteps();
const makeSmoothIntermediateStepTransition = () => {
if (!isProduction()) return;
setIsInterShow(false);
setTimeout(() => {
setIsInterShow(true);
}, 500);
};
const handleNext = useCallback(() => {
makeSmoothIntermediateStepTransition();
if (activeStep === steps.length - 1) {
setIsEntireScreenSelected(false);
setIsApplicationWindowSelected(false);
}
setActiveStep((prevActiveStep) => prevActiveStep + 1);
}, [activeStep, steps]);
const handleNextEntireScreen = useCallback(() => {
makeSmoothIntermediateStepTransition();
setActiveStep((prevActiveStep) => prevActiveStep + 1);
setIsEntireScreenSelected(true);
}, []);
const handleNextApplicationWindow = useCallback(() => {
makeSmoothIntermediateStepTransition();
setActiveStep((prevActiveStep) => prevActiveStep + 1);
setIsApplicationWindowSelected(true);
}, []);
const handleBack = useCallback(() => {
makeSmoothIntermediateStepTransition();
setActiveStep((prevActiveStep) => prevActiveStep - 1);
}, []);
const handleReset = useCallback(() => {
makeSmoothIntermediateStepTransition();
setActiveStep(0);
}, []);
React.useImperativeHandle(ref, () => ({
handleReset() {
handleReset();
},
}));
const handleCancelAlert = () => {
setIsAlertOpen(false);
};
const handleConfirmAlert = useCallback(() => {
setIsAlertOpen(false);
setIsUserAllowedConnection(true);
handleNext();
}, [handleNext]);
const handleUserClickedDeviceDisconnectButton = useCallback(() => {
handleReset();
setPendingConnectionDevice(null);
setIsUserAllowedConnection(false);
addToast(
<Text>
Device is successfully disconnected by you. You can connect new device
</Text>,
{
appearance: 'info',
autoDismiss: true,
// @ts-ignore: works fine here, ignore
isdarktheme: `${isDarkTheme}`,
}
);
}, [addToast, handleReset, isDarkTheme]);
const renderIntermediateOrSuccessStepContent = useCallback(() => {
return activeStep === steps.length ? (
<Zoom duration={300} when={isInterShow}>
<Row middle="xs" center="xs">
<SuccessStep handleReset={handleReset} />
</Row>
</Zoom>
) : (
<Fade duration={isProduction() ? 300 : 0} when={isInterShow}>
<IntermediateStep
activeStep={activeStep}
steps={steps}
handleNext={handleNext}
handleBack={handleBack}
handleNextEntireScreen={handleNextEntireScreen}
handleNextApplicationWindow={handleNextApplicationWindow}
resetPendingConnectionDevice={() => setPendingConnectionDevice(null)}
resetUserAllowedConnection={() => setIsUserAllowedConnection(false)}
/>
</Fade>
);
}, [
activeStep,
steps,
isInterShow,
handleReset,
handleNext,
handleBack,
handleNextEntireScreen,
handleNextApplicationWindow,
]);
const renderStepLabelContent = useCallback(
(label, idx) => {
return (
<StepLabel
id="step-label-deskreen"
className={classes.stepLabelContent}
StepIconComponent={ColorlibStepIcon}
StepIconProps={
{
isEntireScreenSelected,
isApplicationWindowSelected,
} as StepIconPropsDeskreen
}
>
{pendingConnectionDevice && idx === 0 && isUserAllowedConnection ? (
<DeviceConnectedInfoButton
device={pendingConnectionDevice}
onDisconnect={handleUserClickedDeviceDisconnectButton}
/>
) : (
<Text className="bp3-text-muted">{label}</Text>
)}
</StepLabel>
);
},
[
classes.stepLabelContent,
handleUserClickedDeviceDisconnectButton,
isApplicationWindowSelected,
isEntireScreenSelected,
isUserAllowedConnection,
pendingConnectionDevice,
]
);
return (
<>
<Row>
<Col xs={12}>
<Pulse top duration={isProduction() ? 1500 : 0}>
<Stepper
className={classes.stepperComponent}
activeStep={activeStep}
alternativeLabel
style={{ background: 'transparent' }}
connector={<ColorlibConnector />}
>
{steps.map((label, idx) => (
<Step key={label}>{renderStepLabelContent(label, idx)}</Step>
))}
</Stepper>
</Pulse>
</Col>
<Col className={classes.stepContent} xs={12}>
{renderIntermediateOrSuccessStepContent()}
</Col>
</Row>
<AllowConnectionForDeviceAlert
device={pendingConnectionDevice}
isOpen={isAlertOpen}
onCancel={handleCancelAlert}
onConfirm={handleConfirmAlert}
/>
</>
);
});
export default DeskreenStepper;

View File

@ -0,0 +1,30 @@
/* eslint-disable import/prefer-default-export */
// export const hasSync = (name: string) => {
// if (name === 'appLanguage') {
// return true;
// }
// return false;
// };
// export const getSync = (name: string) => {
// if (name === 'appLanguage') {
// return 'en';
// }
// return 'en';
// };
export default {
hasSync: (name: string) => {
if (name === 'appLanguage') {
return true;
}
return true;
},
getSync: (name: string) => {
if (name === 'appLanguage') {
return 'en';
}
return 'en';
},
};

View File

@ -0,0 +1,10 @@
/* eslint-disable import/prefer-default-export */
export const remote = {
getGlobal: (name: string) => {
if (name === 'appPath') {
return __dirname;
}
return '';
},
};

View File

@ -0,0 +1,19 @@
/* eslint-disable import/prefer-default-export */
import { ThirdPartyModule } from 'i18next';
export const useTranslation = () => {
return {
t: (key: string) => {
if (key === 'Language') {
return 'Language';
}
return '';
},
};
};
export const initReactI18next: ThirdPartyModule = {
type: '3rdParty',
init: () => {},
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-new */
import React, { Fragment, Suspense } from 'react';
import { render } from 'react-dom';
import { AppContainer as ReactHotAppContainer } from 'react-hot-loader';
@ -10,6 +12,19 @@ const store = configuredStore();
const AppContainer = process.env.PLAIN_HMR ? Fragment : ReactHotAppContainer;
document.addEventListener('DOMContentLoaded', () => {
if (process.platform === 'darwin') {
const windowTopBar = document.createElement('div');
windowTopBar.style.width = '100%';
windowTopBar.style.height = '50px';
windowTopBar.style.position = 'absolute';
windowTopBar.style.top = '0';
windowTopBar.style.left = '0';
// @ts-ignore: all good here
windowTopBar.style.webkitAppRegion = 'drag';
windowTopBar.style.pointerEvents = 'none';
document.body.appendChild(windowTopBar);
}
// eslint-disable-next-line global-require
const Root = require('./containers/Root').default;
render(

View File

@ -1,6 +1,7 @@
{
"Language": "🌐 Language",
"Language": "Language 🇬🇧 / 🇺🇸",
"Signaling server is running on port": "Signaling server is running on port ⚓",
"ru": "Русский",
"en": "English"
"en": "English",
"ua": "Українська"
}

View File

@ -1,6 +1,7 @@
{
"Language": "🌐 Язык",
"Language": "Язык 🇷🇺",
"Signaling server is running on port": "Сигнальный сервер работает на порте ⚓",
"ru": "Русский",
"en": "English"
"en": "English",
"ua": "Українська"
}

View File

@ -0,0 +1,7 @@
{
"Language": "Мова 🇺🇦",
"Signaling server is running on port": "Сигнальный сервер працює на порту ⚓",
"ru": "Русский",
"en": "English",
"ua": "Українська"
}

View File

View File

@ -15,7 +15,7 @@ import path from 'path';
import { app, BrowserWindow, ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import config from './configs/app.config';
import settings from 'electron-settings';
import i18n from './configs/i18next.config';
import signalingServer from './server';
import MenuBuilder from './menu';
@ -25,6 +25,8 @@ globalAny.appPath = __dirname;
signalingServer.start();
log.error(process.platform);
export default class AppUpdater {
constructor() {
log.transports.file.level = 'info';
@ -68,8 +70,13 @@ const createWindow = async () => {
mainWindow = new BrowserWindow({
show: false,
width: 1024,
height: 728,
width: 820,
height: 480,
// maxWidth: 820,
// maxHeight: 480,
minHeight: 400,
minWidth: 600,
titleBarStyle: 'hiddenInset',
webPreferences:
(process.env.NODE_ENV === 'development' ||
process.env.E2E_BUILD === 'true') &&
@ -115,8 +122,12 @@ const createWindow = async () => {
if (mainWindow === null) return;
menuBuilder = new MenuBuilder(mainWindow, i18n);
menuBuilder.buildMenu();
mainWindow.webContents.send('sending-language-from-main', lng);
console.log(`Language changed! ${lng}`);
setTimeout(async () => {
if (lng !== 'en' && i18n.language !== lng) {
i18n.changeLanguage(lng);
await settings.set('appLanguage', lng);
}
}, 400);
});
// Remove this if your app does not use auto updates
@ -154,15 +165,7 @@ ipcMain.handle('get-signaling-server-port', () => {
mainWindow.webContents.send('sending-port-from-main', signalingServer.port);
});
// ipcMain.on('get-initial-translations', (event, arg) => {
ipcMain.on('get-initial-translations', (event) => {
// i18n.loadLanguages('en', (err, t) => {
i18n.loadLanguages('en', () => {
const initial = {
en: {
translation: i18n.getResourceBundle('en', config.namespace),
},
};
event.returnValue = initial;
});
ipcMain.on('client-changed-language', async (_, newLangCode) => {
i18n.changeLanguage(newLangCode);
await settings.set('appLanguage', newLangCode);
});

View File

@ -305,3 +305,15 @@
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/**
* @preserve
* JS Implementation of incremental MurmurHash3 (r150) (as of May 10, 2013)
*
* @author <a href="mailto:jensyt@gmail.com">Jens Taylor</a>
* @see http://github.com/homebrewing/brauhaus-diff
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
* @see http://github.com/garycourt/murmurhash-js
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
* @see http://sites.google.com/site/murmurhash/
*/

View File

@ -7,7 +7,7 @@ import {
MenuItemConstructorOptions,
} from 'electron';
import config from './configs/app.config';
import config from './configs/app.lang.config';
import signalingServer from './server';
@ -26,7 +26,7 @@ export default class MenuBuilder {
this.i18n = i18n;
}
buildMenu(): Menu {
buildMenu() {
if (
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
@ -34,15 +34,16 @@ export default class MenuBuilder {
this.setupDevelopmentEnvironment();
}
const template =
process.platform === 'darwin'
? this.buildDarwinTemplate()
: this.buildDefaultTemplate();
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
return menu;
if (process.platform === 'darwin') {
const menu = Menu.buildFromTemplate(this.buildDarwinTemplate());
Menu.setApplicationMenu(menu);
} else if (process.env.NODE_ENV === 'development') {
const menu = Menu.buildFromTemplate(this.buildDefaultTemplate());
Menu.setApplicationMenu(menu);
} else {
// for production, no menu for non MacOS app
Menu.setApplicationMenu(null);
}
}
setupDevelopmentEnvironment(): void {

2
app/package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "deskreen",
"version": "0.0.2-alpha",
"version": "0.0.3-alpha",
"lockfileVersion": 1
}

View File

@ -1,7 +1,7 @@
{
"name": "deskreen",
"productName": "deskreen",
"version": "0.0.2-alpha",
"version": "0.0.3-alpha",
"description": "TODO: write description about this project",
"main": "./main.prod.js",
"author": {

View File

@ -20,17 +20,10 @@ import pollForInactiveRooms from './inactiveRooms';
import getStore from './store';
import Logger from '../utils/logger';
import isProduction from '../utils/isProduction';
const log = new Logger('app/server/index.ts');
let isDev;
try {
// eslint-disable-next-line global-require
isDev = require('electron-is-dev');
} catch (e) {
isDev = true;
}
const app = new Koa();
const router = new Router();
@ -54,9 +47,9 @@ function setStaticFileHeaders(
});
}
const clientDistDirectory = isDev
? `${__dirname}/../client/build`
: `${__dirname}/client/build`;
const clientDistDirectory = isProduction()
? `${__dirname}/client/build`
: `${__dirname}/../client/build`;
if (clientDistDirectory) {
app.use(async (ctx, next) => {

View File

@ -0,0 +1,8 @@
export default function isProduction() {
return (
process.env.NODE_ENV === 'production' &&
process.env.RUN_MODE !== 'dev' &&
process.env.RUN_MODE !== 'test'
);
// return true; // for animations and other things debugging as in production mode
}

View File

@ -1,16 +1,20 @@
{
"name": "deskreen",
"productName": "Deskreen",
"version": "0.0.2-alpha",
"version": "0.0.3-alpha",
"description": "TODO: write description of this app",
"scripts": {
"build": "concurrently \"yarn build-main\" \"yarn build-renderer\" \"yarn build-client\"",
"build-test": "concurrently \"yarn build-main-test\" \"yarn build-renderer-test\" \"yarn build-client\"",
"build-dll": "cross-env NODE_ENV=development webpack --config ./configs/webpack.config.renderer.dev.dll.babel.js --colors",
"build-e2e": "cross-env E2E_BUILD=true yarn build",
"build-ux": "cross-env E2E_BUILD=true RUN_MODE=test yarn build-test",
"build-main": "cross-env NODE_ENV=production webpack --config ./configs/webpack.config.main.prod.babel.js --colors",
"build-main-test": "cross-env RUN_MODE=test NODE_ENV=production webpack --config ./configs/webpack.config.main.prod.babel.js --colors",
"build-renderer": "cross-env NODE_ENV=production webpack --config ./configs/webpack.config.renderer.prod.babel.js --colors",
"build-renderer-test": "cross-env RUN_MODE=test NODE_ENV=production webpack --config ./configs/webpack.config.renderer.prod.babel.js --colors",
"build-client": "cd app/client && cross-env SKIP_PREFLIGHT_CHECK=true yarn build",
"dev": "cross-env START_HOT=1 node -r @babel/register ./internals/scripts/CheckPortInUse.js && concurrently \"cross-env START_HOT=1 yarn start-renderer-dev\" \"yarn start-client-dev\"",
"dev": "cross-env START_HOT=1 node -r @babel/register ./internals/scripts/CheckPortInUse.js && concurrently \"cross-env START_HOT=1 RUN_MODE=dev yarn start-renderer-dev\" \"yarn start-client-dev\"",
"coverage": "cross-env BABEL_DISABLE_CACHE=1 jest --coverage && yarn coverage-client",
"coverage-client": "cd app/client && cross-env SKIP_PREFLIGHT_CHECK=true cross-env BABEL_DISABLE_CACHE=1 yarn test -- --coverage --watchAll=false",
"electron-rebuild": "electron-rebuild --parallel --force --types prod,dev,optional --module-dir app",
@ -36,12 +40,15 @@
"start-main-dev": "cross-env START_HOT=1 NODE_ENV=development electron -r ./internals/scripts/BabelRegister ./app/main.dev.ts",
"start-renderer-dev": "cross-env NODE_ENV=development webpack-dev-server --config configs/webpack.config.renderer.dev.babel.js",
"start-client-dev": "cd app/client && cross-env SKIP_PREFLIGHT_CHECK=true yarn start",
"test": "cross-env BABEL_DISABLE_CACHE=1 jest && yarn test-client",
"test": "cross-env BABEL_DISABLE_CACHE=1 jest --silent && yarn test-client",
"test-client": "cd app/client && cross-env BABEL_DISABLE_CACHE=1 SKIP_PREFLIGHT_CHECK=true yarn test:nowatch",
"test-all": "yarn lint && yarn tsc && yarn build && yarn test",
"test-e2e": "node -r @babel/register ./internals/scripts/CheckBuildsExist.js && cross-env NODE_ENV=test testcafe electron:./app ./test/e2e/HomePage.e2e.ts",
"test-e2e-live": "node -r @babel/register ./internals/scripts/CheckBuildsExist.js && cross-env NODE_ENV=test testcafe --live electron:./app ./test/e2e/HomePage.e2e.ts",
"test-watch": "yarn test --watch",
"test-ux": "node -r @babel/register ./internals/scripts/CheckBuildsExist.js && cross-env NODE_ENV=test RUN_MODE=test testcafe electron:./app ./test/ux/Stepper.ux.ts",
"test-ux-live": "node -r @babel/register ./internals/scripts/CheckBuildsExist.js && cross-env NODE_ENV=test RUN_MODE=test testcafe --live electron:./app ./test/ux/Stepper.ux.ts",
"test-watch": "yarn jest --watch --silent",
"test-watch-no-silent": "yarn jest --watch",
"sonar": "concurrently \"sonar-scanner\" \"cd app/client && sonar-scanner\" "
},
"lint-staged": {
@ -184,7 +191,6 @@
"testResultsProcessor": "jest-sonar-reporter"
},
"devDependencies": {
"@amilajack/testcafe-browser-provider-electron": "^0.0.15-alpha.1",
"@babel/core": "^7.11.1",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-proposal-decorators": "^7.10.5",
@ -209,6 +215,7 @@
"@babel/preset-react": "^7.10.4",
"@babel/preset-typescript": "^7.10.4",
"@babel/register": "^7.10.5",
"@types/classnames": "^2.2.10",
"@types/enzyme": "^3.10.5",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/express": "^4.17.7",
@ -222,12 +229,15 @@
"@types/koa-static": "^4.0.1",
"@types/node": "12",
"@types/node-forge": "^0.9.5",
"@types/qrcode.react": "^1.0.1",
"@types/react": "^16.9.44",
"@types/react-dom": "^16.9.8",
"@types/react-redux": "^7.1.9",
"@types/react-router": "^5.1.8",
"@types/react-router-dom": "^5.1.5",
"@types/react-test-renderer": "^16.9.2",
"@types/react-toast-notifications": "^2.4.0",
"@types/react-toastify": "^4.1.0",
"@types/redux-logger": "^3.0.8",
"@types/shortid": "^0.0.29",
"@types/socket.io": "^2.1.11",
@ -289,7 +299,7 @@
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^20.0.0",
"terser-webpack-plugin": "^3.0.7",
"testcafe": "^1.8.8",
"testcafe": "^1.9.2",
"testcafe-browser-provider-electron": "^0.0.15",
"testcafe-react-selectors": "^4.0.0",
"ts-jest": "^26.1.4",
@ -304,35 +314,47 @@
},
"dependencies": {
"@blueprintjs/core": "^3.31.0",
"@blueprintjs/select": "^3.13.7",
"@fortawesome/fontawesome-free": "^5.14.0",
"@hot-loader/react-dom": "^16.13.0",
"@material-ui/core": "^4.11.0",
"@reduxjs/toolkit": "^1.4.0",
"axios": "^0.19.2",
"classnames": "^2.2.6",
"clsx": "^1.1.1",
"connected-react-router": "^6.6.1",
"electron-debug": "^3.1.0",
"electron-is-dev": "^1.2.0",
"electron-log": "^4.2.2",
"electron-settings": "^4.0.2",
"electron-updater": "^4.3.1",
"express": "^4.17.1",
"fontsource-lexend-peta": "^3.0.9",
"get-port": "^5.1.1",
"history": "^4.7.2",
"i18next": "^19.6.3",
"i18next-fs-backend": "^1.0.7",
"i18next-node-fs-backend": "^2.1.3",
"i18next-sync-fs-backend": "^1.1.1",
"internal-ip": "^6.1.0",
"kcors": "^2.2.2",
"koa": "^2.13.0",
"koa-router": "^9.4.0",
"koa-send": "^5.0.1",
"koa-static": "^5.0.0",
"node-forge": "^0.9.1",
"qrcode.react": "^1.0.0",
"react": "^16.13.1",
"react-awesome-reveal": "^3.2.1",
"react-dom": "^16.12.0",
"react-flexbox-grid": "^2.1.2",
"react-hot-loader": "^4.12.21",
"react-i18next": "^11.7.0",
"react-qrcode-logo": "^2.2.1",
"react-redux": "^7.2.0",
"react-reveal": "^1.2.2",
"react-router-dom": "^5.2.0",
"react-toast-notifications": "^2.4.0",
"react-toastify": "^6.0.8",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"regenerator-runtime": "^0.13.5",

View File

@ -20,11 +20,11 @@ const assertNoConsoleErrors = async (t) => {
fixture`Home Page`.page('../../app/app.html').afterEach(assertNoConsoleErrors);
test('e2e', async (t) => {
await t.expect(getPageTitle()).eql('Hello Deskreen!');
await t.expect(getPageTitle()).eql('Deskreen');
});
test('should open window and contain expected page title', async (t) => {
await t.expect(getPageTitle()).eql('Hello Deskreen!');
await t.expect(getPageTitle()).eql('Deskreen');
});
test(

488
test/ux/Stepper.ux.ts Normal file
View File

@ -0,0 +1,488 @@
/* eslint jest/expect-expect: off, jest/no-test-callback: off */
import { ClientFunction, Selector } from 'testcafe';
const getPageTitle = ClientFunction(() => document.title);
const assertNoConsoleErrors = async (t) => {
const { error } = await t.getBrowserConsoleMessages();
await t.expect(error).eql([]);
};
// Deskreen selectors
const connectTestDeviceButton = Selector('button').withText(
'Connect Test Device'
);
const crossCloseDialogButton = Selector('svg').withAttribute(
'data-icon',
'cross'
);
const allowToConnectButton = Selector('button').withText('Allow');
const denyToConnectButton = Selector('button').withText('Deny');
const shareEntireScreenButton = Selector('button').withText('Entire Screen');
const shareApplicationWindowButton = Selector('button').withText(
'Application Window'
);
const magnifyQRCodeButton = Selector('#magnify-qr-code-button');
const largeQRCodeDialog = Selector('#qr-code-dialog-inner');
const connectedInfoStepperButton = Selector(
'#connected-device-info-stepper-button'
);
const popoverDivWithDeviceIP = Selector(
'#connected-button-popover-div-with-ip'
);
const disconnectOneDeviceButton = Selector('button').withExactText(
'Disconnect'
);
const disconnectAllDevicesButton = Selector('button').withText(
'Disconnect all'
);
const reactToastNotificationsContainer = Selector(
'.react-toast-notifications__container'
);
const headerWithTextSelectEntireScreen = Selector('h3').withText(
'Select Entire Screen to Share'
);
const headerWithTextSelectAppWindow = Selector('h3').withText(
'Select App Window to Share'
);
const previewShareButton = Selector('.preview-share-thumb-container');
const step3ConfirmButton = Selector('button').withText('Confirm');
const noINeedToShareOtherThingButton = Selector('button').withText(
'No, I need to share other thing'
);
const step4ConnectNewDeviceButton = Selector('button').withText(
'Connect New Device'
);
const connectedDevicesButton = Selector(
'#top-panel-connected-devices-list-button'
);
const connectedDevicesHeader = Selector('.bp3-text-muted').withText(
'Connected Devices'
);
const getDeviceIPContainerByIP = (ip) =>
Selector('.device-ip-container').withText(ip);
const yesDisconnectAllButton = Selector('button').withText(
'Yes, Disconnect All'
);
const settingsButtonOfTopPanel = Selector('span').withAttribute('icon', 'cog');
const openedSettingsOverlay = Selector('#settings-overlay-inner');
const darkColorAppSettingButton = Selector('button').withText('Dark');
const lightColorAppSettingButton = Selector('button').withText('Light');
const darkUIClassName = Selector('.bp3-dark');
async function getConnectedDeviceIPFromAllowToConnectDeviceAlert() {
const deviceIPTextElement = Selector(
'#allow-connection-device-alert-device-ip-span'
);
const textWithIp = await deviceIPTextElement.innerText;
return textWithIp.trim();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function connectTestDeviceAndGetIP(t) {
await t.click(connectTestDeviceButton());
return getConnectedDeviceIPFromAllowToConnectDeviceAlert();
}
async function connectTestDevice(t) {
await t.click(connectTestDeviceButton());
}
async function connectAndAllowTestDeviceAndGetIP(t) {
await connectTestDevice(t);
const ip = getConnectedDeviceIPFromAllowToConnectDeviceAlert();
await t.click(allowToConnectButton());
return ip;
}
async function connectAndAllowTestDevice(t) {
await connectTestDevice(t);
await t.click(allowToConnectButton());
}
async function openLargeQRCodeDialog(t) {
await t.click(magnifyQRCodeButton());
}
async function clickCrossButtonToCloseDialog(t) {
await t.click(crossCloseDialogButton());
}
async function openConnectedDeviceInfoPopover(t) {
await t.click(connectedInfoStepperButton());
}
async function goToStep3SharingEntireScreen(t) {
await connectAndAllowTestDevice(t);
await t.click(shareEntireScreenButton());
await t.click(previewShareButton());
}
async function goToStep3SharingAppWindow(t) {
await connectAndAllowTestDevice(t);
await t.click(shareApplicationWindowButton());
await t.click(previewShareButton());
}
async function goToStep4SharingAppWindow(t) {
await goToStep3SharingAppWindow(t);
await t.click(step3ConfirmButton());
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function goToStep4SharingEntireScreen(t) {
await goToStep3SharingEntireScreen(t);
await t.click(step3ConfirmButton());
}
async function connectDeviceSharingAppWindow(t) {
await goToStep4SharingAppWindow(t);
}
async function connectDeviceSharingAppWindowAndGetIP(t) {
const ip = await connectAndAllowTestDeviceAndGetIP(t);
await t.click(shareApplicationWindowButton());
await t.click(previewShareButton());
await t.click(step3ConfirmButton());
await t.click(step4ConnectNewDeviceButton());
return ip;
}
async function connectDeviceSharingEntireScreenAndGetIP(t) {
const ip = await connectAndAllowTestDeviceAndGetIP(t);
await t.click(shareEntireScreenButton());
await t.click(previewShareButton());
await t.click(step3ConfirmButton());
await t.click(step4ConnectNewDeviceButton());
return ip;
}
async function openConnectedDevicesListDrawer(t) {
await t.click(connectedDevicesButton());
}
fixture`Home Page`.page('../../app/app.html').afterEach(assertNoConsoleErrors);
test(`when app is launched,
it should have correct app title as "Deskreen"`, async (t) => {
await t.expect(getPageTitle()).eql('Deskreen');
});
test(`when on Scan QR code step (step 1),
and when device is connected,
it should show alert with Allow or Deny buttons`, async (t) => {
await t.click(connectTestDeviceButton());
const allowButtonExists = allowToConnectButton().exists;
const denyButtonExists = denyToConnectButton().exists;
await t.expect(allowButtonExists).ok();
await t.expect(denyButtonExists).ok();
});
test(`when on Scan QR code step (step 1),
and when device is connected,
and when user pressed "Deny" button
it should close alert with Allow or Deny buttons`, async (t) => {
await connectTestDevice(t);
await t.click(denyToConnectButton());
const allowButtonExists = allowToConnectButton().exists;
const denyButtonExists = denyToConnectButton().exists;
await t.expect(allowButtonExists).notOk();
await t.expect(denyButtonExists).notOk();
});
test(`when on Scan QR code step (step 1),
and when device is connected,
and when user pressed "Allow" button,
it should go to "Share App or Screen" step (step 2)`, async (t) => {
await connectAndAllowTestDeviceAndGetIP(t);
const EntireScreenButtonExists = shareEntireScreenButton().exists;
const AppWindoScreenButtonExists = shareApplicationWindowButton().exists;
await t.expect(EntireScreenButtonExists).ok();
await t.expect(AppWindoScreenButtonExists).ok();
});
test(`when on Scan QR code step (step 1),
and when "Magnify QR code" button is clicked,
it should open large QR code overflow`, async (t) => {
await openLargeQRCodeDialog(t);
const largeQRCodeDialogExists = largeQRCodeDialog().exists;
await t.expect(largeQRCodeDialogExists).ok();
});
test(`when large QR overflow is opened,
and when user clicks cross button,
it should close large QR code overflow`, async (t) => {
await openLargeQRCodeDialog(t);
await clickCrossButtonToCloseDialog(t);
const largeQrCodeDialogExists = largeQRCodeDialog().exists;
await t.expect(largeQrCodeDialogExists).notOk();
});
test(`when on step 2,
and when device is connected,
it should show Connected (info) button in UI`, async (t) => {
await connectAndAllowTestDeviceAndGetIP(t);
const connectedInfoStepperButtonExists = connectedInfoStepperButton().exists;
await t.expect(connectedInfoStepperButtonExists).ok();
});
test(`when on step 2,
and when device is connected,
and when user clicked Connected (info) button of stepper panel,
it should show connected device popover with IP of connected device`, async (t) => {
const ip = await connectAndAllowTestDeviceAndGetIP(t);
await openConnectedDeviceInfoPopover(t);
const textWithIp = await popoverDivWithDeviceIP().innerText;
await t.expect(textWithIp.includes(ip)).ok();
});
test(`when on step 2,
and when user Clicks "Disconnect" button of
Connected (info) popover,
it should go back to Scan QR code step 1`, async (t) => {
await connectAndAllowTestDeviceAndGetIP(t);
await openConnectedDeviceInfoPopover(t);
await t.click(disconnectOneDeviceButton());
const magnifyQRCodeButtonExists = magnifyQRCodeButton().exists;
await t.expect(magnifyQRCodeButtonExists).ok();
});
test(`when on step 2,
and when user Clicks "Disconnect" button of
Connected (info) popover,
it should display react toast notification that device has been disconnected`, async (t) => {
await connectAndAllowTestDeviceAndGetIP(t);
await openConnectedDeviceInfoPopover(t);
await t.click(disconnectOneDeviceButton());
const toastText = await reactToastNotificationsContainer().innerText;
await t.expect(toastText !== '').ok();
});
test(`when on step 2,
and when user clicks "Share Entire Screen" button,
it should display "Select Entire Screen to Share" overlay`, async (t) => {
await connectAndAllowTestDeviceAndGetIP(t);
await t.click(shareEntireScreenButton());
const headerWithTextSelectEntireScreenExists = headerWithTextSelectEntireScreen()
.exists;
await t.expect(headerWithTextSelectEntireScreenExists).ok();
});
test(`when on step 2,
and when user clicks "Share Application Window" button,
it should display "Select App Window to Share" overlay`, async (t) => {
await connectAndAllowTestDeviceAndGetIP(t);
await t.click(shareApplicationWindowButton());
const headerWithTextSelectAppWindowExists = headerWithTextSelectAppWindow()
.exists;
await t.expect(headerWithTextSelectAppWindowExists).ok();
});
test(`when on step 2,
and when user clicks "Share Entire Screen" button,
and when user clicks on previev button,
it should go to step 3 and display a "Confirm" button`, async (t) => {
await goToStep3SharingEntireScreen(t);
const step3ConfirmButtonExists = step3ConfirmButton().exists;
await t.expect(step3ConfirmButtonExists).ok();
});
test(`when on step 2,
and when user clicks "Share Application Window" button,
and when user clicks on previev button,
it should go to step 3 and display a "Confirm" button`, async (t) => {
await goToStep3SharingAppWindow(t);
const step3ConfirmButtonExists = step3ConfirmButton().exists;
await t.expect(step3ConfirmButtonExists).ok();
});
test(`when on step 3,
and when user clicks "No, I need to share other thing" button,
it should go back to step 2 and display a OR button group`, async (t) => {
await goToStep3SharingAppWindow(t);
await t.click(noINeedToShareOtherThingButton());
const EntireScreenButtonExists = shareEntireScreenButton().exists;
const AppWindoScreenButtonExists = shareApplicationWindowButton().exists;
await t.expect(EntireScreenButtonExists).ok();
await t.expect(AppWindoScreenButtonExists).ok();
});
test(`when on step 3,
and when user clicks "Confirm" button,
it should go back to step 4 (Success Step) and display a "Connect New Device" button`, async (t) => {
await goToStep3SharingAppWindow(t);
await t.click(step3ConfirmButton());
const ConnectNewDeviceButtonExists = step4ConnectNewDeviceButton().exists;
await t.expect(ConnectNewDeviceButtonExists).ok();
});
test(`when on step 4 (Success Step),
and when user clicks "Connect New Device" button,
it should go back to step 1 and display a QR code`, async (t) => {
await goToStep4SharingAppWindow(t);
await t.click(step4ConnectNewDeviceButton());
const MagnifyQRCodeButtonExists = magnifyQRCodeButton().exists;
await t.expect(MagnifyQRCodeButtonExists).ok();
});
test(`when device is connected,
and when "Connected Devices List" drawer is opened",
it should open "Connected Devices List" panel`, async (t) => {
await connectDeviceSharingAppWindow(t);
await t.click(connectedDevicesButton());
const ConnectedDevicesHeaderExists = connectedDevicesHeader().exists;
await t.expect(ConnectedDevicesHeaderExists).ok();
});
test(`when on step 4 (Success Step),
and when user clicks "Connected Devices" button,
it should open "Connected Devices List" panel with a connected device listed in it`, async (t) => {
const ip = await connectDeviceSharingAppWindowAndGetIP(t);
await openConnectedDevicesListDrawer(t);
await t.expect(getDeviceIPContainerByIP(ip).exists).ok();
});
test(`when multiple devices are connected,
and when user clicks "Connected Devices" button,
and when "Connected Devices List" drawer is opened,
it should list all connected devices with IPs in "Connected Devices List" drawer`, async (t) => {
const ipOne = await connectDeviceSharingAppWindowAndGetIP(t);
const ipTwo = await connectDeviceSharingAppWindowAndGetIP(t);
const ipThree = await connectDeviceSharingEntireScreenAndGetIP(t);
const ipFour = await connectDeviceSharingEntireScreenAndGetIP(t);
await openConnectedDevicesListDrawer(t);
await t.expect(getDeviceIPContainerByIP(ipOne).exists).ok();
await t.expect(getDeviceIPContainerByIP(ipTwo).exists).ok();
await t.expect(getDeviceIPContainerByIP(ipThree).exists).ok();
await t.expect(getDeviceIPContainerByIP(ipFour).exists).ok();
});
test(`when device is connected,
and when "Connected Devices List" drawer is opened,
and when user clicks "Disconnect" button of just connected device,
it should remove a device from connected devices list drawer`, async (t) => {
const ip = await connectDeviceSharingAppWindowAndGetIP(t);
await openConnectedDevicesListDrawer(t);
await t.click(disconnectOneDeviceButton());
await t.expect(getDeviceIPContainerByIP(ip).exists).notOk();
});
test(`when multiple devices are connected,
and when "Connected Devices List" drawer is opened,
and when user clicked "Disconnect all" button,
and when user clicked "Yes, Disconnect All" button in alert,
and when "Connected Devices List" drawer is opened again,
it should remove remove all devices from "Connected Devices List" drawer`, async (t) => {
const ipOne = await connectDeviceSharingAppWindowAndGetIP(t);
const ipTwo = await connectDeviceSharingAppWindowAndGetIP(t);
const ipThree = await connectDeviceSharingEntireScreenAndGetIP(t);
const ipFour = await connectDeviceSharingEntireScreenAndGetIP(t);
await openConnectedDevicesListDrawer(t);
await t.click(disconnectAllDevicesButton());
await t.click(yesDisconnectAllButton());
await openConnectedDevicesListDrawer(t);
await t.expect(getDeviceIPContainerByIP(ipOne).exists).notOk();
await t.expect(getDeviceIPContainerByIP(ipTwo).exists).notOk();
await t.expect(getDeviceIPContainerByIP(ipThree).exists).notOk();
await t.expect(getDeviceIPContainerByIP(ipFour).exists).notOk();
});
test(`when device is connected,
and when user is on "Success" step, (step 4),
and when "Connected Devices List" drawer is opened,
and when user clicked "Disconnect all" button,
and when user clicked "Yes, Disconnect All" in alert,
it should go back to Scan QR code step`, async (t) => {
await goToStep4SharingAppWindow(t);
await openConnectedDevicesListDrawer(t);
await t.click(disconnectAllDevicesButton());
await t.click(yesDisconnectAllButton());
await t.expect(magnifyQRCodeButton().exists).ok();
});
test(`when device is connected,
and when user clicks "Connected Devices List" drawer is opened,
and when user clicks "Disconnect all" button of connected device
it should display "Are you sure you want to disconnect all devices..." alert`, async (t) => {
await connectDeviceSharingAppWindowAndGetIP(t);
await openConnectedDevicesListDrawer(t);
await t.click(Selector('button').withText('Disconnect all'));
await t.expect(yesDisconnectAllButton().exists).ok();
});
/*
//////////////////////////////////////////////////
/////////// SETTINGS OVERLAY TESTING START ///////
//////////////////////////////////////////////////
*/
test(`when clicks "Settings" button of top panel,
it should open "Settings" panel`, async (t) => {
await t.click(settingsButtonOfTopPanel());
await t.expect(openedSettingsOverlay().exists).ok();
});
test(`when "Settings" Panel is opened,
and when user clicks "Dark" and "Light" buttons,
it should change application colors accordingly`, async (t) => {
await t.click(settingsButtonOfTopPanel());
// action and assertion 1
await t.click(darkColorAppSettingButton());
await t.expect(darkUIClassName().exists).ok();
// action and assertion 2
await t.click(lightColorAppSettingButton());
await t.expect(darkUIClassName().exists).notOk();
});

564
yarn.lock
View File

@ -12,25 +12,6 @@
resolved "https://packages.deskreen.com/7zip/-/7zip-0.0.6.tgz#9cafb171af82329490353b4816f03347aa150a30"
integrity sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA=
"@amilajack/testcafe-browser-provider-electron@^0.0.15-alpha.1":
version "0.0.15-alpha.1"
resolved "https://packages.deskreen.com/@amilajack%2ftestcafe-browser-provider-electron/-/testcafe-browser-provider-electron-0.0.15-alpha.1.tgz#506080ec623c1509fae489b13cb2a2894ec6fbb9"
integrity sha512-05JwzcV59rxArehDWPM0Lw4YNvVr5c3J/j2ikJeQKHAQSoA0TsRSdqSMjGDaT8LGem0HAATPLh7hRhI481alIQ==
dependencies:
babel-runtime "^6.25.0"
chrome-remote-interface "^0.27.0"
debug "4.1.1"
dedent "^0.7.0"
endpoint-utils "^1.0.2"
lodash "^4.17.4"
mustache "^2.3.0"
node-ipc "^9.1.0"
os-family "^1.0.0"
pify "^2.3.0"
pinkie "^2.0.4"
promisify-event "^1.0.0"
proxyquire "^1.7.10"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
version "7.10.4"
resolved "https://packages.deskreen.com/@babel%2fcode-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
@ -188,7 +169,7 @@
dependencies:
"@babel/types" "^7.11.0"
"@babel/helper-module-imports@^7.10.4":
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4":
version "7.10.4"
resolved "https://packages.deskreen.com/@babel%2fhelper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==
@ -1093,7 +1074,7 @@
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4":
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.11.2"
resolved "https://packages.deskreen.com/@babel%2fruntime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
@ -1163,6 +1144,15 @@
classnames "^2.2"
tslib "~1.13.0"
"@blueprintjs/select@^3.13.7":
version "3.13.7"
resolved "https://packages.deskreen.com/@blueprintjs%2fselect/-/select-3.13.7.tgz#166675a8caeccacdb31216e92ef114f29888dbf6"
integrity sha512-kJVtbDDGVwIIC1+cN7H0DUrlumSVZGNEq2CnczQNI07RkHpPzuIR5stjn3LU+NjtCa3pidPNr4w78JRTesZzLg==
dependencies:
"@blueprintjs/core" "^3.31.0"
classnames "^2.2"
tslib "~1.13.0"
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://packages.deskreen.com/@cnakazawa%2fwatch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@ -1204,6 +1194,108 @@
global-agent "^2.0.2"
global-tunnel-ng "^2.7.1"
"@emotion/cache@^10.0.27":
version "10.0.29"
resolved "https://packages.deskreen.com/@emotion%2fcache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0"
integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==
dependencies:
"@emotion/sheet" "0.9.4"
"@emotion/stylis" "0.8.5"
"@emotion/utils" "0.11.3"
"@emotion/weak-memoize" "0.2.5"
"@emotion/core@^10.0.14", "@emotion/core@^10.0.35":
version "10.0.35"
resolved "https://packages.deskreen.com/@emotion%2fcore/-/core-10.0.35.tgz#513fcf2e22cd4dfe9d3894ed138c9d7a859af9b3"
integrity sha512-sH++vJCdk025fBlRZSAhkRlSUoqSqgCzYf5fMOmqqi3bM6how+sQpg3hkgJonj8GxXM4WbD7dRO+4tegDB9fUw==
dependencies:
"@babel/runtime" "^7.5.5"
"@emotion/cache" "^10.0.27"
"@emotion/css" "^10.0.27"
"@emotion/serialize" "^0.11.15"
"@emotion/sheet" "0.9.4"
"@emotion/utils" "0.11.3"
"@emotion/css@^10.0.27":
version "10.0.27"
resolved "https://packages.deskreen.com/@emotion%2fcss/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c"
integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==
dependencies:
"@emotion/serialize" "^0.11.15"
"@emotion/utils" "0.11.3"
babel-plugin-emotion "^10.0.27"
"@emotion/hash@0.8.0", "@emotion/hash@^0.8.0":
version "0.8.0"
resolved "https://packages.deskreen.com/@emotion%2fhash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
"@emotion/is-prop-valid@0.8.8":
version "0.8.8"
resolved "https://packages.deskreen.com/@emotion%2fis-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
dependencies:
"@emotion/memoize" "0.7.4"
"@emotion/memoize@0.7.4":
version "0.7.4"
resolved "https://packages.deskreen.com/@emotion%2fmemoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16":
version "0.11.16"
resolved "https://packages.deskreen.com/@emotion%2fserialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad"
integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==
dependencies:
"@emotion/hash" "0.8.0"
"@emotion/memoize" "0.7.4"
"@emotion/unitless" "0.7.5"
"@emotion/utils" "0.11.3"
csstype "^2.5.7"
"@emotion/sheet@0.9.4":
version "0.9.4"
resolved "https://packages.deskreen.com/@emotion%2fsheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5"
integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==
"@emotion/styled-base@^10.0.27":
version "10.0.31"
resolved "https://packages.deskreen.com/@emotion%2fstyled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a"
integrity sha512-wTOE1NcXmqMWlyrtwdkqg87Mu6Rj1MaukEoEmEkHirO5IoHDJ8LgCQL4MjJODgxWxXibGR3opGp1p7YvkNEdXQ==
dependencies:
"@babel/runtime" "^7.5.5"
"@emotion/is-prop-valid" "0.8.8"
"@emotion/serialize" "^0.11.15"
"@emotion/utils" "0.11.3"
"@emotion/styled@^10.0.27":
version "10.0.27"
resolved "https://packages.deskreen.com/@emotion%2fstyled/-/styled-10.0.27.tgz#12cb67e91f7ad7431e1875b1d83a94b814133eaf"
integrity sha512-iK/8Sh7+NLJzyp9a5+vIQIXTYxfT4yB/OJbjzQanB2RZpvmzBQOHZWhpAMZWYEKRNNbsD6WfBw5sVWkb6WzS/Q==
dependencies:
"@emotion/styled-base" "^10.0.27"
babel-plugin-emotion "^10.0.27"
"@emotion/stylis@0.8.5":
version "0.8.5"
resolved "https://packages.deskreen.com/@emotion%2fstylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
"@emotion/unitless@0.7.5":
version "0.7.5"
resolved "https://packages.deskreen.com/@emotion%2funitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@emotion/utils@0.11.3":
version "0.11.3"
resolved "https://packages.deskreen.com/@emotion%2futils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924"
integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
"@emotion/weak-memoize@0.2.5":
version "0.2.5"
resolved "https://packages.deskreen.com/@emotion%2fweak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
"@fortawesome/fontawesome-free@^5.14.0":
version "5.14.0"
resolved "https://packages.deskreen.com/@fortawesome%2ffontawesome-free/-/fontawesome-free-5.14.0.tgz#a371e91029ebf265015e64f81bfbf7d228c9681f"
@ -1416,6 +1508,70 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@material-ui/core@^4.11.0":
version "4.11.0"
resolved "https://packages.deskreen.com/@material-ui%2fcore/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a"
integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/styles" "^4.10.0"
"@material-ui/system" "^4.9.14"
"@material-ui/types" "^5.1.0"
"@material-ui/utils" "^4.10.2"
"@types/react-transition-group" "^4.2.0"
clsx "^1.0.4"
hoist-non-react-statics "^3.3.2"
popper.js "1.16.1-lts"
prop-types "^15.7.2"
react-is "^16.8.0"
react-transition-group "^4.4.0"
"@material-ui/styles@^4.10.0":
version "4.10.0"
resolved "https://packages.deskreen.com/@material-ui%2fstyles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071"
integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==
dependencies:
"@babel/runtime" "^7.4.4"
"@emotion/hash" "^0.8.0"
"@material-ui/types" "^5.1.0"
"@material-ui/utils" "^4.9.6"
clsx "^1.0.4"
csstype "^2.5.2"
hoist-non-react-statics "^3.3.2"
jss "^10.0.3"
jss-plugin-camel-case "^10.0.3"
jss-plugin-default-unit "^10.0.3"
jss-plugin-global "^10.0.3"
jss-plugin-nested "^10.0.3"
jss-plugin-props-sort "^10.0.3"
jss-plugin-rule-value-function "^10.0.3"
jss-plugin-vendor-prefixer "^10.0.3"
prop-types "^15.7.2"
"@material-ui/system@^4.9.14":
version "4.9.14"
resolved "https://packages.deskreen.com/@material-ui%2fsystem/-/system-4.9.14.tgz#4b00c48b569340cefb2036d0596b93ac6c587a5f"
integrity sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/utils" "^4.9.6"
csstype "^2.5.2"
prop-types "^15.7.2"
"@material-ui/types@^5.1.0":
version "5.1.0"
resolved "https://packages.deskreen.com/@material-ui%2ftypes/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2"
integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==
"@material-ui/utils@^4.10.2", "@material-ui/utils@^4.9.6":
version "4.10.2"
resolved "https://packages.deskreen.com/@material-ui%2futils/-/utils-4.10.2.tgz#3fd5470ca61b7341f1e0468ac8f29a70bf6df321"
integrity sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==
dependencies:
"@babel/runtime" "^7.4.4"
prop-types "^15.7.2"
react-is "^16.8.0"
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://packages.deskreen.com/@mrmlnc%2freaddir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@ -1568,6 +1724,11 @@
dependencies:
"@types/node" "*"
"@types/classnames@^2.2.10":
version "2.2.10"
resolved "https://packages.deskreen.com/@types%2fclassnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999"
integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://packages.deskreen.com/@types%2fcolor-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -1880,6 +2041,13 @@
resolved "https://packages.deskreen.com/@types%2fq/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
"@types/qrcode.react@^1.0.1":
version "1.0.1"
resolved "https://packages.deskreen.com/@types%2fqrcode.react/-/qrcode.react-1.0.1.tgz#0904e7a075a6274a5258f19567b4f64013c159d8"
integrity sha512-PcVCjpsiT2KFKfJibOgTQtkt0QQT/6GbQUp1Np/hMPhwUzMJ2DRUkR9j7tXN9Q8X06qukw+RbaJ8lJ22SBod+Q==
dependencies:
"@types/react" "*"
"@types/qs@*":
version "6.9.4"
resolved "https://packages.deskreen.com/@types%2fqs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a"
@ -1931,6 +2099,27 @@
dependencies:
"@types/react" "*"
"@types/react-toast-notifications@^2.4.0":
version "2.4.0"
resolved "https://packages.deskreen.com/@types%2freact-toast-notifications/-/react-toast-notifications-2.4.0.tgz#0ca0732cfae5a6ef5939a676fffac6e64c78bc25"
integrity sha512-nBI6gQ0E5gwi3IcTrVOR3oKoMGRfH1gK67kI6RIKUmiV5Sc3ZC/eymYBFt6iDo0dhlYET6kdtR0tcUh9h5L0sQ==
dependencies:
"@types/react" "*"
"@types/react-toastify@^4.1.0":
version "4.1.0"
resolved "https://packages.deskreen.com/@types%2freact-toastify/-/react-toastify-4.1.0.tgz#604e712855dd677916d5c66af595d3b590f5d95d"
integrity sha512-u7Ie/7LHBsPVz/iJxi/WlRDS7Gh9csCJACTDXx+pSLuZCm94xpkwzhM3jV1L5ZxP/in0Gp2tFbJ91VrSGr1gyQ==
dependencies:
react-toastify "*"
"@types/react-transition-group@^4.2.0":
version "4.4.0"
resolved "https://packages.deskreen.com/@types%2freact-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^16.9.44":
version "16.9.48"
resolved "https://packages.deskreen.com/@types%2freact/-/react-16.9.48.tgz#d3387329f070d1b1bc0ff4a54a54ceefd5a8485c"
@ -3077,6 +3266,22 @@ babel-plugin-dynamic-import-node@^2.3.3:
dependencies:
object.assign "^4.1.0"
babel-plugin-emotion@^10.0.27:
version "10.0.33"
resolved "https://packages.deskreen.com/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz#ce1155dcd1783bbb9286051efee53f4e2be63e03"
integrity sha512-bxZbTTGz0AJQDHm8k6Rf3RQJ8tX2scsfsRyKVgAbiUPUNIRtlK+7JxP+TAd1kRLABFxe0CFm2VdK4ePkoA9FxQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@emotion/hash" "0.8.0"
"@emotion/memoize" "0.7.4"
"@emotion/serialize" "^0.11.16"
babel-plugin-macros "^2.0.0"
babel-plugin-syntax-jsx "^6.18.0"
convert-source-map "^1.5.0"
escape-string-regexp "^1.0.5"
find-root "^1.1.0"
source-map "^0.5.7"
babel-plugin-istanbul@^6.0.0:
version "6.0.0"
resolved "https://packages.deskreen.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765"
@ -3098,6 +3303,15 @@ babel-plugin-jest-hoist@^26.2.0:
"@types/babel__core" "^7.0.0"
"@types/babel__traverse" "^7.0.6"
babel-plugin-macros@^2.0.0:
version "2.8.0"
resolved "https://packages.deskreen.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
dependencies:
"@babel/runtime" "^7.7.2"
cosmiconfig "^6.0.0"
resolve "^1.12.0"
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
resolved "https://packages.deskreen.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
@ -3133,7 +3347,7 @@ babel-plugin-syntax-flow@^6.18.0:
resolved "https://packages.deskreen.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=
babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
babel-plugin-syntax-jsx@^6.18.0, babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
version "6.18.0"
resolved "https://packages.deskreen.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
@ -4426,7 +4640,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.2:
classnames@^2.2, classnames@^2.2.6:
version "2.2.6"
resolved "https://packages.deskreen.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@ -4514,6 +4728,11 @@ clone@^1.0.2:
resolved "https://packages.deskreen.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
clsx@^1.0.4, clsx@^1.1.1:
version "1.1.1"
resolved "https://packages.deskreen.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
co@^4.6.0:
version "4.6.0"
resolved "https://packages.deskreen.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@ -4798,7 +5017,7 @@ content-type@^1.0.4, content-type@~1.0.4:
resolved "https://packages.deskreen.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
convert-source-map@^1.4.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.7.0"
resolved "https://packages.deskreen.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
@ -5081,6 +5300,14 @@ css-tree@1.0.0-alpha.39:
mdn-data "2.0.6"
source-map "^0.6.1"
css-vendor@^2.0.8:
version "2.0.8"
resolved "https://packages.deskreen.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==
dependencies:
"@babel/runtime" "^7.8.3"
is-in-browser "^1.0.2"
css-what@2.1:
version "2.1.3"
resolved "https://packages.deskreen.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
@ -5198,6 +5425,11 @@ cssstyle@^2.2.0:
dependencies:
cssom "~0.3.6"
csstype@^2.5.2, csstype@^2.5.7:
version "2.6.13"
resolved "https://packages.deskreen.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f"
integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==
csstype@^3.0.2:
version "3.0.3"
resolved "https://packages.deskreen.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8"
@ -5364,6 +5596,13 @@ default-gateway@^4.2.0:
execa "^1.0.0"
ip-regex "^2.1.0"
default-gateway@^6.0.0:
version "6.0.2"
resolved "https://packages.deskreen.com/default-gateway/-/default-gateway-6.0.2.tgz#fc14f4a2ae1cbc699c2b40cedd941ab312609ea4"
integrity sha512-bWrj9HZWNXJ/RUkWmBIp67JawNrPGz0il43IGWU84dazEYbNFQ52HbIiqgRQdYUHK3RyGrENrDV9QkwArt6IAQ==
dependencies:
execa "^4.0.3"
defaults@^1.0.3:
version "1.0.3"
resolved "https://packages.deskreen.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
@ -5621,6 +5860,14 @@ dom-helpers@^3.4.0:
dependencies:
"@babel/runtime" "^7.1.2"
dom-helpers@^5.0.1:
version "5.2.0"
resolved "https://packages.deskreen.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b"
integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-serializer@0:
version "0.2.2"
resolved "https://packages.deskreen.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@ -5802,7 +6049,7 @@ electron-is-accelerator@^0.1.0:
resolved "https://packages.deskreen.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b"
integrity sha1-UJ5RDCala1Xhf4Y6SwThEYRqsns=
electron-is-dev@^1.1.0, electron-is-dev@^1.2.0:
electron-is-dev@^1.1.0:
version "1.2.0"
resolved "https://packages.deskreen.com/electron-is-dev/-/electron-is-dev-1.2.0.tgz#2e5cea0a1b3ccf1c86f577cee77363ef55deb05e"
integrity sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==
@ -5851,6 +6098,18 @@ electron-rebuild@^1.10.0:
spawn-rx "^3.0.0"
yargs "^14.2.0"
electron-settings@^4.0.2:
version "4.0.2"
resolved "https://packages.deskreen.com/electron-settings/-/electron-settings-4.0.2.tgz#26ef242397393e0e69119f6fb879fc2287d0f508"
integrity sha512-WnUlrnBsO784oXcag0ym+A3ySoIwonz5GhYFsWroMHVzslzmsP+81f/Fof41T9UrRUxuPPKiZPZMwGO+yvWChg==
dependencies:
lodash.get "^4.4.2"
lodash.has "^4.5.2"
lodash.set "^4.3.2"
lodash.unset "^4.5.2"
mkdirp "^1.0.4"
write-file-atomic "^3.0.3"
electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.523:
version "1.3.555"
resolved "https://packages.deskreen.com/electron-to-chromium/-/electron-to-chromium-1.3.555.tgz#a096716ff77cf8da9a608eb628fd6927869503d2"
@ -7005,6 +7264,11 @@ follow-redirects@^1.0.0:
resolved "https://packages.deskreen.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
fontsource-lexend-peta@^3.0.9:
version "3.0.9"
resolved "https://packages.deskreen.com/fontsource-lexend-peta/-/fontsource-lexend-peta-3.0.9.tgz#c11994728d50d95a348b6f87926c6eb1c086c892"
integrity sha512-pqOcFyjC8RkZcSC9EoXLk2fb2HssgWcvMREk7DvNJInWP+v3/htZaQjlF1el6EQoVuOx9lJwNxqqNiQvW3B8/w==
for-in@^1.0.2:
version "1.0.2"
resolved "https://packages.deskreen.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -7626,7 +7890,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://packages.deskreen.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -7858,6 +8122,11 @@ husky@^4.2.5:
slash "^3.0.0"
which-pm-runs "^1.0.0"
hyphenate-style-name@^1.0.3:
version "1.0.4"
resolved "https://packages.deskreen.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
i18next-fs-backend@^1.0.7:
version "1.0.7"
resolved "https://packages.deskreen.com/i18next-fs-backend/-/i18next-fs-backend-1.0.7.tgz#00ca4587e306f8948740408389dda73461a5d07f"
@ -8063,6 +8332,14 @@ internal-ip@^4.3.0:
default-gateway "^4.2.0"
ipaddr.js "^1.9.0"
internal-ip@^6.1.0:
version "6.1.0"
resolved "https://packages.deskreen.com/internal-ip/-/internal-ip-6.1.0.tgz#3ce3a9155dc9e2a423af0059efcf5f4b0de3399c"
integrity sha512-Cs1iaqrl3z3KJ2ejWyfKkMcuv9NTEJWXtUBSGVc+Eg9BjBLS0k11CsOkf/p5quOkVhhRuq9zwZ/PuJpPUuDP9Q==
dependencies:
default-gateway "^6.0.0"
ipaddr.js "^1.9.1"
internal-slot@^1.0.2:
version "1.0.2"
resolved "https://packages.deskreen.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3"
@ -8094,7 +8371,7 @@ ip@^1.1.0, ip@^1.1.3, ip@^1.1.5:
resolved "https://packages.deskreen.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
ipaddr.js@1.9.1, ipaddr.js@^1.9.0:
ipaddr.js@1.9.1, ipaddr.js@^1.9.0, ipaddr.js@^1.9.1:
version "1.9.1"
resolved "https://packages.deskreen.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
@ -8353,6 +8630,11 @@ is-hexadecimal@^1.0.0:
resolved "https://packages.deskreen.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
is-in-browser@^1.0.2, is-in-browser@^1.1.3:
version "1.1.3"
resolved "https://packages.deskreen.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=
is-installed-globally@^0.3.1:
version "0.3.2"
resolved "https://packages.deskreen.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
@ -9243,6 +9525,76 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
jss-plugin-camel-case@^10.0.3:
version "10.4.0"
resolved "https://packages.deskreen.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.4.0.tgz#46c75ff7fd61c304984c21af5817823f0f501ceb"
integrity sha512-9oDjsQ/AgdBbMyRjc06Kl3P8lDCSEts2vYZiPZfGAxbGCegqE4RnMob3mDaBby5H9vL9gWmyyImhLRWqIkRUCw==
dependencies:
"@babel/runtime" "^7.3.1"
hyphenate-style-name "^1.0.3"
jss "10.4.0"
jss-plugin-default-unit@^10.0.3:
version "10.4.0"
resolved "https://packages.deskreen.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.4.0.tgz#2b10f01269eaea7f36f0f5fd1cfbfcc76ed42854"
integrity sha512-BYJ+Y3RUYiMEgmlcYMLqwbA49DcSWsGgHpVmEEllTC8MK5iJ7++pT9TnKkKBnNZZxTV75ycyFCR5xeLSOzVm4A==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.4.0"
jss-plugin-global@^10.0.3:
version "10.4.0"
resolved "https://packages.deskreen.com/jss-plugin-global/-/jss-plugin-global-10.4.0.tgz#19449425a94e4e74e113139b629fd44d3577f97d"
integrity sha512-b8IHMJUmv29cidt3nI4bUI1+Mo5RZE37kqthaFpmxf5K7r2aAegGliAw4hXvA70ca6ckAoXMUl4SN/zxiRcRag==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.4.0"
jss-plugin-nested@^10.0.3:
version "10.4.0"
resolved "https://packages.deskreen.com/jss-plugin-nested/-/jss-plugin-nested-10.4.0.tgz#017d0c02c0b6b454fd9d7d3fc33470a15eea9fd1"
integrity sha512-cKgpeHIxAP0ygeWh+drpLbrxFiak6zzJ2toVRi/NmHbpkNaLjTLgePmOz5+67ln3qzJiPdXXJB1tbOyYKAP4Pw==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.4.0"
tiny-warning "^1.0.2"
jss-plugin-props-sort@^10.0.3:
version "10.4.0"
resolved "https://packages.deskreen.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.4.0.tgz#7110bf0b6049cc2080b220b506532bf0b70c0e07"
integrity sha512-j/t0R40/2fp+Nzt6GgHeUFnHVY2kPGF5drUVlgkcwYoHCgtBDOhTTsOfdaQFW6sHWfoQYgnGV4CXdjlPiRrzwA==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.4.0"
jss-plugin-rule-value-function@^10.0.3:
version "10.4.0"
resolved "https://packages.deskreen.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.4.0.tgz#7cff4a91e84973536fa49b6ebbdbf7f339b01c82"
integrity sha512-w8504Cdfu66+0SJoLkr6GUQlEb8keHg8ymtJXdVHWh0YvFxDG2l/nS93SI5Gfx0fV29dO6yUugXnKzDFJxrdFQ==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.4.0"
tiny-warning "^1.0.2"
jss-plugin-vendor-prefixer@^10.0.3:
version "10.4.0"
resolved "https://packages.deskreen.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.4.0.tgz#2a78f3c5d57d1e024fe7ad7c41de34d04e72ecc0"
integrity sha512-DpF+/a+GU8hMh/948sBGnKSNfKkoHg2p9aRFUmyoyxgKjOeH9n74Ht3Yt8lOgdZsuWNJbPrvaa3U4PXKwxVpTQ==
dependencies:
"@babel/runtime" "^7.3.1"
css-vendor "^2.0.8"
jss "10.4.0"
jss@10.4.0, jss@^10.0.3:
version "10.4.0"
resolved "https://packages.deskreen.com/jss/-/jss-10.4.0.tgz#473a6fbe42e85441020a07e9519dac1e8a2e79ca"
integrity sha512-l7EwdwhsDishXzqTc3lbsbyZ83tlUl5L/Hb16pHCvZliA9lRDdNBZmHzeJHP0sxqD0t1mrMmMR8XroR12JBYzw==
dependencies:
"@babel/runtime" "^7.3.1"
csstype "^3.0.2"
is-in-browser "^1.1.3"
tiny-warning "^1.0.2"
jsx-ast-utils@^2.4.1:
version "2.4.1"
resolved "https://packages.deskreen.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e"
@ -9594,6 +9946,16 @@ lodash.flattendeep@^4.4.0:
resolved "https://packages.deskreen.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://packages.deskreen.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
lodash.has@^4.5.2:
version "4.5.2"
resolved "https://packages.deskreen.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862"
integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://packages.deskreen.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
@ -9604,6 +9966,11 @@ lodash.memoize@4.1.2, lodash.memoize@4.x, lodash.memoize@^4.1.2:
resolved "https://packages.deskreen.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.set@^4.3.2:
version "4.3.2"
resolved "https://packages.deskreen.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://packages.deskreen.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@ -9614,6 +9981,11 @@ lodash.uniq@^4.5.0:
resolved "https://packages.deskreen.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash.unset@^4.5.2:
version "4.5.2"
resolved "https://packages.deskreen.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed"
integrity sha1-Nw0dPoW3Kn4bDN8tJyEhMG8j5O0=
"lodash@4.6.1 || ^4.16.1", lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.17.5:
version "4.17.20"
resolved "https://packages.deskreen.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
@ -10193,11 +10565,6 @@ nan@^2.12.1:
resolved "https://packages.deskreen.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nanoid@^0.2.2:
version "0.2.2"
resolved "https://packages.deskreen.com/nanoid/-/nanoid-0.2.2.tgz#e2ebc6ad3db5e0454fd8124d30ca39b06555fe56"
integrity sha512-GHoRrvNEKiwdkwQ/enKL8AhQkkrBC/2KxMZkDvQzp8OtkpX8ZAmoYJWFVl7l8F2+HcEJUfdg21Ab2wXXfrvACQ==
nanoid@^1.0.1:
version "1.3.4"
resolved "https://packages.deskreen.com/nanoid/-/nanoid-1.3.4.tgz#ad89f62c9d1f4fd69710d4a90953d2893d2d31f4"
@ -10208,6 +10575,11 @@ nanoid@^2.1.0, nanoid@^2.1.3:
resolved "https://packages.deskreen.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
nanoid@^3.1.12:
version "3.1.12"
resolved "https://packages.deskreen.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654"
integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://packages.deskreen.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -11107,16 +11479,16 @@ pinkie-promise@^2.0.0:
dependencies:
pinkie "^2.0.0"
pinkie@1.0.0, pinkie@^1.0.0:
version "1.0.0"
resolved "https://packages.deskreen.com/pinkie/-/pinkie-1.0.0.tgz#5a47f28ba1015d0201bda7bf0f358e47bec8c7e4"
integrity sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=
pinkie@^2.0.0, pinkie@^2.0.1, pinkie@^2.0.4:
pinkie@2.0.4, pinkie@^2.0.0, pinkie@^2.0.1, pinkie@^2.0.4:
version "2.0.4"
resolved "https://packages.deskreen.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
pinkie@^1.0.0:
version "1.0.0"
resolved "https://packages.deskreen.com/pinkie/-/pinkie-1.0.0.tgz#5a47f28ba1015d0201bda7bf0f358e47bec8c7e4"
integrity sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=
pirates@^4.0.0, pirates@^4.0.1:
version "4.0.1"
resolved "https://packages.deskreen.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
@ -11157,6 +11529,11 @@ pngjs@^3.3.1:
resolved "https://packages.deskreen.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
popper.js@1.16.1-lts:
version "1.16.1-lts"
resolved "https://packages.deskreen.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05"
integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==
popper.js@^1.14.4, popper.js@^1.16.1:
version "1.16.1"
resolved "https://packages.deskreen.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
@ -11655,7 +12032,7 @@ prop-types-exact@^1.2.0:
object.assign "^4.1.0"
reflect.ownkeys "^0.2.0"
prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://packages.deskreen.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -11760,11 +12137,30 @@ q@^1.1.2:
resolved "https://packages.deskreen.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
qr.js@0.0.0:
version "0.0.0"
resolved "https://packages.deskreen.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=
qrcode-generator@^1.4.1:
version "1.4.4"
resolved "https://packages.deskreen.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7"
integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==
qrcode-terminal@^0.10.0:
version "0.10.0"
resolved "https://packages.deskreen.com/qrcode-terminal/-/qrcode-terminal-0.10.0.tgz#a76a48e2610a18f97fa3a2bd532b682acff86c53"
integrity sha1-p2pI4mEKGPl/o6K9UytoKs/4bFM=
qrcode.react@^1.0.0:
version "1.0.0"
resolved "https://packages.deskreen.com/qrcode.react/-/qrcode.react-1.0.0.tgz#7e8889db3b769e555e8eb463d4c6de221c36d5de"
integrity sha512-jBXleohRTwvGBe1ngV+62QvEZ/9IZqQivdwzo9pJM4LQMoCM2VnvNBnKdjvGnKyDZ/l0nCDgsPod19RzlPvm/Q==
dependencies:
loose-envify "^1.4.0"
prop-types "^15.6.0"
qr.js "0.0.0"
qs@6.7.0:
version "6.7.0"
resolved "https://packages.deskreen.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@ -11863,7 +12259,17 @@ rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-dom@^16.12.0:
react-awesome-reveal@^3.2.1:
version "3.2.1"
resolved "https://packages.deskreen.com/react-awesome-reveal/-/react-awesome-reveal-3.2.1.tgz#a1c3d876b666c807219c5ceb6eaa4b75a6bb16fb"
integrity sha512-/jxX6lTjVyARwOIZBth88G+5Curb9/wEjPRd3QV6FDsgEfkCwZ3z8AStR43uCDqOeKlJq5d5Hzm4dLoXt0Lb6g==
dependencies:
"@emotion/core" "^10.0.35"
"@emotion/styled" "^10.0.27"
react-intersection-observer "^8.27.1"
react-is "^16.13.1"
react-dom@^16.12.0, react-dom@^16.4.1:
version "16.13.1"
resolved "https://packages.deskreen.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==
@ -11903,7 +12309,12 @@ react-i18next@^11.7.0:
"@babel/runtime" "^7.3.1"
html-parse-stringify2 "2.0.1"
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0:
react-intersection-observer@^8.27.1:
version "8.28.2"
resolved "https://packages.deskreen.com/react-intersection-observer/-/react-intersection-observer-8.28.2.tgz#6504423575905bb9312be6ff0d22e7bbc78af63f"
integrity sha512-JzyBn3QylXSJmM7P9dxxAioC4F3xHt4Uc3A6ZBXY27PeSNFraqnsd9jBdo7LJOMtzeXxyfJ6rYdDTl35LQ5TnQ==
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0:
version "16.13.1"
resolved "https://packages.deskreen.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -11926,6 +12337,16 @@ react-popper@^1.3.7:
typed-styles "^0.0.7"
warning "^4.0.2"
react-qrcode-logo@^2.2.1:
version "2.2.1"
resolved "https://packages.deskreen.com/react-qrcode-logo/-/react-qrcode-logo-2.2.1.tgz#c409a1a37bc8eb76ef10343ff2e6f00b94facac8"
integrity sha512-eXFSJW8HVPMT2ea4pLkbG8apHJ/aIPpQ4kX0HmsSm0wN+K+bleRNgElbjIPS4G7n0lxcA7N6pw+KAxMWPJFoqA==
dependencies:
lodash.isequal "^4.5.0"
qrcode-generator "^1.4.1"
react "^16.4.1"
react-dom "^16.4.1"
react-redux@^7.2.0:
version "7.2.1"
resolved "https://packages.deskreen.com/react-redux/-/react-redux-7.2.1.tgz#8dedf784901014db2feca1ab633864dee68ad985"
@ -11937,6 +12358,13 @@ react-redux@^7.2.0:
prop-types "^15.7.2"
react-is "^16.9.0"
react-reveal@^1.2.2:
version "1.2.2"
resolved "https://packages.deskreen.com/react-reveal/-/react-reveal-1.2.2.tgz#f47fbc44debc4c185ae2163a215a9e822c7adfef"
integrity sha512-JCv3fAoU6Z+Lcd8U48bwzm4pMZ79qsedSXYwpwt6lJNtj/v5nKJYZZbw3yhaQPPgYePo3Y0NOCoYOq/jcsisuw==
dependencies:
prop-types "^15.5.10"
react-router-dom@^5.2.0:
version "5.2.0"
resolved "https://packages.deskreen.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
@ -11976,6 +12404,23 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0:
react-is "^16.8.6"
scheduler "^0.19.1"
react-toast-notifications@^2.4.0:
version "2.4.0"
resolved "https://packages.deskreen.com/react-toast-notifications/-/react-toast-notifications-2.4.0.tgz#6213730bd1fe158fc01aeef200687ea94c5c5b24"
integrity sha512-8tkrbNh7LxkiFmtqAL/AiI55efIeI+fBk3c6ImsiZ0VObb4yvOq0cqYuJHyUiv9fuD2aBxvXGVH+n4Snt8qoWA==
dependencies:
"@emotion/core" "^10.0.14"
react-transition-group "^4.3.0"
react-toastify@*, react-toastify@^6.0.8:
version "6.0.8"
resolved "https://packages.deskreen.com/react-toastify/-/react-toastify-6.0.8.tgz#84625d81d0fd01902a7f4c6f317eb074cb3bba67"
integrity sha512-NSqCNwv+C4IfR+c92PFZiNyeBwOJvigrP2bcRi2f6Hg3WqcHhEHOknbSQOs9QDFuqUjmK3SOrdvScQ3z63ifXg==
dependencies:
classnames "^2.2.6"
prop-types "^15.7.2"
react-transition-group "^4.4.1"
react-transition-group@^2.9.0:
version "2.9.0"
resolved "https://packages.deskreen.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
@ -11986,7 +12431,17 @@ react-transition-group@^2.9.0:
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"
react@^16.13.1:
react-transition-group@^4.3.0, react-transition-group@^4.4.0, react-transition-group@^4.4.1:
version "4.4.1"
resolved "https://packages.deskreen.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@^16.13.1, react@^16.4.1:
version "16.13.1"
resolved "https://packages.deskreen.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
@ -13864,10 +14319,10 @@ testcafe-browser-tools@2.0.13:
read-file-relative "^1.2.0"
which-promise "^1.0.0"
testcafe-hammerhead@17.1.13:
version "17.1.13"
resolved "https://packages.deskreen.com/testcafe-hammerhead/-/testcafe-hammerhead-17.1.13.tgz#e2ab9589e48aa145fa886c2303891e9e997d33fa"
integrity sha512-swNsC4gbs1vhKM9F9zmCcStA8oMg2FUTJZYUE30xL5ZdHae87x2tfg9T58jUcZK3CX0S9B2NCU1AJbwCsS0uqw==
testcafe-hammerhead@17.1.15:
version "17.1.15"
resolved "https://packages.deskreen.com/testcafe-hammerhead/-/testcafe-hammerhead-17.1.15.tgz#2855db1d795f598ee331eda4ff7b0ca0309255d9"
integrity sha512-FvuaODbFUkoqyzH0OapiMO3/ISCB/v+0kKi0+5DhcIz1tRBishzyfLbHFyKXUf6rm0IhGJlME93uKFOM9NNOMw==
dependencies:
acorn-hammerhead "^0.3.0"
asar "^2.0.1"
@ -13884,10 +14339,10 @@ testcafe-hammerhead@17.1.13:
merge-stream "^1.0.1"
mime "~1.4.1"
mustache "^2.1.1"
nanoid "^0.2.2"
nanoid "^3.1.12"
os-family "^1.0.0"
parse5 "2.2.3"
pinkie "1.0.0"
pinkie "2.0.4"
read-file-relative "^1.2.0"
semver "5.5.0"
tough-cookie "2.3.3"
@ -13943,10 +14398,10 @@ testcafe-reporter-xunit@^2.1.0:
resolved "https://packages.deskreen.com/testcafe-reporter-xunit/-/testcafe-reporter-xunit-2.1.0.tgz#e6d66c572ce15af266706af0fd610b2a841dd443"
integrity sha1-5tZsVyzhWvJmcGrw/WELKoQd1EM=
testcafe@^1.8.8:
version "1.9.1"
resolved "https://packages.deskreen.com/testcafe/-/testcafe-1.9.1.tgz#2e3183d69561bf90da611408436c5b34b22f799c"
integrity sha512-18yGuCJvcdaGFhS3l4NhDwRnvJ24/mwXVfM+gqZm6vxNdw2NCujzXh36/BNr0qP2MhAEN56Y96G8XKkzn+5zYg==
testcafe@^1.9.2:
version "1.9.2"
resolved "https://packages.deskreen.com/testcafe/-/testcafe-1.9.2.tgz#35f3603bdc5241033eda9ad55d600d8c005a2801"
integrity sha512-85du0zDvzFWleVqRTTsAr8Lo+3gn4yETs9qFZBIEufk6oN1fLzgv6Q14GeaH3/IaKi+/smv55umTce/OXX0mbA==
dependencies:
"@types/node" "^10.12.19"
async-exit-hook "^1.1.2"
@ -13977,6 +14432,7 @@ testcafe@^1.8.8:
emittery "^0.4.1"
endpoint-utils "^1.0.2"
error-stack-parser "^1.3.6"
execa "^4.0.3"
globby "^9.2.0"
graceful-fs "^4.1.11"
graphlib "^2.1.5"
@ -14012,7 +14468,7 @@ testcafe@^1.8.8:
source-map-support "^0.5.16"
strip-bom "^2.0.0"
testcafe-browser-tools "2.0.13"
testcafe-hammerhead "17.1.13"
testcafe-hammerhead "17.1.15"
testcafe-legacy-api "4.0.0"
testcafe-reporter-json "^2.1.0"
testcafe-reporter-list "^2.1.0"
@ -14079,7 +14535,7 @@ tiny-invariant@^1.0.2:
resolved "https://packages.deskreen.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://packages.deskreen.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==