diff options
Diffstat (limited to 'src/client/util/reportManager')
4 files changed, 118 insertions, 98 deletions
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx index 0c49aeed4..2224e642d 100644 --- a/src/client/util/reportManager/ReportManager.tsx +++ b/src/client/util/reportManager/ReportManager.tsx @@ -1,33 +1,39 @@ -import * as React from 'react'; -import * as uuid from 'uuid'; -import '.././SettingsManager.scss'; -import './ReportManager.scss'; -import ReactLoading from 'react-loading'; +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/media-has-caption */ +/* eslint-disable react/no-unused-class-component-methods */ +import { Octokit } from '@octokit/core'; +import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components'; import { action, makeObservable, observable } from 'mobx'; -import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { BsArrowsAngleContract, BsArrowsAngleExpand, BsX } from 'react-icons/bs'; import { CgClose } from 'react-icons/cg'; import { HiOutlineArrowLeft } from 'react-icons/hi'; -import { Issue } from './reportManagerSchema'; -import { observer } from 'mobx-react'; +import { MdRefresh } from 'react-icons/md'; +import ReactLoading from 'react-loading'; +import * as uuid from 'uuid'; +import { ClientUtils } from '../../../ClientUtils'; import { Doc } from '../../../fields/Doc'; -import { MainViewModal } from '../../views/MainViewModal'; -import { Octokit } from '@octokit/core'; -import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components'; -import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils'; -import { Filter, FormInput, FormTextArea, IssueCard, IssueView, Tag } from './ReportManagerComponents'; import { StrCast } from '../../../fields/Types'; -import { MdRefresh } from 'react-icons/md'; +import { MainViewModal } from '../../views/MainViewModal'; +import '../SettingsManager.scss'; import { SettingsManager } from '../SettingsManager'; +import './ReportManager.scss'; +import { Filter, FormInput, FormTextArea, IssueCard, IssueView } from './ReportManagerComponents'; +import { Issue } from './reportManagerSchema'; +import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils'; /** * Class for reporting and viewing Github issues within the app. */ @observer export class ReportManager extends React.Component<{}> { + // eslint-disable-next-line no-use-before-define public static Instance: ReportManager; @observable private isOpen = false; @observable private query = ''; + // eslint-disable-next-line react/sort-comp @action private setQuery = (q: string) => { this.query = q; }; @@ -82,7 +88,9 @@ export class ReportManager extends React.Component<{}> { this.formData = newData; }); - public close = action(() => (this.isOpen = false)); + public close = action(() => { + this.isOpen = false; + }); public open = action(async () => { this.isOpen = true; if (this.shownIssues.length === 0) { @@ -133,7 +141,7 @@ export class ReportManager extends React.Component<{}> { const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', { owner: 'brown-dash', repo: 'Dash-Web', - title: formatTitle(this.formData.title, Doc.CurrentUserEmail), + title: formatTitle(this.formData.title, ClientUtils.CurrentUserEmail()), body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`, labels: ['from-dash-app', this.formData.type, this.formData.priority], }); @@ -164,7 +172,7 @@ export class ReportManager extends React.Component<{}> { * @returns JSX element of a piece of media (image, video, audio) */ private getMediaPreview = (fileData: FileData): JSX.Element => { - const file = fileData.file; + const { file } = fileData; const mimeType = file.type; const preview = URL.createObjectURL(file); @@ -179,7 +187,8 @@ export class ReportManager extends React.Component<{}> { </div> </div> ); - } else if (mimeType.startsWith('video/')) { + } + if (mimeType.startsWith('video/')) { return ( <div key={fileData._id} className="report-media-wrapper"> <div className="report-media-content"> @@ -193,7 +202,8 @@ export class ReportManager extends React.Component<{}> { </div> </div> ); - } else if (mimeType.startsWith('audio/')) { + } + if (mimeType.startsWith('audio/')) { return ( <div key={fileData._id} className="report-audio-wrapper"> <audio src={preview} controls /> @@ -203,7 +213,7 @@ export class ReportManager extends React.Component<{}> { </div> ); } - return <></>; + return <div />; }; /** @@ -306,8 +316,8 @@ export class ReportManager extends React.Component<{}> { <div className="report-selects"> <Dropdown color={StrCast(Doc.UserDoc().userColor)} - formLabel={'Type'} - closeOnSelect={true} + formLabel="Type" + closeOnSelect items={bugDropdownItems} selectedVal={this.formData.type} setSelectedVal={val => { @@ -319,8 +329,8 @@ export class ReportManager extends React.Component<{}> { /> <Dropdown color={StrCast(Doc.UserDoc().userColor)} - formLabel={'Priority'} - closeOnSelect={true} + formLabel="Priority" + closeOnSelect items={priorityDropdownItems} selectedVal={this.formData.priority} setSelectedVal={val => { @@ -346,7 +356,7 @@ export class ReportManager extends React.Component<{}> { text="Submit" type={Type.TERT} color={StrCast(Doc.UserDoc().userVariantColor)} - icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />} + icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />} iconPlacement="right" onClick={() => { this.reportIssue(); @@ -363,7 +373,7 @@ export class ReportManager extends React.Component<{}> { /> )} <div style={{ position: 'absolute', top: '4px', right: '4px' }}> - <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size={'16px'} />} onClick={this.close} /> + <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} /> </div> </div> ); @@ -375,9 +385,8 @@ export class ReportManager extends React.Component<{}> { private reportComponent = () => { if (this.viewState === ViewState.VIEW) { return this.viewIssuesComponent(); - } else { - return this.reportIssueComponent(); } + return this.reportIssueComponent(); }; render() { @@ -385,7 +394,7 @@ export class ReportManager extends React.Component<{}> { <MainViewModal contents={this.reportComponent()} isDisplayed={this.isOpen} - interactive={true} + interactive closeOnExternalClick={this.close} dialogueBoxStyle={{ width: 'auto', minWidth: '300px', height: '85vh', maxHeight: '90vh', background: StrCast(Doc.UserDoc().userBackgroundColor), borderRadius: '8px' }} /> diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx index 1e226bf6d..cecebc648 100644 --- a/src/client/util/reportManager/ReportManagerComponents.tsx +++ b/src/client/util/reportManager/ReportManagerComponents.tsx @@ -1,9 +1,15 @@ +/* eslint-disable react/require-default-props */ +/* eslint-disable prefer-destructuring */ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable no-use-before-define */ import * as React from 'react'; -import { Issue } from './reportManagerSchema'; -import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils'; import ReactMarkdown from 'react-markdown'; import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; +import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils'; +import { Issue } from './reportManagerSchema'; import { StrCast } from '../../../fields/Types'; import { Doc } from '../../../fields/Doc'; @@ -18,7 +24,7 @@ interface FilterProps<T> { } // filter ui for issues (horizontal list of tags) -export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => { +export function Filter<T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) { // establishing theme const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)); const colors = darkMode ? darkColors : lightColors; @@ -28,7 +34,7 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }: return ( <div className="issues-filter"> <Tag - text={'All'} + text="All" onClick={() => { setActiveValue(null); }} @@ -38,25 +44,23 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }: borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border} border /> - {items.map(item => { - return ( - <Tag - key={item} - text={item} - onClick={() => { - setActiveValue(item); - }} - fontSize="12px" - backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'} - color={activeValue === item ? activeTagTextColor : colors.textGrey} - border - borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border} - /> - ); - })} + {items.map(item => ( + <Tag + key={item} + text={item} + onClick={() => { + setActiveValue(item); + }} + fontSize="12px" + backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'} + color={activeValue === item ? activeTagTextColor : colors.textGrey} + border + borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border} + /> + ))} </div> ); -}; +} interface IssueCardProps { issue: Issue; @@ -64,7 +68,7 @@ interface IssueCardProps { } // Component for the issue cards list on the left -export const IssueCard = ({ issue, onSelect }: IssueCardProps) => { +export function IssueCard({ issue, onSelect }: IssueCardProps) { const [textColor, setTextColor] = React.useState(''); const [bgColor, setBgColor] = React.useState('transparent'); const [borderColor, setBorderColor] = React.useState('transparent'); @@ -103,14 +107,14 @@ export const IssueCard = ({ issue, onSelect }: IssueCardProps) => { <h3 className="issue-title">{issue.title}</h3> </div> ); -}; +} interface IssueViewProps { issue: Issue; } // Detailed issue view that displays on the right -export const IssueView = ({ issue }: IssueViewProps) => { +export function IssueView({ issue }: IssueViewProps) { const [issueBody, setIssueBody] = React.useState(''); // Parses the issue body into a formatted markdown (main functionality is replacing urls with tags) @@ -127,15 +131,16 @@ export const IssueView = ({ issue }: IssueViewProps) => { parts.map(async part => { if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) { return `\n${await parseFileTag(part)}\n`; - } else if (fileRegex.test(part)) { + } + if (fileRegex.test(part)) { const tag = await parseDashFiles(part); return tag; - } else if (localRegex.test(part)) { + } + if (localRegex.test(part)) { const tag = await parseLocalFiles(part); return tag; - } else { - return part; } + return part; }) ); @@ -143,7 +148,7 @@ export const IssueView = ({ issue }: IssueViewProps) => { }; // 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 parseFileTag = async (tag: string): Promise<string | undefined> => { const regex = /src="([^"]+)"/; let url = ''; const match = tag.match(regex); @@ -160,18 +165,19 @@ export const IssueView = ({ issue }: IssueViewProps) => { case '.png': case '.jpeg': case '.gif': - return await getDisplayedFile(url, 'image'); + return getDisplayedFile(url, 'image'); // video case '.mp4': case '.mpeg': case '.webm': case '.mov': - return await getDisplayedFile(url, 'video'); - //audio + return getDisplayedFile(url, 'video'); + // audio case '.mp3': case '.wav': case '.ogg': - return await getDisplayedFile(url, 'audio'); + return getDisplayedFile(url, 'audio'); + default: } return tag; }; @@ -183,14 +189,15 @@ export const IssueView = ({ issue }: IssueViewProps) => { 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; + return getDisplayedFile(url, 'image'); } + if (dashVideoRegex.test(url)) { + return getDisplayedFile(url, 'video'); + } + if (dashAudioRegex.test(url)) { + return getDisplayedFile(url, 'audio'); + } + return url; }; // Returns the corresponding HTML tag for a src url @@ -200,31 +207,37 @@ export const IssueView = ({ issue }: IssueViewProps) => { const dashAudioRegex = /http:\/\/localhost:1050\.com\/files[/\\]audio/; if (imgRegex.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; + return getDisplayedFile(url, 'image'); + } + if (dashVideoRegex.test(url)) { + return getDisplayedFile(url, 'video'); + } + if (dashAudioRegex.test(url)) { + return getDisplayedFile(url, 'audio'); } + return url; }; - const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => { + const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string | undefined> => { switch (fileType) { - case 'image': + 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': + } + 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': + } + 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`; + } + default: } + return undefined; }; // Loads an image and returns a promise that resolves as whether the image is valid or not @@ -270,7 +283,7 @@ export const IssueView = ({ issue }: IssueViewProps) => { <div className="issue-view"> <span className="issue-label"> Issue{' '} - <a className="issue-link" href={issue.html_url} target="_blank"> + <a className="issue-link" href={issue.html_url} target="_blank" rel="noreferrer"> #{issue.number} </a> </span> @@ -292,7 +305,7 @@ export const IssueView = ({ issue }: IssueViewProps) => { <ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} /> </div> ); -}; +} interface TagProps { text: string; @@ -305,7 +318,7 @@ interface TagProps { } // Small tag for labels of the issue -export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => { +export function Tag({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) { return ( <div onClick={onClick ?? (() => {})} @@ -314,14 +327,14 @@ export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColo {text} </div> ); -}; +} interface FormInputProps { value: string; placeholder: string; onChange: (val: string) => void; } -export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => { +export function FormInput({ value, placeholder, onChange }: FormInputProps) { const [inputBorderColor, setInputBorderColor] = React.useState(''); return ( @@ -349,9 +362,9 @@ export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => { }} /> ); -}; +} -export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => { +export function FormTextArea({ value, placeholder, onChange }: FormInputProps) { const [textAreaBorderColor, setTextAreaBorderColor] = React.useState(''); return ( @@ -378,4 +391,4 @@ export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) = }} /> ); -}; +} diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts index 9a1c7c3e9..171c24393 100644 --- a/src/client/util/reportManager/reportManagerSchema.ts +++ b/src/client/util/reportManager/reportManagerSchema.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ /** * Issue interface schema from Github. */ diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts index 22e5eebbb..f14967e0a 100644 --- a/src/client/util/reportManager/reportManagerUtils.ts +++ b/src/client/util/reportManager/reportManagerUtils.ts @@ -63,9 +63,8 @@ export const getAllIssues = async (octokit: Octokit): Promise<any[]> => { // 200 status means success if (res.status === 200) { return res.data; - } else { - throw new Error('Error getting issues'); } + throw new Error('Error getting issues'); }; /** @@ -104,9 +103,7 @@ export const fileLinktoServerLink = (fileLink: string): string => { * @param link response from file upload * @returns server file path */ -export const getServerPath = (link: any): string => { - return link.result.accessPaths.agnostic.server as string; -}; +export const getServerPath = (link: any): string => link.result.accessPaths.agnostic.server as string; /** * Uploads media files to the server. @@ -124,6 +121,7 @@ export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<strin alert(err); } } + return undefined; }; // helper functions @@ -141,18 +139,16 @@ export const passesTagFilter = (issue: Issue, priorityFilter: string | null, bug passesPriority = issue.labels.some(label => { if (typeof label === 'string') { return label === priorityFilter; - } else { - return label.name === priorityFilter; } + return label.name === priorityFilter; }); } if (bugFilter) { passesBug = issue.labels.some(label => { if (typeof label === 'string') { return label === bugFilter; - } else { - return label.name === bugFilter; } + return label.name === bugFilter; }); } return passesPriority && passesBug; @@ -217,7 +213,8 @@ export const bugColors: { [key: string]: string[] } = { export const getLabelColors = (label: string): string[] => { if (prioritySet.has(label as Priority)) { return priorityColors[label]; - } else if (bugSet.has(label as BugType)) { + } + if (bugSet.has(label as BugType)) { return bugColors[label]; } return ['#0f73f6', '#ffffff']; |