Merge branch 'master' into SDA-2651b
71
.vscode/launch.json
vendored
@ -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"
|
||||
@ -42,7 +47,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mana",
|
||||
"name": "build corp",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
@ -50,7 +55,11 @@
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.exe"
|
||||
},
|
||||
"args": [".", "--url=https://local-dev.symphony.com:9090"],
|
||||
"preLaunchTask": "build",
|
||||
"args": [
|
||||
".",
|
||||
"--url=https://corporate.symphony.com"
|
||||
],
|
||||
"env": {
|
||||
"ELECTRON_DEBUGGING": "true",
|
||||
"ELECTRON_DEV": "true"
|
||||
@ -60,7 +69,54 @@
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/lib/**/*.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mana local",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.exe"
|
||||
},
|
||||
"args": [
|
||||
".",
|
||||
"--url=https://local-dev.symphony.com:9090"
|
||||
],
|
||||
"env": {
|
||||
"ELECTRON_DEBUGGING": "true",
|
||||
"ELECTRON_DEV": "true"
|
||||
},
|
||||
"outputCapture": "std",
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/lib/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "build mana local",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/electron/dist/Electron.exe"
|
||||
},
|
||||
"preLaunchTask": "build",
|
||||
"args": [
|
||||
".",
|
||||
"--url=https://local-dev.symphony.com:9090"
|
||||
],
|
||||
"env": {
|
||||
"ELECTRON_DEBUGGING": "true",
|
||||
"ELECTRON_DEV": "true"
|
||||
},
|
||||
"outputCapture": "std",
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/lib/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "demo",
|
||||
"type": "node",
|
||||
@ -70,7 +126,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
@ -0,0 +1,6 @@
|
||||
{
|
||||
"npm.packageManager": "npm",
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"editor.formatOnSave": true,
|
||||
"jestrunner.configPath": "jest-config.json",
|
||||
}
|
10
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "npx run-s compile browserify"
|
||||
}
|
||||
]
|
||||
}
|
@ -170,6 +170,7 @@ namespace Symphony
|
||||
this.cancel.Text = "[WixUICancel]";
|
||||
this.cancel.UseVisualStyleBackColor = true;
|
||||
this.cancel.Click += new System.EventHandler(this.cancel_Click);
|
||||
this.cancel.Enabled = false;
|
||||
//
|
||||
// bottomBorder
|
||||
//
|
||||
|
@ -150,6 +150,7 @@
|
||||
"typescript": "3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lazy-brush": "^1.0.0",
|
||||
"archiver": "3.1.1",
|
||||
"async.map": "0.5.2",
|
||||
"classnames": "2.2.6",
|
||||
@ -159,6 +160,8 @@
|
||||
"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",
|
||||
"lazy-brush": "^1.0.1",
|
||||
"react": "16.13.0",
|
||||
"react-dom": "16.13.0",
|
||||
"ref-napi": "1.4.3",
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
NODE_REQUIRED_VERSION=v12.13.1
|
||||
SNYK_ORG=sda
|
||||
SNYK_PROJECT_NAME="Symphony Desktop Application"
|
||||
|
||||
if ! [ -x "$(command -v git)" ]; then
|
||||
echo 'GIT does not exist! Please set it up before running this script!' >&2
|
||||
@ -69,7 +70,8 @@ npm install
|
||||
|
||||
# Run Snyk Security Tests
|
||||
echo "Running snyk security tests"
|
||||
snyk test --file=package.json --org="$SNYK_ORG"
|
||||
snyk test --file=package-lock.json --org="$SNYK_ORG"
|
||||
snyk monitor --file=package-lock.json --org="$SNYK_ORG" --project-name="$SNYK_PROJECT_NAME"
|
||||
|
||||
# replace url in config
|
||||
echo "Setting default pod url to https://corporate.symphony.com"
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
NODE_REQUIRED_VERSION=v12.13.1
|
||||
SNYK_ORG=sda
|
||||
SNYK_PROJECT_NAME="Symphony Desktop Application"
|
||||
|
||||
# Check basic dependencies
|
||||
if ! [ -x "$(command -v git)" ]; then
|
||||
@ -85,7 +86,8 @@ codesign --force --options runtime -s "Developer ID Application: Symphony Commun
|
||||
|
||||
# Run Snyk Security Tests
|
||||
echo "Running snyk security tests"
|
||||
snyk test --file=package.json --org="$SNYK_ORG"
|
||||
snyk test --file=package-lock.json --org="$SNYK_ORG"
|
||||
snyk monitor --file=package-lock.json --org="$SNYK_ORG" --project-name="$SNYK_PROJECT_NAME"
|
||||
|
||||
# Replace url in config
|
||||
echo "Setting default pod url to https://my.symphony.com"
|
||||
|
@ -7,6 +7,7 @@ echo %PATH%
|
||||
set DISABLE_REBUILD=true
|
||||
set NODE_REQUIRED_VERSION=12.13.1
|
||||
set SNYK_ORG=sda
|
||||
set SNYK_PROJECT_NAME="Symphony Desktop Application"
|
||||
|
||||
set PATH=%PATH%;C:\Program Files\nodejs\;C:\Program Files\Git\cmd
|
||||
echo %PATH%
|
||||
@ -56,7 +57,8 @@ call npm install
|
||||
|
||||
# Run Snyk Security Tests
|
||||
echo "Running snyk security tests"
|
||||
call snyk test --file=package.json --org=%SNYK_ORG%
|
||||
call snyk test --file=package-lock.json --org=%SNYK_ORG%
|
||||
call snyk monitor --file=package-lock.json --org=%SNYK_ORG% --project-name=%SNYK_PROJECT_NAME%
|
||||
|
||||
:: Set expiry if required
|
||||
IF "%EXPIRY_PERIOD%"=="" (
|
||||
|
@ -7,6 +7,7 @@ echo %PATH%
|
||||
set DISABLE_REBUILD=true
|
||||
set NODE_REQUIRED_VERSION=12.13.1
|
||||
set SNYK_ORG=sda
|
||||
set SNYK_PROJECT_NAME="Symphony Desktop Application"
|
||||
|
||||
set PATH=%PATH%;C:\Program Files\nodejs\;C:\Program Files\Git\cmd
|
||||
echo %PATH%
|
||||
@ -44,7 +45,8 @@ call npm install
|
||||
|
||||
# Run Snyk Security Tests
|
||||
echo "Running snyk security tests"
|
||||
call snyk test --file=package.json --org=%SNYK_ORG%
|
||||
call snyk test --file=package-lock.json --org=%SNYK_ORG%
|
||||
call snyk monitor --file=package-lock.json --org=%SNYK_ORG% --project-name=%SNYK_PROJECT_NAME%
|
||||
|
||||
:: Set expiry if required
|
||||
IF "%EXPIRY_PERIOD%"=="" (
|
||||
|
73
spec/__snapshots__/snippingTool.spec.ts.snap
Normal file
@ -0,0 +1,73 @@
|
||||
// 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]}
|
||||
style={
|
||||
Object {
|
||||
"border": "2px solid rgba(0, 142, 255, 1)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<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>
|
||||
<div>
|
||||
<img
|
||||
alt="Screen snippet"
|
||||
className="SnippetImage"
|
||||
height={600}
|
||||
src="Screen-Snippet"
|
||||
width={800}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<button
|
||||
onClick={[Function]}
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
20
spec/snippingTool.spec.ts
Normal 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));
|
||||
});
|
||||
});
|
@ -378,7 +378,7 @@ export class AppMenu {
|
||||
const defaultSession = session.defaultSession;
|
||||
if (defaultSession) {
|
||||
await defaultSession.clearCache();
|
||||
focusedWindow.reload();
|
||||
reloadWindow(focusedWindow as ICustomBrowserWindow);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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();
|
||||
|
@ -570,6 +570,8 @@ export const reloadWindow = (browserWindow: ICustomBrowserWindow) => {
|
||||
logger.info(`window-utils: reloading the main window`);
|
||||
browserWindow.reload();
|
||||
|
||||
windowHandler.closeAllWindow();
|
||||
|
||||
windowHandler.execCmd(windowHandler.screenShareIndicatorFrameUtil, []);
|
||||
|
||||
return;
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -158,7 +158,8 @@
|
||||
"Erase": "消去",
|
||||
"Highlight": "強調",
|
||||
"Pen": "ペン",
|
||||
"Snipping Tool": "切り取りツール"
|
||||
"Snipping Tool": "切り取りツール",
|
||||
"Clear": "晴れ"
|
||||
},
|
||||
"Select All": "すべてを選択",
|
||||
"Services": "サービス",
|
||||
|
@ -158,7 +158,8 @@
|
||||
"Erase": "消去",
|
||||
"Highlight": "強調",
|
||||
"Pen": "ペン",
|
||||
"Snipping Tool": "切り取りツール"
|
||||
"Snipping Tool": "切り取りツール",
|
||||
"Clear": "晴れ"
|
||||
},
|
||||
"Select All": "すべてを選択",
|
||||
"Services": "サービス",
|
||||
|
4
src/renderer/assets/snip-draw.svg
Normal 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 |
4
src/renderer/assets/snip-erase.svg
Normal 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 |
4
src/renderer/assets/snip-highlight.svg
Normal 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 |
BIN
src/renderer/assets/snip-redo.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
4
src/renderer/assets/snip-redo.svg
Normal 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 |
BIN
src/renderer/assets/snip-undo.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
4
src/renderer/assets/snip-undo.svg
Normal 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 |
@ -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 }
|
||||
|
@ -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 }
|
||||
|
66
src/renderer/components/color-picker-pill.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IColorPickerPillProps {
|
||||
availableColors: IColor[];
|
||||
onChange: (color: string) => void;
|
||||
}
|
||||
|
||||
export interface IColor {
|
||||
rgbaColor: string; // Should be provided as a rgba string i.e. 'rgba(255, 0, 0, 0.3)'
|
||||
outline?: string; // Should be provided as a rgba string i.e. 'rgba(255, 0, 0, 0.3)'
|
||||
chosen?: boolean;
|
||||
}
|
||||
|
||||
const ColorPickerPill = (props: IColorPickerPillProps) => {
|
||||
const getChosenColor = (colors: IColor[]) => {
|
||||
return colors.find((color) => color.chosen === true);
|
||||
};
|
||||
const chosenColor = getChosenColor(props.availableColors);
|
||||
|
||||
const ColorDot = (color: IColor) => {
|
||||
const isChosenColor = color === chosenColor;
|
||||
const hasOutline = !!color.outline;
|
||||
const border = 'solid 1px ' + color.outline;
|
||||
|
||||
const getWidthAndHeight = () => {
|
||||
if (isChosenColor) {
|
||||
return hasOutline ? '22px' : '24px';
|
||||
}
|
||||
return hasOutline ? '6px' : '8px';
|
||||
};
|
||||
|
||||
const widthAndHeight = getWidthAndHeight();
|
||||
|
||||
const chooseColor = () => {
|
||||
props.onChange(color.rgbaColor);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={color.rgbaColor}
|
||||
className='enclosingCircle'
|
||||
onClick={chooseColor}
|
||||
data-testid={'colorDot ' + color.rgbaColor}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
background: color.rgbaColor,
|
||||
width: widthAndHeight,
|
||||
height: widthAndHeight,
|
||||
cursor: 'pointer',
|
||||
border: hasOutline ? border : undefined,
|
||||
}}
|
||||
className='colorDot'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='colorPicker'>
|
||||
{props.availableColors.map((color) => ColorDot(color))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorPickerPill;
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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 }
|
||||
|
@ -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}
|
||||
|
@ -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 }
|
||||
|
282
src/renderer/components/snipping-tool.tsx
Normal file
@ -0,0 +1,282 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import * as React from 'react';
|
||||
import { i18n } from '../../common/i18n-preload';
|
||||
import ColorPickerPill, { IColor } from './color-picker-pill';
|
||||
|
||||
const { useState, useCallback, useRef, useEffect } = React;
|
||||
|
||||
enum Tool {
|
||||
pen = 'PEN',
|
||||
highlight = 'HIGHLIGHT',
|
||||
eraser = 'ERASER',
|
||||
}
|
||||
|
||||
export interface IPath {
|
||||
points: IPoint[];
|
||||
color: string;
|
||||
strokeWidth: number;
|
||||
shouldShow: boolean;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface IPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface ISvgPath {
|
||||
svgPath: string;
|
||||
key: string;
|
||||
strokeWidth: number;
|
||||
color: string;
|
||||
shouldShow: boolean;
|
||||
}
|
||||
|
||||
const availablePenColors: IColor[] = [
|
||||
{ rgbaColor: 'rgba(0, 0, 40, 1)' },
|
||||
{ rgbaColor: 'rgba(0, 142, 255, 1)' },
|
||||
{ rgbaColor: 'rgba(38, 196, 58, 1)' },
|
||||
{ rgbaColor: 'rgba(246, 178, 2, 1)' },
|
||||
{ rgbaColor: 'rgba(255, 255, 255, 1)', outline: 'rgba(0, 0, 0, 1)' },
|
||||
];
|
||||
const availableHighlightColors: IColor[] = [
|
||||
{ rgbaColor: 'rgba(0, 142, 255, 0.64)' },
|
||||
{ rgbaColor: 'rgba(38, 196, 58, 0.64)' },
|
||||
{ rgbaColor: 'rgba(246, 178, 2, 0.64)' },
|
||||
{ rgbaColor: 'rgba(233, 0, 0, 0.64)' },
|
||||
];
|
||||
const SNIPPING_TOOL_NAMESPACE = 'ScreenSnippet';
|
||||
|
||||
const SnippingTool = () => {
|
||||
// State and ref preparation functions
|
||||
|
||||
const [screenSnippet, setScreenSnippet] = useState('Screen-Snippet');
|
||||
const [imageDimensions, setImageDimensions] = useState({
|
||||
height: 600,
|
||||
width: 800,
|
||||
});
|
||||
const [paths, setPaths] = useState<IPath[]>([]);
|
||||
const [chosenTool, setChosenTool] = useState(Tool.pen);
|
||||
const [annotateAreaLocation, setAnnotateAreaLocation] = useState({
|
||||
left: 0,
|
||||
top: 0,
|
||||
});
|
||||
const [penColor, setPenColor] = useState('rgba(0, 142, 255, 1)');
|
||||
const [highlightColor, setHighlightColor] = useState(
|
||||
'rgba(0, 142, 255, 0.64)',
|
||||
);
|
||||
const [
|
||||
shouldRenderHighlightColorPicker,
|
||||
setShouldRenderHighlightColorPicker,
|
||||
] = useState(false);
|
||||
const [shouldRenderPenColorPicker, setShouldRenderPenColorPicker] = useState(
|
||||
false,
|
||||
);
|
||||
|
||||
const getSnipImageData = ({ }, { snipImage, height, width }) => {
|
||||
setScreenSnippet(snipImage);
|
||||
setImageDimensions({ height, width });
|
||||
};
|
||||
|
||||
ipcRenderer.on('snipping-tool-data', getSnipImageData);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
ipcRenderer.removeListener('snipping-tool-data', getSnipImageData);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const annotateRef = useCallback((domNode) => {
|
||||
if (domNode) {
|
||||
setAnnotateAreaLocation(domNode.getBoundingClientRect());
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Hook that alerts clicks outside of the passed ref
|
||||
const useClickOutsideExaminer = (
|
||||
colorPickerRf: React.RefObject<HTMLDivElement>,
|
||||
penRf: React.RefObject<HTMLButtonElement>,
|
||||
highlightRf: React.RefObject<HTMLButtonElement>,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
!colorPickerRf?.current?.contains(event.target as Node) &&
|
||||
!penRf?.current?.contains(event.target as Node) &&
|
||||
!highlightRf?.current?.contains(event.target as Node)
|
||||
) {
|
||||
setShouldRenderHighlightColorPicker(false);
|
||||
setShouldRenderPenColorPicker(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [colorPickerRf, penRf, highlightRf]);
|
||||
};
|
||||
|
||||
const colorPickerRef = useRef<HTMLDivElement>(null);
|
||||
const penRef = useRef<HTMLButtonElement>(null);
|
||||
const highlightRef = useRef<HTMLButtonElement>(null);
|
||||
useClickOutsideExaminer(colorPickerRef, penRef, highlightRef);
|
||||
|
||||
// State mutating functions
|
||||
|
||||
const penColorChosen = (color: string) => {
|
||||
setPenColor(color);
|
||||
setShouldRenderPenColorPicker(false);
|
||||
};
|
||||
|
||||
const highlightColorChosen = (color: string) => {
|
||||
setHighlightColor(color);
|
||||
setShouldRenderHighlightColorPicker(false);
|
||||
};
|
||||
|
||||
const usePen = () => {
|
||||
setChosenTool(Tool.pen);
|
||||
setShouldRenderPenColorPicker(!shouldRenderPenColorPicker);
|
||||
setShouldRenderHighlightColorPicker(false);
|
||||
};
|
||||
|
||||
const useHighlight = () => {
|
||||
setChosenTool(Tool.highlight);
|
||||
setShouldRenderHighlightColorPicker(!shouldRenderHighlightColorPicker);
|
||||
setShouldRenderPenColorPicker(false);
|
||||
};
|
||||
|
||||
const useEraser = () => {
|
||||
setChosenTool(Tool.eraser);
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
const updPaths = [...paths];
|
||||
updPaths.map((p) => {
|
||||
p.shouldShow = false;
|
||||
return p;
|
||||
});
|
||||
setPaths(updPaths);
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
|
||||
const getMousePosition = (e: React.MouseEvent) => {
|
||||
return {
|
||||
x: e.pageX - annotateAreaLocation.left,
|
||||
y: e.pageY - annotateAreaLocation.top,
|
||||
};
|
||||
};
|
||||
|
||||
const markChosenColor = (colors: IColor[], chosenColor: string) => {
|
||||
return colors.map((color) => {
|
||||
if (color.rgbaColor === chosenColor) {
|
||||
return { ...color, chosen: true };
|
||||
} else {
|
||||
return color;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getBorderStyle = (tool: Tool) => {
|
||||
if (chosenTool !== tool) {
|
||||
return undefined;
|
||||
}
|
||||
if (chosenTool === Tool.pen) {
|
||||
return { border: '2px solid ' + penColor };
|
||||
} else if (chosenTool === Tool.highlight) {
|
||||
return { border: '2px solid ' + highlightColor };
|
||||
} else if (chosenTool === Tool.eraser) {
|
||||
return { border: '2px solid #008EFF' };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const done = (e) => {
|
||||
getMousePosition(e);
|
||||
ipcRenderer.send('upload-snippet', screenSnippet);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='SnippingTool' lang={i18n.getLocale()}>
|
||||
<header>
|
||||
<div className='DrawActions'>
|
||||
<button
|
||||
style={getBorderStyle(Tool.pen)}
|
||||
className='ActionButton'
|
||||
onClick={usePen}
|
||||
>
|
||||
<img src='../renderer/assets/snip-draw.svg' />
|
||||
</button>
|
||||
<button
|
||||
style={getBorderStyle(Tool.highlight)}
|
||||
className='ActionButton'
|
||||
onClick={useHighlight}
|
||||
>
|
||||
<img src='../renderer/assets/snip-highlight.svg' />
|
||||
</button>
|
||||
<button
|
||||
style={getBorderStyle(Tool.eraser)}
|
||||
className='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>;
|
||||
|
||||
{
|
||||
shouldRenderPenColorPicker && (
|
||||
<div style={{ marginTop: '64px', position: 'absolute', left: '50%' }} ref={colorPickerRef}>
|
||||
<div style={{ position: 'relative', left: '-50%' }}>
|
||||
<ColorPickerPill
|
||||
availableColors={markChosenColor(availablePenColors, penColor)}
|
||||
onChange={penColorChosen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
shouldRenderHighlightColorPicker && (
|
||||
<div style={{ marginTop: '64px', position: 'absolute', left: '50%' }} ref={colorPickerRef}>
|
||||
<div style={{ position: 'relative', left: '-50%' }}>
|
||||
<ColorPickerPill
|
||||
availableColors={markChosenColor(
|
||||
availableHighlightColors,
|
||||
highlightColor,
|
||||
)}
|
||||
onChange={highlightColorChosen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<main>
|
||||
<div ref={annotateRef}>
|
||||
<img
|
||||
src={screenSnippet}
|
||||
width={imageDimensions.width}
|
||||
height={imageDimensions.height}
|
||||
className='SnippetImage'
|
||||
alt={i18n.t('Screen snippet', SNIPPING_TOOL_NAMESPACE)()}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<button onClick={done}>
|
||||
{i18n.t('Done', SNIPPING_TOOL_NAMESPACE)()}
|
||||
</button>
|
||||
</footer>
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
export default SnippingTool;
|
@ -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:
|
||||
|
207
src/renderer/styles/snipping-tool.less
Normal file
@ -0,0 +1,207 @@
|
||||
@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 {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 15px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 4px 0;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 2px solid white;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.colorPicker {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
height: 40px;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 4px 16px rgba(15, 27, 36, 0.14), 0px 4px 8px rgba(15, 27, 36, 0.26);
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.enclosingCircle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
flex: none;
|
||||
order: 0;
|
||||
flex-grow: 0;
|
||||
margin: 8px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.colorDot {
|
||||
border-radius: 50%;
|
||||
flex: none;
|
||||
order: 0;
|
||||
flex-grow: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #000028;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
@font-family: "Segoe UI", "Helvetica Neue", "Verdana", "Arial", sans-serif;
|
||||
@font-family-ja: "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", Meiryo, "MS 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,
|
||||
'MS Pゴシック', 'MS PGothic', sans-serif;
|
||||
@text-color-primary: #ffffff;
|
||||
@text-color-primary-dark: #2f3237;
|
||||
@text-color-secondary: #6a707c;
|
||||
|
@ -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,
|
||||
|