mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-12-27 17:31:36 -06:00
SDA-2533 Add clear, erase and done functionality. Further optimizations of logic.
This commit is contained in:
parent
6811683e29
commit
064b9b53b2
@ -4,7 +4,7 @@ exports[`<AnnotateArea/> should render correctly 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
data-testid="annotate-area"
|
data-testid="annotate-area"
|
||||||
height={800}
|
height={800}
|
||||||
id="annotate"
|
id="annotate-area"
|
||||||
onMouseDown={[Function]}
|
onMouseDown={[Function]}
|
||||||
onMouseLeave={[Function]}
|
onMouseLeave={[Function]}
|
||||||
onMouseMove={[Function]}
|
onMouseMove={[Function]}
|
||||||
|
@ -57,25 +57,28 @@ exports[`Snipping Tool should render correctly 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main
|
||||||
<div
|
style={
|
||||||
className="imageContainer"
|
Object {
|
||||||
>
|
"minHeight": 200,
|
||||||
<AnnotateArea
|
}
|
||||||
chosenTool="PEN"
|
}
|
||||||
highlightColor="rgba(0, 142, 255, 0.64)"
|
>
|
||||||
imageDimensions={
|
<AnnotateArea
|
||||||
Object {
|
chosenTool="PEN"
|
||||||
"height": 600,
|
data-testid="annotate-component"
|
||||||
"width": 800,
|
highlightColor="rgba(0, 142, 255, 0.64)"
|
||||||
}
|
imageDimensions={
|
||||||
|
Object {
|
||||||
|
"height": 600,
|
||||||
|
"width": 800,
|
||||||
}
|
}
|
||||||
onChange={[Function]}
|
}
|
||||||
paths={Array []}
|
onChange={[Function]}
|
||||||
penColor="rgba(0, 142, 255, 1)"
|
paths={Array []}
|
||||||
screenSnippetPath="Screen-Snippet"
|
penColor="rgba(0, 142, 255, 1)"
|
||||||
/>
|
screenSnippetPath="Screen-Snippet"
|
||||||
</div>
|
/>
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<button
|
<button
|
@ -37,7 +37,7 @@ describe('<AnnotateArea/>', () => {
|
|||||||
expect(defaultProps.onChange).toHaveBeenCalledWith([{
|
expect(defaultProps.onChange).toHaveBeenCalledWith([{
|
||||||
color: 'rgba(38, 196, 58, 1)',
|
color: 'rgba(38, 196, 58, 1)',
|
||||||
key: 'path0',
|
key: 'path0',
|
||||||
points: [{ x: 0, y: 0 }],
|
points: [{ x: 2, y: 1 }],
|
||||||
shouldShow: true,
|
shouldShow: true,
|
||||||
strokeWidth: 5,
|
strokeWidth: 5,
|
||||||
}]);
|
}]);
|
||||||
@ -89,4 +89,32 @@ describe('<AnnotateArea/>', () => {
|
|||||||
const wrapper = shallow(<AnnotateArea {...defaultProps} />);
|
const wrapper = shallow(<AnnotateArea {...defaultProps} />);
|
||||||
expect(wrapper.find('[data-testid="path0"]').exists()).toEqual(false);
|
expect(wrapper.find('[data-testid="path0"]').exists()).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call onChange with hidden path if clicked on path with tool eraser', () => {
|
||||||
|
const pathProps = {
|
||||||
|
paths: [{
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
shouldShow: true,
|
||||||
|
strokeWidth: 5,
|
||||||
|
color: 'rgba(233, 0, 0, 0.64)',
|
||||||
|
key: 'path0',
|
||||||
|
}],
|
||||||
|
highlightColor: 'rgba(233, 0, 0, 0.64)',
|
||||||
|
penColor: 'rgba(38, 196, 58, 1)',
|
||||||
|
onChange: jest.fn(),
|
||||||
|
imageDimensions: { width: 800, height: 800 },
|
||||||
|
chosenTool: Tool.eraser,
|
||||||
|
screenSnippetPath: 'very-nice-path',
|
||||||
|
};
|
||||||
|
const wrapper = shallow(<AnnotateArea {...pathProps} />);
|
||||||
|
const path = wrapper.find('[data-testid="path0"]');
|
||||||
|
path.simulate('click');
|
||||||
|
expect(pathProps.onChange).toHaveBeenCalledWith([{
|
||||||
|
color: 'rgba(233, 0, 0, 0.64)',
|
||||||
|
key: 'path0',
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
shouldShow: false,
|
||||||
|
strokeWidth: 5,
|
||||||
|
}]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
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));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render pen color picker when clicked on pen', () => {
|
|
||||||
const wrapper = shallow(React.createElement(SnippingTool));
|
|
||||||
wrapper.find('[data-testid="pen-button"]').simulate('click');
|
|
||||||
expect(wrapper.find('[data-testid="pen-colorpicker"]').exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render highlight color picker when clicked on highlight', () => {
|
|
||||||
const wrapper = shallow(React.createElement(SnippingTool));
|
|
||||||
wrapper.find('[data-testid="highlight-button"]').simulate('click');
|
|
||||||
expect(wrapper.find('[data-testid="highlight-colorpicker"]').exists()).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
67
spec/snippingTool.spec.tsx
Normal file
67
spec/snippingTool.spec.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { mount, shallow } from 'enzyme';
|
||||||
|
import * as React from 'react';
|
||||||
|
import SnippingTool from '../src/renderer/components/snipping-tool';
|
||||||
|
import { ipcRenderer } from './__mocks__/electron';
|
||||||
|
|
||||||
|
const waitForPromisesToResolve = () => new Promise((resolve) => setTimeout(resolve));
|
||||||
|
|
||||||
|
describe('Snipping Tool', () => {
|
||||||
|
it('should render correctly', () => {
|
||||||
|
const wrapper = shallow(React.createElement(SnippingTool));
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set up a "once" listener for snipping-tool-data event on mounting', () => {
|
||||||
|
const spy = jest.spyOn(ipcRenderer, 'once');
|
||||||
|
shallow(React.createElement(SnippingTool));
|
||||||
|
expect(spy).toBeCalledWith('snipping-tool-data', expect.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render pen color picker when clicked on pen', () => {
|
||||||
|
const wrapper = shallow(React.createElement(SnippingTool));
|
||||||
|
wrapper.find('[data-testid="pen-button"]').simulate('click');
|
||||||
|
expect(wrapper.find('[data-testid="pen-colorpicker"]').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render highlight color picker when clicked on highlight', () => {
|
||||||
|
const wrapper = shallow(React.createElement(SnippingTool));
|
||||||
|
wrapper.find('[data-testid="highlight-button"]').simulate('click');
|
||||||
|
expect(wrapper.find('[data-testid="highlight-colorpicker"]').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear all paths when clicked on clear', () => {
|
||||||
|
const props = {
|
||||||
|
existingPaths: [{
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
shouldShow: true,
|
||||||
|
strokeWidth: 5,
|
||||||
|
color: 'rgba(233, 0, 0, 0.64)',
|
||||||
|
key: 'path0',
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
const wrapper = mount(<SnippingTool {...props} />);
|
||||||
|
const annotateComponent = wrapper.find('[data-testid="annotate-component"]');
|
||||||
|
wrapper.find('[data-testid="clear-button"]').simulate('click');
|
||||||
|
wrapper.update();
|
||||||
|
expect(annotateComponent.prop('paths')).toEqual(
|
||||||
|
[{
|
||||||
|
color: 'rgba(233, 0, 0, 0.64)',
|
||||||
|
key: 'path0',
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
shouldShow: false,
|
||||||
|
strokeWidth: 5,
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send upload-snippet event with correct data when clicked on done', async () => {
|
||||||
|
const spy = jest.spyOn(ipcRenderer, 'send');
|
||||||
|
const wrapper = mount(<SnippingTool />);
|
||||||
|
wrapper.find('[data-testid="done-button"]').simulate('click');
|
||||||
|
wrapper.update();
|
||||||
|
await waitForPromisesToResolve();
|
||||||
|
expect(spy).toBeCalledWith('upload-snippet', {
|
||||||
|
base64PngData: 'NO CANVAS',
|
||||||
|
screenSnippetPath: 'Screen-Snippet',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -230,20 +230,28 @@ class ScreenSnippet {
|
|||||||
}
|
}
|
||||||
// remove tmp file (async)
|
// remove tmp file (async)
|
||||||
if (this.outputFileName) {
|
if (this.outputFileName) {
|
||||||
fs.unlink(this.outputFileName, (removeErr) => {
|
this.deleteFile(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}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a locally stored file
|
||||||
|
* @param filePath Path for the file to delete
|
||||||
|
*/
|
||||||
|
private deleteFile(filePath: string) {
|
||||||
|
fs.unlink(filePath, (removeErr) => {
|
||||||
|
logger.info(
|
||||||
|
`screen-snippet-handler: cleaning up temp snippet file: ${filePath}!`,
|
||||||
|
);
|
||||||
|
if (removeErr) {
|
||||||
|
logger.error(
|
||||||
|
`screen-snippet-handler: error removing temp snippet file: ${filePath}, err: ${removeErr}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify and updates always on top
|
* Verify and updates always on top
|
||||||
*/
|
*/
|
||||||
@ -279,20 +287,19 @@ class ScreenSnippet {
|
|||||||
* @param webContents A browser window's web contents object
|
* @param webContents A browser window's web contents object
|
||||||
*/
|
*/
|
||||||
private uploadSnippet(webContents: Electron.webContents) {
|
private uploadSnippet(webContents: Electron.webContents) {
|
||||||
ipcMain.once('upload-snippet', async (_event, snipImage: string) => {
|
ipcMain.once('upload-snippet', async (_event, snippetData: { screenSnippetPath: string, base64PngData: string }) => {
|
||||||
windowHandler.closeSnippingToolWindow();
|
windowHandler.closeSnippingToolWindow();
|
||||||
if (snipImage) {
|
const [type, data] = snippetData.base64PngData.split(',');
|
||||||
this.outputFileName = snipImage;
|
const payload = {
|
||||||
}
|
message: 'SUCCESS',
|
||||||
const {
|
|
||||||
message,
|
|
||||||
data,
|
data,
|
||||||
type,
|
type,
|
||||||
}: IScreenSnippet = await this.convertFileToData();
|
};
|
||||||
|
this.deleteFile(snippetData.screenSnippetPath);
|
||||||
logger.info(
|
logger.info(
|
||||||
`screen-snippet-handler: Snippet captured! Sending data to SFE`,
|
`screen-snippet-handler: Snippet captured! Sending data to SFE`,
|
||||||
);
|
);
|
||||||
webContents.send('screen-snippet-data', { message, data, type });
|
webContents.send('screen-snippet-data', payload);
|
||||||
await this.verifyAndUpdateAlwaysOnTop();
|
await this.verifyAndUpdateAlwaysOnTop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -33,12 +33,28 @@ const MIN_ANNOTATE_AREA_WIDTH = 312;
|
|||||||
const PEN_WIDTH = 5;
|
const PEN_WIDTH = 5;
|
||||||
const HIGHLIGHT_WIDTH = 28;
|
const HIGHLIGHT_WIDTH = 28;
|
||||||
|
|
||||||
const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({ paths, highlightColor, penColor, onChange, imageDimensions, chosenTool, screenSnippetPath }) => {
|
const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({
|
||||||
|
paths,
|
||||||
|
highlightColor,
|
||||||
|
penColor,
|
||||||
|
onChange,
|
||||||
|
imageDimensions,
|
||||||
|
chosenTool,
|
||||||
|
screenSnippetPath,
|
||||||
|
}) => {
|
||||||
const [isDrawing, setIsDrawing] = useState(false);
|
const [isDrawing, setIsDrawing] = useState(false);
|
||||||
|
|
||||||
const maybeErasePath = (key: string) => {
|
const maybeErasePath = (key: string) => {
|
||||||
// erase logic here
|
if (chosenTool === Tool.eraser) {
|
||||||
return key;
|
const updPaths = [...paths];
|
||||||
|
updPaths.map((p) => {
|
||||||
|
if (p && p.key === key) {
|
||||||
|
p.shouldShow = false;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
onChange(updPaths);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopDrawing = () => {
|
const stopDrawing = () => {
|
||||||
@ -59,10 +75,12 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({ paths, high
|
|||||||
// Render and preparing render functions
|
// Render and preparing render functions
|
||||||
|
|
||||||
const addHighlightPoint = (paths: IPath[], point: IPoint) => {
|
const addHighlightPoint = (paths: IPath[], point: IPoint) => {
|
||||||
const activePath = paths[paths.length - 1];
|
if (isDrawing) {
|
||||||
const shouldShow = true;
|
const activePath = paths[paths.length - 1];
|
||||||
const key = 'path' + paths.length;
|
activePath.points.push(point);
|
||||||
if (!isDrawing) {
|
} else {
|
||||||
|
const shouldShow = true;
|
||||||
|
const key = 'path' + paths.length;
|
||||||
paths.push({
|
paths.push({
|
||||||
points: [point],
|
points: [point],
|
||||||
color: highlightColor,
|
color: highlightColor,
|
||||||
@ -70,17 +88,17 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({ paths, high
|
|||||||
shouldShow,
|
shouldShow,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
activePath.points.push(point);
|
|
||||||
}
|
}
|
||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addPenPoint = (paths: IPath[], point: IPoint) => {
|
const addPenPoint = (paths: IPath[], point: IPoint) => {
|
||||||
const activePath = paths[paths.length - 1];
|
if (isDrawing) {
|
||||||
const shouldShow = true;
|
const activePath = paths[paths.length - 1];
|
||||||
const key = 'path' + paths.length;
|
activePath.points.push(point);
|
||||||
if (!isDrawing) {
|
} else {
|
||||||
|
const shouldShow = true;
|
||||||
|
const key = 'path' + paths.length;
|
||||||
paths.push({
|
paths.push({
|
||||||
points: [point],
|
points: [point],
|
||||||
color: penColor,
|
color: penColor,
|
||||||
@ -88,21 +106,19 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({ paths, high
|
|||||||
shouldShow,
|
shouldShow,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
activePath.points.push(point);
|
|
||||||
}
|
}
|
||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addPathPoint = (e: React.MouseEvent) => {
|
const addPathPoint = (e: React.MouseEvent) => {
|
||||||
const p = [...paths];
|
const p = [...paths];
|
||||||
const mousePos = getMousePosition(e);
|
const mousePos: IPoint = getMousePosition(e);
|
||||||
lazy.update({ x: mousePos.x, y: mousePos.y });
|
|
||||||
const point: IPoint = lazy.getBrushCoordinates();
|
|
||||||
if (chosenTool === Tool.highlight) {
|
if (chosenTool === Tool.highlight) {
|
||||||
|
lazy.update({ x: mousePos.x, y: mousePos.y });
|
||||||
|
const point: IPoint = lazy.getBrushCoordinates();
|
||||||
onChange(addHighlightPoint(p, point));
|
onChange(addHighlightPoint(p, point));
|
||||||
} else {
|
} else {
|
||||||
onChange(addPenPoint(p, point));
|
onChange(addPenPoint(p, mousePos));
|
||||||
}
|
}
|
||||||
if (!isDrawing) {
|
if (!isDrawing) {
|
||||||
setIsDrawing(true);
|
setIsDrawing(true);
|
||||||
@ -204,7 +220,7 @@ const AnnotateArea: React.FunctionComponent<IAnnotateAreaProps> = ({ paths, high
|
|||||||
<svg
|
<svg
|
||||||
data-testid='annotate-area'
|
data-testid='annotate-area'
|
||||||
style={{ cursor: 'crosshair' }}
|
style={{ cursor: 'crosshair' }}
|
||||||
id='annotate'
|
id='annotate-area'
|
||||||
width={imageDimensions.width}
|
width={imageDimensions.width}
|
||||||
height={imageDimensions.height}
|
height={imageDimensions.height}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
|
|
||||||
export interface IColorPickerPillProps {
|
export interface IColorPickerPillProps {
|
||||||
availableColors: IColor[];
|
availableColors: IColor[];
|
||||||
onChange: (color: string) => void;
|
onChange: (color: IColor) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IColor {
|
export interface IColor {
|
||||||
@ -32,7 +32,7 @@ const ColorPickerPill = (props: IColorPickerPillProps) => {
|
|||||||
const widthAndHeight = getWidthAndHeight();
|
const widthAndHeight = getWidthAndHeight();
|
||||||
|
|
||||||
const chooseColor = () => {
|
const chooseColor = () => {
|
||||||
props.onChange(color.rgbaColor);
|
props.onChange(color);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -38,12 +38,16 @@ export interface IImageDimensions {
|
|||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISnippingToolProps {
|
||||||
|
existingPaths?: IPath[];
|
||||||
|
}
|
||||||
|
|
||||||
const availablePenColors: IColor[] = [
|
const availablePenColors: IColor[] = [
|
||||||
{ rgbaColor: 'rgba(0, 0, 40, 1)' },
|
{ rgbaColor: 'rgba(0, 0, 40, 1)' },
|
||||||
{ rgbaColor: 'rgba(0, 142, 255, 1)' },
|
{ rgbaColor: 'rgba(0, 142, 255, 1)' },
|
||||||
{ rgbaColor: 'rgba(38, 196, 58, 1)' },
|
{ rgbaColor: 'rgba(38, 196, 58, 1)' },
|
||||||
{ rgbaColor: 'rgba(246, 178, 2, 1)' },
|
{ rgbaColor: 'rgba(246, 178, 2, 1)' },
|
||||||
{ rgbaColor: 'rgba(255, 255, 255, 1)', outline: 'rgba(0, 0, 0, 1)' },
|
{ rgbaColor: 'rgba(255, 255, 255, 1)', outline: 'rgba(207, 208, 210, 1)' },
|
||||||
];
|
];
|
||||||
const availableHighlightColors: IColor[] = [
|
const availableHighlightColors: IColor[] = [
|
||||||
{ rgbaColor: 'rgba(0, 142, 255, 0.64)' },
|
{ rgbaColor: 'rgba(0, 142, 255, 0.64)' },
|
||||||
@ -53,7 +57,7 @@ const availableHighlightColors: IColor[] = [
|
|||||||
];
|
];
|
||||||
const SNIPPING_TOOL_NAMESPACE = 'ScreenSnippet';
|
const SNIPPING_TOOL_NAMESPACE = 'ScreenSnippet';
|
||||||
|
|
||||||
const SnippingTool = () => {
|
const SnippingTool: React.FunctionComponent<ISnippingToolProps> = ({ existingPaths }) => {
|
||||||
// State preparation functions
|
// State preparation functions
|
||||||
|
|
||||||
const [screenSnippetPath, setScreenSnippetPath] = useState('Screen-Snippet');
|
const [screenSnippetPath, setScreenSnippetPath] = useState('Screen-Snippet');
|
||||||
@ -61,11 +65,11 @@ const SnippingTool = () => {
|
|||||||
height: 600,
|
height: 600,
|
||||||
width: 800,
|
width: 800,
|
||||||
});
|
});
|
||||||
const [paths, setPaths] = useState<IPath[]>([]);
|
const [paths, setPaths] = useState<IPath[]>(existingPaths || []);
|
||||||
const [chosenTool, setChosenTool] = useState(Tool.pen);
|
const [chosenTool, setChosenTool] = useState(Tool.pen);
|
||||||
const [penColor, setPenColor] = useState('rgba(0, 142, 255, 1)');
|
const [penColor, setPenColor] = useState<IColor>({ rgbaColor: 'rgba(0, 142, 255, 1)' });
|
||||||
const [highlightColor, setHighlightColor] = useState(
|
const [highlightColor, setHighlightColor] = useState<IColor>(
|
||||||
'rgba(0, 142, 255, 0.64)',
|
{ rgbaColor: 'rgba(0, 142, 255, 0.64)' },
|
||||||
);
|
);
|
||||||
const [
|
const [
|
||||||
shouldRenderHighlightColorPicker,
|
shouldRenderHighlightColorPicker,
|
||||||
@ -80,15 +84,9 @@ const SnippingTool = () => {
|
|||||||
setImageDimensions({ height, width });
|
setImageDimensions({ height, width });
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.on('snipping-tool-data', getSnipImageData);
|
ipcRenderer.once('snipping-tool-data', getSnipImageData);
|
||||||
|
|
||||||
useEffect(() => {
|
// Hook that alerts clicks outside of the passed refs
|
||||||
return () => {
|
|
||||||
ipcRenderer.removeListener('snipping-tool-data', getSnipImageData);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Hook that alerts clicks outside of the passed ref
|
|
||||||
const useClickOutsideExaminer = (
|
const useClickOutsideExaminer = (
|
||||||
colorPickerRf: React.RefObject<HTMLDivElement>,
|
colorPickerRf: React.RefObject<HTMLDivElement>,
|
||||||
penRf: React.RefObject<HTMLButtonElement>,
|
penRf: React.RefObject<HTMLButtonElement>,
|
||||||
@ -120,12 +118,12 @@ const SnippingTool = () => {
|
|||||||
|
|
||||||
// State mutating functions
|
// State mutating functions
|
||||||
|
|
||||||
const penColorChosen = (color: string) => {
|
const penColorChosen = (color: IColor) => {
|
||||||
setPenColor(color);
|
setPenColor(color);
|
||||||
setShouldRenderPenColorPicker(false);
|
setShouldRenderPenColorPicker(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const highlightColorChosen = (color: string) => {
|
const highlightColorChosen = (color: IColor) => {
|
||||||
setHighlightColor(color);
|
setHighlightColor(color);
|
||||||
setShouldRenderHighlightColorPicker(false);
|
setShouldRenderHighlightColorPicker(false);
|
||||||
};
|
};
|
||||||
@ -147,11 +145,58 @@ const SnippingTool = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
// Clear logic here
|
const updPaths = [...paths];
|
||||||
|
updPaths.map((p) => {
|
||||||
|
p.shouldShow = false;
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
setPaths(updPaths);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Utility functions
|
// 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';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))),
|
||||||
|
);
|
||||||
|
const screenSnippet = document.getElementById('screenSnippet') as HTMLImageElement;
|
||||||
|
|
||||||
|
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(screenSnippet, 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) => {
|
const markChosenColor = (colors: IColor[], chosenColor: string) => {
|
||||||
return colors.map((color) => {
|
return colors.map((color) => {
|
||||||
if (color.rgbaColor === chosenColor) {
|
if (color.rgbaColor === chosenColor) {
|
||||||
@ -167,17 +212,21 @@ const SnippingTool = () => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (chosenTool === Tool.pen) {
|
if (chosenTool === Tool.pen) {
|
||||||
return { border: '2px solid ' + penColor };
|
const color = penColor.outline ? penColor.outline : penColor.rgbaColor;
|
||||||
|
return { border: '2px solid ' + color };
|
||||||
} else if (chosenTool === Tool.highlight) {
|
} else if (chosenTool === Tool.highlight) {
|
||||||
return { border: '2px solid ' + highlightColor };
|
const color = highlightColor.outline ? highlightColor.outline : highlightColor.rgbaColor;
|
||||||
|
return { border: '2px solid ' + color };
|
||||||
} else if (chosenTool === Tool.eraser) {
|
} else if (chosenTool === Tool.eraser) {
|
||||||
return { border: '2px solid #008EFF' };
|
return { border: '2px solid #008EFF' };
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const done = () => {
|
const done = async () => {
|
||||||
ipcRenderer.send('upload-snippet', screenSnippetPath);
|
const base64PngData = await getBase64PngData();
|
||||||
|
// const base64PngData = 'await getBase64PngData();';
|
||||||
|
ipcRenderer.send('upload-snippet', { screenSnippetPath, base64PngData });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -229,7 +278,7 @@ const SnippingTool = () => {
|
|||||||
<div style={{ position: 'relative', left: '-50%' }}>
|
<div style={{ position: 'relative', left: '-50%' }}>
|
||||||
<ColorPickerPill
|
<ColorPickerPill
|
||||||
data-testid='pen-colorpicker'
|
data-testid='pen-colorpicker'
|
||||||
availableColors={markChosenColor(availablePenColors, penColor)}
|
availableColors={markChosenColor(availablePenColors, penColor.rgbaColor)}
|
||||||
onChange={penColorChosen}
|
onChange={penColorChosen}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -244,7 +293,7 @@ const SnippingTool = () => {
|
|||||||
data-testid='highlight-colorpicker'
|
data-testid='highlight-colorpicker'
|
||||||
availableColors={markChosenColor(
|
availableColors={markChosenColor(
|
||||||
availableHighlightColors,
|
availableHighlightColors,
|
||||||
highlightColor,
|
highlightColor.rgbaColor,
|
||||||
)}
|
)}
|
||||||
onChange={highlightColorChosen}
|
onChange={highlightColorChosen}
|
||||||
/>
|
/>
|
||||||
@ -256,9 +305,10 @@ const SnippingTool = () => {
|
|||||||
<main>
|
<main>
|
||||||
<div className='imageContainer'>
|
<div className='imageContainer'>
|
||||||
<AnnotateArea
|
<AnnotateArea
|
||||||
paths={paths}
|
data-testid='annotate-component'
|
||||||
highlightColor={highlightColor}
|
paths={paths}
|
||||||
penColor={penColor}
|
highlightColor={highlightColor.rgbaColor}
|
||||||
|
penColor={penColor.rgbaColor}
|
||||||
onChange={setPaths}
|
onChange={setPaths}
|
||||||
imageDimensions={imageDimensions}
|
imageDimensions={imageDimensions}
|
||||||
screenSnippetPath={screenSnippetPath}
|
screenSnippetPath={screenSnippetPath}
|
||||||
|
Loading…
Reference in New Issue
Block a user