Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/core/src/types-hoist/feedback/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ export interface FeedbackGeneralConfiguration {
name: string;
};

/**
* _experiments allows users to enable experimental or internal features.
* We don't consider such features as part of the public API and hence we don't guarantee semver for them.
* Experimental features can be added, changed or removed at any time.
*
* Default: undefined
*/
_experiments: Partial<{ annotations: boolean }>;

/**
* Set an object that will be merged sent as tags data with the event.
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/feedback/src/core/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const buildFeedbackIntegration = ({
email: 'email',
name: 'username',
},
_experiments = {},
tags,
styleNonce,
scriptNonce,
Expand Down Expand Up @@ -158,6 +159,8 @@ export const buildFeedbackIntegration = ({
onSubmitError,
onSubmitSuccess,
onFormSubmitted,

_experiments,
};

let _shadow: ShadowRoot | null = null;
Expand Down
134 changes: 114 additions & 20 deletions packages/feedback/src/screenshot/components/ScreenshotEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,32 +80,44 @@ export function ScreenshotEditorFactory({
const canvasContainerRef = hooks.useRef<HTMLDivElement>(null);
const cropContainerRef = hooks.useRef<HTMLDivElement>(null);
const croppingRef = hooks.useRef<HTMLCanvasElement>(null);
const annotatingRef = hooks.useRef<HTMLCanvasElement>(null);
const [croppingRect, setCroppingRect] = hooks.useState<Box>({ startX: 0, startY: 0, endX: 0, endY: 0 });
const [confirmCrop, setConfirmCrop] = hooks.useState(false);
const [isResizing, setIsResizing] = hooks.useState(false);
const [isAnnotating, setIsAnnotating] = hooks.useState(false);

hooks.useEffect(() => {
WINDOW.addEventListener('resize', resizeCropper, false);
WINDOW.addEventListener('resize', resize, false);

return () => {
WINDOW.removeEventListener('resize', resize, false);
};
}, []);

function resizeCropper(): void {
const cropper = croppingRef.current;
const imageDimensions = constructRect(getContainedSize(imageBuffer));
if (cropper) {
cropper.width = imageDimensions.width * DPI;
cropper.height = imageDimensions.height * DPI;
cropper.style.width = `${imageDimensions.width}px`;
cropper.style.height = `${imageDimensions.height}px`;
const ctx = cropper.getContext('2d');
function resizeCanvas(canvasRef: Hooks.Ref<HTMLCanvasElement>, imageDimensions: Rect): void {
const canvas = canvasRef.current;
if (canvas) {
canvas.width = imageDimensions.width * DPI;
canvas.height = imageDimensions.height * DPI;
canvas.style.width = `${imageDimensions.width}px`;
canvas.style.height = `${imageDimensions.height}px`;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.scale(DPI, DPI);
}
}
}

const cropButton = cropContainerRef.current;
if (cropButton) {
cropButton.style.width = `${imageDimensions.width}px`;
cropButton.style.height = `${imageDimensions.height}px`;
function resize(): void {
const imageDimensions = constructRect(getContainedSize(imageBuffer));

resizeCanvas(croppingRef, imageDimensions);
resizeCanvas(annotatingRef, imageDimensions);

const cropContainer = cropContainerRef.current;
if (cropContainer) {
cropContainer.style.width = `${imageDimensions.width}px`;
cropContainer.style.height = `${imageDimensions.height}px`;
}

setCroppingRect({ startX: 0, startY: 0, endX: imageDimensions.width, endY: imageDimensions.height });
Expand Down Expand Up @@ -141,6 +153,7 @@ export function ScreenshotEditorFactory({
}, [croppingRect]);

function onGrabButton(e: Event, corner: string): void {
setIsAnnotating(false);
setConfirmCrop(false);
setIsResizing(true);
const handleMouseMove = makeHandleMouseMove(corner);
Expand Down Expand Up @@ -247,7 +260,47 @@ export function ScreenshotEditorFactory({
DOCUMENT.addEventListener('mouseup', handleMouseUp);
}

function submit(): void {
function onAnnotateStart(): void {
if (!isAnnotating) return;

const handleMouseMove = (moveEvent: MouseEvent): void => {
const annotateCanvas = annotatingRef.current;
if (annotateCanvas) {
const rect = annotateCanvas.getBoundingClientRect();

const x = moveEvent.clientX - rect.x;
const y = moveEvent.clientY - rect.y;

const ctx = annotateCanvas.getContext('2d');
if (ctx) {
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
}
}
};

const handleMouseUp = (): void => {
const ctx = annotatingRef.current?.getContext('2d');
// starts a new path so on next mouse down, the lines won't connect
if (ctx) {
ctx.beginPath();
}

// draws the annotation onto the image buffer
// TODO: move this to a better place
applyAnnotation();

DOCUMENT.removeEventListener('mousemove', handleMouseMove);
DOCUMENT.removeEventListener('mouseup', handleMouseUp);
};

DOCUMENT.addEventListener('mousemove', handleMouseMove);
DOCUMENT.addEventListener('mouseup', handleMouseUp);
}

function applyCrop(): void {
const cutoutCanvas = DOCUMENT.createElement('canvas');
const imageBox = constructRect(getContainedSize(imageBuffer));
const croppingBox = constructRect(croppingRect);
Expand Down Expand Up @@ -277,7 +330,32 @@ export function ScreenshotEditorFactory({
imageBuffer.style.width = `${croppingBox.width}px`;
imageBuffer.style.height = `${croppingBox.height}px`;
ctx.drawImage(cutoutCanvas, 0, 0);
resizeCropper();
resize();
}
}

function applyAnnotation(): void {
// draw the annotations onto the image (ie "squash" the canvases)
const imageCtx = imageBuffer.getContext('2d');
const annotateCanvas = annotatingRef.current;
if (imageCtx && annotateCanvas) {
imageCtx.drawImage(
annotateCanvas,
0,
0,
annotateCanvas.width,
annotateCanvas.height,
0,
0,
imageBuffer.width,
imageBuffer.height,
);

// clear the annotation canvas
const annotateCtx = annotateCanvas.getContext('2d');
if (annotateCtx) {
annotateCtx.clearRect(0, 0, annotateCanvas.width, annotateCanvas.height);
}
}
}

Expand All @@ -303,7 +381,7 @@ export function ScreenshotEditorFactory({
(dialog.el as HTMLElement).style.display = 'block';
const container = canvasContainerRef.current;
container?.appendChild(imageBuffer);
resizeCropper();
resize();
}, []),
onError: hooks.useCallback(error => {
(dialog.el as HTMLElement).style.display = 'block';
Expand All @@ -314,11 +392,21 @@ export function ScreenshotEditorFactory({
return (
<div class="editor">
<style nonce={options.styleNonce} dangerouslySetInnerHTML={styles} />
{options._experiments.annotations && (
<button
class="editor__pen-tool"
style={{ background: isAnnotating ? 'red' : 'white' }}
onClick={e => {
e.preventDefault();
setIsAnnotating(!isAnnotating);
}}
></button>
)}
<div class="editor__canvas-container" ref={canvasContainerRef}>
<div class="editor__crop-container" style={{ position: 'absolute', zIndex: 1 }} ref={cropContainerRef}>
<div class="editor__crop-container" style={{ zIndex: isAnnotating ? 1 : 2 }} ref={cropContainerRef}>
<canvas
onMouseDown={onDragStart}
style={{ position: 'absolute', cursor: confirmCrop ? 'move' : 'auto' }}
style={{ cursor: confirmCrop ? 'move' : 'auto' }}
ref={croppingRef}
></canvas>
<CropCorner
Expand Down Expand Up @@ -373,7 +461,7 @@ export function ScreenshotEditorFactory({
<button
onClick={e => {
e.preventDefault();
submit();
applyCrop();
setConfirmCrop(false);
}}
class="btn btn--primary"
Expand All @@ -382,6 +470,12 @@ export function ScreenshotEditorFactory({
</button>
</div>
</div>
<canvas
class="editor__annotation"
onMouseDown={onAnnotateStart}
style={{ zIndex: isAnnotating ? '2' : '1' }}
ref={annotatingRef}
></canvas>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export function createScreenshotInputStyles(styleNonce?: string): HTMLStyleEleme

.editor__canvas-container canvas {
object-fit: contain;
position: relative;
position: absolute;
}

.editor__crop-container {
position: absolute;
}

.editor__crop-btn-group {
Expand Down Expand Up @@ -84,6 +88,10 @@ export function createScreenshotInputStyles(styleNonce?: string): HTMLStyleEleme
border-left: none;
border-top: none;
}
.editor__pen-tool {
width: 30px;
height: 30px;
}
`;

if (styleNonce) {
Expand Down
Loading