Skip to content

Commit

Permalink
feat(feedback): Update user feedback screenshot and cropping to align…
Browse files Browse the repository at this point in the history
… with designs (#11227)

Updated some screenshot styles to align with design:
- Changed cropping to more closely follow photoshop: darker background,
white cropping corners + rectangle and additional black border for
contrast if the screenshot is white
- Changed naming to follow BES naming
- Added padding so that the submit/cancel buttons are always visible
without scrolling
- Reduced blurriness of cropping rectangle
<img width="1270" alt="image"
src="https://github.com/getsentry/sentry-javascript/assets/55311782/df73a537-58f9-4f03-9edc-403039e61122">

Relates to getsentry/sentry#63749

---------
  • Loading branch information
c298lee authored Mar 26, 2024
1 parent 16ea3be commit 8f4f3d8
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 50 deletions.
2 changes: 2 additions & 0 deletions packages/feedback/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export const FEEDBACK_WIDGET_SOURCE = 'widget';
export const FEEDBACK_API_SOURCE = 'api';

export const SUCCESS_MESSAGE_TIMEOUT = 5000;

export const CROP_COLOR = '#ffffff';
80 changes: 42 additions & 38 deletions packages/feedback/src/screenshot/components/ScreenshotEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import type { ComponentType, VNode, h as hType } from 'preact';
// biome-ignore lint: needed for preact
import { h } from 'preact'; // eslint-disable-line @typescript-eslint/no-unused-vars
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { DOCUMENT, WINDOW } from '../../constants';
import { CROP_COLOR, DOCUMENT, WINDOW } from '../../constants';
import type { Dialog } from '../../types';
import { createScreenshotInputStyles } from './ScreenshotInput.css';
import { useTakeScreenshot } from './useTakeScreenshot';

const CROP_BUTTON_SIZE = 30;
const CROP_BUTTON_BORDER = 3;
const CROP_BUTTON_OFFSET = CROP_BUTTON_SIZE + CROP_BUTTON_BORDER;
const DPI = WINDOW.devicePixelRatio;

interface FactoryParams {
h: typeof hType;
Expand Down Expand Up @@ -79,8 +80,14 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
const cropper = croppingRef.current;
const imageDimensions = constructRect(getContainedSize(imageBuffer));
if (cropper) {
cropper.width = imageDimensions.width;
cropper.height = imageDimensions.height;
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');
if (ctx) {
ctx.scale(DPI, DPI);
}
}

const cropButton = cropContainerRef.current;
Expand All @@ -104,9 +111,9 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
if (!ctx) {
return;
}

const imageDimensions = constructRect(getContainedSize(imageBuffer));
const croppingBox = constructRect(croppingRect);

ctx.clearRect(0, 0, imageDimensions.width, imageDimensions.height);

// draw gray overlay around the selection
Expand All @@ -115,9 +122,12 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
ctx.clearRect(croppingBox.x, croppingBox.y, croppingBox.width, croppingBox.height);

// draw selection border
ctx.strokeStyle = 'purple';
ctx.strokeStyle = CROP_COLOR;
ctx.lineWidth = 3;
ctx.strokeRect(croppingBox.x, croppingBox.y, croppingBox.width, croppingBox.height);
ctx.strokeRect(croppingBox.x + 1, croppingBox.y + 1, croppingBox.width - 2, croppingBox.height - 2);
ctx.strokeStyle = '#000000';
ctx.lineWidth = 1;
ctx.strokeRect(croppingBox.x + 3, croppingBox.y + 3, croppingBox.width - 6, croppingBox.height - 6);
}, [croppingRect]);

function onGrabButton(e: Event, corner: string): void {
Expand All @@ -143,32 +153,32 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
const mouseX = e.clientX - cropBoundingRect.x;
const mouseY = e.clientY - cropBoundingRect.y;
switch (corner) {
case 'topleft':
case 'top-left':
setCroppingRect(prev => ({
...prev,
startX: Math.min(Math.max(0, mouseX), prev.endX - CROP_BUTTON_OFFSET),
startY: Math.min(Math.max(0, mouseY), prev.endY - CROP_BUTTON_OFFSET),
}));
break;
case 'topright':
case 'top-right':
setCroppingRect(prev => ({
...prev,
endX: Math.max(Math.min(mouseX, cropCanvas.width), prev.startX + CROP_BUTTON_OFFSET),
endX: Math.max(Math.min(mouseX, cropCanvas.width / DPI), prev.startX + CROP_BUTTON_OFFSET),
startY: Math.min(Math.max(0, mouseY), prev.endY - CROP_BUTTON_OFFSET),
}));
break;
case 'bottomleft':
case 'bottom-left':
setCroppingRect(prev => ({
...prev,
startX: Math.min(Math.max(0, mouseX), prev.endX - CROP_BUTTON_OFFSET),
endY: Math.max(Math.min(mouseY, cropCanvas.height), prev.startY + CROP_BUTTON_OFFSET),
endY: Math.max(Math.min(mouseY, cropCanvas.height / DPI), prev.startY + CROP_BUTTON_OFFSET),
}));
break;
case 'bottomright':
case 'bottom-right':
setCroppingRect(prev => ({
...prev,
endX: Math.max(Math.min(mouseX, cropCanvas.width), prev.startX + CROP_BUTTON_OFFSET),
endY: Math.max(Math.min(mouseY, cropCanvas.height), prev.startY + CROP_BUTTON_OFFSET),
endX: Math.max(Math.min(mouseX, cropCanvas.width / DPI), prev.startX + CROP_BUTTON_OFFSET),
endY: Math.max(Math.min(mouseY, cropCanvas.height / DPI), prev.startY + CROP_BUTTON_OFFSET),
}));
break;
}
Expand Down Expand Up @@ -238,40 +248,40 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
return (
<div class="editor">
<style dangerouslySetInnerHTML={styles} />
<div class="canvasContainer" ref={canvasContainerRef}>
<div class="cropButtonContainer" style={{ position: 'absolute' }} ref={cropContainerRef}>
<div class="editor__canvas-container" ref={canvasContainerRef}>
<div class="editor__crop-container" style={{ position: 'absolute' }} ref={cropContainerRef}>
<canvas style={{ position: 'absolute' }} ref={croppingRef}></canvas>
<CropCorner
left={croppingRect.startX}
top={croppingRect.startY}
left={croppingRect.startX - CROP_BUTTON_BORDER}
top={croppingRect.startY - CROP_BUTTON_BORDER}
onGrabButton={onGrabButton}
corner="topleft"
corner="top-left"
></CropCorner>
<CropCorner
left={croppingRect.endX - CROP_BUTTON_SIZE}
top={croppingRect.startY}
left={croppingRect.endX - CROP_BUTTON_SIZE + CROP_BUTTON_BORDER}
top={croppingRect.startY - CROP_BUTTON_BORDER}
onGrabButton={onGrabButton}
corner="topright"
corner="top-right"
></CropCorner>
<CropCorner
left={croppingRect.startX}
top={croppingRect.endY - CROP_BUTTON_SIZE}
left={croppingRect.startX - CROP_BUTTON_BORDER}
top={croppingRect.endY - CROP_BUTTON_SIZE + CROP_BUTTON_BORDER}
onGrabButton={onGrabButton}
corner="bottomleft"
corner="bottom-left"
></CropCorner>
<CropCorner
left={croppingRect.endX - CROP_BUTTON_SIZE}
top={croppingRect.endY - CROP_BUTTON_SIZE}
left={croppingRect.endX - CROP_BUTTON_SIZE + CROP_BUTTON_BORDER}
top={croppingRect.endY - CROP_BUTTON_SIZE + CROP_BUTTON_BORDER}
onGrabButton={onGrabButton}
corner="bottomright"
corner="bottom-right"
></CropCorner>
<div
style={{
left: Math.max(0, croppingRect.endX - 191),
top: Math.max(0, croppingRect.endY + 8),
display: confirmCrop ? 'flex' : 'none',
}}
class="crop-btn-group"
class="editor__crop-btn-group"
>
<button
onClick={e => {
Expand All @@ -280,8 +290,8 @@ export function makeScreenshotEditorComponent({ h, imageBuffer, dialog }: Factor
setCroppingRect({
startX: 0,
startY: 0,
endX: croppingRef.current.width,
endY: croppingRef.current.height,
endX: croppingRef.current.width / DPI,
endY: croppingRef.current.height / DPI,
});
}
setConfirmCrop(false);
Expand Down Expand Up @@ -321,16 +331,10 @@ function CropCorner({
}): VNode {
return (
<button
class="crop-btn"
class={`editor__crop-corner editor__crop-corner--${corner} `}
style={{
top: top,
left: left,
borderTop: corner === 'topleft' || corner === 'topright' ? 'solid purple' : 'none',
borderLeft: corner === 'topleft' || corner === 'bottomleft' ? 'solid purple' : 'none',
borderRight: corner === 'topright' || corner === 'bottomright' ? 'solid purple' : 'none',
borderBottom: corner === 'bottomleft' || corner === 'bottomright' ? 'solid purple' : 'none',
borderWidth: `${CROP_BUTTON_BORDER}px`,
cursor: corner === 'topleft' || corner === 'bottomright' ? 'nwse-resize' : 'nesw-resize',
}}
onMouseDown={e => {
e.preventDefault();
Expand Down
44 changes: 32 additions & 12 deletions packages/feedback/src/screenshot/components/ScreenshotInput.css.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { DOCUMENT } from '../../constants';
import { CROP_COLOR, DOCUMENT } from '../../constants';

/**
* Creates <style> element for widget dialog
*/
export function createScreenshotInputStyles(): HTMLStyleElement {
const style = DOCUMENT.createElement('style');

const surface200 = '#FAF9FB';
const gray100 = '#F0ECF3';
const surface200 = '#1A141F';
const gray100 = '#302735';

style.textContent = `
.dialog__content:has(.editor) {
Expand All @@ -16,6 +16,9 @@ export function createScreenshotInputStyles(): HTMLStyleElement {
}
.editor {
padding: 10px;
padding-top: 65px;
padding-bottom: 65px;
flex-grow: 1;
background-color: ${surface200};
Expand All @@ -35,24 +38,19 @@ export function createScreenshotInputStyles(): HTMLStyleElement {
);
}
.canvasContainer {
.editor__canvas-container {
width: 100%;
height: 100%;
position: relative;
}
.canvasContainer canvas {
.editor__canvas-container canvas {
width: 100%;
height: 100%;
object-fit: contain;
}
.cropper {
width: 100%;
height: 100%;
}
.crop-btn-group {
.editor__crop-btn-group {
padding: 8px;
gap: 8px;
border-radius: var(--form-content-border-radius);
Expand All @@ -61,11 +59,33 @@ export function createScreenshotInputStyles(): HTMLStyleElement {
position: absolute;
}
.crop-btn {
.editor__crop-corner {
width: 30px;
height: 30px;
position: absolute;
background: none;
border: 3px solid ${CROP_COLOR};
}
.editor__crop-corner--top-left {
cursor: nwse-resize;
border-right: none;
border-bottom: none;
}
.editor__crop-corner--top-right {
cursor: nesw-resize;
border-left: none;
border-bottom: none;
}
.editor__crop-corner--bottom-left {
cursor: nesw-resize;
border-right: none;
border-top: none;
}
.editor__crop-corner--bottom-right {
cursor: nwse-resize;
border-left: none;
border-top: none;
}
`;

Expand Down

0 comments on commit 8f4f3d8

Please sign in to comment.