diff options
author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-07-11 13:30:51 -0400 |
---|---|---|
committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-07-11 13:30:51 -0400 |
commit | c1df53a7616ccbb9afad2deaf3026e70f3e974b4 (patch) | |
tree | ff3ed78bf5de39a31eeaad73d9a44d373aae0185 /src | |
parent | b1f189ffc7dfe558d5895c8f0cb103ab3e5c17d7 (diff) |
starting connecting to new upload endpoint and standardized displaying media
Diffstat (limited to 'src')
-rw-r--r-- | src/client/Network.ts | 2 | ||||
-rw-r--r-- | src/client/util/reportManager/ReportManager.tsx | 75 | ||||
-rw-r--r-- | src/client/util/reportManager/ReportManagerComponents.tsx | 162 | ||||
-rw-r--r-- | src/client/util/reportManager/reportManagerSchema.ts | 2 | ||||
-rw-r--r-- | src/client/util/reportManager/reportManagerUtils.ts | 4 |
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 { |