mirror of
https://github.com/pavlobu/deskreen.git
synced 2025-05-16 07:20:16 -07:00
better client UI code
huge work done on sharing desktop session
This commit is contained in:
parent
99b6cbba7f
commit
b925803d9f
@ -45,3 +45,7 @@ MIT © [Pavlo (Paul) Buidenkov](https://github.com/pavlobu/deskreen)
|
||||
MIT © [Electron React Boilerplate](https://github.com/electron-react-boilerplate)
|
||||
|
||||
## Test ?
|
||||
|
||||
## By
|
||||
|
||||
Crafted with LOVE and support from Pavlo (Paul) Buidenkov
|
||||
|
@ -29,6 +29,14 @@ body {
|
||||
background-color: var(--light-bg-color);
|
||||
}
|
||||
|
||||
#intermediate-step-container > .react-reveal {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#choose-app-or-screen-overlay-container > .react-reveal {
|
||||
height: 0%;
|
||||
}
|
||||
|
||||
/* UI colors FOR LIGHT AND DARK THEME START */
|
||||
.bp3-button:not([class*='bp3-intent-']) {
|
||||
background-color: var(--light-btn-no-intent-color);
|
||||
@ -141,7 +149,7 @@ div.class-allow-device-to-connect-alert
|
||||
> span
|
||||
> svg
|
||||
> path {
|
||||
color: #a82a2a !important;
|
||||
color: #a82a2a;
|
||||
-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;
|
||||
@ -226,8 +234,6 @@ div.class-allow-device-to-connect-alert
|
||||
#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 */
|
||||
@ -241,10 +247,6 @@ div.class-allow-device-to-connect-alert
|
||||
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;
|
||||
@ -253,11 +255,6 @@ div.class-allow-device-to-connect-alert
|
||||
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;
|
||||
|
@ -32,9 +32,9 @@
|
||||
if (process.env.START_HOT) {
|
||||
// Dynamically insert the bundled app script in the renderer process
|
||||
const port = process.env.PORT || 1212;
|
||||
scripts.push(`http://localhost:${port}/dist/renderer.dev.js`);
|
||||
scripts.push(`http://localhost:${port}/dist/mainWindow.renderer.dev.js`);
|
||||
} else {
|
||||
scripts.push('./dist/renderer.prod.js');
|
||||
scripts.push('./dist/mainWindow.renderer.prod.js');
|
||||
}
|
||||
|
||||
if (scripts.length) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.35.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
@ -10,11 +11,22 @@
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/ua-parser-js": "^0.7.33",
|
||||
"jest-sonar-reporter": "^2.0.0",
|
||||
"node-forge": "^0.9.1",
|
||||
"pixelmatch": "^5.2.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-flexbox-grid": "^2.1.2",
|
||||
"react-player": "^2.6.2",
|
||||
"react-reveal": "^1.2.2",
|
||||
"react-scripts": "3.4.3",
|
||||
"typescript": "~3.7.2"
|
||||
"react-spinners": "^0.9.0",
|
||||
"screenfull": "^5.0.2",
|
||||
"shortid": "^2.2.15",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"typescript": "~3.7.2",
|
||||
"ua-parser-js": "^0.7.22"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@ -39,6 +51,13 @@
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node-forge": "^0.9.5",
|
||||
"@types/pixelmatch": "^5.2.2",
|
||||
"@types/resemblejs": "^1.3.29",
|
||||
"@types/simple-peer": "^9.6.0",
|
||||
"@types/socket.io-client": "^1.4.33"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx,ts,tsx}",
|
||||
|
@ -1,38 +1,70 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
#pulsing-circle-1 {
|
||||
animation: pulse1 infinite 6s linear;
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
|
||||
#pulsing-circle-2 {
|
||||
animation: pulse2 infinite 6s linear;
|
||||
}
|
||||
|
||||
.pulse-3-once {
|
||||
animation: pulse3twice 2 750ms linear;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse1 {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(72, 175, 240, 0.7);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 15px rgba(72, 175, 240, 0.4);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(72, 175, 240, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse2 {
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(72, 175, 240, 0);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 33px rgba(72, 175, 240, 0.3);
|
||||
}
|
||||
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(72, 175, 240, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse3twice {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(21, 179, 113, 0.7);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 0 30px rgba(21, 179, 113, 0.3);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(21, 179, 113, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.container > .react-reveal {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
const { getByText } = render(<App />);
|
||||
const linkElement = getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
// const { getByText } = render(<App />);
|
||||
// const linkElement = getByText(/learn react/i);
|
||||
// expect(linkElement).toBeInTheDocument();
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
@ -1,25 +1,284 @@
|
||||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import React, { useEffect, useState, useRef, useContext } from 'react';
|
||||
import { H3, Button } from '@blueprintjs/core';
|
||||
import { Grid, Row, Col } from 'react-flexbox-grid';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import ReactPlayer from 'react-player';
|
||||
import screenfull from 'screenfull';
|
||||
import Crypto from './utils/crypto';
|
||||
import './App.css';
|
||||
import LocalTestPeer from './features/PeerConnection';
|
||||
import VideoAutoQualityOptimizer from './features/VideoAutoQualityOptimizer';
|
||||
import ConnectingIndicator from './components/ConnectingIndicator';
|
||||
import MyDeviceInfoCard from './components/MyDeviceInfoCard';
|
||||
import {
|
||||
DARK_UI_BACKGROUND,
|
||||
LIGHT_UI_BACKGROUND,
|
||||
} from './constants/styleConstants';
|
||||
import { AppContext } from './providers/AppContextProvider';
|
||||
import ToggleDarkModeSwitch from './components/ToggleDarkModeSwitch';
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
const Slide = require('react-reveal/Slide');
|
||||
|
||||
function getPromptContent(step: number) {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return (
|
||||
<H3>Waiting for user to click "Allow" on screen sharing device...</H3>
|
||||
);
|
||||
case 2:
|
||||
return <H3>Connected!</H3>;
|
||||
case 3:
|
||||
return (
|
||||
<H3>
|
||||
Wating for user to select source to share from screen sharing
|
||||
device...
|
||||
</H3>
|
||||
);
|
||||
default:
|
||||
return <H3>Error occured :(</H3>;
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
const { isDarkTheme, setIsDarkThemeHook } = useContext(AppContext);
|
||||
|
||||
const player = useRef(null);
|
||||
const [promptStep, setPromptStep] = useState(1);
|
||||
const [connectionIconType, setConnectionIconType] = useState<
|
||||
'feed' | 'feed-subscribed'
|
||||
>('feed');
|
||||
const [myDeviceDetails, setMyDeviceDetails] = useState<DeviceDetails>({
|
||||
myIP: '',
|
||||
myOS: '',
|
||||
myDeviceType: '',
|
||||
myBrowser: '',
|
||||
});
|
||||
|
||||
const [playing, setPlaying] = useState(true);
|
||||
const [url, setUrl] = useState();
|
||||
const [isWithControls, setIsWithControls] = useState(!screenfull.isEnabled);
|
||||
const [isShownTextPrompt, setIsShownTextPrompt] = useState(false);
|
||||
const [isShownSpinnerIcon, setIsShownSpinnerIcon] = useState(false);
|
||||
const [spinnerIconType, setSpinnerIconType] = useState<
|
||||
'desktop' | 'application'
|
||||
>('desktop');
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.backgroundColor = isDarkTheme
|
||||
? DARK_UI_BACKGROUND
|
||||
: LIGHT_UI_BACKGROUND;
|
||||
|
||||
const peer = new LocalTestPeer(
|
||||
setUrl,
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
setMyDeviceDetails,
|
||||
() => {
|
||||
setConnectionIconType('feed-subscribed');
|
||||
|
||||
setIsShownTextPrompt(false);
|
||||
setIsShownTextPrompt(true);
|
||||
setPromptStep(2);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsShownTextPrompt(false);
|
||||
setIsShownTextPrompt(true);
|
||||
setPromptStep(3);
|
||||
}, 2000);
|
||||
}
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsShownTextPrompt(true);
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// infinite use effect
|
||||
setTimeout(() => {
|
||||
setIsShownSpinnerIcon(!isShownSpinnerIcon);
|
||||
setSpinnerIconType(
|
||||
spinnerIconType === 'desktop' ? 'application' : 'desktop'
|
||||
);
|
||||
}, 1500);
|
||||
}, [isShownSpinnerIcon]);
|
||||
|
||||
const handleClickFullscreen = () => {
|
||||
// @ts-ignore Property 'request' does not exist on type '{ isEnabled: false; }'.
|
||||
screenfull.request(findDOMNode(player.current));
|
||||
};
|
||||
|
||||
const handlePlayPause = () => {
|
||||
setPlaying(!playing);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.backgroundColor = isDarkTheme
|
||||
? DARK_UI_BACKGROUND
|
||||
: LIGHT_UI_BACKGROUND;
|
||||
}, [isDarkTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (url !== undefined) {
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
document.querySelector('.container > .react-reveal').style.display =
|
||||
'none';
|
||||
}, 1000);
|
||||
}
|
||||
}, [url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (promptStep === 3) {
|
||||
// start infinite use effect
|
||||
setIsShownSpinnerIcon(true);
|
||||
}
|
||||
}, [promptStep]);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<Grid>
|
||||
<Slide
|
||||
id="connection-prompts-slide"
|
||||
bottom
|
||||
when={url === undefined ? true : false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 10,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
boxShadow: `0 0 0 5px ${isDarkTheme ? '#000' : '#A7B6C2'}`,
|
||||
backgroundColor: isDarkTheme
|
||||
? DARK_UI_BACKGROUND
|
||||
: LIGHT_UI_BACKGROUND,
|
||||
}}
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
<ToggleDarkModeSwitch />
|
||||
<Row
|
||||
bottom="xs"
|
||||
style={{
|
||||
height: '50vh',
|
||||
width: '100%',
|
||||
marginRight: '0px',
|
||||
marginLeft: '0px',
|
||||
}}
|
||||
>
|
||||
<Row center="xs" style={{ width: '100%', margin: '0 auto' }}>
|
||||
<Col
|
||||
xs={12}
|
||||
style={{
|
||||
marginBottom: '50px',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Row center="xs" style={{ width: '100%', margin: '0 auto' }}>
|
||||
<Col md={6} xl={4}>
|
||||
<MyDeviceInfoCard deviceDetails={myDeviceDetails} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Fade
|
||||
opposite
|
||||
when={isShownTextPrompt}
|
||||
duration={2000}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<div id="prompt-text" style={{ fontSize: '20px' }}>
|
||||
{getPromptContent(promptStep)}
|
||||
</div>
|
||||
</Fade>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Row>
|
||||
<Fade
|
||||
opposite
|
||||
when={isShownTextPrompt}
|
||||
duration={500}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<ConnectingIndicator
|
||||
currentStep={promptStep}
|
||||
connectionIconType={connectionIconType}
|
||||
isShownSelectingSharingIcon={isShownSpinnerIcon}
|
||||
selectingSharingIconType={spinnerIconType}
|
||||
/>
|
||||
</Fade>
|
||||
</div>
|
||||
</Slide>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={() => setIsWithControls(true)}
|
||||
onTouchStart={() => setIsWithControls(true)}
|
||||
>
|
||||
Help! I'm having troubles to zoom in on my device
|
||||
</Button>
|
||||
<Button onClick={handlePlayPause} onTouchStart={handlePlayPause}>
|
||||
PLAY!
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleClickFullscreen}
|
||||
onTouchStart={handleClickFullscreen}
|
||||
>
|
||||
ENTER FULL SCREEN
|
||||
</Button>
|
||||
<ToggleDarkModeSwitch />
|
||||
<div
|
||||
id="video-container"
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="player-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
paddingTop: '56.25%',
|
||||
}}
|
||||
>
|
||||
<ReactPlayer
|
||||
ref={player}
|
||||
id="video-local-test-peer-sees"
|
||||
playing={playing}
|
||||
playsinline={true}
|
||||
controls={isWithControls}
|
||||
muted={true}
|
||||
// url={mergedStream}
|
||||
url={(url as unknown) as MediaStream}
|
||||
// width={url.getVideoTracks()[0].getSettings().width}
|
||||
// height={url.getVideoTracks()[0].getSettings().height}
|
||||
// onPlay={setProperCanvasWidth}
|
||||
// wrapper="div"
|
||||
// width={window.screen.width}
|
||||
// height={window.screen.height}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<canvas id="comparison-canvas" style={{ display: 'none' }}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
|
16
app/client/src/api/config.ts
Normal file
16
app/client/src/api/config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/* istanbul ignore file */
|
||||
let host;
|
||||
let protocol;
|
||||
let port;
|
||||
|
||||
if (!host && !protocol && !port) {
|
||||
host = window.location.host.split(':')[0];
|
||||
protocol = 'http';
|
||||
port = 3131;
|
||||
}
|
||||
|
||||
export default {
|
||||
host,
|
||||
port,
|
||||
protocol,
|
||||
};
|
14
app/client/src/api/generator.ts
Normal file
14
app/client/src/api/generator.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/* istanbul ignore file */
|
||||
import config from './config';
|
||||
|
||||
export default (resourceName = '') => {
|
||||
const { port, protocol, host } = config;
|
||||
|
||||
const resourcePath = resourceName;
|
||||
|
||||
if (!host) {
|
||||
return `/localhost`;
|
||||
}
|
||||
|
||||
return `${protocol}://${host}:${port}/${resourcePath}`;
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@blueprintjs/core';
|
||||
import { Col, Row } from 'react-flexbox-grid';
|
||||
|
||||
interface ConnectingIndicatorIconProps {
|
||||
connectionIconType: "feed" | "feed-subscribed";
|
||||
}
|
||||
|
||||
function ConnectingIndicatorIcon(props: ConnectingIndicatorIconProps) {
|
||||
const { connectionIconType } = props;
|
||||
|
||||
return (
|
||||
<Row
|
||||
middle="xs"
|
||||
center="xs"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Col>
|
||||
<Icon
|
||||
icon={connectionIconType}
|
||||
iconSize={50}
|
||||
color="white"
|
||||
style={{
|
||||
transform: 'translateX(10px)',
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConnectingIndicatorIcon;
|
@ -0,0 +1,66 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Icon } from '@blueprintjs/core';
|
||||
import { Col, Row } from 'react-flexbox-grid';
|
||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||
import { AppContext } from '../../providers/AppContextProvider';
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
|
||||
interface SelectSharingIconProps {
|
||||
selectingSharingIconType: 'desktop' | 'application';
|
||||
isShownSelectingSharingIcon: boolean;
|
||||
}
|
||||
|
||||
function SelectSharingIcon(props: SelectSharingIconProps) {
|
||||
const { isDarkTheme } = useContext(AppContext);
|
||||
|
||||
const { selectingSharingIconType, isShownSelectingSharingIcon } = props;
|
||||
|
||||
return (
|
||||
<Row
|
||||
center="xs"
|
||||
top="xs"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
marginRight: '0px',
|
||||
marginLeft: '0px',
|
||||
}}
|
||||
>
|
||||
<Col>
|
||||
<Row
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
}}
|
||||
center="xs"
|
||||
>
|
||||
<Col xs={8} md={4}>
|
||||
<PropagateLoader
|
||||
loading
|
||||
size={18}
|
||||
color={isDarkTheme ? '#BFCCD6' : '#5C7080'}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row center="xs">
|
||||
<Col>
|
||||
<Fade
|
||||
opposite
|
||||
when={isShownSelectingSharingIcon}
|
||||
duration={500}
|
||||
>
|
||||
<Icon
|
||||
icon={selectingSharingIconType}
|
||||
iconSize={60}
|
||||
color={isDarkTheme ? '#BFCCD6' : '#5C7080'}
|
||||
/>
|
||||
</Fade>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectSharingIcon;
|
119
app/client/src/components/ConnectingIndicator/index.tsx
Normal file
119
app/client/src/components/ConnectingIndicator/index.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import React from 'react';
|
||||
import { Text } from '@blueprintjs/core';
|
||||
import { Row } from 'react-flexbox-grid';
|
||||
import ConnectingIndicatorIcon from './ConnectingIndicatorIcon';
|
||||
import SelectSharingIcon from './SelectSharingIcon';
|
||||
|
||||
const basePulsingCircleStyles = {
|
||||
borderRadius: '100%',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
left: '0',
|
||||
right: '0',
|
||||
textAlign: 'center',
|
||||
position: 'absolute',
|
||||
width: '100px',
|
||||
height: '100px',
|
||||
};
|
||||
|
||||
function getConnectingStepContent(
|
||||
currentStep: number,
|
||||
connectionIconType: 'feed' | 'feed-subscribed',
|
||||
selectingSharingIconType: 'desktop' | 'application',
|
||||
isShownSelectingSharingIcon: boolean,
|
||||
) {
|
||||
|
||||
const pulsingCircle1Styles = {
|
||||
...basePulsingCircleStyles,
|
||||
zIndex: 1,
|
||||
backgroundColor: 'rgba(43, 149, 214, 0.7)',
|
||||
} as React.CSSProperties;
|
||||
|
||||
const pulsingCircle2Styles = {
|
||||
...basePulsingCircleStyles,
|
||||
zIndex: 2,
|
||||
backgroundColor: '#2B95D6',
|
||||
} as React.CSSProperties;
|
||||
|
||||
const pulsingCircle3Styles = {
|
||||
...basePulsingCircleStyles,
|
||||
backgroundColor: '#15B371',
|
||||
} as React.CSSProperties;
|
||||
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="pulsing-circle-1"
|
||||
style={pulsingCircle1Styles}
|
||||
></div>
|
||||
<div
|
||||
id="pulsing-circle-2"
|
||||
style={pulsingCircle2Styles}
|
||||
>
|
||||
<ConnectingIndicatorIcon connectionIconType={connectionIconType} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<div
|
||||
id="pulsing-circle-3"
|
||||
className="pulse-3-once"
|
||||
style={pulsingCircle3Styles}
|
||||
>
|
||||
<ConnectingIndicatorIcon connectionIconType={connectionIconType} />
|
||||
</div>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<SelectSharingIcon
|
||||
isShownSelectingSharingIcon={isShownSelectingSharingIcon}
|
||||
selectingSharingIconType={selectingSharingIconType}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <Text>Error occured :(</Text>;
|
||||
}
|
||||
}
|
||||
|
||||
interface ConnectingIndicatorProps {
|
||||
currentStep: number;
|
||||
connectionIconType: 'feed' | 'feed-subscribed';
|
||||
isShownSelectingSharingIcon: boolean;
|
||||
selectingSharingIconType: 'desktop' | 'application';
|
||||
}
|
||||
|
||||
function ConnectingIndicator(props: ConnectingIndicatorProps) {
|
||||
const {
|
||||
currentStep,
|
||||
connectionIconType,
|
||||
isShownSelectingSharingIcon,
|
||||
selectingSharingIconType,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row
|
||||
id="connecting-screen"
|
||||
top="xs"
|
||||
style={{
|
||||
height: '50vh',
|
||||
width: '100%',
|
||||
marginRight: '0px',
|
||||
marginLeft: '0px',
|
||||
}}
|
||||
>
|
||||
{getConnectingStepContent(
|
||||
currentStep,
|
||||
connectionIconType,
|
||||
selectingSharingIconType,
|
||||
isShownSelectingSharingIcon,
|
||||
)}
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConnectingIndicator;
|
6
app/client/src/components/MyDeviceInfoCard/DeviceDetails.d.ts
vendored
Normal file
6
app/client/src/components/MyDeviceInfoCard/DeviceDetails.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
interface DeviceDetails {
|
||||
myIP: string,
|
||||
myOS: string,
|
||||
myDeviceType: string,
|
||||
myBrowser: string,
|
||||
}
|
48
app/client/src/components/MyDeviceInfoCard/index.tsx
Normal file
48
app/client/src/components/MyDeviceInfoCard/index.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Callout, Card, H3, Text, Tooltip, Position } from '@blueprintjs/core';
|
||||
import { AppContext } from '../../providers/AppContextProvider';
|
||||
|
||||
const LIGHT_UI_BACKGROUND = 'rgba(240, 248, 250, 1)';
|
||||
const DARK_UI_BACKGROUND = '#293742';
|
||||
|
||||
interface MyDeviceDetailsCardProps {
|
||||
deviceDetails: DeviceDetails;
|
||||
}
|
||||
|
||||
function MyDeviceInfoCard(props: MyDeviceDetailsCardProps) {
|
||||
const { isDarkTheme } = useContext(AppContext);
|
||||
|
||||
const { deviceDetails } = props;
|
||||
const { myIP, myOS, myDeviceType, myBrowser } = deviceDetails;
|
||||
|
||||
return (
|
||||
<Card
|
||||
elevation={3}
|
||||
style={{
|
||||
backgroundColor: isDarkTheme ? DARK_UI_BACKGROUND : LIGHT_UI_BACKGROUND,
|
||||
marginBottom: '30px',
|
||||
}}
|
||||
>
|
||||
<H3>My Device Info:</H3>
|
||||
<Callout>
|
||||
<Text>Device Type: {myDeviceType}</Text>
|
||||
<Tooltip
|
||||
content="This should match with 'Device IP' in alert displayed on your computer, where Deskreen is running."
|
||||
position={Position.TOP}
|
||||
>
|
||||
<div style={{ fontWeight: 900, backgroundColor: '#00f99273' }}>
|
||||
<Text>Device IP: {myIP}</Text>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Text>Device Browser: {myBrowser}</Text>
|
||||
<Text>Device OS: {myOS}</Text>
|
||||
</Callout>
|
||||
<Text className="bp3-text-muted">
|
||||
These details should match with the ones that you see in alert on
|
||||
sharing device.
|
||||
</Text>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyDeviceInfoCard;
|
27
app/client/src/components/ToggleDarkModeSwitch/index.tsx
Normal file
27
app/client/src/components/ToggleDarkModeSwitch/index.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Icon, Text, Switch, Classes, Alignment } from '@blueprintjs/core';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { AppContext } from '../../providers/AppContextProvider';
|
||||
|
||||
function ToggleDarkModeSwitch() {
|
||||
const { isDarkTheme, setIsDarkThemeHook } = useContext(AppContext)
|
||||
|
||||
|
||||
return (
|
||||
<Switch
|
||||
onChange={() => {
|
||||
document.body.classList.toggle(Classes.DARK);
|
||||
setIsDarkThemeHook(!isDarkTheme)
|
||||
}}
|
||||
innerLabel={isDarkTheme ? 'ON' : 'OFF'}
|
||||
inline
|
||||
large
|
||||
label={`Dark Theme is`}
|
||||
alignIndicator={Alignment.RIGHT}
|
||||
checked={isDarkTheme}
|
||||
style={{ marginTop: '25px', marginLeft: '15px' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ToggleDarkModeSwitch;
|
2
app/client/src/constants/styleConstants.ts
Normal file
2
app/client/src/constants/styleConstants.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const LIGHT_UI_BACKGROUND = 'rgba(240, 248, 250, 1)';
|
||||
export const DARK_UI_BACKGROUND = '#293742';
|
327
app/client/src/features/PeerConnection/index.ts
Normal file
327
app/client/src/features/PeerConnection/index.ts
Normal file
@ -0,0 +1,327 @@
|
||||
import shortId from 'shortid';
|
||||
// import pixelmatch from 'pixelmatch';
|
||||
import SimplePeer from 'simple-peer';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { connect as connectSocket } from '../../utils/socket';
|
||||
import {
|
||||
prepare as prepareMessage,
|
||||
process as processMessage,
|
||||
} from '../../utils/message';
|
||||
import setSdpMediaBitrate from './setSdpMediaBitrate';
|
||||
import Crypto from '../../utils/crypto';
|
||||
import VideoAutoQualityOptimizer from '../VideoAutoQualityOptimizer';
|
||||
import { getBrowserFromUAParser, getDeviceTypeFromUAParser, getOSFromUAParser } from '../../utils/userAgentParserHelpers';
|
||||
|
||||
interface LocalPeerUser {
|
||||
username: string;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
}
|
||||
|
||||
interface PartnerPeerUser {
|
||||
username: string;
|
||||
publicKey: string;
|
||||
}
|
||||
|
||||
const nullUser = { username: '', publicKey: '', privateKey: '' };
|
||||
|
||||
interface SendEncryptedMessagePayload {
|
||||
type: string;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ReceiveEncryptedMessagePayload {
|
||||
payload: string;
|
||||
signature: string;
|
||||
iv: string;
|
||||
keys: { sessionKey: string; signingKey: string }[];
|
||||
}
|
||||
|
||||
export default class LocalTestPeer {
|
||||
roomId: string;
|
||||
|
||||
socket: any;
|
||||
|
||||
crypto: Crypto;
|
||||
|
||||
user: LocalPeerUser = nullUser;
|
||||
|
||||
partner: PartnerPeerUser = nullUser;
|
||||
|
||||
peer: any;
|
||||
|
||||
myIP = '';
|
||||
|
||||
myOS: any;
|
||||
|
||||
myDeviceType: any;
|
||||
|
||||
myBrowser: any;
|
||||
|
||||
mousePos: any;
|
||||
|
||||
setUrlCallback: any;
|
||||
|
||||
private uaParser: UAParser;
|
||||
|
||||
canvas: any;
|
||||
|
||||
video: any;
|
||||
|
||||
prevFrame: any;
|
||||
|
||||
largeMismatchFramesCount: number;
|
||||
|
||||
isRequestedHalfQuality: boolean;
|
||||
|
||||
videoAutoQualityOptimizer: VideoAutoQualityOptimizer;
|
||||
|
||||
setMyDeviceDetails: (details: DeviceDetails) => void;
|
||||
|
||||
hostAllowedToConnectCallback: () => void;
|
||||
|
||||
constructor(
|
||||
setUrlCallback: any,
|
||||
crypto: Crypto,
|
||||
videoAutoQualityOptimizer: VideoAutoQualityOptimizer,
|
||||
setMyDeviceDetailsCallback: (details: DeviceDetails) => void,
|
||||
hostAllowedToConnectCallback: () => void
|
||||
) {
|
||||
this.setUrlCallback = setUrlCallback;
|
||||
this.crypto = crypto;
|
||||
this.videoAutoQualityOptimizer = videoAutoQualityOptimizer;
|
||||
this.setMyDeviceDetails = setMyDeviceDetailsCallback;
|
||||
this.hostAllowedToConnectCallback = hostAllowedToConnectCallback;
|
||||
this.roomId = encodeURI(window.location.pathname.replace('/', ''));
|
||||
this.socket = connectSocket(this.roomId);
|
||||
this.uaParser = new UAParser();
|
||||
this.createUserAndInitSocket();
|
||||
this.createPeer();
|
||||
|
||||
this.video = null;
|
||||
this.canvas = null;
|
||||
this.largeMismatchFramesCount = 0;
|
||||
this.isRequestedHalfQuality = false;
|
||||
}
|
||||
|
||||
log(...toLog: any[]) {
|
||||
console.log('LocalTestPeer - ', ...toLog);
|
||||
}
|
||||
|
||||
createPeer() {
|
||||
const peer = new SimplePeer({
|
||||
initiator: false,
|
||||
// trickle: true,
|
||||
// stream: null,
|
||||
// allowHalfTrickle: false,
|
||||
config: { iceServers: [] },
|
||||
sdpTransform: (sdp) => {
|
||||
let newSDP = sdp;
|
||||
newSDP = (setSdpMediaBitrate(
|
||||
(newSDP as unknown) as string,
|
||||
'video',
|
||||
500000
|
||||
) as unknown) as typeof sdp;
|
||||
return newSDP;
|
||||
},
|
||||
});
|
||||
|
||||
peer.on('stream', (stream) => {
|
||||
setTimeout(() => {
|
||||
(document.querySelector(
|
||||
'#video-local-test-peer-sees'
|
||||
) as any).srcObject = stream;
|
||||
}, 1000);
|
||||
|
||||
this.videoAutoQualityOptimizer.setGoodQualityCallback(() => {
|
||||
this.peer.send('set good quality');
|
||||
});
|
||||
|
||||
this.videoAutoQualityOptimizer.setHalfQualityCallbak(() => {
|
||||
this.peer.send('set half quality');
|
||||
});
|
||||
|
||||
this.videoAutoQualityOptimizer.startOptimizationLoop();
|
||||
|
||||
this.setUrlCallback(stream);
|
||||
});
|
||||
|
||||
peer.on('signal', (data) => {
|
||||
// fired when webrtc done preparation to start call on this machine
|
||||
this.sendEncryptedMessage({
|
||||
type: 'CALL_ACCEPTED',
|
||||
payload: {
|
||||
signalData: data,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
initApp(user: LocalPeerUser, myIP: string) {
|
||||
if (!this.socket) return;
|
||||
this.socket.emit('USER_ENTER', {
|
||||
username: user.username,
|
||||
publicKey: user.publicKey,
|
||||
ip: myIP,
|
||||
});
|
||||
}
|
||||
|
||||
createUser() {
|
||||
return new Promise<LocalPeerUser>(async (resolve) => {
|
||||
const username = shortId.generate();
|
||||
|
||||
const encryptDecryptKeys = await this.crypto.createEncryptDecryptKeys();
|
||||
const exportedEncryptDecryptPrivateKey = await this.crypto.exportKey(
|
||||
encryptDecryptKeys.privateKey
|
||||
);
|
||||
const exportedEncryptDecryptPublicKey = await this.crypto.exportKey(
|
||||
encryptDecryptKeys.publicKey
|
||||
);
|
||||
|
||||
resolve({
|
||||
username,
|
||||
privateKey: exportedEncryptDecryptPrivateKey,
|
||||
publicKey: exportedEncryptDecryptPublicKey,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async sendEncryptedMessage(payload: SendEncryptedMessagePayload) {
|
||||
if (!this.socket) return;
|
||||
if (!this.user) return;
|
||||
if (!this.partner) return;
|
||||
const msg = (await prepareMessage(payload, this.user, this.partner)) as any;
|
||||
this.log('encrypted message', msg);
|
||||
this.socket.emit('ENCRYPTED_MESSAGE', msg.toSend);
|
||||
}
|
||||
|
||||
async receiveEncryptedMessage(payload: ReceiveEncryptedMessagePayload) {
|
||||
if (!this.user) return;
|
||||
const message = (await processMessage(
|
||||
payload,
|
||||
this.user.privateKey
|
||||
)) as any;
|
||||
if (message.type === 'CALL_USER') {
|
||||
this.log('ACCEPTING CALL USER', message);
|
||||
this.peer.signal(message.payload.signalData);
|
||||
}
|
||||
if (message.type === 'DENY_TO_CONNECT') {
|
||||
this.log('OH NO, deny to connect...');
|
||||
}
|
||||
if (message.type === 'DISCONNECT_BY_HOST_MACHINE_USER') {
|
||||
this.log('DAMN, you were disconnected by host machine user!');
|
||||
}
|
||||
if (message.type === 'ALLOWED_TO_CONNECT') {
|
||||
this.hostAllowedToConnectCallback();
|
||||
}
|
||||
}
|
||||
|
||||
createUserAndInitSocket() {
|
||||
if (!this.socket) return;
|
||||
|
||||
this.socket.removeAllListeners();
|
||||
|
||||
const userCreatedCallback = (createdUser: LocalPeerUser) => {
|
||||
this.user = createdUser;
|
||||
|
||||
this.socket.on('disconnect', () => {
|
||||
// this.props.toggleSocketConnected(false);
|
||||
});
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
this.socket.emit('GET_MY_IP', (ip: string) => {
|
||||
// TODO: use set ip callback here, that will change the UI of react component
|
||||
// @ts-ignore
|
||||
// document.querySelector('#my-ip')?.innerHTML = ip;
|
||||
this.myIP = ip;
|
||||
this.uaParser.setUA(window.navigator.userAgent);
|
||||
// const osFromUAParser = this.uaParser.getResult().os;
|
||||
// const deviceTypeFromUAParser = this.uaParser.getResult().device;
|
||||
// const browserFromUAParser = this.uaParser.getResult().browser;
|
||||
|
||||
// this.myOS = `${osFromUAParser.name ? osFromUAParser.name : ''} ${
|
||||
// osFromUAParser.version ? osFromUAParser.version : ''
|
||||
// }`;
|
||||
// this.myDeviceType = deviceTypeFromUAParser.type
|
||||
// ? deviceTypeFromUAParser.type.toString()
|
||||
// : 'computer';
|
||||
this.myOS = getOSFromUAParser(this.uaParser);
|
||||
this.myDeviceType = getDeviceTypeFromUAParser(this.uaParser);
|
||||
// this.myBrowser = `${browserFromUAParser.name ? browserFromUAParser.name : ''} ${
|
||||
// browserFromUAParser.version ? browserFromUAParser.version : ''
|
||||
// }`;
|
||||
this.myBrowser = getBrowserFromUAParser(this.uaParser);
|
||||
|
||||
this.initApp(createdUser, ip);
|
||||
});
|
||||
});
|
||||
|
||||
this.socket.on('USER_ENTER', (payload: { users: PartnerPeerUser[] }) => {
|
||||
const filteredPartner = payload.users.filter((v) => {
|
||||
return createdUser.publicKey !== v.publicKey;
|
||||
});
|
||||
|
||||
this.partner = filteredPartner[0];
|
||||
|
||||
this.sendEncryptedMessage({
|
||||
type: 'ADD_USER',
|
||||
payload: {
|
||||
username: createdUser.username,
|
||||
publicKey: createdUser.publicKey,
|
||||
isOwner: true,
|
||||
id: createdUser.username,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: send device details as strings here!
|
||||
this.sendEncryptedMessage({
|
||||
type: 'DEVICE_DETAILS',
|
||||
payload: {
|
||||
socketID: this.socket.io.engine.id,
|
||||
os: this.myOS,
|
||||
deviceType: this.myDeviceType,
|
||||
browser: this.myBrowser,
|
||||
deviceScreenWidth: window.screen.width,
|
||||
deviceScreenHeight: window.screen.height,
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setMyDeviceDetails({
|
||||
myIP: this.myIP,
|
||||
myOS: this.myOS,
|
||||
myBrowser: this.myBrowser,
|
||||
myDeviceType: this.myDeviceType,
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
|
||||
this.socket.on('USER_EXIT', (payload: any) => {
|
||||
// this.props.receiveUnencryptedMessage('USER_EXIT', payload);
|
||||
});
|
||||
|
||||
this.socket.on(
|
||||
'ENCRYPTED_MESSAGE',
|
||||
(payload: ReceiveEncryptedMessagePayload) => {
|
||||
this.receiveEncryptedMessage(payload);
|
||||
}
|
||||
);
|
||||
|
||||
this.socket.on('ROOM_LOCKED', (payload: any) => {
|
||||
// TODO: call ROOM LOCKED callback to change react component contain ROOM LOCKED message
|
||||
// @ts-ignore
|
||||
// document.querySelector('#my-ip')?.innerHTML = 'ROOM LOCKED';
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', (_) => {
|
||||
this.socket.emit('USER_DISCONNECT');
|
||||
});
|
||||
};
|
||||
|
||||
this.createUser().then((newUser: LocalPeerUser) =>
|
||||
userCreatedCallback(newUser)
|
||||
);
|
||||
}
|
||||
}
|
33
app/client/src/features/PeerConnection/setSdpMediaBitrate.ts
Normal file
33
app/client/src/features/PeerConnection/setSdpMediaBitrate.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export default (sdp: string, mediaType: string, bitrate: number) => {
|
||||
const sdpLines = sdp.split('\n');
|
||||
let mediaLineIndex = -1;
|
||||
const mediaLine = `m=${mediaType}`;
|
||||
let bitrateLineIndex = -1;
|
||||
const bitrateLine = `b=AS:${bitrate}`;
|
||||
mediaLineIndex = sdpLines.findIndex((line) => line.startsWith(mediaLine));
|
||||
|
||||
// If we find a line matching “m={mediaType}”
|
||||
if (mediaLineIndex && mediaLineIndex < sdpLines.length) {
|
||||
// Skip the media line
|
||||
bitrateLineIndex = mediaLineIndex + 1;
|
||||
|
||||
// Skip both i=* and c=* lines (bandwidths limiters have to come afterwards)
|
||||
while (
|
||||
sdpLines[bitrateLineIndex].startsWith('i=') ||
|
||||
sdpLines[bitrateLineIndex].startsWith('c=')
|
||||
) {
|
||||
bitrateLineIndex += 1;
|
||||
}
|
||||
|
||||
if (sdpLines[bitrateLineIndex].startsWith('b=')) {
|
||||
// If the next line is a b=* line, replace it with our new bandwidth
|
||||
sdpLines[bitrateLineIndex] = bitrateLine;
|
||||
} else {
|
||||
// Otherwise insert a new bitrate line.
|
||||
sdpLines.splice(bitrateLineIndex, 0, bitrateLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Then return the updated sdp content as a string
|
||||
return sdpLines.join('\n');
|
||||
};
|
93
app/client/src/features/VideoAutoQualityOptimizer/index.ts
Normal file
93
app/client/src/features/VideoAutoQualityOptimizer/index.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import pixelmatch from 'pixelmatch';
|
||||
|
||||
export default class VideoAutoQualityOptimizer {
|
||||
video: any;
|
||||
|
||||
canvas: any;
|
||||
|
||||
prevFrame: any;
|
||||
|
||||
largeMismatchFramesCount = 0;
|
||||
|
||||
isRequestedHalfQuality = false;
|
||||
|
||||
goodQualityCallback = () => {};
|
||||
|
||||
halfQualityCallbak = () => {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
setGoodQualityCallback(callback: () => void) {
|
||||
this.goodQualityCallback = callback;
|
||||
}
|
||||
|
||||
setHalfQualityCallbak(callback: () => void) {
|
||||
this.halfQualityCallbak = callback;
|
||||
}
|
||||
|
||||
startOptimizationLoop() {
|
||||
setInterval(() => {
|
||||
this.frameComparisonQualityOptimization();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
frameComparisonQualityOptimization() {
|
||||
if (this.video && this.canvas) {
|
||||
if (this.video.videoWidth === 0 || this.video.videoHeight === 0) return;
|
||||
// scale the canvas accordingly
|
||||
this.canvas
|
||||
.getContext('2d')
|
||||
.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.canvas.width = this.video.videoWidth / 8;
|
||||
this.canvas.height = this.video.videoHeight / 8;
|
||||
// draw the video at that frame
|
||||
this.canvas
|
||||
.getContext('2d')
|
||||
.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
||||
// convert it to a usable data URL
|
||||
let imageData = this.canvas
|
||||
.getContext('2d')
|
||||
.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
if (this.prevFrame) {
|
||||
try {
|
||||
const numMismatchedPixels = pixelmatch(
|
||||
this.prevFrame.data,
|
||||
imageData.data,
|
||||
null,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
const mismatchInPercent =
|
||||
numMismatchedPixels / (this.canvas.width * this.canvas.height);
|
||||
if (mismatchInPercent < 0.1 && this.largeMismatchFramesCount > 0) {
|
||||
this.largeMismatchFramesCount -= 1;
|
||||
} else if (mismatchInPercent < 0.1 && this.isRequestedHalfQuality) {
|
||||
this.largeMismatchFramesCount = 0;
|
||||
this.isRequestedHalfQuality = false;
|
||||
// this.peer.send('set good quality');
|
||||
this.goodQualityCallback();
|
||||
} else if (mismatchInPercent >= 0.1 && !this.isRequestedHalfQuality) {
|
||||
if (this.largeMismatchFramesCount < 3) {
|
||||
this.largeMismatchFramesCount += 1;
|
||||
} else {
|
||||
// this.peer.send('set half quality');
|
||||
this.halfQualityCallbak();
|
||||
this.isRequestedHalfQuality = true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
this.prevFrame = imageData;
|
||||
imageData = null;
|
||||
} else {
|
||||
this.video = document.querySelector(
|
||||
'#video-local-test-peer-sees > video'
|
||||
);
|
||||
this.canvas = document.getElementById('comparison-canvas');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
@import "~normalize.css";
|
||||
@import "~@blueprintjs/core/lib/css/blueprint.css";
|
||||
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
|
@ -3,10 +3,13 @@ import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import { AppContextProvider } from './providers/AppContextProvider';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<AppContextProvider>
|
||||
<App />
|
||||
</AppContextProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
52
app/client/src/providers/AppContextProvider/index.tsx
Normal file
52
app/client/src/providers/AppContextProvider/index.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
// export const LIGHT_UI_BACKGROUND = 'rgba(240, 248, 250, 1)';
|
||||
|
||||
interface AppContextInterface {
|
||||
isDarkTheme: boolean;
|
||||
setIsDarkThemeHook: (val: boolean) => void;
|
||||
}
|
||||
|
||||
const defaultAppContextValue = {
|
||||
isDarkTheme: false,
|
||||
setIsDarkThemeHook: () => {},
|
||||
};
|
||||
|
||||
export const AppContext = React.createContext<AppContextInterface>(
|
||||
defaultAppContextValue
|
||||
);
|
||||
|
||||
export const AppContextProvider: React.FC = ({ children }) => {
|
||||
const [isDarkTheme, setIsDarkTheme] = useState(false);
|
||||
|
||||
const getThemeFromHost = () => {
|
||||
// const gotIsDarkThemeFromSettings = settings.hasSync('appIsDarkTheme')
|
||||
// ? settings.getSync('appIsDarkTheme') === 'true'
|
||||
// : false;
|
||||
|
||||
// if (gotIsDarkThemeFromSettings) {
|
||||
// document.body.classList.toggle(Classes.DARK);
|
||||
// document.body.style.backgroundColor = LIGHT_UI_BACKGROUND;
|
||||
// }
|
||||
|
||||
// setIsDarkTheme(gotIsDarkThemeFromSettings);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getThemeFromHost();
|
||||
}, []);
|
||||
|
||||
const setIsDarkThemeHook = (val: boolean) => {
|
||||
// settings.setSync('appIsDarkTheme', `${val}`);
|
||||
setIsDarkTheme(val);
|
||||
};
|
||||
|
||||
const value = { isDarkTheme, setIsDarkThemeHook };
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={value}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
};
|
22
app/client/src/utils/ProcessedMessage.d.ts
vendored
Normal file
22
app/client/src/utils/ProcessedMessage.d.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
type CallAcceptedMessageWithPayload = {
|
||||
type: 'CALL_ACCEPTED';
|
||||
payload: {
|
||||
signalData: string;
|
||||
};
|
||||
};
|
||||
|
||||
type DeviceDetailsMessageWithPayload = {
|
||||
type: 'DEVICE_DETAILS';
|
||||
payload: {
|
||||
socketID: string;
|
||||
deviceType: { type: string };
|
||||
os: { name: string; version: string };
|
||||
browser: { name: string; version: string; major: string };
|
||||
deviceScreenWidth: number;
|
||||
deviceScreenHeight: number;
|
||||
};
|
||||
};
|
||||
|
||||
type ProcessedMessage =
|
||||
| CallAcceptedMessageWithPayload
|
||||
| DeviceDetailsMessageWithPayload;
|
127
app/client/src/utils/crypto.ts
Normal file
127
app/client/src/utils/crypto.ts
Normal file
@ -0,0 +1,127 @@
|
||||
/* eslint-disable class-methods-use-this */
|
||||
import forge from 'node-forge';
|
||||
|
||||
export default class Crypto {
|
||||
convertStringToArrayBufferView(str: string) {
|
||||
const bytes = new Uint8Array(str.length);
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
bytes[i] = str.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
createEncryptDecryptKeys() {
|
||||
return new Promise<forge.pki.rsa.KeyPair>((resolve) => {
|
||||
const keypair = forge.pki.rsa.generateKeyPair({
|
||||
bits: 2048,
|
||||
e: 0x10001,
|
||||
workers: -1,
|
||||
});
|
||||
resolve(keypair);
|
||||
});
|
||||
}
|
||||
|
||||
encryptMessage(data: string, secretKey: string, iv: string) {
|
||||
return new Promise<string>((resolve) => {
|
||||
const input = forge.util.createBuffer(data, 'utf8');
|
||||
const cipherAES = forge.cipher.createCipher('AES-CBC', secretKey);
|
||||
cipherAES.start({ iv });
|
||||
cipherAES.update(input);
|
||||
cipherAES.finish();
|
||||
const cyphertext = cipherAES.output.getBytes();
|
||||
resolve(cyphertext);
|
||||
});
|
||||
}
|
||||
|
||||
decryptMessage(data: string, secretKey: string, iv: string) {
|
||||
return new Promise<string>((resolve) => {
|
||||
const input = forge.util.createBuffer(data);
|
||||
const decipher = forge.cipher.createDecipher('AES-CBC', secretKey);
|
||||
decipher.start({ iv });
|
||||
decipher.update(input); // input should be a strng here
|
||||
decipher.finish();
|
||||
const decryptedPayload = decipher.output.toString();
|
||||
resolve(decryptedPayload);
|
||||
});
|
||||
}
|
||||
|
||||
importEncryptDecryptKey(keyPemString: string) {
|
||||
return new Promise<forge.pki.rsa.PrivateKey | forge.pki.rsa.PublicKey>(
|
||||
(resolve) => {
|
||||
// keyPemString = this.nodeAtob(keyPemString);
|
||||
if (this.isPublicKeyString(keyPemString)) {
|
||||
const publicKeyPem = forge.pki.publicKeyFromPem(keyPemString);
|
||||
resolve(publicKeyPem);
|
||||
} else {
|
||||
const privateKeyPem = forge.pki.privateKeyFromPem(keyPemString);
|
||||
resolve(privateKeyPem);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
exportKey(key: forge.pki.rsa.PrivateKey | forge.pki.rsa.PublicKey) {
|
||||
return new Promise<string>((resolve) => {
|
||||
if (this.isPublicKeyObject(key)) {
|
||||
const publicKeyPem = forge.pki
|
||||
.publicKeyToPem(key as forge.pki.rsa.PublicKey)
|
||||
.toString();
|
||||
resolve(publicKeyPem);
|
||||
} else {
|
||||
const privateKeyPem = forge.pki
|
||||
.privateKeyToPem(key as forge.pki.rsa.PrivateKey)
|
||||
.toString();
|
||||
resolve(privateKeyPem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
signMessage(data: string, keyToSignWith: string) {
|
||||
return new Promise<string>((resolve) => {
|
||||
const hmac = forge.hmac.create();
|
||||
const input = forge.util.createBuffer(data, 'utf8');
|
||||
hmac.start('sha256', keyToSignWith);
|
||||
hmac.update(input);
|
||||
const signatureString = hmac.digest().getBytes();
|
||||
resolve(signatureString);
|
||||
});
|
||||
}
|
||||
|
||||
verifyPayload(signature: string, data: string, secretKey: string) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const hmac = forge.hmac.create();
|
||||
const input = forge.util.createBuffer(data, 'utf8');
|
||||
hmac.start('sha256', secretKey);
|
||||
hmac.update(input);
|
||||
const recreatedSignature = hmac.digest().getBytes();
|
||||
const verified = recreatedSignature === signature;
|
||||
resolve(verified);
|
||||
});
|
||||
}
|
||||
|
||||
wrapKey(keyToWrap: string, publicKeyToWrapWith: forge.pki.rsa.PublicKey) {
|
||||
return this.nodeBtoa(publicKeyToWrapWith.encrypt(keyToWrap, 'RSA-OAEP'));
|
||||
}
|
||||
|
||||
unwrapKey(privateKey: forge.pki.rsa.PrivateKey, encryptedAESKey: string) {
|
||||
return privateKey.decrypt(this.nodeAtob(encryptedAESKey), 'RSA-OAEP');
|
||||
}
|
||||
|
||||
private isPublicKeyString(key: string) {
|
||||
return key.includes('PUBLIC');
|
||||
}
|
||||
|
||||
private isPublicKeyObject(
|
||||
key: forge.pki.rsa.PublicKey | forge.pki.rsa.PrivateKey
|
||||
) {
|
||||
return (key as forge.pki.rsa.PublicKey).encrypt !== undefined;
|
||||
}
|
||||
|
||||
private nodeBtoa(str: string): string {
|
||||
return Buffer.from(str).toString('base64');
|
||||
}
|
||||
|
||||
private nodeAtob(str: string): string {
|
||||
return Buffer.from(str, 'base64').toString();
|
||||
}
|
||||
}
|
130
app/client/src/utils/message.ts
Normal file
130
app/client/src/utils/message.ts
Normal file
@ -0,0 +1,130 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
import forge from 'node-forge';
|
||||
import Crypto from './crypto';
|
||||
|
||||
const crypto = new Crypto();
|
||||
|
||||
interface EncryptedPayloadToSend {
|
||||
payload: string;
|
||||
signature: string;
|
||||
iv: string;
|
||||
keys: EncryptedKeys[];
|
||||
}
|
||||
|
||||
interface EncryptedKeys {
|
||||
sessionKey: string;
|
||||
signingKey: string;
|
||||
}
|
||||
|
||||
interface ProcessedPayload {
|
||||
toSend: EncryptedPayloadToSend;
|
||||
original: any;
|
||||
}
|
||||
|
||||
export const process = (payload: any, privateKeyString: string) =>
|
||||
new Promise<ProcessedMessage>(async (resolve) => {
|
||||
const privateKey = (await crypto.importEncryptDecryptKey(
|
||||
privateKeyString
|
||||
)) as forge.pki.rsa.PrivateKey;
|
||||
const { signature } = payload;
|
||||
const { iv } = payload;
|
||||
const payloadBuffer = payload.payload;
|
||||
|
||||
let sessionAESKeyUnencrypted = '';
|
||||
let signingHMACKey = '';
|
||||
|
||||
await new Promise((resolvePayload) => {
|
||||
payload.keys.forEach(async (key: any) => {
|
||||
try {
|
||||
sessionAESKeyUnencrypted = crypto.unwrapKey(
|
||||
privateKey,
|
||||
key.sessionKey
|
||||
);
|
||||
signingHMACKey = crypto.unwrapKey(privateKey, key.signingKey);
|
||||
resolvePayload();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const verified = await crypto.verifyPayload(
|
||||
signature,
|
||||
payloadBuffer,
|
||||
signingHMACKey
|
||||
);
|
||||
|
||||
if (!verified) {
|
||||
throw new Error(
|
||||
"recreated signature doesn't match with payload.signature"
|
||||
);
|
||||
}
|
||||
|
||||
const decryptedPayload = await crypto.decryptMessage(
|
||||
payloadBuffer,
|
||||
sessionAESKeyUnencrypted,
|
||||
iv
|
||||
);
|
||||
|
||||
const payloadJson = JSON.parse(decryptedPayload) as ProcessedMessage;
|
||||
resolve(payloadJson);
|
||||
});
|
||||
|
||||
export const prepare = (payload: any, user: any, partner: any) =>
|
||||
new Promise<ProcessedPayload>(async (resolve) => {
|
||||
const myUsername = user.username;
|
||||
const myId = user.id;
|
||||
const members = [partner];
|
||||
const jsonToSend = {
|
||||
...payload,
|
||||
payload: {
|
||||
...payload.payload,
|
||||
sender: myId,
|
||||
username: myUsername,
|
||||
text: encodeURI(payload.payload.text),
|
||||
},
|
||||
};
|
||||
const payloadBuffer = JSON.stringify(jsonToSend);
|
||||
|
||||
const secretKeyRandomAES = forge.random.getBytesSync(16);
|
||||
const iv = forge.random.getBytesSync(16);
|
||||
const encryptedPayloadString = await crypto.encryptMessage(
|
||||
payloadBuffer,
|
||||
secretKeyRandomAES,
|
||||
iv
|
||||
);
|
||||
|
||||
const secretKeyRandomHMAC = forge.random.getBytesSync(32);
|
||||
const signatureString = await crypto.signMessage(
|
||||
encryptedPayloadString,
|
||||
secretKeyRandomHMAC
|
||||
);
|
||||
|
||||
const encryptedKeys = await Promise.all(
|
||||
members.map(async (member) => {
|
||||
const memberPublicKey = (await crypto.importEncryptDecryptKey(
|
||||
member.publicKey
|
||||
)) as forge.pki.rsa.PublicKey;
|
||||
const enc = await Promise.all([
|
||||
crypto.wrapKey(secretKeyRandomAES, memberPublicKey),
|
||||
crypto.wrapKey(secretKeyRandomHMAC, memberPublicKey),
|
||||
]);
|
||||
|
||||
return {
|
||||
sessionKey: enc[0],
|
||||
signingKey: enc[1],
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
resolve({
|
||||
toSend: {
|
||||
payload: encryptedPayloadString,
|
||||
signature: signatureString,
|
||||
iv,
|
||||
keys: encryptedKeys,
|
||||
},
|
||||
original: jsonToSend,
|
||||
});
|
||||
});
|
16
app/client/src/utils/socket.ts
Normal file
16
app/client/src/utils/socket.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import socketIO from 'socket.io-client';
|
||||
import generateUrl from '../api/generator';
|
||||
|
||||
let socket: SocketIOClient.Socket;
|
||||
|
||||
export const connect = (roomId: string) => {
|
||||
socket = socketIO(generateUrl(), {
|
||||
query: {
|
||||
roomId,
|
||||
},
|
||||
forceNew: true,
|
||||
});
|
||||
return socket;
|
||||
};
|
||||
|
||||
export const getSocket = () => socket;
|
25
app/client/src/utils/userAgentParserHelpers.ts
Normal file
25
app/client/src/utils/userAgentParserHelpers.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
|
||||
export function getOSFromUAParser(uaParser: UAParser) {
|
||||
const osFromUAParser = uaParser.getResult().os;
|
||||
|
||||
return `${osFromUAParser.name ? osFromUAParser.name : ''} ${
|
||||
osFromUAParser.version ? osFromUAParser.version : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
export function getDeviceTypeFromUAParser(uaParser: UAParser) {
|
||||
const deviceTypeFromUAParser = uaParser.getResult().device;
|
||||
|
||||
return deviceTypeFromUAParser.type
|
||||
? deviceTypeFromUAParser.type.toString()
|
||||
: 'computer'
|
||||
}
|
||||
|
||||
export function getBrowserFromUAParser(uaParser: UAParser) {
|
||||
const browserFromUAParser = uaParser.getResult().browser;
|
||||
|
||||
return `${browserFromUAParser.name ? browserFromUAParser.name : ''} ${
|
||||
browserFromUAParser.version ? browserFromUAParser.version : ''
|
||||
}`;
|
||||
}
|
@ -10,14 +10,14 @@
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve"
|
||||
"jsx": "react",
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
@ -188,6 +188,13 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.11.0"
|
||||
|
||||
"@babel/helper-module-imports@^7.0.0":
|
||||
version "7.12.5"
|
||||
resolved "https://packages.deskreen.com/@babel%2fhelper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb"
|
||||
integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==
|
||||
dependencies:
|
||||
"@babel/types" "^7.12.5"
|
||||
|
||||
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.8.3":
|
||||
version "7.10.4"
|
||||
resolved "https://packages.deskreen.com/@babel%2fhelper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
|
||||
@ -1115,6 +1122,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.5.5":
|
||||
version "7.12.5"
|
||||
resolved "https://packages.deskreen.com/@babel%2fruntime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
||||
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.10.4", "@babel/template@^7.4.0", "@babel/template@^7.8.6":
|
||||
version "7.10.4"
|
||||
resolved "https://packages.deskreen.com/@babel%2ftemplate/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
|
||||
@ -1148,6 +1162,40 @@
|
||||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.12.5":
|
||||
version "7.12.6"
|
||||
resolved "https://packages.deskreen.com/@babel%2ftypes/-/types-7.12.6.tgz#ae0e55ef1cce1fbc881cd26f8234eb3e657edc96"
|
||||
integrity sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.10.4"
|
||||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@blueprintjs/core@^3.35.0":
|
||||
version "3.35.0"
|
||||
resolved "https://packages.deskreen.com/@blueprintjs%2fcore/-/core-3.35.0.tgz#ed48ad7e6692f7dc32e28200a7984e029102ce3f"
|
||||
integrity sha512-2coEMDX1JJuHvDCt6wZSB6zntDlKvUmi4rqjLeGR+ZOo4TtFB92GSjycMtupka1PURM1A66oQZvnMiBIjuMW6Q==
|
||||
dependencies:
|
||||
"@blueprintjs/icons" "^3.22.0"
|
||||
"@types/dom4" "^2.0.1"
|
||||
classnames "^2.2"
|
||||
dom4 "^2.1.5"
|
||||
normalize.css "^8.0.1"
|
||||
popper.js "^1.16.1"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
react-popper "^1.3.7"
|
||||
react-transition-group "^2.9.0"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
tslib "~1.13.0"
|
||||
|
||||
"@blueprintjs/icons@^3.22.0":
|
||||
version "3.22.0"
|
||||
resolved "https://packages.deskreen.com/@blueprintjs%2ficons/-/icons-3.22.0.tgz#6a7c177e9aa96f0ed10bc93d88f7c6687db336ad"
|
||||
integrity sha512-clfdwRQlzqs2sDxjwQr4p10Z3bGNTnqpsLgN+4TN1ECf7plEEukhvQh6YK/Lfd5xDhEBEEZ/YQCawZbyAYjfXg==
|
||||
dependencies:
|
||||
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"
|
||||
@ -1166,6 +1214,83 @@
|
||||
resolved "https://packages.deskreen.com/@csstools%2fnormalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
|
||||
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
|
||||
|
||||
"@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.15":
|
||||
version "10.1.1"
|
||||
resolved "https://packages.deskreen.com/@emotion%2fcore/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3"
|
||||
integrity sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==
|
||||
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":
|
||||
version "0.8.0"
|
||||
resolved "https://packages.deskreen.com/@emotion%2fhash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
|
||||
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
|
||||
|
||||
"@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/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==
|
||||
|
||||
"@hapi/address@2.x.x":
|
||||
version "2.1.4"
|
||||
resolved "https://packages.deskreen.com/@hapi%2faddress/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
||||
@ -1584,6 +1709,11 @@
|
||||
resolved "https://packages.deskreen.com/@types%2fcolor-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
"@types/dom4@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://packages.deskreen.com/@types%2fdom4/-/dom4-2.0.1.tgz#506d5781b9bcab81bd9a878b198aec7dee2a6033"
|
||||
integrity sha512-kSkVAvWmMZiCYtvqjqQEwOmvKwcH+V4uiv3qPQ8pAh1Xl39xggGEo8gHUqV4waYGHezdFw0rKBR8Jt0CrQSDZA==
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://packages.deskreen.com/@types%2feslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
@ -1641,6 +1771,13 @@
|
||||
resolved "https://packages.deskreen.com/@types%2fminimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||
|
||||
"@types/node-forge@^0.9.5":
|
||||
version "0.9.5"
|
||||
resolved "https://packages.deskreen.com/@types%2fnode-forge/-/node-forge-0.9.5.tgz#648231d79da197216290429020698d4e767365a0"
|
||||
integrity sha512-rrN3xfA/oZIzwOnO3d2wRQz7UdeVkmMMPjWUCfpPTPuKFVb3D6G10LuiVHYYmvrivBBLMx4m0P/FICoDbNZUMA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*":
|
||||
version "14.6.2"
|
||||
resolved "https://packages.deskreen.com/@types%2fnode/-/node-14.6.2.tgz#264b44c5a28dfa80198fc2f7b6d3c8a054b9491f"
|
||||
@ -1656,6 +1793,13 @@
|
||||
resolved "https://packages.deskreen.com/@types%2fparse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
||||
|
||||
"@types/pixelmatch@^5.2.2":
|
||||
version "5.2.2"
|
||||
resolved "https://packages.deskreen.com/@types%2fpixelmatch/-/pixelmatch-5.2.2.tgz#3403238d4b920bf2255fb6cbf9a098bef796ce62"
|
||||
integrity sha512-ndpfW/H8+SAiI3wt+f8DlHGgB7OeBdgFgBJ6v/1l3SpJ0MCn9wtXFb4mUccMujN5S4DMmAh7MVy1O3WcXrHUKw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.3"
|
||||
resolved "https://packages.deskreen.com/@types%2fprop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||
@ -1681,6 +1825,23 @@
|
||||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/resemblejs@^1.3.29":
|
||||
version "1.3.29"
|
||||
resolved "https://packages.deskreen.com/@types%2fresemblejs/-/resemblejs-1.3.29.tgz#0138b9a4e3f48b5bf87a59ed01bc4b5b52d9f863"
|
||||
integrity sha512-3mPH6kI2u9ivHT5kTcNTv63bI0Oiv4VRq95Ub1fgRFv6N/USYV3aOt1VIcb7k8wAKQPUoiAhlA0hEv9+qTkoVw==
|
||||
|
||||
"@types/simple-peer@^9.6.0":
|
||||
version "9.6.0"
|
||||
resolved "https://packages.deskreen.com/@types%2fsimple-peer/-/simple-peer-9.6.0.tgz#b5828d835b7f42dde27db584ba127e7a9f9072f4"
|
||||
integrity sha512-X2y6s+vE/3j03hkI90oqld2JH2J/m1L7yFCYYPyFV/whrOK1h4neYvJL3GIE+UcACJacXZqzdmDKudwec18RbA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/socket.io-client@^1.4.33":
|
||||
version "1.4.34"
|
||||
resolved "https://packages.deskreen.com/@types%2fsocket.io-client/-/socket.io-client-1.4.34.tgz#8ca5f5732a9ad92b79aba71083cda5e5821e3ed9"
|
||||
integrity sha512-Lzia5OTQFJZJ5R4HsEEldywiiqT9+W2rDbyHJiiTGqOcju89sCsQ8aUXDljY6Ls33wKZZGC0bfMhr/VpOyjtXg==
|
||||
|
||||
"@types/stack-utils@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://packages.deskreen.com/@types%2fstack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
|
||||
@ -1709,6 +1870,11 @@
|
||||
"@types/testing-library__dom" "*"
|
||||
pretty-format "^25.1.0"
|
||||
|
||||
"@types/ua-parser-js@^0.7.33":
|
||||
version "0.7.33"
|
||||
resolved "https://packages.deskreen.com/@types%2fua-parser-js/-/ua-parser-js-0.7.33.tgz#4a92089511574e12928a7cb6b99a01831acd1dd7"
|
||||
integrity sha512-ngUKcHnytUodUCL7C6EZ+lVXUjTMQb+9p/e1JjV5tN9TVzS98lHozWEFRPY1QcCdwFeMsmVWfZ3DPPT/udCyIw==
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "15.0.0"
|
||||
resolved "https://packages.deskreen.com/@types%2fyargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
||||
@ -1989,6 +2155,11 @@ adjust-sourcemap-loader@2.0.0:
|
||||
object-path "0.11.4"
|
||||
regex-parser "2.2.10"
|
||||
|
||||
after@0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://packages.deskreen.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
|
||||
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
|
||||
|
||||
aggregate-error@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://packages.deskreen.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
|
||||
@ -2197,6 +2368,11 @@ array.prototype.flat@^1.2.1:
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.17.0-next.1"
|
||||
|
||||
arraybuffer.slice@~0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://packages.deskreen.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
|
||||
integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
|
||||
|
||||
arrify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://packages.deskreen.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
||||
@ -2373,6 +2549,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@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://packages.deskreen.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854"
|
||||
@ -2390,7 +2582,7 @@ babel-plugin-jest-hoist@^24.9.0:
|
||||
dependencies:
|
||||
"@types/babel__traverse" "^7.0.6"
|
||||
|
||||
babel-plugin-macros@2.8.0:
|
||||
babel-plugin-macros@2.8.0, 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==
|
||||
@ -2404,6 +2596,11 @@ babel-plugin-named-asset-import@^0.3.6:
|
||||
resolved "https://packages.deskreen.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz#c9750a1b38d85112c9e166bf3ef7c5dbc605f4be"
|
||||
integrity sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA==
|
||||
|
||||
babel-plugin-syntax-jsx@^6.18.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=
|
||||
|
||||
babel-plugin-syntax-object-rest-spread@^6.8.0:
|
||||
version "6.13.0"
|
||||
resolved "https://packages.deskreen.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
||||
@ -2464,11 +2661,21 @@ babylon@^6.18.0:
|
||||
resolved "https://packages.deskreen.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
|
||||
integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
|
||||
|
||||
backo2@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://packages.deskreen.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
|
||||
integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://packages.deskreen.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
base64-arraybuffer@0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://packages.deskreen.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
|
||||
integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=
|
||||
|
||||
base64-js@^1.0.2:
|
||||
version "1.3.1"
|
||||
resolved "https://packages.deskreen.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
@ -2521,6 +2728,11 @@ bindings@^1.5.0:
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
blob@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://packages.deskreen.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
|
||||
integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
|
||||
|
||||
bluebird@^3.5.5:
|
||||
version "3.7.2"
|
||||
resolved "https://packages.deskreen.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
@ -2993,6 +3205,11 @@ class-utils@^0.3.5:
|
||||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classnames@^2.2:
|
||||
version "2.2.6"
|
||||
resolved "https://packages.deskreen.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||
|
||||
clean-css@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://packages.deskreen.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
|
||||
@ -3145,11 +3362,21 @@ commondir@^1.0.1:
|
||||
resolved "https://packages.deskreen.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
|
||||
|
||||
component-emitter@^1.2.1:
|
||||
component-bind@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://packages.deskreen.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
|
||||
integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=
|
||||
|
||||
component-emitter@^1.2.1, component-emitter@~1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://packages.deskreen.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||
|
||||
component-inherit@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://packages.deskreen.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
|
||||
integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
|
||||
|
||||
compose-function@3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://packages.deskreen.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f"
|
||||
@ -3229,7 +3456,7 @@ 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.7.0, convert-source-map@^1.4.0, convert-source-map@^1.7.0:
|
||||
convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.5.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==
|
||||
@ -3348,6 +3575,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
create-react-context@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://packages.deskreen.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c"
|
||||
integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==
|
||||
dependencies:
|
||||
gud "^1.0.0"
|
||||
warning "^4.0.3"
|
||||
|
||||
cross-spawn@7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://packages.deskreen.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14"
|
||||
@ -3606,6 +3841,11 @@ cssstyle@^1.0.0, cssstyle@^1.1.1:
|
||||
dependencies:
|
||||
cssom "0.3.x"
|
||||
|
||||
csstype@^2.5.7:
|
||||
version "2.6.14"
|
||||
resolved "https://packages.deskreen.com/csstype/-/csstype-2.6.14.tgz#004822a4050345b55ad4dcc00be1d9cf2f4296de"
|
||||
integrity sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A==
|
||||
|
||||
csstype@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://packages.deskreen.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8"
|
||||
@ -3666,6 +3906,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@~3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://packages.deskreen.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://packages.deskreen.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
@ -3676,7 +3923,7 @@ decode-uri-component@^0.2.0:
|
||||
resolved "https://packages.deskreen.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||
|
||||
deep-equal@^1.0.1:
|
||||
deep-equal@^1.0.1, deep-equal@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://packages.deskreen.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
||||
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
|
||||
@ -3693,6 +3940,11 @@ deep-is@~0.1.3:
|
||||
resolved "https://packages.deskreen.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
|
||||
|
||||
deepmerge@^4.0.0:
|
||||
version "4.2.2"
|
||||
resolved "https://packages.deskreen.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||
|
||||
default-gateway@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://packages.deskreen.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
|
||||
@ -3865,6 +4117,13 @@ dom-converter@^0.2:
|
||||
dependencies:
|
||||
utila "~0.4"
|
||||
|
||||
dom-helpers@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://packages.deskreen.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
|
||||
integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
|
||||
dom-serializer@0:
|
||||
version "0.2.2"
|
||||
resolved "https://packages.deskreen.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
|
||||
@ -3873,6 +4132,11 @@ dom-serializer@0:
|
||||
domelementtype "^2.0.1"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom4@^2.1.5:
|
||||
version "2.1.6"
|
||||
resolved "https://packages.deskreen.com/dom4/-/dom4-2.1.6.tgz#c90df07134aa0dbd81ed4d6ba1237b36fc164770"
|
||||
integrity sha512-JkCVGnN4ofKGbjf5Uvc8mmxaATIErKQKSgACdBXpsQ3fY6DlIpAyWfiBSrGkttATssbDCp3psiAKWXk5gmjycA==
|
||||
|
||||
domain-browser@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://packages.deskreen.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
@ -4021,6 +4285,34 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
engine.io-client@~3.4.0:
|
||||
version "3.4.4"
|
||||
resolved "https://packages.deskreen.com/engine.io-client/-/engine.io-client-3.4.4.tgz#77d8003f502b0782dd792b073a4d2cf7ca5ab967"
|
||||
integrity sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==
|
||||
dependencies:
|
||||
component-emitter "~1.3.0"
|
||||
component-inherit "0.0.3"
|
||||
debug "~3.1.0"
|
||||
engine.io-parser "~2.2.0"
|
||||
has-cors "1.1.0"
|
||||
indexof "0.0.1"
|
||||
parseqs "0.0.6"
|
||||
parseuri "0.0.6"
|
||||
ws "~6.1.0"
|
||||
xmlhttprequest-ssl "~1.5.4"
|
||||
yeast "0.1.2"
|
||||
|
||||
engine.io-parser@~2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://packages.deskreen.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7"
|
||||
integrity sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==
|
||||
dependencies:
|
||||
after "0.8.2"
|
||||
arraybuffer.slice "~0.0.7"
|
||||
base64-arraybuffer "0.1.4"
|
||||
blob "0.0.5"
|
||||
has-binary2 "~1.0.2"
|
||||
|
||||
enhanced-resolve@^4.1.0:
|
||||
version "4.3.0"
|
||||
resolved "https://packages.deskreen.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126"
|
||||
@ -4690,6 +4982,11 @@ find-cache-dir@^3.3.1:
|
||||
make-dir "^3.0.2"
|
||||
pkg-dir "^4.1.0"
|
||||
|
||||
find-root@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://packages.deskreen.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
|
||||
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
|
||||
|
||||
find-up@4.1.0, find-up@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://packages.deskreen.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
|
||||
@ -4739,6 +5036,13 @@ flatten@^1.0.2:
|
||||
resolved "https://packages.deskreen.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
||||
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
||||
|
||||
flexboxgrid2@^7.2.0:
|
||||
version "7.2.1"
|
||||
resolved "https://packages.deskreen.com/flexboxgrid2/-/flexboxgrid2-7.2.1.tgz#3ffb9661ca5a9e96468eae648f8caf279bd0b2a4"
|
||||
integrity sha512-O2bO5ZcBXnFy7cYmyt/CKb6CuwzNuUPxWJt8WOiaot8SetE9zyUahXGTSpKDm3+CTYQ5YeEMPeunMdjcxKJz4w==
|
||||
dependencies:
|
||||
normalize.css "^7.0.0"
|
||||
|
||||
flush-write-stream@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://packages.deskreen.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
|
||||
@ -5027,6 +5331,11 @@ growly@^1.3.0:
|
||||
resolved "https://packages.deskreen.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
||||
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
|
||||
|
||||
gud@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://packages.deskreen.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
|
||||
integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==
|
||||
|
||||
gzip-size@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://packages.deskreen.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
|
||||
@ -5065,6 +5374,18 @@ has-ansi@^2.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
has-binary2@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://packages.deskreen.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
|
||||
integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
|
||||
dependencies:
|
||||
isarray "2.0.1"
|
||||
|
||||
has-cors@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://packages.deskreen.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
|
||||
integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://packages.deskreen.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
@ -5412,6 +5733,11 @@ indexes-of@^1.0.1:
|
||||
resolved "https://packages.deskreen.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
|
||||
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
|
||||
|
||||
indexof@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://packages.deskreen.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
|
||||
integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
|
||||
|
||||
infer-owner@^1.0.3, infer-owner@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://packages.deskreen.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
|
||||
@ -5828,6 +6154,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
||||
resolved "https://packages.deskreen.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
isarray@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://packages.deskreen.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
|
||||
integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://packages.deskreen.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
@ -6581,6 +6912,11 @@ load-json-file@^4.0.0:
|
||||
pify "^3.0.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
load-script@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://packages.deskreen.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4"
|
||||
integrity sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=
|
||||
|
||||
loader-fs-cache@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://packages.deskreen.com/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz#f08657646d607078be2f0a032f8bd69dd6f277d9"
|
||||
@ -6764,6 +7100,11 @@ media-typer@0.3.0:
|
||||
resolved "https://packages.deskreen.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
memoize-one@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://packages.deskreen.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
||||
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
||||
|
||||
memory-fs@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://packages.deskreen.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
|
||||
@ -7022,6 +7363,11 @@ nan@^2.12.1:
|
||||
resolved "https://packages.deskreen.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
|
||||
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
|
||||
|
||||
nanoid@^2.1.0:
|
||||
version "2.1.11"
|
||||
resolved "https://packages.deskreen.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
|
||||
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://packages.deskreen.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
@ -7077,6 +7423,11 @@ node-forge@0.9.0:
|
||||
resolved "https://packages.deskreen.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
|
||||
integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==
|
||||
|
||||
node-forge@^0.9.1:
|
||||
version "0.9.2"
|
||||
resolved "https://packages.deskreen.com/node-forge/-/node-forge-0.9.2.tgz#b35a44c28889b2ea55cabf8c79e3563f9676190a"
|
||||
integrity sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw==
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://packages.deskreen.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
@ -7174,6 +7525,16 @@ normalize-url@^3.0.0:
|
||||
resolved "https://packages.deskreen.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
|
||||
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
|
||||
|
||||
normalize.css@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://packages.deskreen.com/normalize.css/-/normalize.css-7.0.0.tgz#abfb1dd82470674e0322b53ceb1aaf412938e4bf"
|
||||
integrity sha1-q/sd2CRwZ04DIrU86xqvQSk45L8=
|
||||
|
||||
normalize.css@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://packages.deskreen.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3"
|
||||
integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==
|
||||
|
||||
npm-run-path@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://packages.deskreen.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
|
||||
@ -7545,6 +7906,16 @@ parse5@5.1.0:
|
||||
resolved "https://packages.deskreen.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
|
||||
integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
|
||||
|
||||
parseqs@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://packages.deskreen.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
|
||||
integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==
|
||||
|
||||
parseuri@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://packages.deskreen.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a"
|
||||
integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==
|
||||
|
||||
parseurl@~1.3.2, parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://packages.deskreen.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
@ -7694,6 +8065,13 @@ pirates@^4.0.1:
|
||||
dependencies:
|
||||
node-modules-regexp "^1.0.0"
|
||||
|
||||
pixelmatch@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://packages.deskreen.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65"
|
||||
integrity sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==
|
||||
dependencies:
|
||||
pngjs "^4.0.1"
|
||||
|
||||
pkg-dir@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://packages.deskreen.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
|
||||
@ -7734,6 +8112,11 @@ pn@^1.1.0:
|
||||
resolved "https://packages.deskreen.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
|
||||
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
|
||||
|
||||
pngjs@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://packages.deskreen.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe"
|
||||
integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==
|
||||
|
||||
pnp-webpack-plugin@1.6.4:
|
||||
version "1.6.4"
|
||||
resolved "https://packages.deskreen.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
|
||||
@ -7741,6 +8124,11 @@ pnp-webpack-plugin@1.6.4:
|
||||
dependencies:
|
||||
ts-pnp "^1.1.6"
|
||||
|
||||
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"
|
||||
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
|
||||
|
||||
portfinder@^1.0.26:
|
||||
version "1.0.28"
|
||||
resolved "https://packages.deskreen.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
|
||||
@ -8504,7 +8892,7 @@ prompts@^2.0.1:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.4"
|
||||
|
||||
prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.5.10, prop-types@^15.5.8, 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==
|
||||
@ -8715,11 +9103,60 @@ react-error-overlay@^6.0.7:
|
||||
resolved "https://packages.deskreen.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
||||
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
|
||||
|
||||
react-fast-compare@^3.0.1:
|
||||
version "3.2.0"
|
||||
resolved "https://packages.deskreen.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
||||
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
|
||||
|
||||
react-flexbox-grid@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://packages.deskreen.com/react-flexbox-grid/-/react-flexbox-grid-2.1.2.tgz#5eadf04e8559f7140cd6867a55a5351ded8d3920"
|
||||
integrity sha512-lj1oVnIJ7TY3W6tPjFUxlUYd1DLFxEg8RiX3HAYVvreE3O9HU9n2390CFoPQ+qk1E+5MXa2t/mLMafFLAa8+7Q==
|
||||
dependencies:
|
||||
flexboxgrid2 "^7.2.0"
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||
version "16.13.1"
|
||||
resolved "https://packages.deskreen.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://packages.deskreen.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-player@^2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://packages.deskreen.com/react-player/-/react-player-2.6.2.tgz#d41fe842f5bd6a2b1d3ce1ba9ed82c3eb26e0df4"
|
||||
integrity sha512-Wi9DynNSVgddKxac5OzsH0Upk6VRYssvLLGgCRw6vsjzqMX6S5N26WDRNYnLaHykxFNtpPSDc53fXDe52hMaCg==
|
||||
dependencies:
|
||||
deepmerge "^4.0.0"
|
||||
load-script "^1.0.0"
|
||||
memoize-one "^5.1.1"
|
||||
prop-types "^15.7.2"
|
||||
react-fast-compare "^3.0.1"
|
||||
|
||||
react-popper@^1.3.7:
|
||||
version "1.3.7"
|
||||
resolved "https://packages.deskreen.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324"
|
||||
integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
create-react-context "^0.3.0"
|
||||
deep-equal "^1.1.1"
|
||||
popper.js "^1.14.4"
|
||||
prop-types "^15.6.1"
|
||||
typed-styles "^0.0.7"
|
||||
warning "^4.0.2"
|
||||
|
||||
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-scripts@3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://packages.deskreen.com/react-scripts/-/react-scripts-3.4.3.tgz#21de5eb93de41ee92cd0b85b0e1298d0bb2e6c51"
|
||||
@ -8780,6 +9217,23 @@ react-scripts@3.4.3:
|
||||
optionalDependencies:
|
||||
fsevents "2.1.2"
|
||||
|
||||
react-spinners@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://packages.deskreen.com/react-spinners/-/react-spinners-0.9.0.tgz#b22c38acbfce580cd6f1b04a4649e812370b1fb8"
|
||||
integrity sha512-+x6eD8tn/aYLdxZjNW7fSR1uoAXLb9qq6TFYZR1dFweJvckcf/HfP8Pa/cy5HOvB/cvI4JgrYXTjh2Me3S6Now==
|
||||
dependencies:
|
||||
"@emotion/core" "^10.0.15"
|
||||
|
||||
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"
|
||||
integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==
|
||||
dependencies:
|
||||
dom-helpers "^3.4.0"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
react@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://packages.deskreen.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||
@ -9055,6 +9509,11 @@ requires-port@^1.0.0:
|
||||
resolved "https://packages.deskreen.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://packages.deskreen.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||
|
||||
resolve-cwd@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://packages.deskreen.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||
@ -9290,6 +9749,11 @@ schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6
|
||||
ajv "^6.12.2"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
screenfull@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://packages.deskreen.com/screenfull/-/screenfull-5.0.2.tgz#b9acdcf1ec676a948674df5cd0ff66b902b0bed7"
|
||||
integrity sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ==
|
||||
|
||||
select-hose@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://packages.deskreen.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
|
||||
@ -9460,6 +9924,13 @@ shellwords@^0.1.1:
|
||||
resolved "https://packages.deskreen.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
|
||||
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
|
||||
|
||||
shortid@^2.2.15:
|
||||
version "2.2.15"
|
||||
resolved "https://packages.deskreen.com/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122"
|
||||
integrity sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw==
|
||||
dependencies:
|
||||
nanoid "^2.1.0"
|
||||
|
||||
side-channel@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://packages.deskreen.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3"
|
||||
@ -9539,6 +10010,32 @@ snapdragon@^0.8.1:
|
||||
source-map-resolve "^0.5.0"
|
||||
use "^3.1.0"
|
||||
|
||||
socket.io-client@^2.3.0:
|
||||
version "2.3.1"
|
||||
resolved "https://packages.deskreen.com/socket.io-client/-/socket.io-client-2.3.1.tgz#91a4038ef4d03c19967bb3c646fec6e0eaa78cff"
|
||||
integrity sha512-YXmXn3pA8abPOY//JtYxou95Ihvzmg8U6kQyolArkIyLd0pgVhrfor/iMsox8cn07WCOOvvuJ6XKegzIucPutQ==
|
||||
dependencies:
|
||||
backo2 "1.0.2"
|
||||
component-bind "1.0.0"
|
||||
component-emitter "~1.3.0"
|
||||
debug "~3.1.0"
|
||||
engine.io-client "~3.4.0"
|
||||
has-binary2 "~1.0.2"
|
||||
indexof "0.0.1"
|
||||
parseqs "0.0.6"
|
||||
parseuri "0.0.6"
|
||||
socket.io-parser "~3.3.0"
|
||||
to-array "0.1.4"
|
||||
|
||||
socket.io-parser@~3.3.0:
|
||||
version "3.3.1"
|
||||
resolved "https://packages.deskreen.com/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199"
|
||||
integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ==
|
||||
dependencies:
|
||||
component-emitter "~1.3.0"
|
||||
debug "~3.1.0"
|
||||
isarray "2.0.1"
|
||||
|
||||
sockjs-client@1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://packages.deskreen.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5"
|
||||
@ -9601,7 +10098,7 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
|
||||
resolved "https://packages.deskreen.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.5.0, source-map@^0.5.6:
|
||||
source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7:
|
||||
version "0.5.7"
|
||||
resolved "https://packages.deskreen.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||
@ -10093,6 +10590,11 @@ tmpl@1.0.x:
|
||||
resolved "https://packages.deskreen.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
|
||||
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
|
||||
|
||||
to-array@0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://packages.deskreen.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
|
||||
integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA=
|
||||
|
||||
to-arraybuffer@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://packages.deskreen.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
||||
@ -10165,7 +10667,7 @@ ts-pnp@^1.1.6:
|
||||
resolved "https://packages.deskreen.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
|
||||
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
|
||||
|
||||
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@~1.13.0:
|
||||
version "1.13.0"
|
||||
resolved "https://packages.deskreen.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
|
||||
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
|
||||
@ -10229,6 +10731,11 @@ type@^2.0.0:
|
||||
resolved "https://packages.deskreen.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f"
|
||||
integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==
|
||||
|
||||
typed-styles@^0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://packages.deskreen.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9"
|
||||
integrity sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://packages.deskreen.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
@ -10239,6 +10746,11 @@ typescript@~3.7.2:
|
||||
resolved "https://packages.deskreen.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
|
||||
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
|
||||
|
||||
ua-parser-js@^0.7.22:
|
||||
version "0.7.22"
|
||||
resolved "https://packages.deskreen.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"
|
||||
integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==
|
||||
|
||||
unicode-canonical-property-names-ecmascript@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://packages.deskreen.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
|
||||
@ -10483,6 +10995,13 @@ walker@^1.0.7, walker@~1.0.5:
|
||||
dependencies:
|
||||
makeerror "1.0.x"
|
||||
|
||||
warning@^4.0.2, warning@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://packages.deskreen.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
watchpack-chokidar2@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://packages.deskreen.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0"
|
||||
@ -10891,6 +11410,13 @@ ws@^6.1.2, ws@^6.2.1:
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@~6.1.0:
|
||||
version "6.1.4"
|
||||
resolved "https://packages.deskreen.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
|
||||
integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://packages.deskreen.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
@ -10906,6 +11432,11 @@ xmlchars@^2.1.1:
|
||||
resolved "https://packages.deskreen.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xmlhttprequest-ssl@~1.5.4:
|
||||
version "1.5.5"
|
||||
resolved "https://packages.deskreen.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
|
||||
integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
|
||||
|
||||
xregexp@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://packages.deskreen.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
|
||||
@ -10961,3 +11492,8 @@ yargs@^13.3.0, yargs@^13.3.2:
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.2"
|
||||
|
||||
yeast@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://packages.deskreen.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
|
||||
integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { Text, H3, Intent, Alert } from '@blueprintjs/core';
|
||||
import { Intent, Alert, H4 } from '@blueprintjs/core';
|
||||
import isProduction from '../utils/isProduction';
|
||||
import DeviceInfoCallout from './DeviceInfoCallout';
|
||||
|
||||
interface AllowConnectionForDeviceAlertProps {
|
||||
device: Device | null;
|
||||
@ -27,30 +27,14 @@ export default function AllowConnectionForDeviceAlert(
|
||||
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?.sharingSessionID}`}</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<H4>Device is trying to connect, do you allow?</H4>
|
||||
<DeviceInfoCallout
|
||||
deviceType={device?.deviceType}
|
||||
deviceIP={device?.deviceIP}
|
||||
deviceOS={device?.deviceOS}
|
||||
sharingSessionID={device?.sharingSessionID}
|
||||
deviceBrowser={device?.deviceBrowser}
|
||||
/>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
/* 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;
|
||||
class CloseOverlayButtonProps {
|
||||
onClick = () => {};
|
||||
|
||||
style? = {};
|
||||
|
||||
isDefaultStyles? = false;
|
||||
|
||||
className? = '';
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
@ -28,15 +29,14 @@ const useStyles = makeStyles(() =>
|
||||
const CloseOverlayButton: React.FC<CloseOverlayButtonProps> = (
|
||||
props: CloseOverlayButtonProps
|
||||
) => {
|
||||
const { className, isDefaultStyles, style, onClick } = props;
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Button
|
||||
id="close-overlay-button"
|
||||
className={
|
||||
props.noDefaultStyles ? '' : `${classes.closeButton} ${props.className}`
|
||||
}
|
||||
onClick={props.onClick}
|
||||
style={props.style}
|
||||
className={isDefaultStyles ? `${classes.closeButton} ${className}` : ''}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
>
|
||||
<Icon icon="cross" iconSize={30} />
|
||||
</Button>
|
||||
|
@ -8,6 +8,21 @@ import ConnectedDevicesListDrawer from './ConnectedDevicesListDrawer';
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('electron', () => {
|
||||
return {
|
||||
remote: {
|
||||
getGlobal: (globalName: string) => {
|
||||
if (globalName === 'connectedDevicesService') {
|
||||
return {
|
||||
getDevices: () => [],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = mount(
|
||||
<>
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import React, { useContext, useEffect, useState, useCallback } from 'react';
|
||||
import { remote } from 'electron';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
@ -14,11 +15,32 @@ import {
|
||||
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';
|
||||
import ConnectedDevicesService from '../features/ConnectedDevicesService';
|
||||
import SharingSessionsService from '../features/SharingSessionsService';
|
||||
import DeviceInfoCallout from './DeviceInfoCallout';
|
||||
import SharingSourcePreviewCard from './SharingSourcePreviewCard';
|
||||
|
||||
const sharingSessionsService = remote.getGlobal(
|
||||
'sharingSessionService'
|
||||
) as SharingSessionsService;
|
||||
const connectedDevicesService = remote.getGlobal(
|
||||
'connectedDevicesService'
|
||||
) as ConnectedDevicesService;
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
|
||||
const disconnectPeerAndDestroySharingSessionBySessionID = (
|
||||
sharingSessionID: string
|
||||
) => {
|
||||
const sharingSession = sharingSessionsService.sharingSessions.get(
|
||||
sharingSessionID
|
||||
);
|
||||
sharingSession?.disconnectByHostMachineUser();
|
||||
sharingSession?.destory();
|
||||
sharingSessionsService.sharingSessions.delete(sharingSessionID);
|
||||
};
|
||||
|
||||
interface ConnectedDevicesListDrawerProps {
|
||||
isOpen: boolean;
|
||||
handleToggle: () => void;
|
||||
@ -49,31 +71,33 @@ export default function ConnectedDevicesListDrawer(
|
||||
|
||||
const [isAlertDisconectAllOpen, setIsAlertDisconectAllOpen] = useState(false);
|
||||
|
||||
const { devices, setDevicesHook } = useContext(ConnectedDevicesContext);
|
||||
const [devicesDisplayed, setDevicesDisplayed] = useState(new Map());
|
||||
|
||||
useEffect(() => {
|
||||
const map = new Map();
|
||||
devices.forEach((el) => {
|
||||
connectedDevicesService.getDevices().forEach((el) => {
|
||||
map.set(el.id, true);
|
||||
});
|
||||
setDevicesDisplayed(map);
|
||||
}, [devices, setDevicesDisplayed]);
|
||||
}, [setDevicesDisplayed]);
|
||||
|
||||
const handleDisconnectOneDevice = useCallback(
|
||||
(id: string) => {
|
||||
const filteredDevices = devices.filter((device) => {
|
||||
return device.id !== id;
|
||||
});
|
||||
|
||||
setDevicesHook(filteredDevices);
|
||||
},
|
||||
[devices, setDevicesHook]
|
||||
);
|
||||
const handleDisconnectOneDevice = useCallback(async (id: string) => {
|
||||
const device = connectedDevicesService.devices.find(
|
||||
(d: Device) => d.id === id
|
||||
);
|
||||
if (!device) return;
|
||||
disconnectPeerAndDestroySharingSessionBySessionID(device.sharingSessionID);
|
||||
connectedDevicesService.removeDeviceByID(id);
|
||||
}, []);
|
||||
|
||||
const handleDisconnectAll = useCallback(() => {
|
||||
setDevicesHook([] as Device[]);
|
||||
}, [setDevicesHook]);
|
||||
connectedDevicesService.devices.forEach((device: Device) => {
|
||||
disconnectPeerAndDestroySharingSessionBySessionID(
|
||||
device.sharingSessionID
|
||||
);
|
||||
});
|
||||
connectedDevicesService.removeAllDevices();
|
||||
}, []);
|
||||
|
||||
const hideOneDeviceInDevicesDisplayed = useCallback(
|
||||
(id) => {
|
||||
@ -94,10 +118,10 @@ export default function ConnectedDevicesListDrawer(
|
||||
|
||||
const handleDisconnectAndHideOneDevice = useCallback(
|
||||
(id) => {
|
||||
hideOneDeviceInDevicesDisplayed(id);
|
||||
setTimeout(
|
||||
() => {
|
||||
handleDisconnectOneDevice(id);
|
||||
async () => {
|
||||
await handleDisconnectOneDevice(id);
|
||||
hideOneDeviceInDevicesDisplayed(id);
|
||||
},
|
||||
isProduction() ? 1000 : 0
|
||||
);
|
||||
@ -126,6 +150,7 @@ export default function ConnectedDevicesListDrawer(
|
||||
isOpen={props.isOpen}
|
||||
onClose={props.handleToggle}
|
||||
transitionDuration={isProduction() ? 700 : 0}
|
||||
// transitionDuration={0}
|
||||
>
|
||||
<Row between="xs" middle="xs" className={classes.drawerInnerTopPanel}>
|
||||
<Col xs={11}>
|
||||
@ -135,7 +160,7 @@ export default function ConnectedDevicesListDrawer(
|
||||
</div>
|
||||
<Button
|
||||
intent="danger"
|
||||
disabled={devices.length === 0}
|
||||
disabled={connectedDevicesService.getDevices().length === 0}
|
||||
onClick={() => {
|
||||
setIsAlertDisconectAllOpen(true);
|
||||
}}
|
||||
@ -146,39 +171,55 @@ export default function ConnectedDevicesListDrawer(
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={1}>
|
||||
<CloseOverlayButton onClick={props.handleToggle} />
|
||||
<CloseOverlayButton onClick={props.handleToggle} isDefaultStyles />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className={classes.connectedDevicesRoot}>
|
||||
<Col xs={12}>
|
||||
<Fade bottom cascade duration={isProduction() ? 700 : 0}>
|
||||
<div className={classes.zoomFullWidth}>
|
||||
{devices.map((device) => {
|
||||
{connectedDevicesService.getDevices().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.sharingSessionID}</Text>
|
||||
<Button
|
||||
intent="danger"
|
||||
onClick={(): void => {
|
||||
handleDisconnectAndHideOneDevice(device.id);
|
||||
}}
|
||||
icon="disable"
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
<Card className="connected-device-card">
|
||||
<Row middle="xs">
|
||||
<Col xs={6}>
|
||||
<DeviceInfoCallout
|
||||
deviceType={device.deviceType}
|
||||
deviceOS={device.deviceOS}
|
||||
deviceIP={device.deviceIP}
|
||||
sharingSessionID={device.sharingSessionID}
|
||||
deviceBrowser={device.deviceBrowser}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<SharingSourcePreviewCard
|
||||
sharingSourceID={
|
||||
sharingSessionsService.sharingSessions.get(
|
||||
device.sharingSessionID
|
||||
)?.desktopCapturerSourceID
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row center="xs">
|
||||
<Button
|
||||
id={`disconnect-device-${device.deviceIP}`}
|
||||
intent="danger"
|
||||
onClick={(): void => {
|
||||
handleDisconnectAndHideOneDevice(device.id);
|
||||
}}
|
||||
icon="disable"
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</Row>
|
||||
</Card>
|
||||
</Fade>
|
||||
</div>
|
||||
|
74
app/components/DeviceInfoCallout/index.tsx
Normal file
74
app/components/DeviceInfoCallout/index.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
/* eslint-disable react/jsx-one-expression-per-line */
|
||||
import React from 'react';
|
||||
import { Callout, Text, H4, Tooltip, Position } from '@blueprintjs/core';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
|
||||
interface DeviceInfoCalloutProps {
|
||||
deviceType: string | undefined;
|
||||
deviceIP: string | undefined;
|
||||
deviceOS: string | undefined;
|
||||
sharingSessionID: string | undefined;
|
||||
deviceBrowser: string | undefined;
|
||||
}
|
||||
|
||||
function getContentOfTooltip() {
|
||||
return (
|
||||
<>
|
||||
<Text>
|
||||
{`This should match with 'Device IP' displayed on the screen of device
|
||||
that is trying to connect.`}
|
||||
</Text>
|
||||
<span style={{ fontWeight: 900 }}>
|
||||
<Text>
|
||||
{`If IPs don't match click 'Deny' or 'Disconnect' button immediately to
|
||||
secure your computer!`}
|
||||
</Text>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DeviceInfoCallout(props: DeviceInfoCalloutProps) {
|
||||
const {
|
||||
deviceType,
|
||||
deviceIP,
|
||||
deviceOS,
|
||||
sharingSessionID,
|
||||
deviceBrowser,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<H4 style={{ margin: '0 auto', textAlign: 'center' }}>
|
||||
Partner Device Info:
|
||||
</H4>
|
||||
<Callout id="device-info-callout">
|
||||
<Row center="xs">
|
||||
<Col xs={12}>
|
||||
<Text>
|
||||
Device Type: <span>{deviceType}</span>
|
||||
</Text>
|
||||
<Tooltip content={getContentOfTooltip()} position={Position.TOP}>
|
||||
<div style={{ fontWeight: 900, backgroundColor: '#00f99273' }}>
|
||||
<Text className="bp3-text-large">
|
||||
Device IP: <span className="device-ip-span">{deviceIP}</span>
|
||||
</Text>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Text>
|
||||
Device Browser: <span>{deviceBrowser}</span>
|
||||
</Text>
|
||||
<Text>
|
||||
Device OS: <span>{deviceOS}</span>
|
||||
</Text>
|
||||
<div style={{ width: '200px', margin: '0 auto' }}>
|
||||
<Text className="bp3-text-muted" ellipsize>
|
||||
Session ID: <span>{sharingSessionID}</span>
|
||||
</Text>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Callout>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
/* 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 React, { useContext } from 'react';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { Icon, Text } from '@blueprintjs/core';
|
||||
import { createStyles, makeStyles } from '@material-ui/core/styles';
|
||||
@ -31,12 +28,10 @@ interface SettingRowLabelAndInput {
|
||||
export default function SettingRowLabelAndInput(
|
||||
props: SettingRowLabelAndInput
|
||||
) {
|
||||
const { icon, label, input } = props;
|
||||
const { isDarkTheme } = useContext(SettingsContext);
|
||||
|
||||
const getClassesCallback = useCallback(() => {
|
||||
// TODO: dont use callback inside callback, then how to use styles with theme?
|
||||
return useStylesWithTheme(isDarkTheme)();
|
||||
}, [isDarkTheme]);
|
||||
const getClassesCallback = useStylesWithTheme(isDarkTheme);
|
||||
|
||||
return (
|
||||
<Row middle="xs" between="xs">
|
||||
@ -44,19 +39,20 @@ export default function SettingRowLabelAndInput(
|
||||
<Row middle="xs" className={getClassesCallback().oneSettingRow}>
|
||||
<Col>
|
||||
<Icon
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: ok here
|
||||
icon={props.icon}
|
||||
icon={icon}
|
||||
iconSize={25}
|
||||
className={getClassesCallback().settingRowIcon}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Text>{props.label}</Text>
|
||||
<Text>{label}</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<Row>{props.input}</Row>
|
||||
<Row>{input}</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable react/jsx-boolean-value */
|
||||
import React, { Suspense } from 'react';
|
||||
import Enzyme, { mount } from 'enzyme';
|
||||
import EnzymeToJson from 'enzyme-to-json';
|
||||
@ -6,7 +5,6 @@ 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();
|
||||
@ -16,11 +14,9 @@ it('should match exact snapshot', () => {
|
||||
<>
|
||||
<Suspense fallback={<div>Loading... </div>}>
|
||||
<SettingsProvider>
|
||||
<ConnectedDevicesProvider>
|
||||
<Router>
|
||||
<SettingsOverlay isSettingsOpen={true} handleClose={() => {}} />
|
||||
</Router>
|
||||
</ConnectedDevicesProvider>
|
||||
<Router>
|
||||
<SettingsOverlay isSettingsOpen handleClose={() => {}} />
|
||||
</Router>
|
||||
</SettingsProvider>
|
||||
</Suspense>
|
||||
</>
|
||||
|
@ -1,15 +1,4 @@
|
||||
/* 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 React, { useContext, useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Overlay,
|
||||
@ -34,8 +23,10 @@ import {
|
||||
SettingsContext,
|
||||
} from '../../containers/SettingsProvider';
|
||||
import CloseOverlayButton from '../CloseOverlayButton';
|
||||
import { getLangNameToLangKeyMap } from '../../configs/i18next.config.client';
|
||||
import config from '../../configs/app.lang.config';
|
||||
import {
|
||||
getLangFullNameToLangISOKeyMap,
|
||||
getLangISOKeyToLangFullNameMap,
|
||||
} from '../../configs/i18next.config.client';
|
||||
import isProduction from '../../utils/isProduction';
|
||||
import SettingRowLabelAndInput from './SettingRowLabelAndInput';
|
||||
|
||||
@ -46,17 +37,14 @@ interface SettingsOverlayProps {
|
||||
handleClose: () => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const useStylesWithTheme = (_isDarkTheme: boolean) =>
|
||||
const useStylesWithTheme = (isDarkTheme: boolean) =>
|
||||
makeStyles(() =>
|
||||
createStyles({
|
||||
checkboxSettings: { margin: '0' },
|
||||
overlayInnerRoot: { width: '90%' },
|
||||
overlayInsideFade: {
|
||||
height: '90vh',
|
||||
backgroundColor: _isDarkTheme
|
||||
? DARK_UI_BACKGROUND
|
||||
: LIGHT_UI_BACKGROUND,
|
||||
backgroundColor: isDarkTheme ? DARK_UI_BACKGROUND : LIGHT_UI_BACKGROUND,
|
||||
},
|
||||
absoluteCloseButton: { position: 'absolute', left: 'calc(100% - 65px)' },
|
||||
tabNavigationRowButton: { fontWeight: 700 },
|
||||
@ -67,28 +55,21 @@ const useStylesWithTheme = (_isDarkTheme: boolean) =>
|
||||
export default function SettingsOverlay(props: SettingsOverlayProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { handleClose, isSettingsOpen } = props;
|
||||
|
||||
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
|
||||
getLangFullNameToLangISOKeyMap().forEach((_, key) => {
|
||||
tmp.push(key);
|
||||
}
|
||||
});
|
||||
setLanguagesList(tmp);
|
||||
}, [LANG_NAME_TO_KEY_MAP]);
|
||||
}, []);
|
||||
|
||||
const getClassesCallback = useCallback(() => {
|
||||
return useStylesWithTheme(isDarkTheme)();
|
||||
}, [isDarkTheme]);
|
||||
const getClassesCallback = useStylesWithTheme(isDarkTheme);
|
||||
|
||||
const handleToggleDarkTheme = useCallback(() => {
|
||||
if (!isDarkTheme) {
|
||||
@ -104,16 +85,59 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
|
||||
}
|
||||
}, [isDarkTheme, setIsDarkThemeHook]);
|
||||
|
||||
const onChangeLangueageHTMLSelectHandler = (event: any) => {
|
||||
const onChangeLangueageHTMLSelectHandler = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
if (
|
||||
event.currentTarget &&
|
||||
event.currentTarget.value in LANG_NAME_TO_KEY_MAP
|
||||
getLangFullNameToLangISOKeyMap().has(event.currentTarget.value)
|
||||
) {
|
||||
// @ts-ignore: fine here
|
||||
i18n.changeLanguage(LANG_NAME_TO_KEY_MAP[event.currentTarget.value]);
|
||||
i18n.changeLanguage(
|
||||
getLangFullNameToLangISOKeyMap().get(event.currentTarget.value) ||
|
||||
'English'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getThemeChangingControlGroupInput = () => {
|
||||
return (
|
||||
<ControlGroup fill vertical={false}>
|
||||
<Button
|
||||
icon="flash"
|
||||
text="Light"
|
||||
onClick={handleToggleLightTheme}
|
||||
active={!isDarkTheme}
|
||||
/>
|
||||
<Button
|
||||
icon="moon"
|
||||
text="Dark"
|
||||
onClick={handleToggleDarkTheme}
|
||||
active={isDarkTheme}
|
||||
/>
|
||||
</ControlGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const getLanguageChangingHTMLSelect = () => {
|
||||
return (
|
||||
<HTMLSelect
|
||||
defaultValue={getLangISOKeyToLangFullNameMap().get(i18n.language)}
|
||||
options={languagesList}
|
||||
onChange={onChangeLangueageHTMLSelectHandler}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getAutomaticUpdatesCheckboxInput = () => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked
|
||||
className={getClassesCallback().checkboxSettings}
|
||||
label="Enabled"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const GeneralSettingsPanel: React.FC = () => (
|
||||
<>
|
||||
<Row middle="xs">
|
||||
@ -122,47 +146,17 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
|
||||
<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>
|
||||
}
|
||||
input={getThemeChangingControlGroupInput()}
|
||||
/>
|
||||
<SettingRowLabelAndInput
|
||||
icon="translate"
|
||||
label={t('Language')}
|
||||
input={
|
||||
<HTMLSelect
|
||||
defaultValue={
|
||||
// @ts-ignore: fine here
|
||||
config.langISOKeyToLangFullNameMap[i18n.language]
|
||||
}
|
||||
options={languagesList}
|
||||
onChange={onChangeLangueageHTMLSelectHandler}
|
||||
/>
|
||||
}
|
||||
input={getLanguageChangingHTMLSelect()}
|
||||
/>
|
||||
<SettingRowLabelAndInput
|
||||
icon="automatic-updates"
|
||||
label="Automatic Updates"
|
||||
input={
|
||||
<Checkbox
|
||||
checked
|
||||
className={getClassesCallback().checkboxSettings}
|
||||
label="Enabled"
|
||||
/>
|
||||
}
|
||||
input={getAutomaticUpdatesCheckboxInput()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -227,14 +221,14 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
onClose={props.handleClose}
|
||||
onClose={handleClose}
|
||||
className={`${Classes.OVERLAY_SCROLL_CONTAINER} bp3-overlay-settings`}
|
||||
autoFocus
|
||||
canEscapeKeyClose
|
||||
canOutsideClickClose
|
||||
enforceFocus
|
||||
hasBackdrop
|
||||
isOpen={props.isSettingsOpen}
|
||||
isOpen={isSettingsOpen}
|
||||
usePortal
|
||||
transitionDuration={0}
|
||||
>
|
||||
@ -248,7 +242,8 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
|
||||
>
|
||||
<CloseOverlayButton
|
||||
className={getClassesCallback().absoluteCloseButton}
|
||||
onClick={props.handleClose}
|
||||
onClick={handleClose}
|
||||
isDefaultStyles
|
||||
/>
|
||||
<Tabs
|
||||
animate
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,22 @@ import ShareAppOrScreenControlGroup from './ShareAppOrScreenControlGroup';
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('electron', () => {
|
||||
return {
|
||||
remote: {
|
||||
getGlobal: (globalName: string) => {
|
||||
if (globalName === 'desktopCapturerSourcesService') {
|
||||
return {
|
||||
getScreenSources: () => [],
|
||||
getAppWindowSources: () => [],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = mount(
|
||||
<>
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* 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';
|
||||
@ -14,7 +11,7 @@ interface ShareEntireScreenOrAppWindowProps {
|
||||
const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
controlGroupRoot: {
|
||||
width: '380px',
|
||||
width: '500px',
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
left: '20px',
|
||||
@ -48,8 +45,7 @@ const useStyles = makeStyles(() =>
|
||||
position: 'relative',
|
||||
top: '72px',
|
||||
left: '-190px !important',
|
||||
// @ts-ignore: need to use !important, can't work without it
|
||||
zIndex: '9999 !important',
|
||||
zIndex: 9999,
|
||||
cursor: 'default',
|
||||
},
|
||||
})
|
||||
@ -58,6 +54,7 @@ const useStyles = makeStyles(() =>
|
||||
export default function ShareEntireScreenOrAppWindowControlGroup(
|
||||
props: ShareEntireScreenOrAppWindowProps
|
||||
) {
|
||||
const { handleNextEntireScreen, handleNextApplicationWindow } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
const [
|
||||
@ -94,6 +91,7 @@ export default function ShareEntireScreenOrAppWindowControlGroup(
|
||||
className={classes.controlGroupRoot}
|
||||
fill
|
||||
vertical={false}
|
||||
style={{ width: '380px' }}
|
||||
>
|
||||
<Button
|
||||
className={classes.shareEntireScreenButton}
|
||||
@ -129,8 +127,8 @@ export default function ShareEntireScreenOrAppWindowControlGroup(
|
||||
isEntireScreenToShareChosen={isEntireScreenToShareChosen}
|
||||
isChooseAppOrScreenOverlayOpen={isChooseAppOrScreenOverlayOpen}
|
||||
handleClose={handleCloseChooseAppOrScreenOverlay}
|
||||
handleNextEntireScreen={props.handleNextEntireScreen}
|
||||
handleNextApplicationWindow={props.handleNextApplicationWindow}
|
||||
handleNextEntireScreen={handleNextEntireScreen}
|
||||
handleNextApplicationWindow={handleNextApplicationWindow}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
121
app/components/SharingSourcePreviewCard/index.tsx
Normal file
121
app/components/SharingSourcePreviewCard/index.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { remote } from 'electron';
|
||||
import { Text, Card, Spinner } from '@blueprintjs/core';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import DesktopCapturerSources from '../../features/DesktopCapturerSourcesService';
|
||||
|
||||
const desktopCapturerSourcesService = remote.getGlobal(
|
||||
'desktopCapturerSourcesService'
|
||||
) as DesktopCapturerSources;
|
||||
|
||||
class SharingSourcePreviewCardProps {
|
||||
sharingSourceID: string | undefined = '';
|
||||
|
||||
onClickCard? = () => {};
|
||||
|
||||
isChangeApperanceOnHover? = false;
|
||||
}
|
||||
|
||||
export default function SharingSourcePreviewCard(
|
||||
props: SharingSourcePreviewCardProps
|
||||
) {
|
||||
const { sharingSourceID, isChangeApperanceOnHover, onClickCard } = props;
|
||||
const [sourceImage, setSourceImage] = useState('');
|
||||
const [sourceName, setSourceName] = useState('');
|
||||
const [appIconSourceImage, setAppIconSourceImage] = useState('');
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
useEffect(() => {
|
||||
setTimeout(async () => {
|
||||
const sources = desktopCapturerSourcesService.getSourcesMap();
|
||||
|
||||
if (sources && sharingSourceID && sources.get(sharingSourceID)) {
|
||||
setSourceImage(
|
||||
sources.get(sharingSourceID)?.source.thumbnail.toDataURL() || ''
|
||||
);
|
||||
if (sources.get(sharingSourceID)?.source.appIcon != null) {
|
||||
setAppIconSourceImage(
|
||||
sources.get(sharingSourceID)?.source.appIcon.toDataURL() || ''
|
||||
);
|
||||
}
|
||||
setSourceName(
|
||||
sources.get(sharingSourceID)?.source.name ||
|
||||
'Failed to get source name...'
|
||||
);
|
||||
}
|
||||
}, 1000);
|
||||
}, [sharingSourceID]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="preview-share-thumb-container"
|
||||
onClick={onClickCard ? () => onClickCard() : () => {}}
|
||||
style={{
|
||||
height: '200px',
|
||||
minWidth: '250px',
|
||||
backgroundColor:
|
||||
isHovered && isChangeApperanceOnHover ? '#2B95D6' : 'rgba(0,0,0,0.0)',
|
||||
}}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseOver={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<Row center="xs" middle="xs" style={{ height: '95%', minWidth: '200px' }}>
|
||||
<Col xs={12}>
|
||||
{sourceImage !== '' ? (
|
||||
<>
|
||||
<img
|
||||
src={sourceImage}
|
||||
alt=""
|
||||
style={{ height: '143px', maxWidth: '100%' }}
|
||||
/>
|
||||
{appIconSourceImage !== '' ? (
|
||||
<Card
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
transform: 'translate(0px, -45px)',
|
||||
borderRadius: '500px',
|
||||
padding: '0px',
|
||||
margin: '0px',
|
||||
}}
|
||||
elevation={4}
|
||||
>
|
||||
<Row center="xs" middle="xs" style={{ height: '100%' }}>
|
||||
<img
|
||||
src={appIconSourceImage}
|
||||
alt=""
|
||||
style={{
|
||||
width: '25px',
|
||||
height: '25px',
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
</Card>
|
||||
) : (
|
||||
<> </>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Spinner size={60} />
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row center="xs">
|
||||
<Col
|
||||
xs={12}
|
||||
style={{
|
||||
backgroundColor:
|
||||
isHovered && isChangeApperanceOnHover
|
||||
? 'rgba(0,0,0,0.8)'
|
||||
: 'rgba(0,0,0,0.45)',
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Text ellipsize>{sourceName}</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
/* 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';
|
||||
@ -35,7 +33,15 @@ const useColorlibStepIconStyles = makeStyles({
|
||||
stepContent: {},
|
||||
});
|
||||
|
||||
const getDesktopOrAppIcon = (isDesktop: boolean, color: string) => {
|
||||
if (isDesktop) {
|
||||
return <Icon icon="desktop" iconSize={25} color={color} />;
|
||||
}
|
||||
return <Icon icon="application" iconSize={25} color={color} />;
|
||||
};
|
||||
|
||||
export default function ColorlibStepIcon(props: StepIconPropsDeskreen) {
|
||||
const { icon } = props;
|
||||
const classes = useColorlibStepIconStyles();
|
||||
const { active, completed, isEntireScreenSelected } = props;
|
||||
|
||||
@ -48,11 +54,7 @@ export default function ColorlibStepIcon(props: StepIconPropsDeskreen) {
|
||||
<Icon icon="feed" iconSize={25} color={color} />
|
||||
),
|
||||
2: completed ? (
|
||||
isEntireScreenSelected ? (
|
||||
<Icon icon="desktop" iconSize={25} color={color} />
|
||||
) : (
|
||||
<Icon icon="application" iconSize={25} color={color} />
|
||||
)
|
||||
getDesktopOrAppIcon(isEntireScreenSelected, color)
|
||||
) : (
|
||||
<Icon icon="flow-branch" iconSize={25} color={color} />
|
||||
),
|
||||
@ -70,7 +72,7 @@ export default function ColorlibStepIcon(props: StepIconPropsDeskreen) {
|
||||
[classes.completed]: completed,
|
||||
})} ${active ? 'active-stepper-pulse-icon' : ''}`}
|
||||
>
|
||||
{icons[String(props.icon)]}
|
||||
{icons[String(icon)]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { Icon, Text, Button, Popover, H6, Tooltip } from '@blueprintjs/core';
|
||||
import { Icon, Text, Button, Popover, Tooltip } from '@blueprintjs/core';
|
||||
import isProduction from '../../utils/isProduction';
|
||||
import DeviceInfoCallout from '../DeviceInfoCallout';
|
||||
|
||||
interface DeviceConnectedInfoButtonProps {
|
||||
device: Device;
|
||||
@ -15,16 +16,14 @@ const getDeviceConnectedPopoverContent = (
|
||||
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>{`sharingSessionID: ${pendingConnectionDevice?.sharingSessionID}`}</Text>
|
||||
</Col>
|
||||
<Row style={{ margin: '0 px 10px 10px 10px' }}>
|
||||
<DeviceInfoCallout
|
||||
deviceType={pendingConnectionDevice?.deviceType}
|
||||
deviceIP={pendingConnectionDevice?.deviceIP}
|
||||
deviceOS={pendingConnectionDevice?.deviceOS}
|
||||
sharingSessionID={pendingConnectionDevice?.sharingSessionID}
|
||||
deviceBrowser={pendingConnectionDevice?.deviceBrowser}
|
||||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
|
@ -44,33 +44,11 @@ exports[`should match exact snapshot 1`] = `
|
||||
<Row
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
"margin": "0 px 10px 10px 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>
|
||||
sharingSessionID: undefined
|
||||
</Blueprint3.Text>
|
||||
</Col>
|
||||
<DeviceInfoCallout />
|
||||
</Row>
|
||||
<Row>
|
||||
<Col
|
||||
|
@ -8,6 +8,22 @@ import ChooseAppOrScreeenStep from './ChooseAppOrScreeenStep';
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('electron', () => {
|
||||
return {
|
||||
remote: {
|
||||
getGlobal: (globalName: string) => {
|
||||
if (globalName === 'desktopCapturerSourcesService') {
|
||||
return {
|
||||
getScreenSources: () => [],
|
||||
getAppWindowSources: () => [],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = mount(
|
||||
<>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { Text } from '@blueprintjs/core';
|
||||
import ShareEntireScreenOrAppWindowControlGroup from '../ShareAppOrScreenControlGroup';
|
||||
|
||||
@ -11,18 +11,28 @@ interface ChooseAppOrScreeenStepProps {
|
||||
const ChooseAppOrScreeenStep: React.FC<ChooseAppOrScreeenStepProps> = (
|
||||
props: ChooseAppOrScreeenStepProps
|
||||
) => {
|
||||
const { handleNextEntireScreen, handleNextApplicationWindow } = props;
|
||||
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col xs={12}>
|
||||
<Row center="xs">
|
||||
<Col xs={6}>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<Text>Choose Entire Screen or App window you want to share</Text>
|
||||
</div>
|
||||
<Row center="xs">
|
||||
<Col>
|
||||
<ShareEntireScreenOrAppWindowControlGroup
|
||||
handleNextEntireScreen={handleNextEntireScreen}
|
||||
handleNextApplicationWindow={handleNextApplicationWindow}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,22 @@ import ChooseAppOrScreenOverlay from './ChooseAppOrScreenOverlay';
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('electron', () => {
|
||||
return {
|
||||
remote: {
|
||||
getGlobal: (globalName: string) => {
|
||||
if (globalName === 'desktopCapturerSourcesService') {
|
||||
return {
|
||||
getScreenSources: () => [],
|
||||
getAppWindowSources: () => [],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = mount(
|
||||
<>
|
||||
|
@ -1,14 +1,16 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import React from 'react';
|
||||
import { remote } from 'electron';
|
||||
import React, { useEffect, useState } 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';
|
||||
import DesktopCapturerSources from '../../../features/DesktopCapturerSourcesService';
|
||||
|
||||
const desktopCapturerSourcesService = remote.getGlobal(
|
||||
'desktopCapturerSourcesService'
|
||||
) as DesktopCapturerSources;
|
||||
|
||||
const Zoom = require('react-reveal/Zoom');
|
||||
const Fade = require('react-reveal/Fade');
|
||||
@ -48,22 +50,66 @@ interface ChooseAppOrScreenOverlayProps {
|
||||
export default function ChooseAppOrScreenOverlay(
|
||||
props: ChooseAppOrScreenOverlayProps
|
||||
) {
|
||||
const {
|
||||
handleClose,
|
||||
isChooseAppOrScreenOverlayOpen,
|
||||
isEntireScreenToShareChosen,
|
||||
handleNextEntireScreen,
|
||||
handleNextApplicationWindow,
|
||||
} = props;
|
||||
const classes = useStyles();
|
||||
|
||||
const [
|
||||
screenViewSharingObjectsMap,
|
||||
setScreenViewSharingObjectsMap,
|
||||
] = useState<Map<string, ViewSharingObject>>(new Map());
|
||||
|
||||
const [
|
||||
appsWindowsViewSharingObjectsMap,
|
||||
setAppsWindowsViewSharingObjectsMap,
|
||||
] = useState<Map<string, ViewSharingObject>>(new Map());
|
||||
|
||||
useEffect(() => {
|
||||
if (isEntireScreenToShareChosen) {
|
||||
const sourcesToShare = desktopCapturerSourcesService.getScreenSources();
|
||||
const screenViewMap = new Map<string, ViewSharingObject>();
|
||||
sourcesToShare.forEach((source) => {
|
||||
screenViewMap.set(source.id, {
|
||||
thumbnailUrl: source.thumbnail.toDataURL(),
|
||||
name: source.name,
|
||||
});
|
||||
});
|
||||
setScreenViewSharingObjectsMap(screenViewMap);
|
||||
} else {
|
||||
const sourcesToShare = desktopCapturerSourcesService.getAppWindowSources();
|
||||
const appViewMap = new Map<string, ViewSharingObject>();
|
||||
sourcesToShare.forEach((source) => {
|
||||
appViewMap.set(source.id, {
|
||||
thumbnailUrl: source.thumbnail.toDataURL(),
|
||||
name: source.name,
|
||||
});
|
||||
});
|
||||
setAppsWindowsViewSharingObjectsMap(appViewMap);
|
||||
}
|
||||
}, [isEntireScreenToShareChosen]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={props.handleClose}
|
||||
onClose={handleClose}
|
||||
className={`${classes.dialogRoot} choose-app-or-screen-dialog`}
|
||||
autoFocus
|
||||
canEscapeKeyClose
|
||||
canOutsideClickClose
|
||||
enforceFocus
|
||||
hasBackdrop
|
||||
isOpen={props.isChooseAppOrScreenOverlayOpen}
|
||||
isOpen={isChooseAppOrScreenOverlayOpen}
|
||||
usePortal
|
||||
transitionDuration={isProduction() ? 750 : 0}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="choose-app-or-screen-overlay-container"
|
||||
style={{ minHeight: '95%' }}
|
||||
>
|
||||
<Fade
|
||||
duration={isProduction() ? 1000 : 0}
|
||||
delay={isProduction() ? 1000 : 0}
|
||||
@ -95,7 +141,7 @@ export default function ChooseAppOrScreenOverlay(
|
||||
}}
|
||||
>
|
||||
<Col xs={11}>
|
||||
{props.isEntireScreenToShareChosen ? (
|
||||
{isEntireScreenToShareChosen ? (
|
||||
<div>
|
||||
<H3 style={{ marginBottom: '0px' }}>
|
||||
Select Entire Screen to Share
|
||||
@ -112,13 +158,12 @@ export default function ChooseAppOrScreenOverlay(
|
||||
|
||||
<Col xs={1}>
|
||||
<CloseOverlayButton
|
||||
onClick={props.handleClose}
|
||||
onClick={handleClose}
|
||||
style={{
|
||||
borderRadius: '100px',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
}}
|
||||
noDefaultStyles
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -136,22 +181,26 @@ export default function ChooseAppOrScreenOverlay(
|
||||
<Card
|
||||
style={{
|
||||
position: 'relative',
|
||||
// @ts-ignore
|
||||
zIndex: '1',
|
||||
zIndex: 1,
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
<div className={classes.sharePreviewsContainer}>
|
||||
<PreviewGridList
|
||||
screenSharingObjects={TEST_SCREEN_SHARING_OBECTS}
|
||||
isEntireScreen={props.isEntireScreenToShareChosen}
|
||||
viewSharingObjectsMap={
|
||||
isEntireScreenToShareChosen
|
||||
? screenViewSharingObjectsMap
|
||||
: appsWindowsViewSharingObjectsMap
|
||||
}
|
||||
isEntireScreen={isEntireScreenToShareChosen}
|
||||
handleNextEntireScreen={() => {
|
||||
props.handleNextEntireScreen();
|
||||
props.handleClose();
|
||||
handleNextEntireScreen();
|
||||
handleClose();
|
||||
}}
|
||||
handleNextApplicationWindow={() => {
|
||||
props.handleNextApplicationWindow();
|
||||
props.handleClose();
|
||||
handleNextApplicationWindow();
|
||||
handleClose();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,166 +1,82 @@
|
||||
/* 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 { remote } from 'electron';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import isProduction from '../../../utils/isProduction';
|
||||
import SharingSessionService from '../../../features/SharingSessionsService';
|
||||
import SharingSourcePreviewCard from '../../SharingSourcePreviewCard';
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
const sharingSessionService = remote.getGlobal(
|
||||
'sharingSessionService'
|
||||
) as SharingSessionService;
|
||||
|
||||
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)',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
const EMPTY_VIEW_SHARING_OBJECTS_MAP = new Map<string, ViewSharingObject>();
|
||||
|
||||
export default function PreviewGridList(props: any) {
|
||||
const classes = useStyles();
|
||||
class PreviewGridListProps {
|
||||
viewSharingObjectsMap = EMPTY_VIEW_SHARING_OBJECTS_MAP;
|
||||
|
||||
const [showPreviewNamesMap, setShowPreviewNamesMap] = useState(new Map());
|
||||
isEntireScreen = true;
|
||||
|
||||
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
|
||||
}, []);
|
||||
handleNextEntireScreen = () => {};
|
||||
|
||||
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]
|
||||
handleNextApplicationWindow = () => {};
|
||||
}
|
||||
|
||||
export default function PreviewGridList(props: PreviewGridListProps) {
|
||||
const {
|
||||
viewSharingObjectsMap,
|
||||
isEntireScreen,
|
||||
handleNextEntireScreen,
|
||||
handleNextApplicationWindow,
|
||||
} = props;
|
||||
const [showPreviewNamesMap, setShowPreviewNamesMap] = useState(
|
||||
new Map<string, boolean>()
|
||||
);
|
||||
|
||||
const onPreviewMouseLeave = useCallback(() => {
|
||||
const newShowPreviewNamesMap = new Map(showPreviewNamesMap);
|
||||
[...newShowPreviewNamesMap.keys()].forEach((key) => {
|
||||
newShowPreviewNamesMap.set(key, false);
|
||||
useEffect(() => {
|
||||
const map = new Map<string, boolean>();
|
||||
if (viewSharingObjectsMap === EMPTY_VIEW_SHARING_OBJECTS_MAP) {
|
||||
setShowPreviewNamesMap(map);
|
||||
return;
|
||||
}
|
||||
[...viewSharingObjectsMap.keys()].forEach((id: string) => {
|
||||
map.set(id, false);
|
||||
});
|
||||
setShowPreviewNamesMap(newShowPreviewNamesMap);
|
||||
}, [showPreviewNamesMap, setShowPreviewNamesMap]);
|
||||
setShowPreviewNamesMap(map);
|
||||
}, [viewSharingObjectsMap]);
|
||||
|
||||
return (
|
||||
<div
|
||||
<Row
|
||||
center="xs"
|
||||
around="xs"
|
||||
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();
|
||||
<Col xs={12} md={6} key={id}>
|
||||
<SharingSourcePreviewCard
|
||||
sharingSourceID={id}
|
||||
isChangeApperanceOnHover
|
||||
onClickCard={async () => {
|
||||
let sharingSession;
|
||||
if (
|
||||
sharingSessionService.waitingForConnectionSharingSession !==
|
||||
null
|
||||
) {
|
||||
sharingSession =
|
||||
sharingSessionService.waitingForConnectionSharingSession;
|
||||
sharingSession.setDesktopCapturerSourceID(id);
|
||||
}
|
||||
if (isEntireScreen) {
|
||||
handleNextEntireScreen();
|
||||
} else {
|
||||
props.handleNextApplicationWindow();
|
||||
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>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
1
app/components/StepsOfStepper/ChooseAppOrScreenOverlay/ViewSharingObject.d.ts
vendored
Normal file
1
app/components/StepsOfStepper/ChooseAppOrScreenOverlay/ViewSharingObject.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
type ViewSharingObject = { thumbnailUrl: string; name: string };
|
File diff suppressed because it is too large
Load Diff
@ -1,30 +1,66 @@
|
||||
/* eslint-disable react/jsx-curly-brace-presence */
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import React from 'react';
|
||||
import { Text, Card } from '@blueprintjs/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { remote } from 'electron';
|
||||
import { Text } from '@blueprintjs/core';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import SharingSourcePreviewCard from '../SharingSourcePreviewCard';
|
||||
import SharingSessionService from '../../features/SharingSessionsService';
|
||||
import DeviceInfoCallout from '../DeviceInfoCallout';
|
||||
import SharingSession from '../../features/SharingSessionsService/SharingSession';
|
||||
|
||||
const sharingSessionService = remote.getGlobal(
|
||||
'sharingSessionService'
|
||||
) as SharingSessionService;
|
||||
|
||||
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>
|
||||
const { device } = props;
|
||||
const [sharingSession, setSharingSession] = useState<
|
||||
SharingSession | undefined
|
||||
>();
|
||||
|
||||
<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?.sharingSessionID}`}</Text>
|
||||
</Card>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<Text className="bp3-text-muted">
|
||||
{`Click "Back" if you need to change something`}
|
||||
</Text>
|
||||
</div>
|
||||
</>
|
||||
useEffect(() => {
|
||||
if (sharingSessionService.waitingForConnectionSharingSession !== null) {
|
||||
setSharingSession(
|
||||
sharingSessionService.waitingForConnectionSharingSession
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ width: '80%', marginTop: '50px' }}>
|
||||
<Row style={{ marginBottom: '10px' }}>
|
||||
<Col xs={12} style={{ textAlign: 'center' }}>
|
||||
{/* eslint-disable-next-line react/no-unescaped-entities */}
|
||||
<Text>Check if all is OK and click "Confirm"</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row middle="xs" center="xs">
|
||||
<Col xs={5}>
|
||||
<DeviceInfoCallout
|
||||
deviceType={device?.deviceType}
|
||||
deviceIP={device?.deviceIP}
|
||||
deviceOS={device?.deviceOS}
|
||||
sharingSessionID={device?.sharingSessionID}
|
||||
deviceBrowser={device?.deviceBrowser}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={5}>
|
||||
<SharingSourcePreviewCard
|
||||
sharingSourceID={sharingSession?.desktopCapturerSourceID}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ marginBottom: '10px' }}>
|
||||
<Col xs={12} style={{ textAlign: 'center' }}>
|
||||
<Text className="bp3-text-muted">
|
||||
{/* eslint-disable-next-line react/no-unescaped-entities */}
|
||||
Click "Back" if you need to change something
|
||||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,22 @@ import IntermediateStep from './IntermediateStep';
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('electron', () => {
|
||||
return {
|
||||
remote: {
|
||||
getGlobal: (globalName: string) => {
|
||||
if (globalName === 'desktopCapturerSourcesService') {
|
||||
return {
|
||||
getScreenSources: () => [],
|
||||
getAppWindowSources: () => [],
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const confirmButtonSelector = 'button.bp3-button.bp3-intent-success';
|
||||
|
||||
function setup(
|
||||
@ -56,7 +72,7 @@ it('should match exact snapshot on each step', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should call resetPendingConnectionDevice when Confirm button clicked on confirm step', () => {
|
||||
it('should call resetPendingConnectionDevice when Confirm button clicked on confirm step', async () => {
|
||||
const confirmStepNumber = 2;
|
||||
const mockResetPendingConnectionDeviceCallback = jest.fn();
|
||||
const { buttons } = setup(
|
||||
@ -67,7 +83,9 @@ it('should call resetPendingConnectionDevice when Confirm button clicked on conf
|
||||
|
||||
buttons.confirmButton.simulate('click');
|
||||
|
||||
expect(mockResetPendingConnectionDeviceCallback).toBeCalled();
|
||||
setTimeout(() => {
|
||||
expect(mockResetPendingConnectionDeviceCallback).toBeCalled();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('should call resetUserAllowedConnection when Confirm button clicked on confirm step', () => {
|
||||
@ -81,5 +99,7 @@ it('should call resetUserAllowedConnection when Confirm button clicked on confir
|
||||
|
||||
buttons.confirmButton.simulate('click');
|
||||
|
||||
expect(mockResetUserAllowedConnectionCallback).toBeCalled();
|
||||
setTimeout(() => {
|
||||
expect(mockResetUserAllowedConnectionCallback).toBeCalled();
|
||||
}, 500);
|
||||
});
|
||||
|
@ -1,13 +1,20 @@
|
||||
/* eslint-disable react/jsx-curly-brace-presence */
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { remote } from 'electron';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { Col } from 'react-flexbox-grid';
|
||||
import { Col, Row } 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';
|
||||
import ConnectedDevicesService from '../../features/ConnectedDevicesService';
|
||||
import SharingSessionService from '../../features/SharingSessionsService';
|
||||
|
||||
const sharingSessionService = remote.getGlobal(
|
||||
'sharingSessionService'
|
||||
) as SharingSessionService;
|
||||
const connectedDevicesService = remote.getGlobal(
|
||||
'connectedDevicesService'
|
||||
) as ConnectedDevicesService;
|
||||
|
||||
interface IntermediateStepProps {
|
||||
activeStep: number;
|
||||
@ -49,15 +56,18 @@ function isConfirmStep(activeStep: number, steps: string[]) {
|
||||
|
||||
export default function IntermediateStep(props: IntermediateStepProps) {
|
||||
const {
|
||||
devices,
|
||||
setPendingConnectionDeviceHook,
|
||||
setDevicesHook,
|
||||
pendingConnectionDevice,
|
||||
resetPendingConnectionDeviceHook,
|
||||
} = useContext(ConnectedDevicesContext);
|
||||
activeStep,
|
||||
steps,
|
||||
handleNext,
|
||||
handleBack,
|
||||
handleNextEntireScreen,
|
||||
handleNextApplicationWindow,
|
||||
resetPendingConnectionDevice,
|
||||
resetUserAllowedConnection,
|
||||
} = props;
|
||||
|
||||
const connectDevice = (device: Device) => {
|
||||
setPendingConnectionDeviceHook(device);
|
||||
connectedDevicesService.setPendingConnectionDevice(device);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -69,22 +79,24 @@ export default function IntermediateStep(props: IntermediateStepProps) {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '260px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{getStepContent(
|
||||
props.activeStep,
|
||||
props.handleNextEntireScreen,
|
||||
props.handleNextApplicationWindow,
|
||||
pendingConnectionDevice
|
||||
activeStep,
|
||||
handleNextEntireScreen,
|
||||
handleNextApplicationWindow,
|
||||
connectedDevicesService.pendingConnectionDevice
|
||||
)}
|
||||
|
||||
{
|
||||
// TODO: uncomment this for TRUE production use ! no Connect Test Device buttons in production!
|
||||
// // eslint-disable-next-line no-nested-ternary
|
||||
// process.env.NODE_ENV === 'production' &&
|
||||
// process.env.RUN_MODE !== 'dev' &&
|
||||
// process.env.RUN_MODE !== 'test' ? (
|
||||
// <></>
|
||||
// ) : props.activeStep === 0 ? (
|
||||
// ) : activeStep === 0 ? (
|
||||
// // eslint-disable-next-line react/jsx-indent
|
||||
// <Button
|
||||
// onClick={() => {
|
||||
@ -98,11 +110,12 @@ export default function IntermediateStep(props: IntermediateStepProps) {
|
||||
// ) : (
|
||||
// <></>
|
||||
// )
|
||||
props.activeStep === 0 ? (
|
||||
// eslint-disable-next-line react/jsx-indent
|
||||
activeStep === 0 ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
connectDevice(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
DEVICES[Math.floor(Math.random() * DEVICES.length)]
|
||||
);
|
||||
}}
|
||||
@ -115,47 +128,65 @@ export default function IntermediateStep(props: IntermediateStepProps) {
|
||||
}
|
||||
{
|
||||
/**/
|
||||
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>
|
||||
activeStep !== 0 ? (
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<Button
|
||||
intent={activeStep === 2 ? 'success' : 'none'}
|
||||
onClick={async () => {
|
||||
handleNext();
|
||||
if (isConfirmStep(activeStep, steps)) {
|
||||
if (
|
||||
sharingSessionService.waitingForConnectionSharingSession !==
|
||||
null
|
||||
) {
|
||||
const sharingSession =
|
||||
sharingSessionService.waitingForConnectionSharingSession;
|
||||
sharingSession.callPeer();
|
||||
sharingSessionService.changeSharingSessionStatusToSharing(
|
||||
sharingSession
|
||||
);
|
||||
}
|
||||
connectedDevicesService.addDevice(
|
||||
connectedDevicesService.pendingConnectionDevice
|
||||
);
|
||||
connectedDevicesService.resetPendingConnectionDevice();
|
||||
resetPendingConnectionDevice();
|
||||
resetUserAllowedConnection();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
display: activeStep === 1 ? 'none' : 'inline',
|
||||
borderRadius: '100px',
|
||||
width: '300px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
rightIcon={
|
||||
isConfirmStep(activeStep, steps)
|
||||
? 'small-tick'
|
||||
: 'chevron-right'
|
||||
}
|
||||
>
|
||||
{isConfirmStep(activeStep, steps) ? 'Confirm' : 'Next'}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
<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"
|
||||
/>
|
||||
<Row style={{ display: activeStep === 2 ? 'inline-block' : 'none' }}>
|
||||
<Button
|
||||
intent="danger"
|
||||
style={{
|
||||
marginTop: '10px',
|
||||
borderRadius: '100px',
|
||||
}}
|
||||
onClick={handleBack}
|
||||
icon="chevron-left"
|
||||
text="No, I need to share other thing"
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
@ -14,13 +14,8 @@ 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;
|
||||
let wrapper = Enzyme.shallow(<ScanQRStep />);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Enzyme.shallow(<ScanQRStep />);
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* 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 { clipboard, remote } from 'electron';
|
||||
import React, { useContext, useEffect, 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';
|
||||
@ -9,12 +8,20 @@ import { Row, Col } from 'react-flexbox-grid';
|
||||
import { SettingsContext } from '../../containers/SettingsProvider';
|
||||
import isProduction from '../../utils/isProduction';
|
||||
import CloseOverlayButton from '../CloseOverlayButton';
|
||||
import SharingSessionService from '../../features/SharingSessionsService';
|
||||
|
||||
const sharingSessionService = remote.getGlobal(
|
||||
'sharingSessionService'
|
||||
) as SharingSessionService;
|
||||
|
||||
const LOCAL_LAN_IP =
|
||||
process.env.RUN_MODE === 'dev' || process.env.NODE_ENV === 'production'
|
||||
? require('internal-ip').v4.sync()
|
||||
: '255.255.255.255';
|
||||
|
||||
// TODO: change 3131 to user defined port, if it is really defined
|
||||
const CLIENT_VIEWER_PORT = isProduction() ? '3131' : '3000';
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
smallQRCode: {
|
||||
@ -45,6 +52,22 @@ const ScanQRStep: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
const { isDarkTheme } = useContext(SettingsContext);
|
||||
|
||||
const [roomID, setRoomID] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const thisInterval = setInterval(() => {
|
||||
if (sharingSessionService.waitingForConnectionSharingSession !== null) {
|
||||
setRoomID(
|
||||
sharingSessionService.waitingForConnectionSharingSession.roomID
|
||||
);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(thisInterval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [isQRCodeMagnified, setIsQRCodeMagnified] = useState(false);
|
||||
|
||||
return (
|
||||
@ -57,27 +80,26 @@ const ScanQRStep: React.FC = () => {
|
||||
</div>
|
||||
<div>
|
||||
<Tooltip content="Click to make bigger" position={Position.LEFT}>
|
||||
<div
|
||||
<Button
|
||||
id="magnify-qr-code-button"
|
||||
className={classes.smallQRCode}
|
||||
onClick={() => setIsQRCodeMagnified(true)}
|
||||
>
|
||||
<QRCode
|
||||
value={`http://${LOCAL_LAN_IP}:65000/99999`}
|
||||
value={`http://${LOCAL_LAN_IP}:${CLIENT_VIEWER_PORT}/${roomID}`}
|
||||
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}`,
|
||||
// TODO: change image to app icon
|
||||
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>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
@ -92,8 +114,13 @@ const ScanQRStep: React.FC = () => {
|
||||
intent="primary"
|
||||
icon="duplicate"
|
||||
style={{ borderRadius: '100px' }}
|
||||
onClick={() => {
|
||||
clipboard.writeText(
|
||||
`http://${LOCAL_LAN_IP}:${CLIENT_VIEWER_PORT}/${roomID}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
{`http://${LOCAL_LAN_IP}:65000/99999`}
|
||||
{`http://${LOCAL_LAN_IP}:${CLIENT_VIEWER_PORT}/${roomID}`}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@ -108,7 +135,7 @@ const ScanQRStep: React.FC = () => {
|
||||
transitionDuration={isProduction() ? 700 : 0}
|
||||
style={{ position: 'relative', top: '-30px' }}
|
||||
>
|
||||
<div
|
||||
<Button
|
||||
id="qr-code-dialog-inner"
|
||||
onClick={() => setIsQRCodeMagnified(false)}
|
||||
style={{ paddingTop: '20px', paddingBottom: '13px' }}
|
||||
@ -128,17 +155,17 @@ const ScanQRStep: React.FC = () => {
|
||||
position: 'relative',
|
||||
borderRadius: '100px',
|
||||
}}
|
||||
noDefaultStyles
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row center="xs">
|
||||
<div className={classes.dialogQRWrapper}>
|
||||
<QRCode
|
||||
value={`http://${LOCAL_LAN_IP}:65000/99999`}
|
||||
value={`http://${LOCAL_LAN_IP}:${CLIENT_VIEWER_PORT}/${roomID}`}
|
||||
level="H"
|
||||
renderAs="svg"
|
||||
imageSettings={{
|
||||
// TODO: change image to app icon
|
||||
src:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png',
|
||||
width: 25,
|
||||
@ -149,7 +176,7 @@ const ScanQRStep: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</Button>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
|
@ -28,222 +28,287 @@ exports[`should match exact snapshot 1`] = `
|
||||
handleNextApplicationWindow={[Function]}
|
||||
handleNextEntireScreen={[Function]}
|
||||
>
|
||||
<div
|
||||
<Row
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<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="row"
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="bp3-control-group bp3-fill makeStyles-controlGroupRoot-1"
|
||||
id="share-screen-or-app-btn-group"
|
||||
<Col
|
||||
xs={12}
|
||||
>
|
||||
<Blueprint3.Button
|
||||
className="makeStyles-shareEntireScreenButton-2"
|
||||
intent="primary"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
className="col-xs-12"
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-intent-primary makeStyles-shareEntireScreenButton-2"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
type="button"
|
||||
<Row
|
||||
center="xs"
|
||||
>
|
||||
<Blueprint3.Icon
|
||||
key="leftIcon"
|
||||
/>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
key="text"
|
||||
<div
|
||||
className="row center-xs"
|
||||
>
|
||||
<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"
|
||||
<Col
|
||||
xs={6}
|
||||
>
|
||||
<div
|
||||
className="bp3-running-text"
|
||||
className="col-xs-6"
|
||||
>
|
||||
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}
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<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
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Choose Entire Screen or App window you want to share
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
<Row
|
||||
center="xs"
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
>
|
||||
<Col>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<ShareEntireScreenOrAppWindowControlGroup
|
||||
handleNextApplicationWindow={[Function]}
|
||||
handleNextEntireScreen={[Function]}
|
||||
>
|
||||
<Blueprint3.ControlGroup
|
||||
className="makeStyles-controlGroupRoot-1"
|
||||
fill={true}
|
||||
id="share-screen-or-app-btn-group"
|
||||
style={
|
||||
Object {
|
||||
"width": "380px",
|
||||
}
|
||||
}
|
||||
vertical={false}
|
||||
>
|
||||
<div
|
||||
className="bp3-control-group bp3-fill makeStyles-controlGroupRoot-1"
|
||||
id="share-screen-or-app-btn-group"
|
||||
style={
|
||||
Object {
|
||||
"width": "380px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
</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>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
</ChooseAppOrScreeenStep>
|
||||
</Router>
|
||||
</BrowserRouter>
|
||||
|
@ -30,81 +30,482 @@ exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
"marginTop": "50px",
|
||||
"width": "80%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<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"
|
||||
<Row
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
<div
|
||||
className="row"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Col
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
xs={12}
|
||||
>
|
||||
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="col-xs-12"
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Check if all is OK and click "Confirm"
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
<Row
|
||||
center="xs"
|
||||
middle="xs"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
className="row center-xs middle-xs"
|
||||
>
|
||||
Click "Back" if you need to change something
|
||||
<Col
|
||||
xs={5}
|
||||
>
|
||||
<div
|
||||
className="col-xs-5"
|
||||
>
|
||||
<DeviceInfoCallout>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h4
|
||||
className="bp3-heading"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Partner Device Info:
|
||||
</h4>
|
||||
</Component>
|
||||
<Blueprint3.Callout
|
||||
id="device-info-callout"
|
||||
>
|
||||
<div
|
||||
className="bp3-callout"
|
||||
id="device-info-callout"
|
||||
>
|
||||
<Row
|
||||
center="xs"
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
>
|
||||
<Col
|
||||
xs={12}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Type:
|
||||
<span />
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
<Blueprint3.Tooltip
|
||||
content={
|
||||
<React.Fragment>
|
||||
<Blueprint3.Text>
|
||||
This should match with 'Device IP' displayed on the screen of device
|
||||
that is trying to connect.
|
||||
</Blueprint3.Text>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontWeight": 900,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
If IPs don't match click 'Deny' or 'Disconnect' button immediately to
|
||||
secure your computer!
|
||||
</Blueprint3.Text>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
position="top"
|
||||
transitionDuration={100}
|
||||
>
|
||||
<Blueprint3.Popover
|
||||
autoFocus={false}
|
||||
boundary="scrollParent"
|
||||
canEscapeKeyClose={false}
|
||||
captureDismiss={false}
|
||||
content={
|
||||
<React.Fragment>
|
||||
<Blueprint3.Text>
|
||||
This should match with 'Device IP' displayed on the screen of device
|
||||
that is trying to connect.
|
||||
</Blueprint3.Text>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontWeight": 900,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
If IPs don't match click 'Deny' or 'Disconnect' button immediately to
|
||||
secure your computer!
|
||||
</Blueprint3.Text>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
enforceFocus={false}
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
inheritDarkTheme={true}
|
||||
interactionKind="hover-target"
|
||||
lazy={true}
|
||||
minimal={false}
|
||||
modifiers={Object {}}
|
||||
openOnTargetFocus={true}
|
||||
popoverClassName="bp3-tooltip"
|
||||
position="top"
|
||||
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]}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
key=".0"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#00f99273",
|
||||
"fontWeight": 900,
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-large"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-large"
|
||||
>
|
||||
Device IP:
|
||||
<span
|
||||
className="device-ip-span"
|
||||
/>
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
</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>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Browser:
|
||||
<span />
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device OS:
|
||||
<span />
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "200px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-muted"
|
||||
ellipsize={true}
|
||||
>
|
||||
<div
|
||||
className="bp3-text-overflow-ellipsis bp3-text-muted"
|
||||
>
|
||||
Session ID:
|
||||
<span />
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</Blueprint3.Callout>
|
||||
</DeviceInfoCallout>
|
||||
</div>
|
||||
</Col>
|
||||
<Col
|
||||
xs={5}
|
||||
>
|
||||
<div
|
||||
className="col-xs-5"
|
||||
>
|
||||
<SharingSourcePreviewCard>
|
||||
<Blueprint3.Card
|
||||
className="preview-share-thumb-container"
|
||||
elevation={0}
|
||||
interactive={false}
|
||||
onClick={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(0,0,0,0.0)",
|
||||
"height": "200px",
|
||||
"minWidth": "250px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="bp3-card bp3-elevation-0 preview-share-thumb-container"
|
||||
onClick={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(0,0,0,0.0)",
|
||||
"height": "200px",
|
||||
"minWidth": "250px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Row
|
||||
center="xs"
|
||||
middle="xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "95%",
|
||||
"minWidth": "200px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "95%",
|
||||
"minWidth": "200px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Col
|
||||
xs={12}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
>
|
||||
<Blueprint3.Spinner
|
||||
size={60}
|
||||
>
|
||||
<div
|
||||
className="bp3-spinner"
|
||||
>
|
||||
<div
|
||||
className="bp3-spinner-animation"
|
||||
>
|
||||
<svg
|
||||
height={60}
|
||||
strokeWidth="6.67"
|
||||
viewBox="1.67 1.67 96.67 96.67"
|
||||
width={60}
|
||||
>
|
||||
<path
|
||||
className="bp3-spinner-track"
|
||||
d="M 50,50 m 0,-45 a 45,45 0 1 1 0,90 a 45,45 0 1 1 0,-90"
|
||||
/>
|
||||
<path
|
||||
className="bp3-spinner-head"
|
||||
d="M 50,50 m 0,-45 a 45,45 0 1 1 0,90 a 45,45 0 1 1 0,-90"
|
||||
pathLength={280}
|
||||
strokeDasharray="280 280"
|
||||
strokeDashoffset={210}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</Blueprint3.Spinner>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
<Row
|
||||
center="xs"
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
>
|
||||
<Col
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(0,0,0,0.45)",
|
||||
"color": "white",
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
xs={12}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(0,0,0,0.45)",
|
||||
"color": "white",
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
ellipsize={true}
|
||||
>
|
||||
<div
|
||||
className="bp3-text-overflow-ellipsis"
|
||||
/>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</Blueprint3.Card>
|
||||
</SharingSourcePreviewCard>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</Row>
|
||||
<Row
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Col
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
xs={12}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
Click "Back" if you need to change something
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</ConfirmStep>
|
||||
</Router>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,7 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
|
||||
position="left"
|
||||
transitionDuration={100}
|
||||
>
|
||||
<div
|
||||
<Blueprint3.Button
|
||||
className="makeStyles-smallQRCode-1"
|
||||
id="magnify-qr-code-button"
|
||||
onClick={[Function]}
|
||||
@ -47,9 +47,9 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
|
||||
level="H"
|
||||
renderAs="svg"
|
||||
size={128}
|
||||
value="http://255.255.255.255:65000/99999"
|
||||
value="http://255.255.255.255:3000/"
|
||||
/>
|
||||
</div>
|
||||
</Blueprint3.Button>
|
||||
</Blueprint3.Tooltip>
|
||||
</div>
|
||||
<div
|
||||
@ -75,13 +75,14 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
|
||||
<Blueprint3.Button
|
||||
icon="duplicate"
|
||||
intent="primary"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
}
|
||||
}
|
||||
>
|
||||
http://255.255.255.255:65000/99999
|
||||
http://255.255.255.255:3000/
|
||||
</Blueprint3.Button>
|
||||
</Blueprint3.Tooltip>
|
||||
<Blueprint3.Dialog
|
||||
@ -99,7 +100,7 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
|
||||
}
|
||||
transitionDuration={0}
|
||||
>
|
||||
<div
|
||||
<Blueprint3.Button
|
||||
id="qr-code-dialog-inner"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
@ -132,7 +133,6 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
|
||||
xs={2}
|
||||
>
|
||||
<CloseOverlayButton
|
||||
noDefaultStyles={true}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@ -166,12 +166,12 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
|
||||
level="H"
|
||||
renderAs="svg"
|
||||
size={128}
|
||||
value="http://255.255.255.255:65000/99999"
|
||||
value="http://255.255.255.255:3000/"
|
||||
width="390px"
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</Blueprint3.Button>
|
||||
</Blueprint3.Dialog>
|
||||
</Fragment>
|
||||
`;
|
||||
|
@ -116,63 +116,77 @@ exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
class="bp3-alert-contents"
|
||||
>
|
||||
<h3
|
||||
<h4
|
||||
class="bp3-heading"
|
||||
>
|
||||
Device is trying to connect
|
||||
</h3>
|
||||
Device is trying to connect, do you allow?
|
||||
</h4>
|
||||
<h4
|
||||
class="bp3-heading"
|
||||
style="margin: 0px auto; text-align: center;"
|
||||
>
|
||||
Partner Device Info:
|
||||
</h4>
|
||||
<div
|
||||
class="row"
|
||||
class="bp3-callout"
|
||||
id="device-info-callout"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="row center-xs"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="col-xs-12"
|
||||
>
|
||||
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
|
||||
class=""
|
||||
>
|
||||
Device Type:
|
||||
<span />
|
||||
</div>
|
||||
<span
|
||||
class="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
class="bp3-popover-target"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
style="font-weight: 900; background-color: rgba(0, 249, 146, 0.451);"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="bp3-text-large"
|
||||
>
|
||||
Device IP:
|
||||
<span
|
||||
class="device-ip-span"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
Device Browser:
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
Device OS:
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
style="width: 200px; margin: 0px auto;"
|
||||
>
|
||||
<div
|
||||
class="bp3-text-overflow-ellipsis bp3-text-muted"
|
||||
>
|
||||
Session ID:
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -252,63 +266,77 @@ exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
class="bp3-alert-contents"
|
||||
>
|
||||
<h3
|
||||
<h4
|
||||
class="bp3-heading"
|
||||
>
|
||||
Device is trying to connect
|
||||
</h3>
|
||||
Device is trying to connect, do you allow?
|
||||
</h4>
|
||||
<h4
|
||||
class="bp3-heading"
|
||||
style="margin: 0px auto; text-align: center;"
|
||||
>
|
||||
Partner Device Info:
|
||||
</h4>
|
||||
<div
|
||||
class="row"
|
||||
class="bp3-callout"
|
||||
id="device-info-callout"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="row center-xs"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
class="col-xs-12"
|
||||
>
|
||||
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
|
||||
class=""
|
||||
>
|
||||
Device Type:
|
||||
<span />
|
||||
</div>
|
||||
<span
|
||||
class="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
class="bp3-popover-target"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
style="font-weight: 900; background-color: rgba(0, 249, 146, 0.451);"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="bp3-text-large"
|
||||
>
|
||||
Device IP:
|
||||
<span
|
||||
class="device-ip-span"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
Device Browser:
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
Device OS:
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
style="width: 200px; margin: 0px auto;"
|
||||
>
|
||||
<div
|
||||
class="bp3-text-overflow-ellipsis bp3-text-muted"
|
||||
>
|
||||
Session ID:
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -448,91 +476,242 @@ exports[`should match exact snapshot 1`] = `
|
||||
className="bp3-alert-contents"
|
||||
>
|
||||
<Component>
|
||||
<h3
|
||||
<h4
|
||||
className="bp3-heading"
|
||||
>
|
||||
Device is trying to connect
|
||||
</h3>
|
||||
Device is trying to connect, do you allow?
|
||||
</h4>
|
||||
</Component>
|
||||
<Row>
|
||||
<div
|
||||
className="row"
|
||||
<DeviceInfoCallout>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<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"
|
||||
<h4
|
||||
className="bp3-heading"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
Partner Device Info:
|
||||
</h4>
|
||||
</Component>
|
||||
<Blueprint3.Callout
|
||||
id="device-info-callout"
|
||||
>
|
||||
<Col>
|
||||
<div
|
||||
className=""
|
||||
<div
|
||||
className="bp3-callout"
|
||||
id="device-info-callout"
|
||||
>
|
||||
<Row
|
||||
center="xs"
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
<div
|
||||
className="row center-xs"
|
||||
>
|
||||
<Col
|
||||
xs={12}
|
||||
>
|
||||
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
|
||||
className="col-xs-12"
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Type:
|
||||
<span />
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
<Blueprint3.Tooltip
|
||||
content={
|
||||
<React.Fragment>
|
||||
<Blueprint3.Text>
|
||||
This should match with 'Device IP' displayed on the screen of device
|
||||
that is trying to connect.
|
||||
</Blueprint3.Text>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontWeight": 900,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
If IPs don't match click 'Deny' or 'Disconnect' button immediately to
|
||||
secure your computer!
|
||||
</Blueprint3.Text>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
position="top"
|
||||
transitionDuration={100}
|
||||
>
|
||||
<Blueprint3.Popover
|
||||
autoFocus={false}
|
||||
boundary="scrollParent"
|
||||
canEscapeKeyClose={false}
|
||||
captureDismiss={false}
|
||||
content={
|
||||
<React.Fragment>
|
||||
<Blueprint3.Text>
|
||||
This should match with 'Device IP' displayed on the screen of device
|
||||
that is trying to connect.
|
||||
</Blueprint3.Text>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontWeight": 900,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
If IPs don't match click 'Deny' or 'Disconnect' button immediately to
|
||||
secure your computer!
|
||||
</Blueprint3.Text>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
enforceFocus={false}
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
inheritDarkTheme={true}
|
||||
interactionKind="hover-target"
|
||||
lazy={true}
|
||||
minimal={false}
|
||||
modifiers={Object {}}
|
||||
openOnTargetFocus={true}
|
||||
popoverClassName="bp3-tooltip"
|
||||
position="top"
|
||||
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]}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
key=".0"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#00f99273",
|
||||
"fontWeight": 900,
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-large"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-large"
|
||||
>
|
||||
Device IP:
|
||||
<span
|
||||
className="device-ip-span"
|
||||
/>
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
</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>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Browser:
|
||||
<span />
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
<Blueprint3.Text>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device OS:
|
||||
<span />
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "200px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-muted"
|
||||
ellipsize={true}
|
||||
>
|
||||
<div
|
||||
className="bp3-text-overflow-ellipsis bp3-text-muted"
|
||||
>
|
||||
Session ID:
|
||||
<span />
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</Blueprint3.Callout>
|
||||
</DeviceInfoCallout>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -466,6 +466,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
className="col-xs-1"
|
||||
>
|
||||
<CloseOverlayButton
|
||||
isDefaultStyles={true}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<Blueprint3.Button
|
||||
|
@ -32,11 +32,21 @@ exports[`should match exact snapshot 1`] = `
|
||||
className="makeStyles-controlGroupRoot-1"
|
||||
fill={true}
|
||||
id="share-screen-or-app-btn-group"
|
||||
style={
|
||||
Object {
|
||||
"width": "380px",
|
||||
}
|
||||
}
|
||||
vertical={false}
|
||||
>
|
||||
<div
|
||||
className="bp3-control-group bp3-fill makeStyles-controlGroupRoot-1"
|
||||
id="share-screen-or-app-btn-group"
|
||||
style={
|
||||
Object {
|
||||
"width": "380px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Button
|
||||
className="makeStyles-shareEntireScreenButton-2"
|
||||
|
@ -8,14 +8,24 @@ import settings from 'electron-settings';
|
||||
import config from './app.lang.config';
|
||||
import isProduction from '../utils/isProduction';
|
||||
|
||||
export const getLangNameToLangKeyMap = () => {
|
||||
const res = {};
|
||||
export const getLangFullNameToLangISOKeyMap = (): Map<string, string> => {
|
||||
const res = new Map<string, string>();
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [key, value] of Object.entries(
|
||||
config.langISOKeyToLangFullNameMap
|
||||
)) {
|
||||
// @ts-ignore: fine here
|
||||
res[value] = key;
|
||||
res.set(value, key);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getLangISOKeyToLangFullNameMap = (): Map<string, string> => {
|
||||
const res = new Map<string, string>();
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [key, value] of Object.entries(
|
||||
config.langISOKeyToLangFullNameMap
|
||||
)) {
|
||||
res.set(key, value);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
@ -1,88 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
@ -1,99 +0,0 @@
|
||||
/* 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>
|
||||
);
|
||||
};
|
@ -5,21 +5,40 @@ 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();
|
||||
|
||||
jest.mock('electron', () => {
|
||||
return {
|
||||
remote: {
|
||||
getGlobal: (globalName: string) => {
|
||||
if (globalName === 'connectedDevicesService') {
|
||||
return {
|
||||
getDevices: () => [],
|
||||
addPendingConnectedDeviceListener: () => {},
|
||||
};
|
||||
}
|
||||
if (globalName === 'sharingSessionService') {
|
||||
return {
|
||||
createWaitingForConnectionSharingSession: () =>
|
||||
new Promise(() => {}),
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = mount(
|
||||
<>
|
||||
<Suspense fallback={<div>Loading... </div>}>
|
||||
<SettingsProvider>
|
||||
<ConnectedDevicesProvider>
|
||||
<Router>
|
||||
<HomePage />
|
||||
</Router>
|
||||
</ConnectedDevicesProvider>
|
||||
<Router>
|
||||
<HomePage />
|
||||
</Router>
|
||||
</SettingsProvider>
|
||||
</Suspense>
|
||||
</>
|
||||
|
@ -9,7 +9,6 @@ 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();
|
||||
|
||||
@ -30,11 +29,9 @@ const Root = ({ store, history }: Props) => {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<SettingsProvider>
|
||||
<ConnectedDevicesProvider>
|
||||
<ConnectedRouter history={history}>
|
||||
<Routes />
|
||||
</ConnectedRouter>
|
||||
</ConnectedDevicesProvider>
|
||||
<ConnectedRouter history={history}>
|
||||
<Routes />
|
||||
</ConnectedRouter>
|
||||
</SettingsProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
@ -1,10 +1,9 @@
|
||||
/* 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';
|
||||
|
||||
// TODO: move to 'constants' tsx file ?
|
||||
export const LIGHT_UI_BACKGROUND = 'rgba(240, 248, 250, 1)';
|
||||
export const DARK_UI_BACKGROUND = '#293742';
|
||||
|
||||
@ -32,7 +31,6 @@ export const SettingsProvider: React.FC = ({ children }) => {
|
||||
|
||||
if (gotIsDarkThemeFromSettings) {
|
||||
document.body.classList.toggle(Classes.DARK);
|
||||
// if ()
|
||||
document.body.style.backgroundColor = LIGHT_UI_BACKGROUND;
|
||||
}
|
||||
|
||||
@ -46,9 +44,6 @@ export const SettingsProvider: React.FC = ({ children }) => {
|
||||
const setIsDarkThemeHook = (val: boolean) => {
|
||||
settings.setSync('appIsDarkTheme', `${val}`);
|
||||
setIsDarkTheme(val);
|
||||
// if (!val) {
|
||||
// document.body.style.backgroundColor = `${LIGHT_UI_BACKGROUND} !important`;
|
||||
// }
|
||||
};
|
||||
|
||||
const value = { isDarkTheme, setIsDarkThemeHook };
|
||||
|
@ -9,6 +9,28 @@ import DeskreenStepper from './Stepper';
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('electron', () => {
|
||||
return {
|
||||
remote: {
|
||||
getGlobal: (globalName: string) => {
|
||||
if (globalName === 'sharingSessionService') {
|
||||
return {
|
||||
createWaitingForConnectionSharingSession: () =>
|
||||
new Promise(() => {}),
|
||||
};
|
||||
}
|
||||
if (globalName === 'connectedDevicesService') {
|
||||
return {
|
||||
getDevices: () => [],
|
||||
addPendingConnectedDeviceListener: () => {},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = mount(
|
||||
<>
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import React, { useState, useCallback, useContext, useEffect } from 'react';
|
||||
import { remote } from 'electron';
|
||||
import { makeStyles, createStyles } from '@material-ui/core/styles';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import Step from '@material-ui/core/Step';
|
||||
@ -11,7 +12,6 @@ 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, {
|
||||
@ -20,6 +20,19 @@ import ColorlibStepIcon, {
|
||||
import ColorlibConnector from '../components/StepperPanel/ColorlibConnector';
|
||||
import { SettingsContext } from './SettingsProvider';
|
||||
import isProduction from '../utils/isProduction';
|
||||
import SharingSessionService from '../features/SharingSessionsService';
|
||||
import ConnectedDevicesService from '../features/ConnectedDevicesService';
|
||||
import SharingSessionStatusEnum from '../features/SharingSessionsService/SharingSessionStatusEnum';
|
||||
import Logger from '../utils/logger';
|
||||
|
||||
const log = new Logger(__filename);
|
||||
|
||||
const sharingSessionService = remote.getGlobal(
|
||||
'sharingSessionService'
|
||||
) as SharingSessionService;
|
||||
const connectedDevicesService = remote.getGlobal(
|
||||
'connectedDevicesService'
|
||||
) as ConnectedDevicesService;
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
const Zoom = require('react-reveal/Zoom');
|
||||
@ -60,20 +73,30 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
|
||||
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);
|
||||
});
|
||||
sharingSessionService
|
||||
.createWaitingForConnectionSharingSession()
|
||||
// eslint-disable-next-line promise/always-return
|
||||
.then((waitingForConnectionSharingSession) => {
|
||||
waitingForConnectionSharingSession.setOnDeviceConnectedCallback(
|
||||
(device: Device) => {
|
||||
connectedDevicesService.setPendingConnectionDevice(device);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((e) => log.error(e));
|
||||
|
||||
connectedDevicesService.addPendingConnectedDeviceListener(
|
||||
(device: Device) => {
|
||||
setPendingConnectionDevice(device);
|
||||
setIsAlertOpen(true);
|
||||
}
|
||||
);
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
@ -81,7 +104,6 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
|
||||
},
|
||||
isProduction() ? 500 : 0
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
@ -129,6 +151,20 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
|
||||
const handleReset = useCallback(() => {
|
||||
makeSmoothIntermediateStepTransition();
|
||||
setActiveStep(0);
|
||||
setPendingConnectionDevice(null);
|
||||
setIsUserAllowedConnection(false);
|
||||
sharingSessionService.waitingForConnectionSharingSession = null;
|
||||
sharingSessionService
|
||||
.createWaitingForConnectionSharingSession()
|
||||
// eslint-disable-next-line promise/always-return
|
||||
.then((waitingForConnectionSharingSession) => {
|
||||
waitingForConnectionSharingSession.setOnDeviceConnectedCallback(
|
||||
(device: Device) => {
|
||||
connectedDevicesService.setPendingConnectionDevice(device);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((e) => log.error(e));
|
||||
}, []);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
@ -137,20 +173,49 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
|
||||
},
|
||||
}));
|
||||
|
||||
const handleCancelAlert = () => {
|
||||
const handleCancelAlert = async () => {
|
||||
setIsAlertOpen(false);
|
||||
|
||||
if (sharingSessionService.waitingForConnectionSharingSession !== null) {
|
||||
const sharingSession =
|
||||
sharingSessionService.waitingForConnectionSharingSession;
|
||||
sharingSession.denyConnectionForPartner();
|
||||
sharingSession.destory();
|
||||
sharingSession.setStatus(SharingSessionStatusEnum.NOT_CONNECTED);
|
||||
sharingSessionService.sharingSessions.delete(sharingSession.id);
|
||||
|
||||
const prevRoomID =
|
||||
sharingSessionService.waitingForConnectionSharingSession.roomID;
|
||||
|
||||
sharingSessionService.waitingForConnectionSharingSession = null;
|
||||
sharingSessionService
|
||||
.createWaitingForConnectionSharingSession(prevRoomID)
|
||||
// eslint-disable-next-line promise/always-return
|
||||
.then((waitingForConnectionSharingSession) => {
|
||||
waitingForConnectionSharingSession.setOnDeviceConnectedCallback(
|
||||
(device: Device) => {
|
||||
connectedDevicesService.setPendingConnectionDevice(device);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((e) => log.error(e));
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmAlert = useCallback(() => {
|
||||
const handleConfirmAlert = useCallback(async () => {
|
||||
setIsAlertOpen(false);
|
||||
setIsUserAllowedConnection(true);
|
||||
handleNext();
|
||||
|
||||
if (sharingSessionService.waitingForConnectionSharingSession !== null) {
|
||||
const sharingSession =
|
||||
sharingSessionService.waitingForConnectionSharingSession;
|
||||
sharingSession.setStatus(SharingSessionStatusEnum.CONNECTED);
|
||||
}
|
||||
}, [handleNext]);
|
||||
|
||||
const handleUserClickedDeviceDisconnectButton = useCallback(() => {
|
||||
const handleUserClickedDeviceDisconnectButton = useCallback(async () => {
|
||||
handleReset();
|
||||
setPendingConnectionDevice(null);
|
||||
setIsUserAllowedConnection(false);
|
||||
|
||||
addToast(
|
||||
<Text>
|
||||
@ -163,28 +228,48 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
|
||||
isdarktheme: `${isDarkTheme}`,
|
||||
}
|
||||
);
|
||||
|
||||
if (sharingSessionService.waitingForConnectionSharingSession !== null) {
|
||||
const sharingSession =
|
||||
sharingSessionService.waitingForConnectionSharingSession;
|
||||
sharingSession.disconnectByHostMachineUser();
|
||||
sharingSession.destory();
|
||||
sharingSession.setStatus(SharingSessionStatusEnum.NOT_CONNECTED);
|
||||
sharingSessionService.sharingSessions.delete(sharingSession.id);
|
||||
}
|
||||
}, [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>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Zoom duration={300} when={isInterShow} style={{ width: '100%' }}>
|
||||
<Row middle="xs" center="xs">
|
||||
<SuccessStep handleReset={handleReset} />
|
||||
</Row>
|
||||
</Zoom>
|
||||
</div>
|
||||
) : (
|
||||
<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>
|
||||
<div id="intermediate-step-container" style={{ width: '100%' }}>
|
||||
<Fade
|
||||
duration={isProduction() ? 300 : 0}
|
||||
when={isInterShow}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<IntermediateStep
|
||||
activeStep={activeStep}
|
||||
steps={steps}
|
||||
handleNext={handleNext}
|
||||
handleBack={handleBack}
|
||||
handleNextEntireScreen={handleNextEntireScreen}
|
||||
handleNextApplicationWindow={handleNextApplicationWindow}
|
||||
resetPendingConnectionDevice={
|
||||
() => setPendingConnectionDevice(null)
|
||||
// eslint-disable-next-line react/jsx-curly-newline
|
||||
}
|
||||
resetUserAllowedConnection={() => setIsUserAllowedConnection(false)}
|
||||
/>
|
||||
</Fade>
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
activeStep,
|
||||
@ -234,7 +319,7 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col xs={12}>
|
||||
<Pulse top duration={isProduction() ? 1500 : 0}>
|
||||
<Stepper
|
||||
|
@ -1,19 +1,3 @@
|
||||
/* 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') {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -37,9 +37,20 @@ exports[`should match exact snapshot 1`] = `
|
||||
}
|
||||
>
|
||||
<ForwardRef>
|
||||
<Row>
|
||||
<Row
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row"
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Col
|
||||
xs={12}
|
||||
@ -937,79 +948,84 @@ exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
className="col-xs-12 makeStyles-stepContent-1"
|
||||
>
|
||||
<Fade
|
||||
duration={0}
|
||||
when={false}
|
||||
<div
|
||||
id="intermediate-step-container"
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<RevealBase
|
||||
fraction={0.2}
|
||||
inEffect={
|
||||
<Fade
|
||||
duration={0}
|
||||
style={
|
||||
Object {
|
||||
"count": 1,
|
||||
"delay": 0,
|
||||
"duration": 0,
|
||||
"forever": undefined,
|
||||
"make": [Function],
|
||||
"reverse": undefined,
|
||||
"style": Object {
|
||||
"animationFillMode": "both",
|
||||
},
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
outEffect={
|
||||
Object {
|
||||
"count": 1,
|
||||
"delay": 0,
|
||||
"duration": 0,
|
||||
"forever": undefined,
|
||||
"make": [Function],
|
||||
"reverse": undefined,
|
||||
"style": Object {
|
||||
"animationFillMode": "both",
|
||||
},
|
||||
}
|
||||
}
|
||||
refProp="ref"
|
||||
when={false}
|
||||
>
|
||||
<div
|
||||
className="react-reveal"
|
||||
style={
|
||||
<RevealBase
|
||||
fraction={0.2}
|
||||
inEffect={
|
||||
Object {
|
||||
"opacity": 0,
|
||||
"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"
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
when={false}
|
||||
>
|
||||
<IntermediateStep
|
||||
activeStep={0}
|
||||
handleBack={[Function]}
|
||||
handleNext={[Function]}
|
||||
handleNextApplicationWindow={[Function]}
|
||||
handleNextEntireScreen={[Function]}
|
||||
resetPendingConnectionDevice={[Function]}
|
||||
resetUserAllowedConnection={[Function]}
|
||||
steps={
|
||||
Array [
|
||||
"Connect",
|
||||
"Select",
|
||||
"Confirm",
|
||||
]
|
||||
<div
|
||||
className="react-reveal"
|
||||
style={
|
||||
Object {
|
||||
"opacity": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Col
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"flexDirection": "column",
|
||||
"height": "260px",
|
||||
"justifyContent": "center",
|
||||
}
|
||||
<IntermediateStep
|
||||
activeStep={0}
|
||||
handleBack={[Function]}
|
||||
handleNext={[Function]}
|
||||
handleNextApplicationWindow={[Function]}
|
||||
handleNextEntireScreen={[Function]}
|
||||
resetPendingConnectionDevice={[Function]}
|
||||
resetUserAllowedConnection={[Function]}
|
||||
steps={
|
||||
Array [
|
||||
"Connect",
|
||||
"Select",
|
||||
"Confirm",
|
||||
]
|
||||
}
|
||||
xs={12}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
<Col
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
@ -1017,39 +1033,234 @@ exports[`should match exact snapshot 1`] = `
|
||||
"flexDirection": "column",
|
||||
"height": "260px",
|
||||
"justifyContent": "center",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
xs={12}
|
||||
>
|
||||
<ScanQRStep>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
<div
|
||||
className="col-xs-12"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"flexDirection": "column",
|
||||
"height": "260px",
|
||||
"justifyContent": "center",
|
||||
"width": "100%",
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text"
|
||||
}
|
||||
>
|
||||
<ScanQRStep>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
<Blueprint3.Text
|
||||
className="bp3-text"
|
||||
>
|
||||
Scan the QR code
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
<div
|
||||
<div
|
||||
className="bp3-text"
|
||||
>
|
||||
Scan the QR code
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
( make sure your computer and device are connected on same WiFi )
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
( make sure your computer and device are connected on same WiFi )
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Blueprint3.Tooltip
|
||||
content="Click to make bigger"
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
position="left"
|
||||
transitionDuration={100}
|
||||
>
|
||||
<Blueprint3.Popover
|
||||
autoFocus={false}
|
||||
boundary="scrollParent"
|
||||
canEscapeKeyClose={false}
|
||||
captureDismiss={false}
|
||||
content="Click to make bigger"
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
enforceFocus={false}
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
inheritDarkTheme={true}
|
||||
interactionKind="hover-target"
|
||||
lazy={true}
|
||||
minimal={false}
|
||||
modifiers={Object {}}
|
||||
openOnTargetFocus={true}
|
||||
popoverClassName="bp3-tooltip"
|
||||
position="left"
|
||||
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="makeStyles-smallQRCode-12"
|
||||
id="magnify-qr-code-button"
|
||||
key=".0"
|
||||
onClick={[Function]}
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
className="bp3-button makeStyles-smallQRCode-12"
|
||||
id="magnify-qr-code-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<Blueprint3.Icon
|
||||
key="leftIcon"
|
||||
/>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
key="text"
|
||||
>
|
||||
<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://255.255.255.255:3000/"
|
||||
>
|
||||
<QRCodeSVG
|
||||
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"
|
||||
size={128}
|
||||
value="http://255.255.255.255:3000/"
|
||||
>
|
||||
<svg
|
||||
height={128}
|
||||
shapeRendering="crispEdges"
|
||||
viewBox="0 0 33 33"
|
||||
width={128}
|
||||
>
|
||||
<path
|
||||
d="M0,0 h33v33H0z"
|
||||
fill="rgba(0,0,0,0.0)"
|
||||
/>
|
||||
<path
|
||||
d="M0 0h7v1H0zM8 0h1v1H8zM10 0h1v1H10zM12 0h2v1H12zM15 0h2v1H15zM20 0h1v1H20zM23 0h2v1H23zM26,0 h7v1H26zM0 1h1v1H0zM6 1h1v1H6zM8 1h2v1H8zM12 1h1v1H12zM15 1h4v1H15zM22 1h1v1H22zM26 1h1v1H26zM32,1 h1v1H32zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h1v1H8zM10 2h1v1H10zM12 2h1v1H12zM14 2h1v1H14zM17 2h2v1H17zM20 2h2v1H20zM23 2h1v1H23zM26 2h1v1H26zM28 2h3v1H28zM32,2 h1v1H32zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM9 3h1v1H9zM11 3h1v1H11zM14 3h1v1H14zM16 3h3v1H16zM23 3h1v1H23zM26 3h1v1H26zM28 3h3v1H28zM32,3 h1v1H32zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM9 4h1v1H9zM11 4h1v1H11zM15 4h3v1H15zM20 4h3v1H20zM24 4h1v1H24zM26 4h1v1H26zM28 4h3v1H28zM32,4 h1v1H32zM0 5h1v1H0zM6 5h1v1H6zM8 5h2v1H8zM11 5h4v1H11zM19 5h1v1H19zM26 5h1v1H26zM32,5 h1v1H32zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18 6h1v1H18zM20 6h1v1H20zM22 6h1v1H22zM24 6h1v1H24zM26,6 h7v1H26zM8 7h3v1H8zM12 7h3v1H12zM17 7h1v1H17zM19 7h4v1H19zM24 7h1v1H24zM2 8h3v1H2zM6 8h1v1H6zM8 8h4v1H8zM13 8h2v1H13zM19 8h1v1H19zM25 8h3v1H25zM30,8 h3v1H30zM0 9h1v1H0zM2 9h1v1H2zM8 9h1v1H8zM10 9h2v1H10zM14 9h1v1H14zM17 9h2v1H17zM20 9h1v1H20zM22 9h5v1H22zM29,9 h4v1H29zM0 10h4v1H0zM5 10h2v1H5zM8 10h4v1H8zM13 10h1v1H13zM16 10h5v1H16zM22 10h1v1H22zM24 10h1v1H24zM27 10h1v1H27zM29 10h2v1H29zM1 11h1v1H1zM8 11h2v1H8zM11 11h3v1H11zM16 11h1v1H16zM18 11h1v1H18zM20 11h1v1H20zM23 11h2v1H23zM26 11h1v1H26zM30 11h2v1H30zM1 12h1v1H1zM6 12h1v1H6zM8 12h2v1H8zM12 12h2v1H12zM15 12h2v1H15zM18 12h1v1H18zM20 12h5v1H20zM27 12h1v1H27zM31 12h1v1H31zM4 13h2v1H4zM7 13h1v1H7zM9 13h1v1H9zM13 13h2v1H13zM17 13h1v1H17zM19 13h1v1H19zM23 13h4v1H23zM28 13h1v1H28zM30 13h1v1H30zM32,13 h1v1H32zM0 14h3v1H0zM6 14h3v1H6zM10 14h1v1H10zM12 14h1v1H12zM16 14h2v1H16zM19 14h1v1H19zM22 14h1v1H22zM25 14h1v1H25zM27 14h2v1H27zM30 14h2v1H30zM0 15h6v1H0zM8 15h2v1H8zM12 15h4v1H12zM17 15h1v1H17zM19 15h4v1H19zM25 15h1v1H25zM28 15h1v1H28zM30 15h1v1H30zM0 16h4v1H0zM6 16h3v1H6zM10 16h1v1H10zM13 16h1v1H13zM15 16h3v1H15zM19 16h2v1H19zM24 16h1v1H24zM28 16h1v1H28zM31,16 h2v1H31zM1 17h3v1H1zM9 17h1v1H9zM11 17h2v1H11zM15 17h2v1H15zM20 17h8v1H20zM31,17 h2v1H31zM2 18h2v1H2zM5 18h3v1H5zM9 18h1v1H9zM11 18h2v1H11zM14 18h2v1H14zM18 18h3v1H18zM24 18h1v1H24zM27 18h2v1H27zM30 18h1v1H30zM1 19h2v1H1zM4 19h1v1H4zM8 19h3v1H8zM12 19h3v1H12zM17 19h1v1H17zM19 19h1v1H19zM21 19h1v1H21zM23 19h3v1H23zM27 19h1v1H27zM29 19h2v1H29zM0 20h1v1H0zM3 20h1v1H3zM5 20h2v1H5zM8 20h3v1H8zM12 20h5v1H12zM18 20h1v1H18zM20 20h1v1H20zM23 20h2v1H23zM28 20h2v1H28zM0 21h1v1H0zM2 21h2v1H2zM5 21h1v1H5zM8 21h2v1H8zM11 21h3v1H11zM15 21h2v1H15zM18 21h1v1H18zM21 21h2v1H21zM25 21h1v1H25zM27 21h1v1H27zM29,21 h4v1H29zM0 22h1v1H0zM4 22h3v1H4zM8 22h1v1H8zM11 22h4v1H11zM16 22h2v1H16zM19 22h1v1H19zM21 22h1v1H21zM23 22h2v1H23zM27 22h4v1H27zM0 23h1v1H0zM2 23h1v1H2zM4 23h1v1H4zM7 23h1v1H7zM9 23h1v1H9zM11 23h2v1H11zM14 23h2v1H14zM19 23h3v1H19zM23 23h1v1H23zM28 23h1v1H28zM30 23h2v1H30zM0 24h1v1H0zM2 24h1v1H2zM6 24h2v1H6zM9 24h2v1H9zM13 24h1v1H13zM15 24h2v1H15zM20 24h1v1H20zM23 24h6v1H23zM31 24h1v1H31zM8 25h3v1H8zM13 25h1v1H13zM15 25h3v1H15zM21 25h4v1H21zM28 25h2v1H28zM31,25 h2v1H31zM0 26h7v1H0zM10 26h1v1H10zM12 26h2v1H12zM15 26h1v1H15zM18 26h1v1H18zM20 26h2v1H20zM23 26h2v1H23zM26 26h1v1H26zM28 26h1v1H28zM0 27h1v1H0zM6 27h1v1H6zM9 27h1v1H9zM11 27h1v1H11zM15 27h1v1H15zM18 27h1v1H18zM20 27h3v1H20zM24 27h1v1H24zM28 27h1v1H28zM30 27h1v1H30zM32,27 h1v1H32zM0 28h1v1H0zM2 28h3v1H2zM6 28h1v1H6zM8 28h1v1H8zM11 28h1v1H11zM14 28h1v1H14zM17 28h2v1H17zM20 28h2v1H20zM24 28h6v1H24zM0 29h1v1H0zM2 29h3v1H2zM6 29h1v1H6zM8 29h3v1H8zM13 29h1v1H13zM15 29h2v1H15zM18 29h2v1H18zM21 29h4v1H21zM28 29h2v1H28zM31 29h1v1H31zM0 30h1v1H0zM2 30h3v1H2zM6 30h1v1H6zM8 30h1v1H8zM10 30h1v1H10zM12 30h2v1H12zM17 30h1v1H17zM20 30h2v1H20zM23 30h4v1H23zM29 30h1v1H29zM0 31h1v1H0zM6 31h1v1H6zM9 31h1v1H9zM13 31h2v1H13zM16 31h2v1H16zM20 31h4v1H20zM25 31h1v1H25zM27 31h1v1H27zM30 31h1v1H30zM0 32h7v1H0zM9 32h2v1H9zM12 32h1v1H12zM15 32h1v1H15zM17 32h6v1H17zM24 32h2v1H24zM30 32h2v1H30z"
|
||||
fill="#000000"
|
||||
/>
|
||||
<image
|
||||
height={10.3125}
|
||||
preserveAspectRatio="none"
|
||||
width={10.3125}
|
||||
x={11.34375}
|
||||
xlinkHref="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png"
|
||||
y={11.34375}
|
||||
/>
|
||||
</svg>
|
||||
</QRCodeSVG>
|
||||
</QRCode>
|
||||
</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>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
or type the following address manualy in browser address bar on any device:
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
<Blueprint3.Tooltip
|
||||
content="Click to make bigger"
|
||||
content="Click to copy"
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
position="left"
|
||||
@ -1060,7 +1271,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
boundary="scrollParent"
|
||||
canEscapeKeyClose={false}
|
||||
captureDismiss={false}
|
||||
content="Click to make bigger"
|
||||
content="Click to copy"
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
enforceFocus={false}
|
||||
@ -1102,70 +1313,68 @@ exports[`should match exact snapshot 1`] = `
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className="makeStyles-smallQRCode-12"
|
||||
id="magnify-qr-code-button"
|
||||
<Blueprint3.Button
|
||||
className=""
|
||||
icon="duplicate"
|
||||
intent="primary"
|
||||
key=".0"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<QRCode
|
||||
bgColor="rgba(0,0,0,0.0)"
|
||||
fgColor="#000000"
|
||||
imageSettings={
|
||||
<button
|
||||
className="bp3-button bp3-intent-primary"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
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,
|
||||
"borderRadius": "100px",
|
||||
}
|
||||
}
|
||||
includeMargin={false}
|
||||
level="H"
|
||||
renderAs="svg"
|
||||
size={128}
|
||||
value="http://255.255.255.255:65000/99999"
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<QRCodeSVG
|
||||
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"
|
||||
size={128}
|
||||
value="http://255.255.255.255:65000/99999"
|
||||
<Blueprint3.Icon
|
||||
icon="duplicate"
|
||||
key="leftIcon"
|
||||
>
|
||||
<svg
|
||||
height={128}
|
||||
shapeRendering="crispEdges"
|
||||
viewBox="0 0 33 33"
|
||||
width={128}
|
||||
<span
|
||||
className="bp3-icon bp3-icon-duplicate"
|
||||
icon="duplicate"
|
||||
>
|
||||
<path
|
||||
d="M0,0 h33v33H0z"
|
||||
fill="rgba(0,0,0,0.0)"
|
||||
/>
|
||||
<path
|
||||
d="M0 0h7v1H0zM10 0h1v1H10zM13 0h2v1H13zM16 0h1v1H16zM19 0h1v1H19zM23 0h2v1H23zM26,0 h7v1H26zM0 1h1v1H0zM6 1h1v1H6zM11 1h2v1H11zM14 1h2v1H14zM19 1h6v1H19zM26 1h1v1H26zM32,1 h1v1H32zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM9 2h1v1H9zM12 2h3v1H12zM17 2h1v1H17zM19 2h1v1H19zM22 2h3v1H22zM26 2h1v1H26zM28 2h3v1H28zM32,2 h1v1H32zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM9 3h1v1H9zM11 3h2v1H11zM14 3h1v1H14zM16 3h5v1H16zM23 3h1v1H23zM26 3h1v1H26zM28 3h3v1H28zM32,3 h1v1H32zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM8 4h1v1H8zM12 4h3v1H12zM16 4h5v1H16zM22 4h1v1H22zM26 4h1v1H26zM28 4h3v1H28zM32,4 h1v1H32zM0 5h1v1H0zM6 5h1v1H6zM11 5h1v1H11zM13 5h1v1H13zM17 5h5v1H17zM24 5h1v1H24zM26 5h1v1H26zM32,5 h1v1H32zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18 6h1v1H18zM20 6h1v1H20zM22 6h1v1H22zM24 6h1v1H24zM26,6 h7v1H26zM8 7h1v1H8zM11 7h1v1H11zM14 7h1v1H14zM19 7h1v1H19zM23 7h1v1H23zM2 8h2v1H2zM6 8h3v1H6zM11 8h2v1H11zM14 8h1v1H14zM16 8h1v1H16zM20 8h7v1H20zM28 8h1v1H28zM0 9h4v1H0zM5 9h1v1H5zM7 9h1v1H7zM10 9h1v1H10zM12 9h1v1H12zM14 9h1v1H14zM16 9h4v1H16zM22 9h5v1H22zM29,9 h4v1H29zM1 10h1v1H1zM3 10h1v1H3zM6 10h1v1H6zM13 10h1v1H13zM18 10h1v1H18zM23 10h1v1H23zM26 10h1v1H26zM32,10 h1v1H32zM0 11h1v1H0zM2 11h3v1H2zM7 11h1v1H7zM9 11h1v1H9zM18 11h1v1H18zM20 11h1v1H20zM23 11h1v1H23zM25 11h4v1H25zM1 12h3v1H1zM6 12h1v1H6zM8 12h1v1H8zM11 12h2v1H11zM15 12h2v1H15zM19 12h2v1H19zM23 12h2v1H23zM28 12h1v1H28zM31 12h1v1H31zM7 13h2v1H7zM11 13h2v1H11zM15 13h1v1H15zM19 13h4v1H19zM25 13h1v1H25zM29 13h1v1H29zM1 14h1v1H1zM3 14h2v1H3zM6 14h3v1H6zM13 14h1v1H13zM15 14h1v1H15zM17 14h2v1H17zM21 14h1v1H21zM24 14h1v1H24zM27 14h1v1H27zM0 15h2v1H0zM3 15h1v1H3zM5 15h1v1H5zM9 15h1v1H9zM12 15h1v1H12zM14 15h2v1H14zM17 15h6v1H17zM25 15h1v1H25zM28 15h1v1H28zM30 15h1v1H30zM32,15 h1v1H32zM4 16h3v1H4zM9 16h1v1H9zM11 16h1v1H11zM19 16h1v1H19zM21 16h1v1H21zM23 16h1v1H23zM26 16h3v1H26zM30 16h1v1H30zM32,16 h1v1H32zM3 17h3v1H3zM10 17h4v1H10zM19 17h1v1H19zM23 17h1v1H23zM26 17h1v1H26zM28 17h1v1H28zM30 17h1v1H30zM32,17 h1v1H32zM2 18h1v1H2zM4 18h1v1H4zM6 18h4v1H6zM12 18h1v1H12zM14 18h3v1H14zM18 18h3v1H18zM24 18h1v1H24zM26 18h3v1H26zM30 18h1v1H30zM1 19h1v1H1zM4 19h2v1H4zM7 19h2v1H7zM10 19h1v1H10zM13 19h1v1H13zM18 19h3v1H18zM25 19h2v1H25zM32,19 h1v1H32zM2 20h1v1H2zM4 20h1v1H4zM6 20h1v1H6zM8 20h1v1H8zM11 20h1v1H11zM14 20h1v1H14zM16 20h1v1H16zM19 20h7v1H19zM27 20h1v1H27zM29 20h3v1H29zM0 21h1v1H0zM2 21h2v1H2zM8 21h2v1H8zM11 21h3v1H11zM17 21h2v1H17zM20 21h8v1H20zM29,21 h4v1H29zM2 22h3v1H2zM6 22h1v1H6zM11 22h2v1H11zM16 22h1v1H16zM18 22h1v1H18zM20 22h1v1H20zM22 22h3v1H22zM26 22h1v1H26zM28 22h1v1H28zM32,22 h1v1H32zM1 23h3v1H1zM5 23h1v1H5zM9 23h1v1H9zM12 23h1v1H12zM14 23h4v1H14zM19 23h2v1H19zM25 23h1v1H25zM27 23h1v1H27zM0 24h1v1H0zM2 24h2v1H2zM6 24h4v1H6zM13 24h2v1H13zM16 24h4v1H16zM23 24h6v1H23zM31 24h1v1H31zM8 25h8v1H8zM19 25h4v1H19zM24 25h1v1H24zM28 25h1v1H28zM30 25h2v1H30zM0 26h7v1H0zM8 26h2v1H8zM12 26h4v1H12zM19 26h1v1H19zM22 26h3v1H22zM26 26h1v1H26zM28 26h1v1H28zM30 26h2v1H30zM0 27h1v1H0zM6 27h1v1H6zM9 27h1v1H9zM11 27h3v1H11zM16 27h2v1H16zM19 27h1v1H19zM21 27h2v1H21zM24 27h1v1H24zM28 27h1v1H28zM30 27h1v1H30zM32,27 h1v1H32zM0 28h1v1H0zM2 28h3v1H2zM6 28h1v1H6zM11 28h5v1H11zM17 28h2v1H17zM23 28h6v1H23zM30 28h1v1H30zM32,28 h1v1H32zM0 29h1v1H0zM2 29h3v1H2zM6 29h1v1H6zM8 29h3v1H8zM12 29h3v1H12zM18 29h1v1H18zM21 29h3v1H21zM25 29h1v1H25zM27 29h1v1H27zM31,29 h2v1H31zM0 30h1v1H0zM2 30h3v1H2zM6 30h1v1H6zM8 30h3v1H8zM12 30h2v1H12zM20 30h7v1H20zM30 30h1v1H30zM0 31h1v1H0zM6 31h1v1H6zM10 31h4v1H10zM15 31h2v1H15zM18 31h1v1H18zM24 31h3v1H24zM29 31h1v1H29zM32,31 h1v1H32zM0 32h7v1H0zM9 32h2v1H9zM12 32h2v1H12zM16 32h2v1H16zM20 32h1v1H20zM27 32h2v1H27zM30 32h1v1H30z"
|
||||
fill="#000000"
|
||||
/>
|
||||
<image
|
||||
height={10.3125}
|
||||
preserveAspectRatio="none"
|
||||
width={10.3125}
|
||||
x={11.34375}
|
||||
xlinkHref="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png"
|
||||
y={11.34375}
|
||||
/>
|
||||
</svg>
|
||||
</QRCodeSVG>
|
||||
</QRCode>
|
||||
</div>
|
||||
<svg
|
||||
data-icon="duplicate"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
duplicate
|
||||
</desc>
|
||||
<path
|
||||
d="M15 0H5c-.55 0-1 .45-1 1v2h2V2h8v7h-1v2h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-4 4H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-1 10H2V6h8v8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Blueprint3.Icon>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
key="text"
|
||||
>
|
||||
http://255.255.255.255:3000/
|
||||
</span>
|
||||
<Blueprint3.Icon
|
||||
key="rightIcon"
|
||||
/>
|
||||
</button>
|
||||
</Blueprint3.Button>
|
||||
</span>
|
||||
</Blueprint3.ResizeSensor>
|
||||
</InnerReference>
|
||||
@ -1189,187 +1398,12 @@ exports[`should match exact snapshot 1`] = `
|
||||
</Manager>
|
||||
</Blueprint3.Popover>
|
||||
</Blueprint3.Tooltip>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
or type the following address manualy in browser address bar on any device:
|
||||
</div>
|
||||
</Blueprint3.Text>
|
||||
</div>
|
||||
<Blueprint3.Tooltip
|
||||
content="Click to copy"
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
position="left"
|
||||
transitionDuration={100}
|
||||
>
|
||||
<Blueprint3.Popover
|
||||
autoFocus={false}
|
||||
boundary="scrollParent"
|
||||
canEscapeKeyClose={false}
|
||||
captureDismiss={false}
|
||||
content="Click to copy"
|
||||
defaultIsOpen={false}
|
||||
disabled={false}
|
||||
enforceFocus={false}
|
||||
fill={false}
|
||||
hasBackdrop={false}
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={100}
|
||||
inheritDarkTheme={true}
|
||||
interactionKind="hover-target"
|
||||
lazy={true}
|
||||
minimal={false}
|
||||
modifiers={Object {}}
|
||||
openOnTargetFocus={true}
|
||||
popoverClassName="bp3-tooltip"
|
||||
position="left"
|
||||
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=""
|
||||
icon="duplicate"
|
||||
intent="primary"
|
||||
key=".0"
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-intent-primary"
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<Blueprint3.Icon
|
||||
icon="duplicate"
|
||||
key="leftIcon"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-duplicate"
|
||||
icon="duplicate"
|
||||
>
|
||||
<svg
|
||||
data-icon="duplicate"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
duplicate
|
||||
</desc>
|
||||
<path
|
||||
d="M15 0H5c-.55 0-1 .45-1 1v2h2V2h8v7h-1v2h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-4 4H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-1 10H2V6h8v8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Blueprint3.Icon>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
key="text"
|
||||
>
|
||||
http://255.255.255.255:65000/99999
|
||||
</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>
|
||||
<Blueprint3.Dialog
|
||||
canEscapeKeyClose={true}
|
||||
canOutsideClickClose={true}
|
||||
className="makeStyles-bigQRCodeDialogRoot-14"
|
||||
id="bp3-qr-code-dialog-root"
|
||||
isOpen={false}
|
||||
onClose={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"position": "relative",
|
||||
"top": "-30px",
|
||||
}
|
||||
}
|
||||
transitionDuration={0}
|
||||
>
|
||||
<Blueprint3.Overlay
|
||||
autoFocus={true}
|
||||
backdropProps={Object {}}
|
||||
<Blueprint3.Dialog
|
||||
canEscapeKeyClose={true}
|
||||
canOutsideClickClose={true}
|
||||
className="bp3-overlay-scroll-container"
|
||||
enforceFocus={true}
|
||||
hasBackdrop={true}
|
||||
className="makeStyles-bigQRCodeDialogRoot-14"
|
||||
id="bp3-qr-code-dialog-root"
|
||||
isOpen={false}
|
||||
lazy={true}
|
||||
onClose={[Function]}
|
||||
style={
|
||||
Object {
|
||||
@ -1378,104 +1412,140 @@ exports[`should match exact snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
transitionDuration={0}
|
||||
transitionName="bp3-overlay"
|
||||
usePortal={true}
|
||||
/>
|
||||
</Blueprint3.Dialog>
|
||||
</ScanQRStep>
|
||||
<Blueprint3.Button
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<Blueprint3.Icon
|
||||
key="leftIcon"
|
||||
/>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
key="text"
|
||||
>
|
||||
Connect Test Device
|
||||
</span>
|
||||
<Blueprint3.Icon
|
||||
key="rightIcon"
|
||||
/>
|
||||
</button>
|
||||
</Blueprint3.Button>
|
||||
<Blueprint3.Button
|
||||
icon="chevron-left"
|
||||
intent="danger"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
"display": "none",
|
||||
"marginTop": "10px",
|
||||
}
|
||||
}
|
||||
text="No, I need to share other thing"
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-intent-danger"
|
||||
<Blueprint3.Overlay
|
||||
autoFocus={true}
|
||||
backdropProps={Object {}}
|
||||
canEscapeKeyClose={true}
|
||||
canOutsideClickClose={true}
|
||||
className="bp3-overlay-scroll-container"
|
||||
enforceFocus={true}
|
||||
hasBackdrop={true}
|
||||
id="bp3-qr-code-dialog-root"
|
||||
isOpen={false}
|
||||
lazy={true}
|
||||
onClose={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"position": "relative",
|
||||
"top": "-30px",
|
||||
}
|
||||
}
|
||||
transitionDuration={0}
|
||||
transitionName="bp3-overlay"
|
||||
usePortal={true}
|
||||
/>
|
||||
</Blueprint3.Dialog>
|
||||
</ScanQRStep>
|
||||
<Blueprint3.Button
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<Blueprint3.Icon
|
||||
key="leftIcon"
|
||||
/>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
key="text"
|
||||
>
|
||||
Connect Test Device
|
||||
</span>
|
||||
<Blueprint3.Icon
|
||||
key="rightIcon"
|
||||
/>
|
||||
</button>
|
||||
</Blueprint3.Button>
|
||||
<Row
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
"display": "none",
|
||||
"marginTop": "10px",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<Blueprint3.Icon
|
||||
icon="chevron-left"
|
||||
key="leftIcon"
|
||||
<div
|
||||
className="row"
|
||||
style={
|
||||
Object {
|
||||
"display": "none",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-chevron-left"
|
||||
<Blueprint3.Button
|
||||
icon="chevron-left"
|
||||
intent="danger"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
"marginTop": "10px",
|
||||
}
|
||||
}
|
||||
text="No, I need to share other thing"
|
||||
>
|
||||
<svg
|
||||
data-icon="chevron-left"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
<button
|
||||
className="bp3-button bp3-intent-danger"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
"marginTop": "10px",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<desc>
|
||||
chevron-left
|
||||
</desc>
|
||||
<path
|
||||
d="M7.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71l-4 4C5.11 7.47 5 7.72 5 8c0 .28.11.53.29.71l4 4a1.003 1.003 0 001.42-1.42L7.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
<Blueprint3.Icon
|
||||
icon="chevron-left"
|
||||
key="leftIcon"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-chevron-left"
|
||||
icon="chevron-left"
|
||||
>
|
||||
<svg
|
||||
data-icon="chevron-left"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
chevron-left
|
||||
</desc>
|
||||
<path
|
||||
d="M7.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71l-4 4C5.11 7.47 5 7.72 5 8c0 .28.11.53.29.71l4 4a1.003 1.003 0 001.42-1.42L7.41 8z"
|
||||
fillRule="evenodd"
|
||||
key="0"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Blueprint3.Icon>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
key="text"
|
||||
>
|
||||
No, I need to share other thing
|
||||
</span>
|
||||
<Blueprint3.Icon
|
||||
key="rightIcon"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Blueprint3.Icon>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
key="text"
|
||||
>
|
||||
No, I need to share other thing
|
||||
</span>
|
||||
<Blueprint3.Icon
|
||||
key="rightIcon"
|
||||
/>
|
||||
</button>
|
||||
</Blueprint3.Button>
|
||||
</div>
|
||||
</Col>
|
||||
</IntermediateStep>
|
||||
</div>
|
||||
</RevealBase>
|
||||
</Fade>
|
||||
</button>
|
||||
</Blueprint3.Button>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</Col>
|
||||
</IntermediateStep>
|
||||
</div>
|
||||
</RevealBase>
|
||||
</Fade>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</div>
|
||||
|
@ -4,4 +4,7 @@ interface Device {
|
||||
deviceOS: string;
|
||||
deviceType: string;
|
||||
deviceIP: string;
|
||||
deviceBrowser: string;
|
||||
deviceScreenWidth: number;
|
||||
deviceScreenHeight: number;
|
||||
}
|
62
app/features/ConnectedDevicesService/index.ts
Normal file
62
app/features/ConnectedDevicesService/index.ts
Normal file
@ -0,0 +1,62 @@
|
||||
const nullDevice: Device = {
|
||||
id: '',
|
||||
sharingSessionID: '',
|
||||
deviceOS: '',
|
||||
deviceType: '',
|
||||
deviceIP: '',
|
||||
deviceBrowser: '',
|
||||
deviceScreenWidth: -1,
|
||||
deviceScreenHeight: -1,
|
||||
};
|
||||
|
||||
class ConnectedDevices {
|
||||
devices: Device[] = [];
|
||||
|
||||
pendingConnectionDevice: Device = nullDevice;
|
||||
|
||||
pendingDeviceConnectedListeners: ((device: Device) => void)[] = [];
|
||||
|
||||
resetPendingConnectionDevice() {
|
||||
this.pendingConnectionDevice = nullDevice;
|
||||
}
|
||||
|
||||
getDevices() {
|
||||
return this.devices;
|
||||
}
|
||||
|
||||
removeAllDevices() {
|
||||
this.devices = [] as Device[];
|
||||
}
|
||||
|
||||
removeDeviceByID(deviceIDToRemove: string) {
|
||||
return new Promise<undefined>((resolve) => {
|
||||
this.devices = this.devices.filter((d) => {
|
||||
return d.id !== deviceIDToRemove;
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
addDevice(device: Device) {
|
||||
this.devices.push(device);
|
||||
}
|
||||
|
||||
addPendingConnectedDeviceListener(callback: (device: Device) => void) {
|
||||
this.pendingDeviceConnectedListeners.push(callback);
|
||||
}
|
||||
|
||||
setPendingConnectionDevice(device: Device) {
|
||||
this.pendingConnectionDevice = device;
|
||||
this.emitPendingConnectionDeviceConnected();
|
||||
}
|
||||
|
||||
private emitPendingConnectionDeviceConnected() {
|
||||
this.pendingDeviceConnectedListeners.forEach(
|
||||
(callback: (device: Device) => void) => {
|
||||
callback(this.pendingConnectionDevice);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ConnectedDevices;
|
7
app/features/DesktopCapturerSourcesService/DesktopCapturerSource.d.ts
vendored
Normal file
7
app/features/DesktopCapturerSourcesService/DesktopCapturerSource.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import DesktopCapturerSourceType from './DesktopCapturerSourceType';
|
||||
|
||||
interface DesktopCapturerSource {
|
||||
id: string;
|
||||
type: DesktopCapturerSourceType;
|
||||
name: string;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
enum DesktopCapturerSourceType {
|
||||
WINDOW,
|
||||
SCREEN,
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const getDesktopCapturerSourceTypeFromSourceID = (_id: string) => {
|
||||
// TODO: implement this function!
|
||||
return DesktopCapturerSourceType.WINDOW;
|
||||
};
|
||||
|
||||
export default DesktopCapturerSourceType;
|
222
app/features/DesktopCapturerSourcesService/index.ts
Normal file
222
app/features/DesktopCapturerSourcesService/index.ts
Normal file
@ -0,0 +1,222 @@
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable class-methods-use-this */
|
||||
import { desktopCapturer, DesktopCapturerSource } from 'electron';
|
||||
import Logger from '../../utils/logger';
|
||||
import DesktopCapturerSourceType from './DesktopCapturerSourceType';
|
||||
|
||||
const log = new Logger(__filename);
|
||||
|
||||
const getSourceTypeFromSourceID = (id: string): DesktopCapturerSourceType => {
|
||||
if (id.includes('screen')) {
|
||||
return DesktopCapturerSourceType.SCREEN;
|
||||
}
|
||||
return DesktopCapturerSourceType.WINDOW;
|
||||
};
|
||||
|
||||
type DesktopCapturerSourceWithType = {
|
||||
type: DesktopCapturerSourceType;
|
||||
source: DesktopCapturerSource;
|
||||
};
|
||||
type SourcesDisappearListener = (ids: string[]) => void;
|
||||
type SharingSessionID = string;
|
||||
|
||||
class DesktopCapturerSources {
|
||||
sources: Map<string, DesktopCapturerSourceWithType>;
|
||||
|
||||
lastAvailableScreenIDs: string[];
|
||||
|
||||
lastAvailableWindowIDs: string[];
|
||||
|
||||
onWindowClosedListeners: Map<SharingSessionID, SourcesDisappearListener[]>;
|
||||
|
||||
onScreenDisconnectedListeners: Map<
|
||||
SharingSessionID,
|
||||
SourcesDisappearListener[]
|
||||
>;
|
||||
|
||||
constructor() {
|
||||
this.sources = new Map<string, DesktopCapturerSourceWithType>();
|
||||
this.lastAvailableScreenIDs = [];
|
||||
this.lastAvailableWindowIDs = [];
|
||||
this.onWindowClosedListeners = new Map<
|
||||
SharingSessionID,
|
||||
SourcesDisappearListener[]
|
||||
>();
|
||||
this.onScreenDisconnectedListeners = new Map<
|
||||
SharingSessionID,
|
||||
SourcesDisappearListener[]
|
||||
>();
|
||||
|
||||
setTimeout(() => {
|
||||
setInterval(() => {
|
||||
this.refreshDesktopCapturerSources();
|
||||
}, 5000);
|
||||
}, 4000);
|
||||
this.pollForInactiveListeners();
|
||||
}
|
||||
|
||||
getSourcesMap(): Map<string, DesktopCapturerSourceWithType> {
|
||||
return this.sources;
|
||||
}
|
||||
|
||||
getScreenSources(): DesktopCapturerSource[] {
|
||||
const screenSources: DesktopCapturerSource[] = [];
|
||||
[...this.sources.keys()].forEach((key) => {
|
||||
const source = this.sources.get(key);
|
||||
if (!source) return;
|
||||
if (source.type === DesktopCapturerSourceType.SCREEN) {
|
||||
screenSources.push(source.source);
|
||||
}
|
||||
});
|
||||
return screenSources;
|
||||
}
|
||||
|
||||
getAppWindowSources(): DesktopCapturerSource[] {
|
||||
const appWindowSources: DesktopCapturerSource[] = [];
|
||||
[...this.sources.keys()].forEach((key) => {
|
||||
const source = this.sources.get(key);
|
||||
if (!source) return;
|
||||
if (source.type === DesktopCapturerSourceType.WINDOW) {
|
||||
appWindowSources.push(source.source);
|
||||
}
|
||||
});
|
||||
return appWindowSources;
|
||||
}
|
||||
|
||||
getSourceDisplayIDBySourceID(sourceID: string) {
|
||||
let displayID = '';
|
||||
[...this.sources.keys()].forEach((key) => {
|
||||
const source = this.sources.get(key);
|
||||
if (!source) return;
|
||||
if (source.source.id === sourceID) {
|
||||
displayID = source.source.display_id;
|
||||
}
|
||||
});
|
||||
return displayID;
|
||||
}
|
||||
|
||||
addWindowClosedListener(
|
||||
_sharingSessionID: string,
|
||||
_callback: SourcesDisappearListener
|
||||
) {
|
||||
// TODO: implement logic
|
||||
}
|
||||
|
||||
addScreenDisconnectedListener(
|
||||
_sharingSessionID: string,
|
||||
_callback: SourcesDisappearListener
|
||||
) {
|
||||
// TODO: implement logic
|
||||
}
|
||||
|
||||
private async updateDesktopCapturerSources() {
|
||||
this.lastAvailableScreenIDs = [];
|
||||
this.lastAvailableWindowIDs = [];
|
||||
|
||||
[...this.sources.keys()].forEach((key) => {
|
||||
const oldSource = this.sources.get(key);
|
||||
if (!oldSource) return;
|
||||
if (oldSource.type === DesktopCapturerSourceType.WINDOW) {
|
||||
this.lastAvailableWindowIDs.push(oldSource.source.id);
|
||||
} else if (oldSource.type === DesktopCapturerSourceType.SCREEN) {
|
||||
this.lastAvailableScreenIDs.push(oldSource.source.id);
|
||||
}
|
||||
});
|
||||
|
||||
this.sources = await this.getDesktopCapturerSources();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
private getDesktopCapturerSources(): Promise<
|
||||
Map<string, DesktopCapturerSourceWithType>
|
||||
> {
|
||||
return new Promise<Map<string, DesktopCapturerSourceWithType>>(
|
||||
async (resolve, reject) => {
|
||||
const newSources = new Map<string, DesktopCapturerSourceWithType>();
|
||||
try {
|
||||
const capturerSources = await desktopCapturer.getSources({
|
||||
types: ['window', 'screen'],
|
||||
thumbnailSize: { width: 500, height: 500 },
|
||||
fetchWindowIcons: true, // TODO: use window icons in app UI !
|
||||
});
|
||||
// for (const source of capturerSources) {
|
||||
// newSources.set(source.id, {
|
||||
// type: getSourceTypeFromSourceID(source.id),
|
||||
// source,
|
||||
// });
|
||||
// }
|
||||
|
||||
capturerSources.forEach((source) => {
|
||||
newSources.set(source.id, {
|
||||
type: getSourceTypeFromSourceID(source.id),
|
||||
source,
|
||||
});
|
||||
});
|
||||
// .catch((e) => {
|
||||
// console.error(e);
|
||||
// throw new Error('error getting desktopCapturer sources');
|
||||
// });
|
||||
resolve(newSources);
|
||||
} catch (e) {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private refreshDesktopCapturerSources() {
|
||||
// TODO: implement get available sources logic here;
|
||||
this.updateDesktopCapturerSources()
|
||||
// eslint-disable-next-line promise/always-return
|
||||
.then(() => {
|
||||
// eventually run checkers that emit events
|
||||
this.checkForClosedWindows();
|
||||
this.checkForScreensDisconnected();
|
||||
})
|
||||
.catch((e) => {
|
||||
log.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
private pollForInactiveListeners() {
|
||||
// TODO: implement logic
|
||||
// if session ID no longer exists in SharingSessionsService -> remove its listener object
|
||||
|
||||
setTimeout(() => {
|
||||
this.pollForInactiveListeners();
|
||||
}, 1000 * 60 * 60); // runs every hour in infinite loop
|
||||
}
|
||||
|
||||
private checkForClosedWindows() {
|
||||
// TODO: implement logic
|
||||
const isSomeWindowsClosed = false;
|
||||
const closedWindowsIDs: string[] = [];
|
||||
|
||||
if (isSomeWindowsClosed) {
|
||||
this.notifyOnWindowsClosedListeners(closedWindowsIDs);
|
||||
}
|
||||
}
|
||||
|
||||
private notifyOnWindowsClosedListeners(_closedWindowsIDs: string[]) {
|
||||
// TODO: implement logic
|
||||
}
|
||||
|
||||
private checkForScreensDisconnected() {
|
||||
// TODO: implement logic
|
||||
const isSomeScreensDisconnected = false;
|
||||
const disconnectedScreensIDs: string[] = [];
|
||||
|
||||
if (isSomeScreensDisconnected) {
|
||||
this.notifyOnScreensDisconnectedListeners(disconnectedScreensIDs);
|
||||
}
|
||||
}
|
||||
|
||||
private notifyOnScreensDisconnectedListeners(
|
||||
_disconnectedScreensIDs: string[]
|
||||
) {
|
||||
// TODO: implement logic
|
||||
}
|
||||
}
|
||||
|
||||
export default DesktopCapturerSources;
|
@ -0,0 +1,45 @@
|
||||
export default async (
|
||||
sourceID: string,
|
||||
width: number | null | undefined = undefined,
|
||||
height: number | null | undefined = undefined,
|
||||
minSizeDivisor = 1,
|
||||
maxSizeDivisor = 1,
|
||||
minFrameRate = 15,
|
||||
maxFrameRate = 60
|
||||
) => {
|
||||
if (width && height) {
|
||||
return navigator.mediaDevices.getUserMedia({
|
||||
audio: false,
|
||||
video: {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: fine here, mandatory does not exist, it's a problem with types
|
||||
mandatory: {
|
||||
chromeMediaSource: 'desktop',
|
||||
chromeMediaSourceId: sourceID,
|
||||
|
||||
minWidth: width / minSizeDivisor,
|
||||
maxWidth: width / maxSizeDivisor,
|
||||
minHeight: height / minSizeDivisor,
|
||||
maxHeight: height / maxSizeDivisor,
|
||||
|
||||
minFrameRate,
|
||||
maxFrameRate,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({
|
||||
audio: false,
|
||||
video: {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: fine here, mandatory does not exist, it's a problem with types
|
||||
mandatory: {
|
||||
chromeMediaSource: 'desktop',
|
||||
chromeMediaSourceId: sourceID,
|
||||
minFrameRate,
|
||||
maxFrameRate,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
435
app/features/PeerConnection/index.ts
Normal file
435
app/features/PeerConnection/index.ts
Normal file
@ -0,0 +1,435 @@
|
||||
/* eslint-disable promise/catch-or-return */
|
||||
/* eslint-disable class-methods-use-this */
|
||||
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
||||
import { remote, ipcRenderer } from 'electron';
|
||||
import uuid from 'uuid';
|
||||
import SimplePeer from 'simple-peer';
|
||||
import {
|
||||
prepare as prepareMessage,
|
||||
process as processMessage,
|
||||
} from '../../utils/message';
|
||||
import DeskreenCrypto from '../../utils/crypto';
|
||||
import ConnectedDevicesService from '../ConnectedDevicesService';
|
||||
import SharingSessionStatusEnum from '../SharingSessionsService/SharingSessionStatusEnum';
|
||||
import RoomIDService from '../../server/RoomIDService';
|
||||
import SharingSessionsService from '../SharingSessionsService';
|
||||
import connectSocket from '../../server/connectSocket';
|
||||
import Logger from '../../utils/logger';
|
||||
import DesktopCapturerSources from '../DesktopCapturerSourcesService';
|
||||
import setSdpMediaBitrate from './setSdpMediaBitrate';
|
||||
import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
|
||||
|
||||
const log = new Logger(__filename);
|
||||
|
||||
interface PartnerPeerUser {
|
||||
username: string;
|
||||
publicKey: string;
|
||||
}
|
||||
|
||||
interface ReceiveEncryptedMessagePayload {
|
||||
payload: string;
|
||||
signature: string;
|
||||
iv: string;
|
||||
keys: { sessionKey: string; signingKey: string }[];
|
||||
}
|
||||
|
||||
interface SendEncryptedMessagePayload {
|
||||
type: string;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
type DisplaySize = { width: number; height: number };
|
||||
|
||||
const desktopCapturerSourcesService = remote.getGlobal(
|
||||
'desktopCapturerSourcesService'
|
||||
) as DesktopCapturerSources;
|
||||
|
||||
const nullUser = { username: '', publicKey: '', privateKey: '' };
|
||||
const nullSimplePeer = new SimplePeer();
|
||||
|
||||
export default class PeerConnection {
|
||||
sharingSessionID: string;
|
||||
roomID: string;
|
||||
socket: SocketIOClient.Socket;
|
||||
crypto: DeskreenCrypto;
|
||||
user: LocalPeerUser;
|
||||
partner: PartnerPeerUser;
|
||||
peer = nullSimplePeer;
|
||||
desktopCapturerSourceID: string;
|
||||
localStream: MediaStream | null;
|
||||
isSocketRoomLocked: boolean;
|
||||
partnerDeviceDetails = {} as Device;
|
||||
signalsDataToCallUser: string[];
|
||||
isCallStarted: boolean;
|
||||
roomIDService: RoomIDService;
|
||||
connectedDevicesService: ConnectedDevicesService;
|
||||
sharingSessionsService: SharingSessionsService;
|
||||
onDeviceConnectedCallback: (device: Device) => void;
|
||||
prevStreamWidth: number;
|
||||
prevStreamHeight: number;
|
||||
displayID: string;
|
||||
sourceDisplaySize: DisplaySize | undefined;
|
||||
|
||||
constructor(
|
||||
roomID: string,
|
||||
sharingSessionID: string,
|
||||
user: LocalPeerUser,
|
||||
roomIDService: RoomIDService,
|
||||
connectedDevicesService: ConnectedDevicesService,
|
||||
sharingSessionsService: SharingSessionsService
|
||||
) {
|
||||
this.roomIDService = roomIDService;
|
||||
this.connectedDevicesService = connectedDevicesService;
|
||||
this.sharingSessionsService = sharingSessionsService;
|
||||
this.sharingSessionID = sharingSessionID;
|
||||
this.isSocketRoomLocked = false;
|
||||
this.roomID = encodeURI(roomID);
|
||||
this.crypto = new DeskreenCrypto();
|
||||
this.socket = connectSocket(this.roomID);
|
||||
this.user = user;
|
||||
this.partner = nullUser;
|
||||
this.desktopCapturerSourceID = '';
|
||||
this.signalsDataToCallUser = [];
|
||||
this.isCallStarted = false;
|
||||
this.localStream = null;
|
||||
this.prevStreamWidth = -1;
|
||||
this.prevStreamHeight = -1;
|
||||
this.displayID = '';
|
||||
this.sourceDisplaySize = undefined;
|
||||
this.onDeviceConnectedCallback = () => {};
|
||||
|
||||
this.initSocketWhenUserCreatedCallback();
|
||||
}
|
||||
|
||||
setDesktopCapturerSourceID(id: string) {
|
||||
this.desktopCapturerSourceID = id;
|
||||
if (process.env.RUN_MODE === 'test') return;
|
||||
|
||||
if (id.includes('screen')) {
|
||||
this.displayID = desktopCapturerSourcesService.getSourceDisplayIDBySourceID(
|
||||
id
|
||||
);
|
||||
|
||||
if (this.displayID !== '') {
|
||||
ipcRenderer
|
||||
.invoke('get-display-size-by-display-id', this.displayID)
|
||||
.then((size: DisplaySize | 'undefined') => {
|
||||
if (size !== 'undefined') {
|
||||
this.sourceDisplaySize = size;
|
||||
}
|
||||
return size;
|
||||
})
|
||||
.then(() => {
|
||||
this.createPeer();
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.createPeer();
|
||||
}
|
||||
}
|
||||
|
||||
setOnDeviceConnectedCallback(callback: (device: Device) => void) {
|
||||
this.onDeviceConnectedCallback = callback;
|
||||
}
|
||||
|
||||
denyConnectionForPartner() {
|
||||
this.sendEncryptedMessage({
|
||||
type: 'DENY_TO_CONNECT',
|
||||
payload: {},
|
||||
});
|
||||
|
||||
this.disconnectPartner();
|
||||
}
|
||||
|
||||
sendUserAllowedToConnect() {
|
||||
this.sendEncryptedMessage({
|
||||
type: 'ALLOWED_TO_CONNECT',
|
||||
payload: {},
|
||||
});
|
||||
}
|
||||
|
||||
disconnectByHostMachineUser() {
|
||||
this.sendEncryptedMessage({
|
||||
type: 'DISCONNECT_BY_HOST_MACHINE_USER',
|
||||
payload: {},
|
||||
});
|
||||
|
||||
this.disconnectPartner();
|
||||
this.selfDestrory();
|
||||
}
|
||||
|
||||
disconnectPartner() {
|
||||
this.socket.emit('DISCONNECT_SOCKET_BY_DEVICE_IP', {
|
||||
ip: this.partnerDeviceDetails.deviceIP,
|
||||
});
|
||||
|
||||
this.partnerDeviceDetails = {} as Device;
|
||||
}
|
||||
|
||||
private initSocketWhenUserCreatedCallback() {
|
||||
this.socket.removeAllListeners();
|
||||
|
||||
this.socket.on('disconnect', () => {
|
||||
this.selfDestrory();
|
||||
});
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
// this.emitUserEnter();
|
||||
});
|
||||
|
||||
this.socket.on('USER_ENTER', (payload: { users: PartnerPeerUser[] }) => {
|
||||
const filteredPartner = payload.users.filter((user: PartnerPeerUser) => {
|
||||
return this.user.publicKey !== user.publicKey;
|
||||
});
|
||||
|
||||
if (filteredPartner[0] === undefined) return;
|
||||
|
||||
[this.partner] = filteredPartner;
|
||||
|
||||
this.sendEncryptedMessage({
|
||||
type: 'ADD_USER',
|
||||
payload: {
|
||||
username: this.user.username,
|
||||
publicKey: this.user.publicKey,
|
||||
isOwner: true,
|
||||
id: this.user.username,
|
||||
},
|
||||
});
|
||||
|
||||
if (this.partner.publicKey !== '') {
|
||||
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {
|
||||
this.isSocketRoomLocked = true;
|
||||
this.emitUserEnter();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('USER_EXIT', () => {
|
||||
if (this.isSocketRoomLocked) {
|
||||
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {});
|
||||
this.isSocketRoomLocked = false;
|
||||
|
||||
if (this.isCallStarted) {
|
||||
// TODO: display toast device is gone ....
|
||||
this.selfDestrory();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on(
|
||||
'ENCRYPTED_MESSAGE',
|
||||
(payload: ReceiveEncryptedMessagePayload) => {
|
||||
this.receiveEncryptedMessage(payload);
|
||||
}
|
||||
);
|
||||
|
||||
this.socket.on('USER_DISCONNECT', () => {
|
||||
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {});
|
||||
});
|
||||
|
||||
// socketConnection.on('TOGGLE_LOCK_ROOM', payload => {
|
||||
// this.props.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload);
|
||||
// });
|
||||
|
||||
// socketConnection.on('ROOM_LOCKED', payload => {
|
||||
// this.props.openModal('Room Locked');
|
||||
// });
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this.socket.emit('USER_DISCONNECT');
|
||||
});
|
||||
}
|
||||
|
||||
private selfDestrory() {
|
||||
this.partner = nullUser;
|
||||
this.connectedDevicesService.removeDeviceByID(this.partnerDeviceDetails.id);
|
||||
if (this.peer !== nullSimplePeer) {
|
||||
this.peer.destroy();
|
||||
}
|
||||
if (this.localStream) {
|
||||
this.localStream.getTracks().forEach((track) => {
|
||||
track.stop();
|
||||
});
|
||||
this.localStream = null;
|
||||
}
|
||||
const sharingSession = this.sharingSessionsService.sharingSessions.get(
|
||||
this.sharingSessionID
|
||||
);
|
||||
sharingSession?.setStatus(SharingSessionStatusEnum.DESTROYED);
|
||||
sharingSession?.destory();
|
||||
this.sharingSessionsService.sharingSessions.delete(this.sharingSessionID);
|
||||
this.onDeviceConnectedCallback = () => {};
|
||||
this.isCallStarted = false;
|
||||
this.socket.disconnect();
|
||||
this.roomIDService.unmarkRoomIDAsTaken(this.roomID);
|
||||
}
|
||||
|
||||
private emitUserEnter() {
|
||||
if (!this.socket) return;
|
||||
this.socket.emit('USER_ENTER', {
|
||||
username: this.user.username,
|
||||
publicKey: this.user.publicKey,
|
||||
});
|
||||
}
|
||||
|
||||
async sendEncryptedMessage(payload: SendEncryptedMessagePayload) {
|
||||
if (!this.socket) return;
|
||||
if (!this.user) return;
|
||||
if (!this.partner) return;
|
||||
const msg = await prepareMessage(payload, this.user, this.partner);
|
||||
this.socket.emit('ENCRYPTED_MESSAGE', msg.toSend);
|
||||
}
|
||||
|
||||
async receiveEncryptedMessage(payload: ReceiveEncryptedMessagePayload) {
|
||||
if (!this.user) return;
|
||||
const message = await processMessage(payload, this.user.privateKey);
|
||||
log.info(message);
|
||||
if (message.type === 'CALL_ACCEPTED') {
|
||||
this.peer.signal(message.payload.signalData);
|
||||
}
|
||||
if (message.type === 'DEVICE_DETAILS') {
|
||||
log.info(message);
|
||||
log.info(message.payload.browser);
|
||||
this.socket.emit(
|
||||
'GET_IP_BY_SOCKET_ID',
|
||||
message.payload.socketID,
|
||||
(deviceIP: string) => {
|
||||
const device = {
|
||||
id: uuid.v4(),
|
||||
deviceIP,
|
||||
deviceType: message.payload.deviceType,
|
||||
deviceOS: message.payload.os,
|
||||
deviceBrowser: message.payload.browser,
|
||||
deviceScreenWidth: message.payload.deviceScreenWidth,
|
||||
deviceScreenHeight: message.payload.deviceScreenHeight,
|
||||
sharingSessionID: this.sharingSessionID,
|
||||
};
|
||||
this.partnerDeviceDetails = device;
|
||||
this.onDeviceConnectedCallback(device);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
callPeer() {
|
||||
if (process.env.RUN_MODE === 'test') return;
|
||||
if (this.isCallStarted) return;
|
||||
this.isCallStarted = true;
|
||||
|
||||
this.signalsDataToCallUser.forEach((data: string) => {
|
||||
this.sendEncryptedMessage({
|
||||
type: 'CALL_USER',
|
||||
payload: {
|
||||
signalData: data,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createPeer() {
|
||||
this.createDesktopCapturerStream(this.desktopCapturerSourceID).then(() => {
|
||||
const peer = new SimplePeer({
|
||||
initiator: true,
|
||||
// trickle: true,
|
||||
// stream: this.localStream,
|
||||
// allowHalfTrickle: false,
|
||||
config: { iceServers: [] },
|
||||
sdpTransform: (sdp) => {
|
||||
let newSDP = sdp;
|
||||
newSDP = setSdpMediaBitrate(
|
||||
newSDP as string,
|
||||
'video',
|
||||
500000
|
||||
) as typeof sdp;
|
||||
return newSDP;
|
||||
},
|
||||
});
|
||||
|
||||
if (this.localStream !== null) {
|
||||
peer.addStream(this.localStream);
|
||||
}
|
||||
|
||||
peer.on('signal', (data: string) => {
|
||||
// fired when simple peer and webrtc done preparation to start call on this machine
|
||||
this.signalsDataToCallUser.push(data);
|
||||
});
|
||||
|
||||
this.peer = peer;
|
||||
|
||||
this.peer.on('data', async (data) => {
|
||||
if (`${data}` === 'set half quality') {
|
||||
// TODO: later on change to more sophisticated quality change for app window
|
||||
if (!this.desktopCapturerSourceID.includes('screen')) return;
|
||||
|
||||
const newStream = await getDesktopSourceStreamBySourceID(
|
||||
this.desktopCapturerSourceID,
|
||||
this.sourceDisplaySize?.width,
|
||||
this.sourceDisplaySize?.height,
|
||||
2,
|
||||
2,
|
||||
15,
|
||||
30
|
||||
);
|
||||
const newVideoTrack = newStream.getVideoTracks()[0];
|
||||
const oldTrack = this.localStream?.getVideoTracks()[0];
|
||||
|
||||
if (oldTrack && this.localStream) {
|
||||
peer.replaceTrack(oldTrack, newVideoTrack, this.localStream);
|
||||
oldTrack.stop();
|
||||
}
|
||||
} else if (`${data}` === 'set good quality') {
|
||||
// TODO: later on change to more sophisticated quality change for app window
|
||||
if (!this.desktopCapturerSourceID.includes('screen')) return;
|
||||
|
||||
const newStream = await getDesktopSourceStreamBySourceID(
|
||||
this.desktopCapturerSourceID,
|
||||
this.sourceDisplaySize?.width,
|
||||
this.sourceDisplaySize?.height,
|
||||
2,
|
||||
1
|
||||
);
|
||||
const newVideoTrack = newStream.getVideoTracks()[0];
|
||||
const oldTrack = this.localStream?.getVideoTracks()[0];
|
||||
if (oldTrack && this.localStream) {
|
||||
peer.replaceTrack(oldTrack, newVideoTrack, this.localStream);
|
||||
oldTrack.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
return peer;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: move outside this file
|
||||
createDesktopCapturerStream(sourceID: string) {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
if (process.env.RUN_MODE === 'test') resolve();
|
||||
|
||||
if (!sourceID.includes('screen')) {
|
||||
getDesktopSourceStreamBySourceID(sourceID).then((stream) => {
|
||||
this.localStream = stream;
|
||||
resolve();
|
||||
return stream;
|
||||
});
|
||||
} else {
|
||||
// when screen source id
|
||||
getDesktopSourceStreamBySourceID(
|
||||
sourceID,
|
||||
this.sourceDisplaySize?.width,
|
||||
this.sourceDisplaySize?.height,
|
||||
2,
|
||||
1
|
||||
).then((stream) => {
|
||||
this.localStream = stream;
|
||||
resolve();
|
||||
return stream;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
33
app/features/PeerConnection/setSdpMediaBitrate.ts
Normal file
33
app/features/PeerConnection/setSdpMediaBitrate.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export default (sdp: string, mediaType: string, bitrate: number) => {
|
||||
const sdpLines = sdp.split('\n');
|
||||
let mediaLineIndex = -1;
|
||||
const mediaLine = `m=${mediaType}`;
|
||||
let bitrateLineIndex = -1;
|
||||
const bitrateLine = `b=AS:${bitrate}`;
|
||||
mediaLineIndex = sdpLines.findIndex((line) => line.startsWith(mediaLine));
|
||||
|
||||
// If we find a line matching “m={mediaType}”
|
||||
if (mediaLineIndex && mediaLineIndex < sdpLines.length) {
|
||||
// Skip the media line
|
||||
bitrateLineIndex = mediaLineIndex + 1;
|
||||
|
||||
// Skip both i=* and c=* lines (bandwidths limiters have to come afterwards)
|
||||
while (
|
||||
sdpLines[bitrateLineIndex].startsWith('i=') ||
|
||||
sdpLines[bitrateLineIndex].startsWith('c=')
|
||||
) {
|
||||
bitrateLineIndex += 1;
|
||||
}
|
||||
|
||||
if (sdpLines[bitrateLineIndex].startsWith('b=')) {
|
||||
// If the next line is a b=* line, replace it with our new bandwidth
|
||||
sdpLines[bitrateLineIndex] = bitrateLine;
|
||||
} else {
|
||||
// Otherwise insert a new bitrate line.
|
||||
sdpLines.splice(bitrateLineIndex, 0, bitrateLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Then return the updated sdp content as a string
|
||||
return sdpLines.join('\n');
|
||||
};
|
71
app/features/PeerConnectionHelperRendererService/index.ts
Normal file
71
app/features/PeerConnectionHelperRendererService/index.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
||||
import path from 'path';
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
type RendererHelperWebcontentsID = number;
|
||||
|
||||
export default class RendererWebrtcHelpersService {
|
||||
helpers: Map<RendererHelperWebcontentsID, BrowserWindow>;
|
||||
appPath: string;
|
||||
|
||||
constructor(_appPath: string) {
|
||||
this.helpers = new Map<RendererHelperWebcontentsID, BrowserWindow>();
|
||||
this.appPath = _appPath;
|
||||
}
|
||||
|
||||
createPeerConnectionHelperRenderer() {
|
||||
let helperRendererWindow: BrowserWindow | null = null;
|
||||
|
||||
helperRendererWindow = new BrowserWindow({
|
||||
show: false,
|
||||
width: 300,
|
||||
height: 300,
|
||||
// x: 2147483647,
|
||||
// y: 2147483647,
|
||||
// transparent: true,
|
||||
// frame: false,
|
||||
// // skipTaskbar: true,
|
||||
// focusable: false,
|
||||
// // parent: mainWindow,
|
||||
// hasShadow: false,
|
||||
// titleBarStyle: 'hidden',
|
||||
webPreferences:
|
||||
(process.env.NODE_ENV === 'development' ||
|
||||
process.env.E2E_BUILD === 'true') &&
|
||||
process.env.ERB_SECURE !== 'true'
|
||||
? {
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
}
|
||||
: {
|
||||
preload: path.join(
|
||||
this.appPath,
|
||||
'dist/peerConnectionHelperRendererWindow.renderer.prod.js'
|
||||
),
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
});
|
||||
|
||||
helperRendererWindow.loadURL(
|
||||
`file://${this.appPath}/peerConnectionHelperRendererWindow.html`
|
||||
);
|
||||
|
||||
helperRendererWindow.webContents.on('did-finish-load', () => {
|
||||
if (!helperRendererWindow) {
|
||||
throw new Error('"helperRendererWindow" is not defined');
|
||||
}
|
||||
});
|
||||
|
||||
helperRendererWindow.on('closed', () => {
|
||||
helperRendererWindow = null;
|
||||
});
|
||||
|
||||
this.helpers.set(helperRendererWindow.webContents.id, helperRendererWindow);
|
||||
|
||||
if (process.env.NODE_ENV === 'dev') {
|
||||
helperRendererWindow.webContents.toggleDevTools();
|
||||
}
|
||||
|
||||
return helperRendererWindow;
|
||||
}
|
||||
}
|
5
app/features/SharingSessionsService/LocalPeerUser.d.ts
vendored
Normal file
5
app/features/SharingSessionsService/LocalPeerUser.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
interface LocalPeerUser {
|
||||
username: string;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
}
|
@ -1,34 +1,130 @@
|
||||
import SharingSessionService from '.';
|
||||
import SharingType from './SharingType';
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import SharingSession from './SharingSession';
|
||||
import SharingSessionStatusEnum from './SharingSessionStatusEnum';
|
||||
import SharingType from './SharingTypeEnum';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('SharingSession unit tests', () => {
|
||||
let sharingSession: SharingSessionService;
|
||||
let sharingSession: SharingSession;
|
||||
|
||||
beforeEach(() => {
|
||||
sharingSession = new SharingSessionService();
|
||||
sharingSession = new SharingSession(
|
||||
'1234',
|
||||
{
|
||||
username: '',
|
||||
privateKey: '',
|
||||
publicKey: '',
|
||||
},
|
||||
{
|
||||
// @ts-ignore: fine here
|
||||
createPeerConnectionHelperRenderer: () => {
|
||||
return {
|
||||
webContents: {
|
||||
on: () => {},
|
||||
toggleDevTools: () => {},
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when new SahringSession() is called', () => {
|
||||
it('should create sharing session with id', () => {
|
||||
describe('when new SahringSession() is created', () => {
|
||||
it('should create new SharingSession with id', () => {
|
||||
expect(sharingSession.id).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should crete sharing session with deviceID equal to "" ', () => {
|
||||
it('should crete new SharingSession with deviceID equal to "" ', () => {
|
||||
expect(sharingSession.deviceID).toBe('');
|
||||
});
|
||||
|
||||
it('should create sharing session with sharingType equal to NOT_SET', () => {
|
||||
it('should create new SharingSession with sharingType equal to NOT_SET', () => {
|
||||
expect(sharingSession.sharingType).toBe(SharingType.NOT_SET);
|
||||
});
|
||||
|
||||
it('should create sharing session with sharingStream set to null', () => {
|
||||
it('should create new SharingSession with sharingStream set to null', () => {
|
||||
expect(sharingSession.sharingStream).toBe(null);
|
||||
});
|
||||
|
||||
it('should create new SharingSession with roomID', () => {
|
||||
expect(sharingSession.roomID).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should create new SharingSession with connectedDeviceAt set to null', () => {
|
||||
expect(sharingSession.connectedDeviceAt).toBe(null);
|
||||
});
|
||||
|
||||
it('should create new SharingSession with sharingStartedAt set to null', () => {
|
||||
expect(sharingSession.sharingStartedAt).toBe(null);
|
||||
});
|
||||
|
||||
it('should create new SharingSession with status set to NOT_CONNECTED', () => {
|
||||
expect(sharingSession.status).toBe(
|
||||
SharingSessionStatusEnum.NOT_CONNECTED
|
||||
);
|
||||
});
|
||||
|
||||
it('should create new SharingSession with statusChangeListeners.length to be 0', () => {
|
||||
expect(sharingSession.statusChangeListeners.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when addStatusChangeListener is called', () => {
|
||||
it('should have statusChangeListeners.length of 1', () => {
|
||||
sharingSession.addStatusChangeListener(() => {});
|
||||
|
||||
expect(sharingSession.statusChangeListeners.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when notifyStatusChangeListeners is called', () => {
|
||||
it('should invoke all statusChangeListeners', async () => {
|
||||
const mockStatusChangeListener1 = jest.fn();
|
||||
const mockStatusChangeListener2 = jest.fn();
|
||||
sharingSession.addStatusChangeListener(mockStatusChangeListener1);
|
||||
sharingSession.addStatusChangeListener(mockStatusChangeListener2);
|
||||
|
||||
await sharingSession.notifyStatusChangeListeners();
|
||||
|
||||
expect(mockStatusChangeListener1).toBeCalled();
|
||||
expect(mockStatusChangeListener2).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setStatus is called', () => {
|
||||
it('should invoke notifyStatusChangeListeners', async () => {
|
||||
const mockNotifyStatusChangeListeners = jest.fn();
|
||||
sharingSession.notifyStatusChangeListeners = mockNotifyStatusChangeListeners;
|
||||
|
||||
sharingSession.setStatus(SharingSessionStatusEnum.CONNECTED);
|
||||
|
||||
expect(mockNotifyStatusChangeListeners).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when updateStatus is called with SharingSessionStatus argument', () => {
|
||||
it('should set SharingSession.status as passed in updateStatus argument', async () => {
|
||||
sharingSession.setStatus(SharingSessionStatusEnum.CONNECTED);
|
||||
|
||||
expect(sharingSession.status).toBe(SharingSessionStatusEnum.CONNECTED);
|
||||
|
||||
sharingSession.setStatus(SharingSessionStatusEnum.SHARING);
|
||||
|
||||
expect(sharingSession.status).toBe(SharingSessionStatusEnum.SHARING);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setDeviceID is called with deviceID argument', () => {
|
||||
it('should set SharingSession.deviceID as in setDeviceID passed argument', async () => {
|
||||
const testDeviceID = '8989';
|
||||
sharingSession.setDeviceID(testDeviceID);
|
||||
|
||||
expect(sharingSession.deviceID).toBe(testDeviceID);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
131
app/features/SharingSessionsService/SharingSession.ts
Normal file
131
app/features/SharingSessionsService/SharingSession.ts
Normal file
@ -0,0 +1,131 @@
|
||||
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
||||
import { BrowserWindow } from 'electron';
|
||||
import uuid from 'uuid';
|
||||
import SharingSessionStatusEnum from './SharingSessionStatusEnum';
|
||||
import SharingTypeEnum from './SharingTypeEnum';
|
||||
import PeerConnectionHelperRendererService from '../PeerConnectionHelperRendererService';
|
||||
|
||||
type OnDeviceConnectedCallbackType = (device: Device) => void;
|
||||
|
||||
export default class SharingSession {
|
||||
id: string;
|
||||
deviceID: string;
|
||||
sharingType: SharingTypeEnum;
|
||||
sharingStream: MediaStream | null;
|
||||
roomID: string;
|
||||
connectedDeviceAt: Date | null;
|
||||
sharingStartedAt: Date | null;
|
||||
status: SharingSessionStatusEnum;
|
||||
statusChangeListeners: SharingSessionStatusChangeListener[];
|
||||
peerConnectionHelperRenderer: BrowserWindow | undefined;
|
||||
onDeviceConnectedCallback: OnDeviceConnectedCallbackType;
|
||||
desktopCapturerSourceID: string;
|
||||
|
||||
constructor(
|
||||
_roomID: string,
|
||||
user: LocalPeerUser,
|
||||
peerConnectionHelperRendererService: PeerConnectionHelperRendererService
|
||||
) {
|
||||
this.id = uuid.v4();
|
||||
this.deviceID = '';
|
||||
this.sharingType = SharingTypeEnum.NOT_SET;
|
||||
this.sharingStream = null;
|
||||
this.roomID = _roomID;
|
||||
this.connectedDeviceAt = null;
|
||||
this.sharingStartedAt = null;
|
||||
this.status = SharingSessionStatusEnum.NOT_CONNECTED;
|
||||
this.statusChangeListeners = [] as SharingSessionStatusChangeListener[];
|
||||
this.desktopCapturerSourceID = '';
|
||||
this.onDeviceConnectedCallback = (() => {}) as OnDeviceConnectedCallbackType;
|
||||
|
||||
if (process.env.RUN_MODE === 'test') return;
|
||||
|
||||
this.peerConnectionHelperRenderer = peerConnectionHelperRendererService.createPeerConnectionHelperRenderer();
|
||||
|
||||
this.peerConnectionHelperRenderer.webContents.on('did-finish-load', () => {
|
||||
this.peerConnectionHelperRenderer?.webContents.send(
|
||||
'create-peer-connection-with-data',
|
||||
{
|
||||
roomID: this.roomID,
|
||||
sharingSessionID: this.id,
|
||||
user,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.peerConnectionHelperRenderer.webContents.on(
|
||||
'ipc-message',
|
||||
(_, channel, data) => {
|
||||
if (channel === 'peer-connected') {
|
||||
if (this.onDeviceConnectedCallback) {
|
||||
this.onDeviceConnectedCallback(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.statusChangeListeners.push(() => {
|
||||
if (this.status === SharingSessionStatusEnum.CONNECTED) {
|
||||
this.peerConnectionHelperRenderer?.webContents.send(
|
||||
'send-user-allowed-to-connect'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destory() {
|
||||
this.peerConnectionHelperRenderer?.close();
|
||||
}
|
||||
|
||||
setOnDeviceConnectedCallback(callback: (device: Device) => void) {
|
||||
this.onDeviceConnectedCallback = callback;
|
||||
}
|
||||
|
||||
setDesktopCapturerSourceID(id: string) {
|
||||
this.desktopCapturerSourceID = id;
|
||||
if (process.env.RUN_MODE === 'test') return;
|
||||
this.peerConnectionHelperRenderer?.webContents.send(
|
||||
'set-desktop-capturer-source-id',
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
callPeer() {
|
||||
if (process.env.RUN_MODE === 'test') return;
|
||||
this.peerConnectionHelperRenderer?.webContents.send('call-peer');
|
||||
}
|
||||
|
||||
disconnectByHostMachineUser() {
|
||||
this.peerConnectionHelperRenderer?.webContents.send(
|
||||
'disconnect-by-host-machine-user'
|
||||
);
|
||||
}
|
||||
|
||||
denyConnectionForPartner() {
|
||||
this.peerConnectionHelperRenderer?.webContents.send(
|
||||
'deny-connection-for-partner'
|
||||
);
|
||||
}
|
||||
|
||||
addStatusChangeListener(callback: SharingSessionStatusChangeListener): void {
|
||||
this.statusChangeListeners.push(callback);
|
||||
}
|
||||
|
||||
notifyStatusChangeListeners(): Promise<undefined> {
|
||||
return new Promise((resolve) => {
|
||||
for (let i = 0; i < this.statusChangeListeners.length; i += 1) {
|
||||
this.statusChangeListeners[i](this.id);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
setStatus(newStatus: SharingSessionStatusEnum) {
|
||||
this.status = newStatus;
|
||||
this.notifyStatusChangeListeners();
|
||||
}
|
||||
|
||||
setDeviceID(deviceID: string): void {
|
||||
this.deviceID = deviceID;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
import SharingSessionStatusEnum from './SharingSessionStatusEnum';
|
||||
import SharingSession from './SharingSession';
|
||||
import SharingSessionService from '.';
|
||||
import RoomIDService from '../../server/RoomIDService';
|
||||
import ConnectedDevicesService from '../ConnectedDevicesService';
|
||||
import PeerConnectionHelperRendererService from '../PeerConnectionHelperRendererService';
|
||||
|
||||
// this may look as an ugly mock, but hey, this works! and don't forget that it is a unit test
|
||||
// why do we make it like that ? bacuse jest doesnt allow ex.
|
||||
// duplicated __mock__/electron in different subfolders of the project, so.. better do mainual mock in a test file itself
|
||||
// jest bug reference on duplicated mocks found: https://github.com/facebook/jest/issues/2070
|
||||
// it is a bad design of jest itself by default, so this is the best workaround, simply by making manual mock in this way:
|
||||
jest.mock('../PeerConnectionHelperRendererService', () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
createPeerConnectionHelper: () => {
|
||||
return {
|
||||
webContents: {
|
||||
on: () => {},
|
||||
toggleDevTools: () => {},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('SharingSessionService unit tests', () => {
|
||||
let sharingSessionService: SharingSessionService;
|
||||
|
||||
beforeEach(() => {
|
||||
sharingSessionService = new SharingSessionService(
|
||||
new RoomIDService(),
|
||||
new ConnectedDevicesService(),
|
||||
new PeerConnectionHelperRendererService('')
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when new SharingSessionService() is created', () => {
|
||||
it('should have empty sharingSessions Map', () => {
|
||||
expect(sharingSessionService.sharingSessions.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should have waitingForConnectionSharingSession set to null', () => {
|
||||
expect(sharingSessionService.waitingForConnectionSharingSession).toBe(
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
it('should have pollForInactiveSessions be called', () => {
|
||||
const backup = SharingSessionService.prototype.pollForInactiveSessions;
|
||||
|
||||
const mockPollForInactiveSessions = jest.fn();
|
||||
jest
|
||||
.spyOn(SharingSessionService.prototype, 'pollForInactiveSessions')
|
||||
.mockImplementation(mockPollForInactiveSessions);
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new SharingSessionService(
|
||||
new RoomIDService(),
|
||||
new ConnectedDevicesService(),
|
||||
new PeerConnectionHelperRendererService('')
|
||||
);
|
||||
|
||||
jest.advanceTimersByTime(1000 * 60 * 60 * 30); // thirty hours later
|
||||
|
||||
expect(mockPollForInactiveSessions).toBeCalled();
|
||||
|
||||
SharingSessionService.prototype.pollForInactiveSessions = backup;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when createNewSharingSession is called', () => {
|
||||
it('should have sharingSessions Map with size equal to 1', async () => {
|
||||
await sharingSessionService.createNewSharingSession('');
|
||||
|
||||
expect(sharingSessionService.sharingSessions.size).toBe(1);
|
||||
});
|
||||
|
||||
it('should have returned SharingSession object', async () => {
|
||||
expect(sharingSessionService.createNewSharingSession('')).toBeInstanceOf(
|
||||
SharingSession
|
||||
);
|
||||
|
||||
const sharingSession = await sharingSessionService.createNewSharingSession(
|
||||
''
|
||||
);
|
||||
expect(sharingSession).toBeInstanceOf(SharingSession);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when pollForInactiveSessions is called', () => {
|
||||
it('should have removed SharingSession with status ERROR from sharingSessions Map', async () => {
|
||||
const testSharingSession = await sharingSessionService.createNewSharingSession(
|
||||
''
|
||||
);
|
||||
testSharingSession.status = SharingSessionStatusEnum.ERROR;
|
||||
|
||||
sharingSessionService.pollForInactiveSessions();
|
||||
|
||||
expect(sharingSessionService.sharingSessions.size).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,33 +0,0 @@
|
||||
import SharingType from './SharingType';
|
||||
|
||||
export default interface SharingSessionType {
|
||||
id: string;
|
||||
deviceID: string;
|
||||
sharingType: SharingType;
|
||||
sharingStream: MediaStream | null;
|
||||
roomID: string;
|
||||
connectedDeviceAt: Date;
|
||||
sharingStartedAt: Date;
|
||||
status: SharingSessionStatus;
|
||||
statusObserverCallbacks: SharingSessionStatusObserverCallback[];
|
||||
notifyStatusObservers: () => void;
|
||||
updateStatus: (newStatus: SharingSessionStatus) => void;
|
||||
setDevice: (id: string) => void; // updates connectedDeviceAt with timestamp
|
||||
setSharingStream: (stream: MediaStream) => void;
|
||||
getSharingStreamForUsage: () => MediaStream;
|
||||
setSharingType: (type: SharingType) => void;
|
||||
addStatusObserverCallback: (
|
||||
callback: SharingSessionStatusObserverCallback
|
||||
) => void;
|
||||
}
|
||||
|
||||
export enum SharingSessionStatus {
|
||||
NOT_CONNECTED,
|
||||
CONNECTED,
|
||||
SHARING,
|
||||
ERROR,
|
||||
}
|
||||
|
||||
export type SharingSessionStatusObserverCallback = (
|
||||
sharingSessionID: string
|
||||
) => void;
|
1
app/features/SharingSessionsService/SharingSessionStatusChangeListener.d.ts
vendored
Normal file
1
app/features/SharingSessionsService/SharingSessionStatusChangeListener.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
type SharingSessionStatusChangeListener = (sharingSessionID: string) => void;
|
@ -0,0 +1,9 @@
|
||||
enum SharingSessionStatusEnum {
|
||||
NOT_CONNECTED,
|
||||
CONNECTED,
|
||||
SHARING,
|
||||
ERROR,
|
||||
DESTROYED,
|
||||
}
|
||||
|
||||
export default SharingSessionStatusEnum;
|
@ -1,6 +0,0 @@
|
||||
interface SharingSessionService {
|
||||
sharingSessions: SharingSession[];
|
||||
createNewSharingSession: () => SharingSession;
|
||||
pollForInactiveSessions: () => void;
|
||||
getSharingSessionByID: (id: string) => SharingSession;
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
enum SharingType {
|
||||
NOT_SET,
|
||||
SCREEN,
|
||||
APP,
|
||||
}
|
||||
|
||||
export default SharingType;
|
7
app/features/SharingSessionsService/SharingTypeEnum.ts
Normal file
7
app/features/SharingSessionsService/SharingTypeEnum.ts
Normal file
@ -0,0 +1,7 @@
|
||||
enum SharingTypeEnum {
|
||||
NOT_SET,
|
||||
SCREEN,
|
||||
APP,
|
||||
}
|
||||
|
||||
export default SharingTypeEnum;
|
@ -0,0 +1,7 @@
|
||||
export default class PeerConnection {
|
||||
roomID: string;
|
||||
|
||||
constructor(roomID: string) {
|
||||
this.roomID = roomID;
|
||||
}
|
||||
}
|
3
app/features/SharingSessionsService/__mocks__/shortid.ts
Normal file
3
app/features/SharingSessionsService/__mocks__/shortid.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default () => {
|
||||
return '1234';
|
||||
};
|
@ -1,36 +1,116 @@
|
||||
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import uuid from 'uuid';
|
||||
import SharingSessionServiceType, {
|
||||
SharingSessionStatus,
|
||||
SharingSessionStatusObserverCallback,
|
||||
} from './SharingSessionServiceType';
|
||||
import SharingType from './SharingType';
|
||||
import RoomIDService from '../../server/RoomIDService';
|
||||
import DeskreenCrypto from '../../utils/crypto';
|
||||
import ConnectedDevicesService from '../ConnectedDevicesService';
|
||||
import RendererWebrtcHelpersService from '../PeerConnectionHelperRendererService';
|
||||
import SharingSession from './SharingSession';
|
||||
import SharingSessionStatusEnum from './SharingSessionStatusEnum';
|
||||
|
||||
export default class SharingSessionService
|
||||
implements SharingSessionServiceType {
|
||||
id: string;
|
||||
deviceID: string;
|
||||
sharingType: SharingType;
|
||||
sharingStream: MediaStream | null;
|
||||
roomID: string;
|
||||
connectedDeviceAt: Date;
|
||||
sharingStartedAt: Date;
|
||||
status: SharingSessionStatus;
|
||||
statusObserverCallbacks: SharingSessionStatusObserverCallback[];
|
||||
notifyStatusObservers: () => void;
|
||||
updateStatus: (newStatus: SharingSessionStatus) => void;
|
||||
setDevice: (id: string) => void;
|
||||
setSharingStream: (stream: MediaStream) => void;
|
||||
getSharingStreamForUsage: () => MediaStream;
|
||||
setSharingType: (type: SharingType) => void;
|
||||
addStatusObserverCallback: (
|
||||
callback: SharingSessionStatusObserverCallback
|
||||
) => void;
|
||||
export default class SharingSessionService {
|
||||
crypto: DeskreenCrypto;
|
||||
user: LocalPeerUser | null;
|
||||
sharingSessions: Map<string, SharingSession>;
|
||||
waitingForConnectionSharingSession: SharingSession | null;
|
||||
roomIDService: RoomIDService;
|
||||
connectedDevicesService: ConnectedDevicesService;
|
||||
rendererWebrtcHelpersService: RendererWebrtcHelpersService;
|
||||
isCreatingNewSharingSession: boolean;
|
||||
|
||||
constructor() {
|
||||
this.id = uuid.v4();
|
||||
this.deviceID = '';
|
||||
this.sharingType = SharingType.NOT_SET;
|
||||
this.sharingStream = null;
|
||||
constructor(
|
||||
_roomIDService: RoomIDService,
|
||||
_connectedDevicesService: ConnectedDevicesService,
|
||||
_rendererWebrtcHelpersService: RendererWebrtcHelpersService
|
||||
) {
|
||||
this.roomIDService = _roomIDService;
|
||||
this.connectedDevicesService = _connectedDevicesService;
|
||||
this.rendererWebrtcHelpersService = _rendererWebrtcHelpersService;
|
||||
this.crypto = new DeskreenCrypto();
|
||||
this.waitingForConnectionSharingSession = null;
|
||||
this.sharingSessions = new Map<string, SharingSession>();
|
||||
this.user = null;
|
||||
this.isCreatingNewSharingSession = false;
|
||||
this.createUser();
|
||||
|
||||
setInterval(() => {
|
||||
this.pollForInactiveSessions();
|
||||
}, 1000 * 60 * 60); // every hour
|
||||
}
|
||||
|
||||
createUser(): Promise<undefined> {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve) => {
|
||||
if (process.env.RUN_MODE === 'test') resolve();
|
||||
const username = uuid.v4();
|
||||
|
||||
const encryptDecryptKeys = await this.crypto.createEncryptDecryptKeys();
|
||||
const exportedEncryptDecryptPrivateKey = await this.crypto.exportKey(
|
||||
encryptDecryptKeys.privateKey
|
||||
);
|
||||
const exportedEncryptDecryptPublicKey = await this.crypto.exportKey(
|
||||
encryptDecryptKeys.publicKey
|
||||
);
|
||||
|
||||
this.user = {
|
||||
username,
|
||||
privateKey: exportedEncryptDecryptPrivateKey,
|
||||
publicKey: exportedEncryptDecryptPublicKey,
|
||||
};
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
createWaitingForConnectionSharingSession(roomID?: string) {
|
||||
return new Promise<SharingSession>((resolve) => {
|
||||
return this.waitWhileUserIsNotCreated().then(() => {
|
||||
this.waitingForConnectionSharingSession = this.createNewSharingSession(
|
||||
roomID || ''
|
||||
);
|
||||
resolve(this.waitingForConnectionSharingSession);
|
||||
return this.waitingForConnectionSharingSession;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createNewSharingSession(_roomID: string): SharingSession {
|
||||
const roomID = _roomID || this.roomIDService.getSimpleAvailableRoomID();
|
||||
const sharingSession = new SharingSession(
|
||||
roomID,
|
||||
this.user as LocalPeerUser,
|
||||
this.rendererWebrtcHelpersService
|
||||
);
|
||||
this.sharingSessions.set(sharingSession.id, sharingSession);
|
||||
return sharingSession;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
changeSharingSessionStatusToSharing(sharingSession: SharingSession) {
|
||||
sharingSession.status = SharingSessionStatusEnum.SHARING;
|
||||
this.roomIDService.markRoomIDAsTaken(sharingSession.roomID);
|
||||
}
|
||||
|
||||
pollForInactiveSessions(): void {
|
||||
[...this.sharingSessions.keys()].forEach((key) => {
|
||||
// @ts-ignore
|
||||
const { status } = this.sharingSessions.get(key);
|
||||
if (
|
||||
status === SharingSessionStatusEnum.ERROR ||
|
||||
status === SharingSessionStatusEnum.DESTROYED
|
||||
) {
|
||||
this.sharingSessions.delete(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
waitWhileUserIsNotCreated(): Promise<undefined> {
|
||||
return new Promise((resolve) => {
|
||||
const currentInterval = setInterval(() => {
|
||||
if (this.user !== null) {
|
||||
resolve();
|
||||
clearInterval(currentInterval);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable no-new */
|
||||
import { ipcRenderer } from 'electron';
|
||||
import React, { Fragment, Suspense } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { AppContainer as ReactHotAppContainer } from 'react-hot-loader';
|
||||
@ -36,3 +36,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('root')
|
||||
);
|
||||
});
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
ipcRenderer.invoke('main-window-onbeforeunload');
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint global-require: off, no-console: off */
|
||||
|
||||
/**
|
||||
@ -11,22 +10,25 @@
|
||||
*/
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import { Display } from 'electron/main';
|
||||
import path from 'path';
|
||||
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
import { app, BrowserWindow, ipcMain, screen } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import log from 'electron-log';
|
||||
import settings from 'electron-settings';
|
||||
import i18n from './configs/i18next.config';
|
||||
import signalingServer from './server';
|
||||
import MenuBuilder from './menu';
|
||||
import initGlobals from './mainProcessHelpers/initGlobals';
|
||||
import ConnectedDevicesService from './features/ConnectedDevicesService';
|
||||
import RoomIDService from './server/RoomIDService';
|
||||
import SharingSession from './features/SharingSessionsService/SharingSession';
|
||||
import getDeskreenGlobal from './mainProcessHelpers/getDeskreenGlobal';
|
||||
|
||||
const globalAny: any = global;
|
||||
globalAny.appPath = __dirname;
|
||||
initGlobals(__dirname);
|
||||
|
||||
signalingServer.start();
|
||||
|
||||
log.error(process.platform);
|
||||
|
||||
export default class AppUpdater {
|
||||
constructor() {
|
||||
log.transports.file.level = 'info';
|
||||
@ -71,21 +73,22 @@ const createWindow = async () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
show: false,
|
||||
width: 820,
|
||||
height: 480,
|
||||
// maxWidth: 820,
|
||||
// maxHeight: 480,
|
||||
height: 540,
|
||||
minHeight: 400,
|
||||
minWidth: 600,
|
||||
titleBarStyle: 'hiddenInset',
|
||||
useContentSize: true,
|
||||
webPreferences:
|
||||
(process.env.NODE_ENV === 'development' ||
|
||||
process.env.E2E_BUILD === 'true') &&
|
||||
process.env.ERB_SECURE !== 'true'
|
||||
? {
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
}
|
||||
: {
|
||||
preload: path.join(__dirname, 'dist/renderer.prod.js'),
|
||||
preload: path.join(__dirname, 'dist/mainWindow.renderer.prod.js'),
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -107,12 +110,18 @@ const createWindow = async () => {
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
// TODO: when app will be set to auto start on login, this will be not required,
|
||||
// TODO: the app will run until user didn't kill it in system tray
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
// mainWindow.webContents.toggleDevTools();
|
||||
|
||||
menuBuilder = new MenuBuilder(mainWindow, i18n);
|
||||
menuBuilder.buildMenu();
|
||||
|
||||
// i18n.on('loaded', (loaded) => {
|
||||
i18n.on('loaded', () => {
|
||||
i18n.changeLanguage('en');
|
||||
i18n.off('loaded');
|
||||
@ -140,6 +149,8 @@ const createWindow = async () => {
|
||||
*/
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// TODO: when app will be set to auto start on login, this will be not required,
|
||||
// TODO: the app will run until user didn't kill it in system tray
|
||||
// Respect the OSX convention of having the application in memory even
|
||||
// after all windows have been closed
|
||||
if (process.platform !== 'darwin') {
|
||||
@ -169,3 +180,42 @@ ipcMain.on('client-changed-language', async (_, newLangCode) => {
|
||||
i18n.changeLanguage(newLangCode);
|
||||
await settings.set('appLanguage', newLangCode);
|
||||
});
|
||||
|
||||
ipcMain.handle('get-all-displays', () => {
|
||||
return screen.getAllDisplays();
|
||||
});
|
||||
|
||||
ipcMain.handle('get-display-size-by-display-id', (_, displayID: string) => {
|
||||
const display = screen.getAllDisplays().find((d: Display) => {
|
||||
return `${d.id}` === displayID;
|
||||
});
|
||||
|
||||
if (display) {
|
||||
return display.size;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
ipcMain.handle('main-window-onbeforeunload', () => {
|
||||
const deskreenGlobal = getDeskreenGlobal();
|
||||
deskreenGlobal.connectedDevicesService = new ConnectedDevicesService();
|
||||
deskreenGlobal.roomIDService = new RoomIDService();
|
||||
deskreenGlobal.sharingSessionService.sharingSessions.forEach(
|
||||
(sharingSession: SharingSession) => {
|
||||
sharingSession.denyConnectionForPartner();
|
||||
sharingSession.destory();
|
||||
}
|
||||
);
|
||||
|
||||
deskreenGlobal.rendererWebrtcHelpersService.helpers.forEach(
|
||||
(helperWindow) => {
|
||||
helperWindow.close();
|
||||
}
|
||||
);
|
||||
|
||||
deskreenGlobal.sharingSessionService.waitingForConnectionSharingSession = null;
|
||||
deskreenGlobal.rendererWebrtcHelpersService.helpers.clear();
|
||||
deskreenGlobal.sharingSessionService.sharingSessions.clear();
|
||||
});
|
||||
|
||||
app.commandLine.appendSwitch('webrtc-max-cpu-consumption-percentage', '100');
|
||||
|
@ -5,10 +5,6 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* base64id v0.1.0
|
||||
*/
|
||||
|
||||
/*!
|
||||
* body-parser
|
||||
* Copyright(c) 2014 Jonathan Ong
|
||||
@ -295,8 +291,6 @@
|
||||
|
||||
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
|
||||
|
||||
/*! https://mths.be/utf8js v2.1.2 by @mathias */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Lodash <https://lodash.com/>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user