aboutsummaryrefslogtreecommitdiff
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
parentb1f189ffc7dfe558d5895c8f0cb103ab3e5c17d7 (diff)
starting connecting to new upload endpoint and standardized displaying media
-rw-r--r--src/client/Network.ts2
-rw-r--r--src/client/util/reportManager/ReportManager.tsx75
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx162
-rw-r--r--src/client/util/reportManager/reportManagerSchema.ts2
-rw-r--r--src/client/util/reportManager/reportManagerUtils.ts4
5 files changed, 161 insertions, 84 deletions
diff --git a/src/client/Network.ts b/src/client/Network.ts
index eb827e0c8..70b51d036 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -68,7 +68,7 @@ export namespace Networking {
body: formData,
};
- const endpoint = '/uploadFormData';
+ const endpoint = browndash ? 'http://10.38.71.246:1050/uploadFormData' : '/uploadFormData';
const response = await fetch(endpoint, parameters);
return response.json();
}
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index ff17a5097..d091e2a34 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -164,11 +164,11 @@ export class ReportManager extends React.Component<{}> {
this.setSubmitting(true);
const links = await this.uploadFilesToServer();
+ console.log(links);
if (!links) {
// error uploading files to the server
return;
}
- console.log(links);
const formattedLinks = (links ?? []).map(this.fileLinktoServerLink);
// const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
@@ -252,6 +252,7 @@ export class ReportManager extends React.Component<{}> {
/**
* Handles file upload.
+ *
* @param files uploaded files
*/
private onDrop = (files: File[]) => {
@@ -259,6 +260,36 @@ export class ReportManager extends React.Component<{}> {
};
/**
+ * Returns when the issue passes the current filters.
+ *
+ * @param issue issue to check
+ * @returns boolean indicating whether the issue passes the current filters
+ */
+ private passesTagFilter = (issue: Issue) => {
+ let passesPriority = true;
+ let passesBug = true;
+ if (this.priorityFilter) {
+ passesPriority = issue.labels.some(label => {
+ if (typeof label === 'string') {
+ return label === this.priorityFilter;
+ } else {
+ return label.name === this.priorityFilter;
+ }
+ });
+ }
+ if (this.bugFilter) {
+ passesBug = issue.labels.some(label => {
+ if (typeof label === 'string') {
+ return label === this.bugFilter;
+ } else {
+ return label.name === this.bugFilter;
+ }
+ });
+ }
+ return passesPriority && passesBug;
+ };
+
+ /**
* Gets a JSX element to render a media preview
* @param fileData file data
* @returns JSX element of a piece of media (image, video, audio)
@@ -306,30 +337,6 @@ export class ReportManager extends React.Component<{}> {
return <></>;
};
- private passesTagFilter = (issue: Issue) => {
- let passesPriority = true;
- let passesBug = true;
- if (this.priorityFilter) {
- passesPriority = issue.labels.some(label => {
- if (typeof label === 'string') {
- return label === this.priorityFilter;
- } else {
- return label.name === this.priorityFilter;
- }
- });
- }
- if (this.bugFilter) {
- passesBug = issue.labels.some(label => {
- if (typeof label === 'string') {
- return label === this.bugFilter;
- } else {
- return label.name === this.bugFilter;
- }
- });
- }
- return passesPriority && passesBug;
- };
-
/**
* @returns the component that dispays all issues
*/
@@ -487,26 +494,26 @@ export class ReportManager extends React.Component<{}> {
<option value="" disabled selected>
Type
</option>
- <option className="report-opt" value="bug">
+ <option className="report-opt" value={BugType.BUG}>
Bug
</option>
- <option className="report-opt" value="cosmetic">
+ <option className="report-opt" value={BugType.COSMETIC}>
Poor Design or Cosmetic
</option>
- <option className="report-opt" value="documentation">
+ <option className="report-opt" value={BugType.DOCUMENTATION}>
Poor Documentation
</option>
- <option className="report-opt" value="enhancement">
+ <option className="report-opt" value={BugType.ENHANCEMENT}>
New feature or request
</option>
</select>
- <select className="report-select" name="bigPriority" onChange={e => (this.bugPriority = e.target.value)}>
+ <select className="report-select" name="priority" onChange={e => (this.bugPriority = e.target.value)}>
<option className="report-opt" value="" disabled selected>
Priority
</option>
- <option value="priority-low">Low</option>
- <option value="priority-medium">Medium</option>
- <option value="priority-high">High</option>
+ <option value={Priority.LOW}>Low</option>
+ <option value={Priority.MEDIUM}>Medium</option>
+ <option value={Priority.HIGH}>High</option>
</select>
</div>
<Dropzone
@@ -536,7 +543,7 @@ export class ReportManager extends React.Component<{}> {
this.reportIssue();
}}>
Submit
- {this.submitting && <ReactLoading type="spin" color={theme.palette.primary.main} width={20} height={20} />}
+ {this.submitting && <ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
</Button>
<IconButton onClick={this.close} sx={{ position: 'absolute', top: '4px', right: '4px' }}>
<CgClose size={'16px'} />
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
diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
index 56b9aa32b..9a1c7c3e9 100644
--- a/src/client/util/reportManager/reportManagerSchema.ts
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -1,5 +1,5 @@
/**
- * Issue interface schema.
+ * Issue interface schema from Github.
*/
export interface Issue {
active_lock_reason?: null | string;
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
index 3c919856a..51517e80d 100644
--- a/src/client/util/reportManager/reportManagerUtils.ts
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -22,7 +22,7 @@ export enum BugType {
export const priorityColors: { [key: string]: string[] } = {
'priority-low': ['#d4e0ff', '#000000'],
'priority-medium': ['#6a91f6', '#ffffff'],
- 'priority-high': ['#4476f7', '#ffffff'],
+ 'priority-high': ['#0f4ce7', '#ffffff'],
};
// [bgColor, color]
@@ -42,7 +42,7 @@ export const getLabelColors = (label: string): string[] => {
} else if (bugSet.has(label as BugType)) {
return bugColors[label];
}
- return ['#347bff', '#ffffff'];
+ return ['#0f73f6', '#ffffff'];
};
export interface FileData {