Merge branch 'master' into SDA-2651

This commit is contained in:
mattias-symphony 2020-11-06 08:13:42 +01:00 committed by GitHub
commit 16c3ebf741
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2545 additions and 1561 deletions

19
.vscode/launch.json vendored
View File

@ -10,7 +10,9 @@
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.exe"
},
"args": ["."],
"args": [
"."
],
"env": {
"ELECTRON_DEBUGGING": "true",
"ELECTRON_DEV": "true"
@ -30,7 +32,10 @@
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.exe"
},
"args": [".", "--url=https://corporate.symphony.com"],
"args": [
".",
"--url=https://corporate.symphony.com"
],
"env": {
"ELECTRON_DEBUGGING": "true",
"ELECTRON_DEV": "true"
@ -50,7 +55,10 @@
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.exe"
},
"args": [".", "--url=https://local-dev.symphony.com:9090"],
"args": [
".",
"--url=https://local-dev.symphony.com:9090"
],
"env": {
"ELECTRON_DEBUGGING": "true",
"ELECTRON_DEV": "true"
@ -70,7 +78,10 @@
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.exe"
},
"args": [".", "--url=${workspaceFolder}/src/demo/index.html"],
"args": [
".",
"--url=file://${workspaceFolder}/src/demo/index.html"
],
"env": {
"ELECTRON_DEBUGGING": "true",
"ELECTRON_DEV": "true"

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"npm.packageManager": "npm",
"typescript.tsdk": "./node_modules/typescript/lib",
"editor.formatOnSave": true,
"jestrunner.configPath": "jest-config.json",
}

View File

@ -159,6 +159,7 @@
"electron-spellchecker": "git+https://github.com/symphonyoss/electron-spellchecker.git#v2.0.4",
"ffi-napi": "3.0.0",
"filesize": "6.1.0",
"image-size": "^0.9.3",
"react": "16.13.0",
"react-dom": "16.13.0",
"ref-napi": "1.4.3",

View File

@ -0,0 +1,65 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Snipping Tool should render correctly 1`] = `
<div
className="SnippingTool"
lang="en-US"
>
<header>
<div
className="DrawActions"
>
<button
className="ActionButton"
onClick={[Function]}
>
<img
src="../renderer/assets/snip-draw.svg"
/>
</button>
<button
className="ActionButton"
onClick={[Function]}
>
<img
src="../renderer/assets/snip-highlight.svg"
/>
</button>
<button
className="ActionButton"
onClick={[Function]}
>
<img
src="../renderer/assets/snip-erase.svg"
/>
</button>
</div>
<div
className="ClearActions"
>
<button
className="ClearButton"
onClick={[Function]}
>
Clear
</button>
</div>
</header>
<main>
<img
alt="Screen snippet"
className="SnippetImage"
height={600}
src="Screen-Snippet"
width={800}
/>
</main>
<footer>
<button
onClick={[Function]}
>
Done
</button>
</footer>
</div>
`;

20
spec/snippingTool.spec.ts Normal file
View File

@ -0,0 +1,20 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import SnippingTool from '../src/renderer/components/snipping-tool';
import { ipcRenderer } from './__mocks__/electron';
describe('Snipping Tool', () => {
const snippingToolLabel = 'snipping-tool-data';
const onLabelEvent = 'on';
it('should render correctly', () => {
const wrapper = shallow(React.createElement(SnippingTool));
expect(wrapper).toMatchSnapshot();
});
it('should call `snipping-tool-data` event when component is mounted', () => {
const spy = jest.spyOn(ipcRenderer, onLabelEvent);
shallow(React.createElement(SnippingTool));
expect(spy).toBeCalledWith(snippingToolLabel, expect.any(Function));
});
});

View File

@ -1,12 +1,19 @@
import { app, BrowserWindow } from 'electron';
import { app, BrowserWindow, ipcMain } from 'electron';
import * as fs from 'fs';
import sizeOf from 'image-size';
import * as os from 'os';
import * as path from 'path';
import { ChildProcess, ExecException, execFile } from 'child_process';
import * as util from 'util';
import { IScreenSnippet } from '../common/api-interface';
import { isDevEnv, isElectronQA, isLinux, isMac, isWindowsOS } from '../common/env';
import {
isDevEnv,
isElectronQA,
isLinux,
isMac,
isWindowsOS,
} from '../common/env';
import { i18n } from '../common/i18n';
import { logger } from '../common/logger';
import { updateAlwaysOnTop } from './window-actions';
@ -33,9 +40,13 @@ class ScreenSnippet {
fs.mkdirSync(this.tempDir);
}
}
this.captureUtil = isMac ? '/usr/sbin/screencapture' : isDevEnv
? path.join(__dirname,
'../../../node_modules/screen-snippet/ScreenSnippet.exe')
this.captureUtil = isMac
? '/usr/sbin/screencapture'
: isDevEnv
? path.join(
__dirname,
'../../../node_modules/screen-snippet/ScreenSnippet.exe',
)
: path.join(path.dirname(app.getPath('exe')), 'ScreenSnippet.exe');
if (isLinux) {
@ -58,12 +69,19 @@ class ScreenSnippet {
}
}
logger.info(`screen-snippet-handler: Starting screen capture!`);
this.outputFileName = path.join(this.tempDir, 'symphonyImage-' + Date.now() + '.png');
this.outputFileName = path.join(
this.tempDir,
'symphonyImage-' + Date.now() + '.png',
);
if (isMac) {
this.captureUtilArgs = ['-i', '-s', '-t', 'png', this.outputFileName];
} else if (isWindowsOS) {
if (windowHandler.isMana) {
this.captureUtilArgs = [ '--no-annotate', this.outputFileName, i18n.getLocale() ];
this.captureUtilArgs = [
'--no-annotate',
this.outputFileName,
i18n.getLocale(),
];
} else {
this.captureUtilArgs = [this.outputFileName, i18n.getLocale()];
}
@ -74,22 +92,41 @@ class ScreenSnippet {
}
this.focusedWindow = BrowserWindow.getFocusedWindow();
logger.info(`screen-snippet-handler: Capturing snippet with file ${this.outputFileName} and args ${this.captureUtilArgs}!`);
logger.info(
`screen-snippet-handler: Capturing snippet with file ${this.outputFileName} and args ${this.captureUtilArgs}!`,
);
// only allow one screen capture at a time.
if (this.child) {
logger.info(`screen-snippet-handler: Child screen capture exists, killing it and keeping only 1 instance!`);
logger.info(
`screen-snippet-handler: Child screen capture exists, killing it and keeping only 1 instance!`,
);
this.killChildProcess();
}
try {
await this.execCmd(this.captureUtil, this.captureUtilArgs);
const { message, data, type }: IScreenSnippet = await this.convertFileToData();
logger.info(`screen-snippet-handler: Snippet captured! Sending data to SFE`);
if (windowHandler.isMana) {
windowHandler.closeSnippingToolWindow();
const dimensions = this.getImageSize();
windowHandler.createSnippingToolWindow(this.outputFileName, dimensions);
this.uploadSnippet(webContents);
return;
}
const {
message,
data,
type,
}: IScreenSnippet = await this.convertFileToData();
logger.info(
`screen-snippet-handler: Snippet captured! Sending data to SFE`,
);
webContents.send('screen-snippet-data', { message, data, type });
await this.verifyAndUpdateAlwaysOnTop();
} catch (error) {
await this.verifyAndUpdateAlwaysOnTop();
logger.error(`screen-snippet-handler: screen capture failed with error: ${error}!`);
logger.error(
`screen-snippet-handler: screen capture failed with error: ${error}!`,
);
}
}
@ -108,7 +145,9 @@ class ScreenSnippet {
await this.verifyAndUpdateAlwaysOnTop();
} catch (error) {
await this.verifyAndUpdateAlwaysOnTop();
logger.error(`screen-snippet-handler: screen capture cancel failed with error: ${error}!`);
logger.error(
`screen-snippet-handler: screen capture cancel failed with error: ${error}!`,
);
}
}
@ -132,16 +171,25 @@ class ScreenSnippet {
* @param captureUtilArgs {captureUtilArgs}
* @example execCmd('-i -s', '/user/desktop/symphonyImage-1544025391698.png')
*/
private execCmd(captureUtil: string, captureUtilArgs: ReadonlyArray<string>): Promise<ChildProcess> {
logger.info(`screen-snippet-handlers: execCmd ${captureUtil} ${captureUtilArgs}`);
private execCmd(
captureUtil: string,
captureUtilArgs: ReadonlyArray<string>,
): Promise<ChildProcess> {
logger.info(
`screen-snippet-handlers: execCmd ${captureUtil} ${captureUtilArgs}`,
);
return new Promise<ChildProcess>((resolve, reject) => {
return this.child = execFile(captureUtil, captureUtilArgs, (error: ExecException | null) => {
return (this.child = execFile(
captureUtil,
captureUtilArgs,
(error: ExecException | null) => {
if (error && error.killed) {
// processs was killed, just resolve with no data.
return reject(error);
}
resolve();
});
},
));
});
}
@ -154,12 +202,16 @@ class ScreenSnippet {
private async convertFileToData(): Promise<IScreenSnippet> {
try {
if (!this.outputFileName) {
logger.info(`screen-snippet-handler: screen capture failed! output file doesn't exist!`);
logger.info(
`screen-snippet-handler: screen capture failed! output file doesn't exist!`,
);
return { message: 'output file name is required', type: 'ERROR' };
}
const data = await readFile(this.outputFileName);
if (!data) {
logger.info(`screen-snippet-handler: screen capture failed! data doesn't exist!`);
logger.info(
`screen-snippet-handler: screen capture failed! data doesn't exist!`,
);
return { message: `no file data provided`, type: 'ERROR' };
}
// convert binary data to base64 encoded string
@ -179,9 +231,13 @@ class ScreenSnippet {
// remove tmp file (async)
if (this.outputFileName) {
fs.unlink(this.outputFileName, (removeErr) => {
logger.info(`screen-snippet-handler: cleaning up temp snippet file: ${this.outputFileName}!`);
logger.info(
`screen-snippet-handler: cleaning up temp snippet file: ${this.outputFileName}!`,
);
if (removeErr) {
logger.error(`screen-snippet-handler: error removing temp snippet file: ${this.outputFileName}, err: ${removeErr}`);
logger.error(
`screen-snippet-handler: error removing temp snippet file: ${this.outputFileName}, err: ${removeErr}`,
);
}
});
}
@ -197,6 +253,49 @@ class ScreenSnippet {
this.shouldUpdateAlwaysOnTop = false;
}
}
/**
* Gets the height & width of an image
*/
private getImageSize():
| {
height: number | undefined;
width: number | undefined;
}
| undefined {
if (!this.outputFileName) {
return undefined;
}
const dimensions = sizeOf(this.outputFileName);
return {
height: dimensions.height,
width: dimensions.width,
};
}
/**
* Uploads a screen snippet
* @param webContents A browser window's web contents object
*/
private uploadSnippet(webContents: Electron.webContents) {
ipcMain.once('upload-snippet', async (_event, snipImage: string) => {
windowHandler.closeSnippingToolWindow();
if (snipImage) {
this.outputFileName = snipImage;
}
const {
message,
data,
type,
}: IScreenSnippet = await this.convertFileToData();
logger.info(
`screen-snippet-handler: Snippet captured! Sending data to SFE`,
);
webContents.send('screen-snippet-data', { message, data, type });
await this.verifyAndUpdateAlwaysOnTop();
});
}
}
const screenSnippet = new ScreenSnippet();

File diff suppressed because it is too large Load Diff

View File

@ -158,7 +158,8 @@
"Erase": "Erase",
"Highlight": "Highlight",
"Pen": "Pen",
"Snipping Tool": "Snipping Tool"
"Snipping Tool": "Snipping Tool",
"Clear": "Clear"
},
"Select All": "Select All",
"Services": "Services",

View File

@ -158,7 +158,8 @@
"Erase": "Erase",
"Highlight": "Highlight",
"Pen": "Pen",
"Snipping Tool": "Snipping Tool"
"Snipping Tool": "Snipping Tool",
"Clear": "Clear"
},
"Select All": "Select All",
"Services": "Services",

View File

@ -31,7 +31,6 @@
"Build expired": "Construit expiré",
"Cancel": "Annuler",
"Certificate Error": "Erreur de certificat",
"Changing GPU settings requires Symphony to relaunch.": "La modification des paramètres du GPU nécessite la relance de Symphony.",
"Clear cache and Reload": "Vider le cache et rafraîchir Symphony",
"Close": "Fermer",
"ContextMenu": {
@ -159,7 +158,8 @@
"Erase": "Effacer",
"Highlight": "Surligner",
"Pen": "Stylo",
"Snipping Tool": "Outil Capture"
"Snipping Tool": "Outil Capture",
"Clear": "Claire"
},
"Select All": "Tout sélectionner",
"Services": "Services",

View File

@ -158,7 +158,8 @@
"Erase": "Effacer",
"Highlight": "Surligner",
"Pen": "Stylo",
"Snipping Tool": "Outil Capture"
"Snipping Tool": "Outil Capture",
"Clear": "Claire"
},
"Select All": "Tout sélectionner",
"Services": "Services",

View File

@ -158,7 +158,8 @@
"Erase": "消去",
"Highlight": "強調",
"Pen": "ペン",
"Snipping Tool": "切り取りツール"
"Snipping Tool": "切り取りツール",
"Clear": "晴れ"
},
"Select All": "すべてを選択",
"Services": "サービス",

View File

@ -158,7 +158,8 @@
"Erase": "消去",
"Highlight": "強調",
"Pen": "ペン",
"Snipping Tool": "切り取りツール"
"Snipping Tool": "切り取りツール",
"Clear": "晴れ"
},
"Select All": "すべてを選択",
"Services": "サービス",

View File

@ -0,0 +1,4 @@
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 12V16H10V10L5 0L0 10V16H2V12H8ZM7.5 10L5.54186 6H4.45814L2.5 10H7.5Z" fill="#525760"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 12V16H10V10L5 0L0 10V16H2V12H8ZM7.5 10L5.54186 6H4.45814L2.5 10H7.5Z" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 399 B

View File

@ -0,0 +1,4 @@
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 5V16H2V10H8V16H10V5C10 2.23858 7.76142 0 5 0C2.23858 0 0 2.23858 0 5ZM8 8V6H2V8H8Z" fill="#525760"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 5V16H2V10H8V16H10V5C10 2.23858 7.76142 0 5 0C2.23858 0 0 2.23858 0 5ZM8 8V6H2V8H8Z" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 427 B

View File

@ -0,0 +1,4 @@
<svg width="12" height="16" viewBox="0 0 12 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 1.5L2.95814 5H2.90657L0 10V16H2V12H10V16H12V10L8.62966 5.07316L8 0L3.5 1.5ZM10 10L7.75 7H3.75L2 10H10Z" fill="#525760"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 1.5L2.95814 5H2.90657L0 10V16H2V12H10V16H12V10L8.62966 5.07316L8 0L3.5 1.5ZM10 10L7.75 7H3.75L2 10H10Z" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7071 6.70706C16.0976 6.31654 16.0976 5.68337 15.7071 5.29285L10.7071 0.29289C10.3166 -0.0976323 9.68341 -0.0976299 9.29289 0.292896C8.90237 0.683422 8.90237 1.31659 9.2929 1.70711L12.5858 5.00001H5.5C2.46243 5.00001 0 7.46245 0 10.5C0 13.5376 2.46243 16 5.5 16H7C7.55228 16 8 15.5523 8 15C8 14.4477 7.55228 14 7 14H5.5C3.567 14 2 12.433 2 10.5C2 8.56701 3.567 7.00001 5.5 7.00001H12.5858L9.29289 10.2929C8.90237 10.6834 8.90237 11.3166 9.2929 11.7071C9.68342 12.0976 10.3166 12.0976 10.7071 11.7071L15.7071 6.70706Z" fill="#525760"/>
<path d="M15.7071 6.70706C16.0976 6.31654 16.0976 5.68337 15.7071 5.29285L10.7071 0.29289C10.3166 -0.0976323 9.68341 -0.0976299 9.29289 0.292896C8.90237 0.683422 8.90237 1.31659 9.2929 1.70711L12.5858 5.00001H5.5C2.46243 5.00001 0 7.46245 0 10.5C0 13.5376 2.46243 16 5.5 16H7C7.55228 16 8 15.5523 8 15C8 14.4477 7.55228 14 7 14H5.5C3.567 14 2 12.433 2 10.5C2 8.56701 3.567 7.00001 5.5 7.00001H12.5858L9.29289 10.2929C8.90237 10.6834 8.90237 11.3166 9.2929 11.7071C9.68342 12.0976 10.3166 12.0976 10.7071 11.7071L15.7071 6.70706Z" fill="white" fill-opacity="0.72"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.292891 6.70706C-0.0976315 6.31654 -0.0976276 5.68337 0.292897 5.29285L5.2929 0.29289C5.68342 -0.0976323 6.31659 -0.0976299 6.70711 0.292896C7.09763 0.683422 7.09763 1.31659 6.7071 1.70711L3.41418 5.00001H10.5C13.5376 5.00001 16 7.46245 16 10.5C16 13.5376 13.5376 16 10.5 16H9C8.44772 16 8 15.5523 8 15C8 14.4477 8.44772 14 9 14H10.5C12.433 14 14 12.433 14 10.5C14 8.56701 12.433 7.00001 10.5 7.00001H3.41425L6.70711 10.2929C7.09763 10.6834 7.09763 11.3166 6.7071 11.7071C6.31658 12.0976 5.68341 12.0976 5.29289 11.7071L0.292891 6.70706Z" fill="#525760"/>
<path d="M0.292891 6.70706C-0.0976315 6.31654 -0.0976276 5.68337 0.292897 5.29285L5.2929 0.29289C5.68342 -0.0976323 6.31659 -0.0976299 6.70711 0.292896C7.09763 0.683422 7.09763 1.31659 6.7071 1.70711L3.41418 5.00001H10.5C13.5376 5.00001 16 7.46245 16 10.5C16 13.5376 13.5376 16 10.5 16H9C8.44772 16 8 15.5523 8 15C8 14.4477 8.44772 14 9 14H10.5C12.433 14 14 12.433 14 10.5C14 8.56701 12.433 7.00001 10.5 7.00001H3.41425L6.70711 10.2929C7.09763 10.6834 7.09763 11.3166 6.7071 11.7071C6.31658 12.0976 5.68341 12.0976 5.29289 11.7071L0.292891 6.70706Z" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -138,7 +138,7 @@ export default class AboutApp extends React.Component<{}, IState> {
}
/**
* Sets the About app state
* Sets the component state
*
* @param _event
* @param data {Object} { buildNumber, clientVersion, version }

View File

@ -111,7 +111,7 @@ export default class BasicAuth extends React.Component<{}, IState> {
}
/**
* Sets the About app state
* Sets the component state
*
* @param _event
* @param data {Object} { hostname, isValidCredentials }

View File

@ -104,7 +104,7 @@ export default class LoadingScreen extends React.Component<{}, IState> {
}
/**
* Sets the About app state
* Sets the component state
*
* @param _event
* @param data {Object}

View File

@ -223,7 +223,7 @@ export default class NotificationComp extends React.Component<{}, IState> {
}
/**
* Sets the About app state
* Sets the component state
*
* @param _event
* @param data {Object}

View File

@ -174,7 +174,7 @@ export default class NotificationSettings extends React.Component<{}, IState> {
}
/**
* Sets the About app state
* Sets the component state
*
* @param _event
* @param data {Object} { buildNumber, clientVersion, version }

View File

@ -379,7 +379,7 @@ export default class ScreenPicker extends React.Component<{}, IState> {
}
/**
* Sets the About app state
* Sets the component state
*
* @param _event
* @param data {Object}

View File

@ -82,7 +82,7 @@ export default class ScreenSharingIndicator extends React.Component<{}, IState>
}
/**
* Sets the About app state
* Sets the component state
*
* @param _event
* @param data {Object} { buildNumber, clientVersion, version }

View File

@ -0,0 +1,124 @@
import { ipcRenderer } from 'electron';
import * as React from 'react';
import { i18n } from '../../common/i18n-preload';
const { useState, useEffect } = React;
interface IProps {
drawEnabled: boolean;
highlightEnabled: boolean;
eraseEnabled: boolean;
}
const SNIPPING_TOOL_NAMESPACE = 'ScreenSnippet';
const SnippingTool: React.FunctionComponent<IProps> = ({drawEnabled, highlightEnabled, eraseEnabled}) => {
const [screenSnippet, setScreenSnippet] = useState('Screen-Snippet');
const [imageDimensions, setImageDimensions] = useState({height: 600, width: 800});
const getSnipImageData = (_event, {snipImage, height, width}) => {
setScreenSnippet(snipImage);
setImageDimensions({height, width});
};
ipcRenderer.on('snipping-tool-data', getSnipImageData);
useEffect(() => {
return () => {
ipcRenderer.removeListener('snipping-tool-data', getSnipImageData);
};
}, []);
const usePen = () => {
// setTool("pen");
// setShouldRenderPenColorPicker(shouldRenderPenColorPicker => !shouldRenderPenColorPicker);
// setShouldRenderHighlightColorPicker(false);
};
const useHighlight = () => {
// setTool("highlight");
// setShouldRenderHighlightColorPicker(shouldRenderHighlightColorPicker => !shouldRenderHighlightColorPicker);
// setShouldRenderPenColorPicker(false);
};
const useEraser = () => {
// setTool("eraser");
};
const clear = () => {
// const updPaths = [...paths];
// updPaths.map((p) => {
// p.shouldShow = false;
// return p;
// });
// setPaths(updPaths);
};
const done = () => {
ipcRenderer.send('upload-snippet', screenSnippet);
};
return (
<div className='SnippingTool' lang={i18n.getLocale()}>
<header>
<div className='DrawActions'>
<button
className={
drawEnabled ? 'ActionButtonSelected' : 'ActionButton'
}
onClick={usePen}
>
<img src='../renderer/assets/snip-draw.svg' />
</button>
<button
className={
highlightEnabled
? 'ActionButtonSelected'
: 'ActionButton'
}
onClick={useHighlight}
>
<img src='../renderer/assets/snip-highlight.svg' />
</button>
<button
className={
eraseEnabled
? 'ActionButtonSelected'
: 'ActionButton'
}
onClick={useEraser}
>
<img src='../renderer/assets/snip-erase.svg' />
</button>
</div>
<div className='ClearActions'>
<button
className='ClearButton'
onClick={clear}
>
{i18n.t('Clear', SNIPPING_TOOL_NAMESPACE)()}
</button>
</div>
</header>
<main>
<img
src={screenSnippet}
width={imageDimensions.width}
height={imageDimensions.height}
className='SnippetImage'
alt={i18n.t('Screen snippet', SNIPPING_TOOL_NAMESPACE)()}
/>
</main>
<footer>
<button onClick={done}>
{i18n.t('Done', SNIPPING_TOOL_NAMESPACE)()}
</button>
</footer>
</div>
);
};
export default SnippingTool;

View File

@ -10,6 +10,7 @@ import NotificationSettings from './components/notification-settings';
import ScreenPicker from './components/screen-picker';
import ScreenSharingFrame from './components/screen-sharing-frame';
import ScreenSharingIndicator from './components/screen-sharing-indicator';
import SnippingTool from './components/snipping-tool';
import Welcome from './components/welcome';
const enum components {
@ -21,6 +22,7 @@ const enum components {
notification = 'notification-comp',
notificationSettings = 'notification-settings',
welcome = 'welcome',
snippingTool = 'snipping-tool',
}
const loadStyle = (style) => {
@ -47,26 +49,31 @@ const load = () => {
break;
case components.screenPicker:
loadStyle(components.screenPicker);
document.title = 'Screen Picker - Symphony';
document.title = i18n.t('Screen Picker - Symphony')();
component = ScreenPicker;
break;
case components.screenSharingIndicator:
loadStyle(components.screenSharingIndicator);
document.title = 'Screen Sharing Indicator - Symphony';
document.title = i18n.t('Screen Sharing Indicator - Symphony')();
component = ScreenSharingIndicator;
break;
case components.screenSharingFrame:
loadStyle(components.screenSharingFrame);
component = ScreenSharingFrame;
break;
case components.snippingTool:
loadStyle(components.snippingTool);
document.title = i18n.t('Symphony')();
component = SnippingTool;
break;
case components.basicAuth:
loadStyle(components.basicAuth);
document.title = 'Basic Authentication - Symphony';
document.title = i18n.t('Basic Authentication - Symphony')();
component = BasicAuth;
break;
case components.notification:
loadStyle(components.notification);
document.title = 'Notification - Symphony';
document.title = i18n.t('Notification - Symphony')();
component = NotificationComp;
break;
case components.notificationSettings:

View File

@ -0,0 +1,182 @@
@import 'theme';
@white: rgb(255, 255, 255, 1);
@version-text-color: rgb(47, 47, 47, 1);
@text-padding: 10px;
::-webkit-scrollbar {
display: none;
}
body {
background-color: white;
margin: 0;
height: 100%;
width: 100%;
}
.SnippingTool:lang(ja-JP) {
font-family: @font-family-ja;
h4 {
margin: 3px 0;
}
.SnippingTool-symphony-section {
padding-left: 10px;
}
}
.SnippingTool:lang(fr-FR) {
.SnippingTool-symphony-section {
padding-left: 10px;
}
}
.SnippingTool {
display: flex;
flex-direction: column;
font-family: @font-family;
header {
display: flex;
flex-direction: row;
justify-content: center;
text-align: center;
padding: 4px 0;
max-height: 48px;
.ActionButton {
margin-left: 24px;
background: white;
border-radius: 4px;
cursor: pointer;
padding: 4px 0;
border: none;
&:first-child {
margin-left: 0;
}
img {
width: 24px;
height: 24px;
}
}
.ActionButtonSelected {
margin-left: 24px;
background: white;
border-radius: 4px;
cursor: pointer;
padding: 4px 0;
border: 2px solid #008eff;
box-sizing: border-box;
border-radius: 4px;
&:first-child {
margin-left: 0;
}
img {
width: 24px;
height: 24px;
}
}
.ClearButton {
display: block;
padding: 4px 10px;
border: 2px solid #7c7f86;
border-radius: 16px;
color: #7c7f86;
font-weight: 600;
font-size: 12px;
line-height: 16px;
margin-left: 24px;
text-transform: uppercase;
cursor: pointer;
background-color: #ffffff;
}
.DrawActions {
display: flex;
flex-direction: row;
justify-content: center;
text-align: center;
}
.ClearActions {
display: flex;
flex-direction: row;
justify-content: center;
text-align: center;
position: absolute;
right: 24px;
align-items: center;
.ActionButton {
img {
width: 16px;
}
}
}
}
main {
text-align: center;
margin: 0;
background: linear-gradient(
0deg,
rgba(255, 255, 255, 0.96),
rgba(255, 255, 255, 0.96)
),
#525760;
.SnippetImage {
width: 100%;
height: 100%;
object-fit: contain;
}
}
footer {
display: flex;
justify-content: flex-end;
padding: 2px 32px 4px 0;
max-height: 72px;
button {
box-shadow: none;
border: none;
border-radius: 16px;
font-size: 0.75rem;
font-weight: 600;
text-align: center;
padding: 8px 24px;
display: inline-block;
text-decoration: none;
line-height: 16px;
background-color: #008eff;
color: rgba(255, 255, 255, 0.96);
cursor: pointer;
text-transform: uppercase;
margin: 10px 30px 4px 0;
&:focus {
box-shadow: 0 0 10px rgba(61, 162, 253, 1);
outline: none;
}
}
}
@media only screen and (max-width: 400px) {
header {
.ClearActions {
position: relative;
right: auto;
margin-left: 24px;
}
}
}
}

View File

@ -1,5 +1,6 @@
@font-family: "Segoe UI", "Helvetica Neue", "Verdana", "Arial", sans-serif;
@font-family-ja: "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", Meiryo, " Pゴシック", "MS PGothic", sans-serif;
@text-color-primary: #FFFFFF;
@text-color-primary-dark: #2F3237;
@text-color-secondary: #6A707C;
@font-family: 'Segoe UI', 'Helvetica Neue', 'Verdana', 'Arial', sans-serif;
@font-family-ja: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo,
' Pゴシック', 'MS PGothic', sans-serif;
@text-color-primary: #ffffff;
@text-color-primary-dark: #2f3237;
@text-color-secondary: #6a707c;

View File

@ -26,7 +26,7 @@
"no-trailing-whitespace": true,
"no-duplicate-variable": true,
"no-var-keyword": true,
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"],
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"],
"no-empty": true,
"no-unused-expression": true,
"no-use-before-declare": true,