diff --git a/package.json b/package.json index 4d78517d..7f7a6f38 100644 --- a/package.json +++ b/package.json @@ -167,6 +167,7 @@ "react-dom": "16.13.0", "ref-napi": "1.4.3", "rimraf": "^3.0.2", + "save-svg-as-png": "^1.4.17", "shell-path": "2.1.0" }, "optionalDependencies": { diff --git a/spec/snippingTool.spec.tsx b/spec/snippingTool.spec.tsx index a769e704..4133fe2c 100644 --- a/spec/snippingTool.spec.tsx +++ b/spec/snippingTool.spec.tsx @@ -82,7 +82,7 @@ describe('Snipping Tool', () => { wrapper.update(); await waitForPromisesToResolve(); expect(spy).toBeCalledWith('upload-snippet', { - base64PngData: 'NO CANVAS', + mergedImageData: 'MERGE_FAIL', screenSnippetPath: '', }); }); diff --git a/src/app/screen-snippet-handler.ts b/src/app/screen-snippet-handler.ts index 24804066..0f2d839f 100644 --- a/src/app/screen-snippet-handler.ts +++ b/src/app/screen-snippet-handler.ts @@ -287,20 +287,28 @@ class ScreenSnippet { * @param webContents A browser window's web contents object */ private uploadSnippet(webContents: Electron.webContents) { - ipcMain.once('upload-snippet', async (_event, snippetData: { screenSnippetPath: string, base64PngData: string }) => { - windowHandler.closeSnippingToolWindow(); - const [type, data] = snippetData.base64PngData.split(','); - const payload = { - message: 'SUCCESS', - data, - type, - }; - this.deleteFile(snippetData.screenSnippetPath); - logger.info( - `screen-snippet-handler: Snippet captured! Sending data to SFE`, - ); - webContents.send('screen-snippet-data', payload); - await this.verifyAndUpdateAlwaysOnTop(); + ipcMain.once('upload-snippet', async (_event, snippetData: { screenSnippetPath: string, mergedImageData: string }) => { + try { + windowHandler.closeSnippingToolWindow(); + const [type, data] = snippetData.mergedImageData.split(','); + const payload = { + message: 'SUCCESS', + data, + type, + }; + logger.info( + `screen-snippet-handler: Snippet captured! Sending data to SFE`, + ); + webContents.send('screen-snippet-data', payload); + await this.verifyAndUpdateAlwaysOnTop(); + } catch (error) { + await this.verifyAndUpdateAlwaysOnTop(); + logger.error( + `screen-snippet-handler: screen capture failed with error: ${error}!`, + ); + } finally { + this.deleteFile(snippetData.screenSnippetPath); + } }); } } diff --git a/src/renderer/components/snipping-tool.tsx b/src/renderer/components/snipping-tool.tsx index 21f87cf3..4bb6e4aa 100644 --- a/src/renderer/components/snipping-tool.tsx +++ b/src/renderer/components/snipping-tool.tsx @@ -1,5 +1,6 @@ import { ipcRenderer } from 'electron'; import * as React from 'react'; +import { svgAsPngUri } from 'save-svg-as-png'; import { i18n } from '../../common/i18n-preload'; import { analytics, AnalyticsElements, ScreenSnippetActionTypes } from './../../app/analytics-handler'; import AnnotateArea from './annotate-area'; @@ -168,57 +169,6 @@ const SnippingTool: React.FunctionComponent = ({ existingPat // Utility functions - const getBase64PngData = () => { - const canvas = document.createElement('canvas'); - canvas.width = imageDimensions.width; - canvas.height = imageDimensions.height; - - // Creates an in memory canvas for mounting img data without adding it to the DOM - const ctx = canvas?.getContext('2d') as CanvasRenderingContext2D; - - if (!ctx) { - // Will only be the case in headless browsers, such as with unit tests - return 'NO CANVAS'; - } - - const backgroundImage = document.getElementById('backgroundImage') as HTMLImageElement; - - // Fast lane in case there is no drawn SVG paths - if (paths.length === 0) { - ctx.drawImage(backgroundImage, 0, 0); - // Extracts base 64 png img data from the canvas - const data = canvas.toDataURL('image/png'); - return data; - } - - // Creates an in memory img without adding it to the DOM - const img = document.createElement('img'); - - const svg = document.getElementById('annotate-area') as HTMLImageElement; - // Parses SVG image to XML data - const svgData = new XMLSerializer().serializeToString(svg); - // Adds the extracted XML data to the in memory img - img.setAttribute( - 'src', - 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData))), - ); - - return new Promise((resolve, reject) => { - // Listens to when the img is loaded in memory and adds the data from the SVG paths + screenSnippet to the canvas - img.onload = () => { - ctx.drawImage(backgroundImage, 0, 0); - ctx.drawImage(img, 0, 0); - try { - // Extracts base 64 png img data from the canvas - const data = canvas.toDataURL('image/png'); - resolve(data); - } catch (e) { - reject(e); - } - }; - }); - }; - const markChosenColor = (colors: IColor[], chosenColor: string) => { return colors.map((color) => { if (color.rgbaColor === chosenColor) { @@ -246,12 +196,13 @@ const SnippingTool: React.FunctionComponent = ({ existingPat }; const done = async () => { - const base64PngData = await getBase64PngData(); + const svg = document.getElementById('annotate-area'); + const mergedImageData = svg ? await svgAsPngUri(document.getElementById('annotate-area'), {}) : 'MERGE_FAIL'; analytics.track({ element: AnalyticsElements.SCREEN_SNIPPET, action_type: ScreenSnippetActionTypes.CAPTURE_SENT, }); - ipcRenderer.send('upload-snippet', { screenSnippetPath, base64PngData }); + ipcRenderer.send('upload-snippet', { screenSnippetPath, mergedImageData }); }; return ( diff --git a/src/renderer/styles/snipping-tool.less b/src/renderer/styles/snipping-tool.less index 9e6201cd..d4ab3f62 100644 --- a/src/renderer/styles/snipping-tool.less +++ b/src/renderer/styles/snipping-tool.less @@ -5,6 +5,7 @@ @text-padding: 10px; body { + overflow: hidden; background-color: white; margin: 0; height: 100%;