Skip to content

Commit fedf6de

Browse files
committed
integrate the take-screenshot function
1 parent 48cd500 commit fedf6de

File tree

7 files changed

+120
-32
lines changed

7 files changed

+120
-32
lines changed

packages/feedback2/src/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export { DEFAULT_THEME } from './theme';
77
// circular dependency between the browser and feedback packages
88
export const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
99
export const DOCUMENT = WINDOW.document;
10+
export const NAVIGATOR = WINDOW.navigator;
1011

1112
export const ACTOR_LABEL = 'Report a Bug';
1213
export const CANCEL_BUTTON_LABEL = 'Cancel';

packages/feedback2/src/modal/createDialog.tsx

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,32 @@ export function createDialog({ options, screenshotIntegration, sendFeedback, sha
2222
const el = DOCUMENT.createElement('div');
2323
const style = createDialogStyles();
2424

25+
const dialog = {
26+
get el() {
27+
return el;
28+
},
29+
appendToDom(): void {
30+
if (!shadow.contains(style) && !shadow.contains(el)) {
31+
shadow.appendChild(style);
32+
shadow.appendChild(el);
33+
}
34+
},
35+
removeFromDom(): void {
36+
shadow.removeChild(el);
37+
shadow.removeChild(style);
38+
},
39+
open() {
40+
renderContent(true);
41+
options.onFormOpen && options.onFormOpen();
42+
},
43+
close() {
44+
renderContent(false);
45+
},
46+
};
47+
48+
const screenshotInput = screenshotIntegration && screenshotIntegration.createInput(h, dialog);
49+
2550
const renderContent = (open: boolean): void => {
26-
const screenshotInput = screenshotIntegration && screenshotIntegration.createInput(h);
2751
render(
2852
<DialogComponent
2953
screenshotInput={screenshotInput}
@@ -66,23 +90,5 @@ export function createDialog({ options, screenshotIntegration, sendFeedback, sha
6690
);
6791
};
6892

69-
return {
70-
appendToDom(): void {
71-
if (!shadow.contains(style) && !shadow.contains(el)) {
72-
shadow.appendChild(style);
73-
shadow.appendChild(el);
74-
}
75-
},
76-
removeFromDom(): void {
77-
shadow.removeChild(el);
78-
shadow.removeChild(style);
79-
},
80-
open() {
81-
renderContent(true);
82-
options.onFormOpen && options.onFormOpen();
83-
},
84-
close() {
85-
renderContent(false);
86-
},
87-
};
93+
return dialog;
8894
}

packages/feedback2/src/screenshot/components/ScreenshotInput.css.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export function createScreenshotInputStyles(): HTMLStyleElement {
1616
background: red;
1717
flex: 1 0 auto;
1818
}
19+
20+
.editor .image {
21+
width: 100%;
22+
height: 100%;
23+
}
1924
`;
2025

2126
return style;

packages/feedback2/src/screenshot/components/ScreenshotInput.tsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,48 @@
11
import type { ComponentType, VNode, h as hType } from 'preact';
2-
import { useEffect, useMemo, useRef } from 'preact/hooks';
2+
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
3+
import type { Dialog } from '../../types';
34
import { createScreenshotInputStyles } from './ScreenshotInput.css';
5+
import { useTakeScreenshot } from './useTakeScreenshot';
46

57
// eslint-disable-next-line @typescript-eslint/no-unused-vars
6-
export function makeInput(h: typeof hType, canvasEl: HTMLCanvasElement): ComponentType {
8+
export function makeInput(h: typeof hType, canvas: HTMLCanvasElement, dialog: Dialog): ComponentType {
9+
console.log('makeInput');
10+
711
return function ScreenshotToggle(): VNode {
812
const styles = useMemo(() => ({ __html: createScreenshotInputStyles().innerText }), []);
913

1014
const canvasContainerRef = useRef<HTMLDivElement>(null);
1115
useEffect(() => {
1216
const container = canvasContainerRef.current;
13-
container && container.appendChild(canvasEl);
14-
return () => container && container.removeChild(canvasEl);
15-
}, [canvasEl]);
17+
container && container.appendChild(canvas);
18+
return () => container && container.removeChild(canvas);
19+
}, [canvas]);
20+
21+
useTakeScreenshot({
22+
onBeforeScreenshot: useCallback(() => {
23+
dialog.el.style.display = 'none';
24+
}, []),
25+
onScreenshot: useCallback(
26+
(imageSource: HTMLVideoElement) => {
27+
const context = canvas.getContext('2d');
28+
if (!context) {
29+
throw new Error('Could not get canvas context');
30+
}
31+
// canvas.width = imageSource.videoWidth;
32+
// canvas.height = imageSource.videoHeight;
33+
context.drawImage(imageSource, 0, 0);
34+
},
35+
[canvas],
36+
),
37+
onAfterScreenshot: useCallback(() => {
38+
dialog.el.style.display = 'block';
39+
}, []),
40+
});
1641

1742
return (
1843
<div class="editor">
1944
<style dangerouslySetInnerHTML={styles} />
20-
<input type="text" />
21-
<div ref={canvasContainerRef} style={{ display: 'none' }} />
45+
<div ref={canvasContainerRef} />
2246
</div>
2347
);
2448
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// biome-ignore lint/nursery/noUnusedImports: reason
2+
import { h } from 'preact'; // eslint-disable-line @typescript-eslint/no-unused-vars
3+
import { useEffect } from 'preact/hooks';
4+
import { DOCUMENT, NAVIGATOR, WINDOW } from '../../constants';
5+
6+
interface Props {
7+
onBeforeScreenshot: () => void;
8+
onScreenshot: (imageSource: HTMLVideoElement) => void;
9+
onAfterScreenshot: () => void;
10+
}
11+
12+
export const useTakeScreenshot = ({ onBeforeScreenshot, onScreenshot, onAfterScreenshot }: Props): void => {
13+
useEffect(() => {
14+
const takeScreenshot = async (): Promise<void> => {
15+
onBeforeScreenshot();
16+
const stream = await NAVIGATOR.mediaDevices.getDisplayMedia({
17+
video: {
18+
width: WINDOW.innerWidth * WINDOW.devicePixelRatio,
19+
height: WINDOW.innerHeight * WINDOW.devicePixelRatio,
20+
},
21+
audio: false,
22+
preferCurrentTab: true,
23+
surfaceSwitching: 'exclude',
24+
} as any);
25+
26+
await new Promise<void>((resolve, reject) => {
27+
const video = DOCUMENT.createElement('video');
28+
const videoTrack = stream.getVideoTracks()[0];
29+
video.srcObject = new MediaStream([videoTrack]);
30+
video.onloadedmetadata = () => {
31+
onScreenshot(video);
32+
stream.getTracks().forEach(track => track.stop());
33+
resolve();
34+
};
35+
video.play().catch(reject);
36+
});
37+
onAfterScreenshot();
38+
};
39+
40+
takeScreenshot().catch(_error => {
41+
// TODO
42+
});
43+
}, []);
44+
};

packages/feedback2/src/screenshot/createInput.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import type { Attachment } from '@sentry/types';
22
import type { h as hType } from 'preact';
33
import { DOCUMENT } from '../constants';
4-
import type { ScreenshotInput } from '../types';
4+
import type { Dialog, ScreenshotInput } from '../types';
55
import { makeInput } from './components/ScreenshotInput';
66

77
/**
88
*
99
*/
10-
export function createInput(h: typeof hType): ScreenshotInput {
11-
const canvasEl = DOCUMENT.createElement('canvas');
10+
export function createInput(h: typeof hType, dialog: Dialog): ScreenshotInput {
11+
const canvas = DOCUMENT.createElement('canvas');
1212

1313
return {
14-
input: makeInput(h, canvasEl),
14+
input: makeInput(h, canvas, dialog),
1515

1616
value: async () => {
1717
const blob = await new Promise<Parameters<BlobCallback>[0]>(resolve => {
18-
canvasEl.toBlob(resolve, 'image/png');
18+
canvas.toBlob(resolve, 'image/png');
1919
});
2020
if (blob) {
2121
const data = new Uint8Array(await blob.arrayBuffer());

packages/feedback2/src/types/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ export interface SendFeedbackOptions {
6060
}
6161

6262
export interface Dialog {
63+
/**
64+
* The HTMLElement that is containing all the form content
65+
*/
66+
el: HTMLElement;
67+
68+
/**
69+
*
70+
*/
6371
appendToDom: () => void;
6472

6573
removeFromDom: () => void;

0 commit comments

Comments
 (0)