aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/reportManager/ReportManagerComponents.tsx
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-07-11 13:30:51 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2023-07-11 13:30:51 -0400
commitc1df53a7616ccbb9afad2deaf3026e70f3e974b4 (patch)
treeff3ed78bf5de39a31eeaad73d9a44d373aae0185 /src/client/util/reportManager/ReportManagerComponents.tsx
parentb1f189ffc7dfe558d5895c8f0cb103ab3e5c17d7 (diff)
starting connecting to new upload endpoint and standardized displaying media
Diffstat (limited to 'src/client/util/reportManager/ReportManagerComponents.tsx')
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx162
1 files changed, 116 insertions, 46 deletions
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
index 1a4ddb3a3..21c0cb43c 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -5,12 +5,16 @@ import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
-// Mini components to render issues
+/**
+ * Mini components to render issues.
+ */
interface IssueCardProps {
issue: Issue;
onSelect: () => void;
}
+
+// Component for the issue cards list on the left
export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
return (
<div className="issue-card" onClick={onSelect}>
@@ -33,62 +37,25 @@ interface IssueViewProps {
issue: Issue;
}
+// Detailed issue view that displays on the right
export const IssueView = ({ issue }: IssueViewProps) => {
const [issueBody, setIssueBody] = React.useState('');
- const isVideoValid = (src: string) => {
- const videoElement = document.createElement('video');
- const validPromise: Promise<boolean> = new Promise(resolve => {
- videoElement.addEventListener('loadeddata', () => resolve(true));
- videoElement.addEventListener('error', () => resolve(false));
- });
- videoElement.src = src;
- return validPromise;
- };
-
- const getLinkFromTag = async (tag: string) => {
- const regex = /src="([^"]+)"/;
- let url = '';
- const match = tag.match(regex);
- if (match) {
- url = match[1];
- }
-
- if (url.startsWith('https://github.com/brown-dash/Dash-Web/assets')) {
- return `\n${url} (Not authorized to display image here)`;
- }
- return await getTagFromUrl(url);
- };
-
- const getTagFromUrl = async (url: string) => {
- const imgRegex = /https:\/\/browndash\.com\/files[/\\]images/;
- const videoRegex = /https:\/\/browndash\.com\/files[/\\]videos/;
- const audioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
-
- if (imgRegex.test(url) || url.includes('user-images.githubusercontent.com')) {
- return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
- } else if (videoRegex.test(url)) {
- const videoValid = await isVideoValid(url);
- if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
- return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
- } else if (audioRegex.test(url)) {
- return `\n${url}\n<audio src=${url} controls />\n`;
- } else {
- return url;
- }
- };
-
+ // Parses the issue body into a formatted markdown (main functionality is replacing urls with tags)
const parseBody = async (body: string) => {
const imgTagRegex = /<img\b[^>]*\/?>/;
+ const videoTagRegex = /<video\b[^>]*\/?>/;
+ const audioTagRegex = /<audio\b[^>]*\/?>/;
+
const fileRegex = /https:\/\/browndash\.com\/files/;
const parts = body.split('\n');
const modifiedParts = await Promise.all(
parts.map(async part => {
- if (imgTagRegex.test(part)) {
- return `\n${await getLinkFromTag(part)}\n`;
+ if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) {
+ return `\n${await parseFileTag(part)}\n`;
} else if (fileRegex.test(part)) {
- const tag = await getTagFromUrl(part);
+ const tag = await parseDashFiles(part);
return tag;
} else {
return part;
@@ -99,6 +66,108 @@ export const IssueView = ({ issue }: IssueViewProps) => {
setIssueBody(modifiedParts.join('\n'));
};
+ // Extracts the src from an image tag and either returns the raw url if not accessible or a new image tag
+ const parseFileTag = async (tag: string): Promise<string> => {
+ const regex = /src="([^"]+)"/;
+ let url = '';
+ const match = tag.match(regex);
+ if (!match) return tag;
+ url = match[1];
+ if (!url) return tag;
+
+ const mimeType = url.split('.').pop();
+ if (!mimeType) return tag;
+
+ switch (mimeType) {
+ // image
+ case '.jpg':
+ case '.png':
+ case '.jpeg':
+ case '.gif':
+ return await getDisplayedFile(url, 'image');
+ // video
+ case '.mp4':
+ case '.mpeg':
+ case '.webm':
+ case '.mov':
+ return await getDisplayedFile(url, 'video');
+ //audio
+ case '.mp3':
+ case '.wav':
+ case '.ogg':
+ return await getDisplayedFile(url, 'audio');
+ }
+ return tag;
+ };
+
+ // Returns the corresponding HTML tag for a src url
+ const parseDashFiles = async (url: string) => {
+ const dashImgRegex = /https:\/\/browndash\.com\/files[/\\]images/;
+ const dashVideoRegex = /https:\/\/browndash\.com\/files[/\\]videos/;
+ const dashAudioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
+
+ if (dashImgRegex.test(url)) {
+ return await getDisplayedFile(url, 'image');
+ } else if (dashVideoRegex.test(url)) {
+ return await getDisplayedFile(url, 'video');
+ } else if (dashAudioRegex.test(url)) {
+ return await getDisplayedFile(url, 'audio');
+ } else {
+ return url;
+ }
+ };
+
+ const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => {
+ switch (fileType) {
+ case 'image':
+ const imgValid = await isImgValid(url);
+ if (!imgValid) return `\n${url} (This image could not be loaded)\n`;
+ return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
+ case 'video':
+ const videoValid = await isVideoValid(url);
+ if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
+ return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
+ case 'audio':
+ const audioValid = await isAudioValid(url);
+ if (!audioValid) return `\n${url} (This audio could not be loaded)\n`;
+ return `\n${url}\n<audio src=${url} controls />\n`;
+ }
+ };
+
+ // Loads an image and returns a promise that resolves as whether the image is valid or not
+ const isImgValid = (src: string) => {
+ const imgElement = document.createElement('video');
+ const validPromise: Promise<boolean> = new Promise(resolve => {
+ imgElement.addEventListener('load', () => resolve(true));
+ imgElement.addEventListener('error', () => resolve(false));
+ });
+ imgElement.src = src;
+ return validPromise;
+ };
+
+ // Loads a video and returns a promise that resolves as whether the video is valid or not
+ const isVideoValid = (src: string) => {
+ const videoElement = document.createElement('video');
+ const validPromise: Promise<boolean> = new Promise(resolve => {
+ videoElement.addEventListener('loadeddata', () => resolve(true));
+ videoElement.addEventListener('error', () => resolve(false));
+ });
+ videoElement.src = src;
+ return validPromise;
+ };
+
+ // Loads audio and returns a promise that resolves as whether the audio is valid or not
+ const isAudioValid = (src: string) => {
+ const audioElement = document.createElement('audio');
+ const validPromise: Promise<boolean> = new Promise(resolve => {
+ audioElement.addEventListener('loadeddata', () => resolve(true));
+ audioElement.addEventListener('error', () => resolve(false));
+ });
+ audioElement.src = src;
+ return validPromise;
+ };
+
+ // Called on mount to parse the body
React.useEffect(() => {
parseBody((issue.body as string) ?? '');
}, [issue]);
@@ -141,6 +210,7 @@ interface TagProps {
onClick?: () => void;
}
+// Small tag for labels of the issue
export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
return (
<div