diff --git a/.vscode/launch.json b/.vscode/launch.json
index 27de8dd5..736e2733 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -47,7 +47,31 @@
]
},
{
- "name": "mana",
+ "name": "build corp",
+ "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://corporate.symphony.com"
+ ],
+ "env": {
+ "ELECTRON_DEBUGGING": "true",
+ "ELECTRON_DEV": "true"
+ },
+ "outputCapture": "std",
+ "sourceMaps": true,
+ "outFiles": [
+ "${workspaceFolder}/lib/**/*.js"
+ ]
+ },
+ {
+ "name": "mana local",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
@@ -68,7 +92,31 @@
"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",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 00000000..62e9bd7d
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,10 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "type": "shell",
+ "command": "npx run-s compile browserify"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/package.json b/package.json
index 31508552..37ff9504 100644
--- a/package.json
+++ b/package.json
@@ -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",
@@ -160,6 +161,7 @@
"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",
diff --git a/src/renderer/components/color-picker-pill.tsx b/src/renderer/components/color-picker-pill.tsx
new file mode 100644
index 00000000..111a614a
--- /dev/null
+++ b/src/renderer/components/color-picker-pill.tsx
@@ -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 (
+
+ );
+ };
+
+ return (
+
+ {props.availableColors.map((color) => ColorDot(color))}
+
+ );
+};
+
+export default ColorPickerPill;
diff --git a/src/renderer/components/snipping-tool.tsx b/src/renderer/components/snipping-tool.tsx
index 5e677ff2..3fa1bdbd 100644
--- a/src/renderer/components/snipping-tool.tsx
+++ b/src/renderer/components/snipping-tool.tsx
@@ -1,25 +1,81 @@
import { ipcRenderer } from 'electron';
import * as React from 'react';
import { i18n } from '../../common/i18n-preload';
+import ColorPickerPill, { IColor } from './color-picker-pill';
-const { useState, useEffect } = React;
+const { useState, useCallback, useRef, useEffect } = React;
-interface IProps {
- drawEnabled: boolean;
- highlightEnabled: boolean;
- eraseEnabled: boolean;
+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: React.FunctionComponent = ({drawEnabled, highlightEnabled, eraseEnabled}) => {
+const SnippingTool = () => {
+ // State and ref preparation functions
const [screenSnippet, setScreenSnippet] = useState('Screen-Snippet');
- const [imageDimensions, setImageDimensions] = useState({height: 600, width: 800});
+ const [imageDimensions, setImageDimensions] = useState({
+ height: 600,
+ width: 800,
+ });
+ const [paths, setPaths] = useState([]);
+ 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 = (_event, {snipImage, height, width}) => {
+ const getSnipImageData = ({ }, { snipImage, height, width }) => {
setScreenSnippet(snipImage);
- setImageDimensions({height, width});
+ setImageDimensions({ height, width });
};
ipcRenderer.on('snipping-tool-data', getSnipImageData);
@@ -30,32 +86,114 @@ const SnippingTool: React.FunctionComponent = ({drawEnabled, highlightEn
};
}, []);
+ const annotateRef = useCallback((domNode) => {
+ if (domNode) {
+ setAnnotateAreaLocation(domNode.getBoundingClientRect());
+ }
+ }, []);
+
+ // Hook that alerts clicks outside of the passed ref
+ const useClickOutsideExaminer = (
+ colorPickerRf: React.RefObject,
+ penRf: React.RefObject,
+ highlightRf: React.RefObject,
+ ) => {
+ 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(null);
+ const penRef = useRef(null);
+ const highlightRef = useRef(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 = () => {
- // setTool("pen");
- // setShouldRenderPenColorPicker(shouldRenderPenColorPicker => !shouldRenderPenColorPicker);
- // setShouldRenderHighlightColorPicker(false);
+ setChosenTool(Tool.pen);
+ setShouldRenderPenColorPicker(!shouldRenderPenColorPicker);
+ setShouldRenderHighlightColorPicker(false);
};
const useHighlight = () => {
- // setTool("highlight");
- // setShouldRenderHighlightColorPicker(shouldRenderHighlightColorPicker => !shouldRenderHighlightColorPicker);
- // setShouldRenderPenColorPicker(false);
+ setChosenTool(Tool.highlight);
+ setShouldRenderHighlightColorPicker(!shouldRenderHighlightColorPicker);
+ setShouldRenderPenColorPicker(false);
};
const useEraser = () => {
- // setTool("eraser");
+ setChosenTool(Tool.eraser);
};
const clear = () => {
- // const updPaths = [...paths];
- // updPaths.map((p) => {
- // p.shouldShow = false;
- // return p;
- // });
- // setPaths(updPaths);
+ const updPaths = [...paths];
+ updPaths.map((p) => {
+ p.shouldShow = false;
+ return p;
+ });
+ setPaths(updPaths);
};
- const done = () => {
+ // 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);
};
@@ -64,52 +202,72 @@ const SnippingTool: React.FunctionComponent = ({drawEnabled, highlightEn
+ ;
+
+ {
+ shouldRenderPenColorPicker && (
+
+ )
+ }
+ {
+ shouldRenderHighlightColorPicker && (
+
+ )
+ }
-
+
+

+
-
+
);
};
diff --git a/src/renderer/styles/snipping-tool.less b/src/renderer/styles/snipping-tool.less
index 66664507..1681c01f 100644
--- a/src/renderer/styles/snipping-tool.less
+++ b/src/renderer/styles/snipping-tool.less
@@ -47,40 +47,27 @@ body {
max-height: 48px;
.ActionButton {
- margin-left: 24px;
+ width: 24px;
+ height: 24px;
+ margin-left: 15px;
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;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 2px solid white;
&:first-child {
margin-left: 0;
}
- img {
- width: 24px;
- height: 24px;
+ &:focus {
+ outline: none;
}
}
@@ -180,3 +167,41 @@ body {
}
}
}
+
+.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;
+}
\ No newline at end of file