aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/theme.ts8
-rw-r--r--src/client/util/reportManager/ReportManager.scss (renamed from src/client/util/ReportManager.scss)114
-rw-r--r--src/client/util/reportManager/ReportManager.tsx (renamed from src/client/util/ReportManager.tsx)304
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx153
-rw-r--r--src/client/util/reportManager/reportManagerSchema.ts (renamed from src/client/util/reportManagerSchema.ts)2
-rw-r--r--src/client/util/reportManager/reportManagerUtils.ts54
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx3
-rw-r--r--src/client/views/nodes/LoadingBox.scss4
-rw-r--r--src/client/views/topbar/TopBar.tsx2
10 files changed, 477 insertions, 169 deletions
diff --git a/src/client/theme.ts b/src/client/theme.ts
index f0a11fe6e..0ef2e5e2c 100644
--- a/src/client/theme.ts
+++ b/src/client/theme.ts
@@ -19,5 +19,13 @@ export const theme = createTheme({
},
},
},
+
+ MuiTooltip: {
+ styleOverrides: {
+ tooltip: {
+ fontSize: '12px',
+ },
+ },
+ },
},
});
diff --git a/src/client/util/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss
index b3bd998e6..81af41cb0 100644
--- a/src/client/util/ReportManager.scss
+++ b/src/client/util/reportManager/ReportManager.scss
@@ -1,7 +1,10 @@
-@import '../views/global/globalCssVariables';
+@import '../../views/global/globalCssVariables';
// header
+$text-gray: #64748b;
+$outline-gray: #cbd5e1;
+
.report-header {
display: flex;
justify-content: space-between;
@@ -45,7 +48,7 @@
.report-label {
font-size: 14px;
font-weight: 400;
- color: #747474;
+ color: $text-gray;
}
.report-section {
@@ -57,7 +60,8 @@
width: 100%;
height: 80px;
padding: 8px;
- resize: none;
+ resize: vertical;
+ // resize: none;
}
.report-selects {
@@ -68,7 +72,7 @@
.report-select {
padding: 8px;
- border-color: #c6c6c6;
+ border-color: $outline-gray;
.report-opt {
padding: 8px;
@@ -80,13 +84,13 @@
.report-input {
border: none;
outline: none;
- border-bottom: 1px solid #c6c6c6;
+ border-bottom: 1px solid $outline-gray;
padding: 8px;
padding-left: 0;
transition: all 0.2s ease;
&:hover {
- border-bottom-color: #7f7f7f;
+ border-bottom-color: $text-gray;
}
&:focus {
border-bottom-color: #4476f7;
@@ -96,15 +100,20 @@
// View issues
.view-issues {
- width: 65vw;
+ width: 75vw;
min-width: 500px;
display: flex;
gap: 16px;
height: 100%;
+ overflow-x: auto;
+
+ video::-webkit-media-controls {
+ display: flex !important;
+ }
.left {
- height: 100%;
flex: 1;
+ height: 100%;
padding: 16px;
display: flex;
flex-direction: column;
@@ -114,6 +123,7 @@
position: relative;
.issues {
+ padding-top: 24px;
position: relative;
flex-grow: 1;
overflow-y: auto;
@@ -125,43 +135,47 @@
}
.right {
+ position: relative;
+ flex: 1;
padding: 16px;
+ min-width: 300px;
height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
- flex: 1;
}
}
-// video
-
-.default-video::-webkit-media-controls {
- display: block !important;
-}
-
// Issue
-.issue {
+.issue-card {
cursor: pointer;
padding: 16px;
background-color: #ffffff;
- border: 1px solid #d3d3d3;
+ border: 1px solid $outline-gray;
transition: all 0.1s ease;
display: flex;
flex-direction: column;
gap: 8px;
border-radius: 8px;
transition: all 0.2s ease;
- // box-shadow: 0 0 8px #d0d0d07c;
+
+ .issue-top {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding-bottom: 8px;
+ }
.issue-label {
cursor: pointer;
font-size: 14px;
font-weight: 400;
- color: #7f7f7f;
+ color: $text-gray;
+ padding: 0;
+ margin: 0;
}
.issue-title {
@@ -192,14 +206,14 @@
.dropzone {
padding: 2rem;
border-radius: 0.5rem;
- border: 2px dashed #ebebeb;
+ border: 2px dashed #f1f5f9;
.dropzone-instructions {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
- color: #7f7f7f;
+ color: $text-gray;
p {
text-align: center;
@@ -212,11 +226,12 @@
margin: 0;
padding: 0;
font-size: 14px;
- color: #7f7f7f;
+ color: $text-gray;
width: 100%;
overflow-x: auto;
list-style-type: none;
display: flex;
+ align-items: center;
gap: 16px;
.file-name {
@@ -242,7 +257,7 @@
overflow: auto;
.issue-label {
- color: #7f7f7f;
+ color: $text-gray;
.issue-link {
cursor: pointer;
@@ -256,16 +271,47 @@
padding: 0;
}
+ .issue-date {
+ font-size: 14px;
+ color: $text-gray;
+ }
+
.issue-content {
font-size: 14px;
- color: #7f7f7f;
+ color: $text-gray;
+ }
+}
+
+// tags flex lists
+
+.issues-filters {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .issues-filter {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ white-space: nowrap;
+ overflow-x: auto;
}
}
+.issue-tags {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ white-space: nowrap;
+ overflow-x: auto;
+}
+
// Media previews
.report-media-wrapper {
position: relative;
+ cursor: pointer;
.close-btn {
position: absolute;
@@ -277,7 +323,10 @@
.report-media-content {
position: relative;
display: inline block;
- cursor: pointer;
+
+ video::-webkit-media-controls {
+ display: flex !important;
+ }
}
.report-media-content::after {
@@ -290,6 +339,11 @@
background-color: rgba(0, 0, 0, 0.5); /* Adjust the opacity as desired */
opacity: 0;
transition: opacity 0.3s ease; /* Transition for smooth effect */
+ pointer-events: none;
+
+ video::-webkit-media-controls {
+ pointer-events: all;
+ }
}
&:hover {
@@ -317,6 +371,16 @@
}
}
+// Tag styling
+
+.report-tag {
+ box-sizing: border-box;
+ padding: 4px 10px;
+ font-size: 10px;
+ border-radius: 32px;
+ transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
+}
+
// Old code
// <---------------------------------------------------------------------------->
diff --git a/src/client/util/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index 125d20876..ff17a5097 100644
--- a/src/client/util/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -1,52 +1,29 @@
-import { ColorState, SketchPicker } from 'react-color';
-import { Id } from '../../fields/FieldSymbols';
-import { BoolCast, Cast, StrCast } from '../../fields/Types';
-import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
-import { DocServer } from '../DocServer';
-import { FontIconBox } from '../views/nodes/button/FontIconBox';
-import { DragManager } from './DragManager';
-import { GroupManager } from './GroupManager';
-import { CheckBox } from '../views/search/CheckBox';
-import { undoBatch } from './UndoManager';
import * as React from 'react';
-import './SettingsManager.scss';
+import '.././SettingsManager.scss';
import './ReportManager.scss';
-import { action, computed, observable, runInAction } from 'mobx';
-import { BsX } from 'react-icons/bs';
-import { BiX } from 'react-icons/bi';
+import { action, observable } from 'mobx';
+import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
+import { CgClose } from 'react-icons/cg';
import { AiOutlineUpload } from 'react-icons/ai';
import { HiOutlineArrowLeft } from 'react-icons/hi';
import { Issue } from './reportManagerSchema';
import { observer } from 'mobx-react';
-import { Doc } from '../../fields/Doc';
-import { Networking } from '../Network';
-import { MainViewModal } from '../views/MainViewModal';
+import { Doc } from '../../../fields/Doc';
+import { Networking } from '../../Network';
+import { MainViewModal } from '../../views/MainViewModal';
import { Octokit } from '@octokit/core';
-import { Button, IconButton } from '@mui/material';
-import { Oval } from 'react-loader-spinner';
+import { Button, IconButton, Tooltip } from '@mui/material';
import Dropzone from 'react-dropzone';
-import ReactMarkdown from 'react-markdown';
-import rehypeRaw from 'rehype-raw';
-import remarkGfm from 'remark-gfm';
-import { theme } from '../theme';
+import { theme } from '../../theme';
+import ReactLoading from 'react-loading';
import v4 = require('uuid/v4');
+import { BugType, FileData, Priority, ViewState, inactiveBorderColor, inactiveColor } from './reportManagerUtils';
+import { IssueCard, IssueView, Tag } from './ReportManagerComponents';
+// import { IconButton } from 'browndash-components';
const higflyout = require('@hig/flyout');
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-enum ViewState {
- VIEW,
- CREATE,
-}
-
-interface FileData {
- _id: string;
- file: File;
-}
-
-// Format reference: "https://browndash.com/files/images/upload_cb31bc0fda59c96ca14193ec494f80cf_o.jpg" />
-
/**
* Class for reporting and viewing Github issues within the app.
*/
@@ -82,11 +59,28 @@ export class ReportManager extends React.Component<{}> {
this.shownIssues = issues;
});
+ @observable
+ public priorityFilter: Priority | null = null;
+ @action setPriorityFilter = action((priority: Priority | null) => {
+ this.priorityFilter = priority;
+ });
+
+ @observable
+ public bugFilter: BugType | null = null;
+ @action setBugFilter = action((bug: BugType | null) => {
+ this.bugFilter = bug;
+ });
+
@observable selectedIssue: Issue | undefined = undefined;
@action setSelectedIssue = action((issue: Issue | undefined) => {
this.selectedIssue = issue;
});
+ @observable rightExpanded: boolean = false;
+ @action setRightExpanded = action((expanded: boolean) => {
+ this.rightExpanded = expanded;
+ });
+
// Form state
@observable private bugTitle = '';
@@ -119,6 +113,8 @@ export class ReportManager extends React.Component<{}> {
try {
// load in the issues if not already loaded
const issues = (await this.getAllIssues()) as Issue[];
+ console.log(issues);
+ // filtering to include only open issues and exclude pull requests, maybe add a separate tab for pr's?
this.setShownIssues(issues.filter(issue => issue.state === 'open' && !issue.pull_request));
} catch (err) {
console.log(err);
@@ -133,7 +129,7 @@ export class ReportManager extends React.Component<{}> {
// initializing Github connection
this.octokit = new Octokit({
- auth: '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',
+ auth: process.env.GITHUB_ACCESS_TOKEN,
});
}
@@ -145,6 +141,7 @@ export class ReportManager extends React.Component<{}> {
const res = await this.octokit.request('GET /repos/{owner}/{repo}/issues', {
owner: 'brown-dash',
repo: 'Dash-Web',
+ per_page: 80,
});
// 200 status means success
@@ -171,21 +168,22 @@ export class ReportManager extends React.Component<{}> {
// 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', {
- owner: 'brown-dash',
- repo: 'Dash-Web',
- title: this.formatTitle(this.bugTitle, Doc.CurrentUserEmail),
- body: `${this.bugDescription} ${formattedLinks.length > 0 && `\n\nFiles:\n${formattedLinks.join('\n')}`}`,
- labels: ['from-dash-app', this.bugType, this.bugPriority],
- });
+ // const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
+ // owner: 'brown-dash',
+ // repo: 'Dash-Web',
+ // title: this.formatTitle(this.bugTitle, Doc.CurrentUserEmail),
+ // body: `${this.bugDescription} ${formattedLinks.length > 0 && `\n\nFiles:\n${formattedLinks.join('\n')}`}`,
+ // labels: ['from-dash-app', this.bugType, this.bugPriority],
+ // });
- // 201 status means success
- if (req.status !== 201) {
- alert('Error creating issue on github.');
- return;
- }
+ // // 201 status means success
+ // if (req.status !== 201) {
+ // alert('Error creating issue on github.');
+ // return;
+ // }
// Reset fields
this.setBugTitle('');
@@ -207,7 +205,7 @@ export class ReportManager extends React.Component<{}> {
*/
private formatTitle = (title: string, userEmail: string): string => `${title} - ${userEmail.replace('@brown.edu', '')}`;
- // turns an upload link into a servable link
+ // turns an upload link -> server link
// ex:
// C: /Users/dash/Documents/GitHub/Dash-Web/src/server/public/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2.png
// -> https://browndash.com/files/images/upload_8008dbc4b6424fbff14da7345bb32eb2_l.png
@@ -237,6 +235,7 @@ export class ReportManager extends React.Component<{}> {
*/
private uploadFilesToServer = async (): Promise<string[] | undefined> => {
try {
+ // need to always upload to browndash
const links = await Networking.UploadFilesToServer(
this.mediaFiles.map(file => ({ file: file.file })),
true
@@ -273,7 +272,7 @@ export class ReportManager extends React.Component<{}> {
return (
<div key={fileData._id} className="report-media-wrapper">
<div className="report-media-content">
- <img height={50} alt={`Preview of ${file.name}`} src={preview} style={{ display: 'block' }} />
+ <img height={100} alt={`Preview of ${file.name}`} src={preview} style={{ display: 'block' }} />
</div>
<IconButton onClick={() => this.setMediaFiles(this.mediaFiles.filter(f => f._id !== fileData._id))} className="close-btn">
<BsX color="#ffffff" />
@@ -284,7 +283,7 @@ export class ReportManager extends React.Component<{}> {
return (
<div key={fileData._id} className="report-media-wrapper">
<div className="report-media-content">
- <video controls style={{ height: '50px', width: 'auto', display: 'block' }}>
+ <video className="report-default-video" controls style={{ height: '100px', width: 'auto', display: 'block' }}>
<source src={preview} type="video/mp4" />
Your browser does not support the video tag.
</video>
@@ -307,13 +306,37 @@ 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
*/
private viewIssuesComponent = () => {
return (
<div className="view-issues">
- <div className="left">
+ <div className="left" style={{ display: this.rightExpanded ? 'none' : 'flex' }}>
<div className="report-header">
<h2>Open Issues</h2>
<Button
@@ -327,20 +350,81 @@ export class ReportManager extends React.Component<{}> {
<input
className="report-input"
type="text"
- placeholder="Filter..."
+ placeholder="Filter by query..."
onChange={e => {
this.setQuery(e.target.value);
}}
required
/>
+ <div className="issues-filters">
+ <div className="issues-filter">
+ <Tag
+ text={'All'}
+ onClick={() => {
+ this.setPriorityFilter(null);
+ }}
+ fontSize="12px"
+ backgroundColor={this.priorityFilter === null ? theme.palette.primary.main : '#ffffff'}
+ color={this.priorityFilter === null ? '#ffffff' : inactiveColor}
+ border
+ borderColor={this.priorityFilter === null ? theme.palette.primary.main : inactiveBorderColor}
+ />
+ {Object.values(Priority).map(p => {
+ return (
+ <Tag
+ key={p}
+ text={p}
+ onClick={() => {
+ this.setPriorityFilter(p);
+ }}
+ fontSize="12px"
+ backgroundColor={this.priorityFilter === p ? theme.palette.primary.main : '#ffffff'}
+ color={this.priorityFilter === p ? '#ffffff' : inactiveColor}
+ border
+ borderColor={this.priorityFilter === p ? theme.palette.primary.main : inactiveBorderColor}
+ />
+ );
+ })}
+ </div>
+ <div className="issues-filter">
+ <Tag
+ text={'All'}
+ onClick={() => {
+ this.setBugFilter(null);
+ }}
+ fontSize="12px"
+ backgroundColor={this.bugFilter === null ? theme.palette.primary.main : '#ffffff'}
+ color={this.bugFilter === null ? '#ffffff' : inactiveColor}
+ border
+ borderColor={this.bugFilter === null ? theme.palette.primary.main : inactiveBorderColor}
+ />
+ {Object.values(BugType).map(b => {
+ return (
+ <Tag
+ key={b}
+ text={b}
+ onClick={() => {
+ this.setBugFilter(b);
+ }}
+ fontSize="12px"
+ backgroundColor={this.bugFilter === b ? theme.palette.primary.main : '#ffffff'}
+ color={this.bugFilter === b ? '#ffffff' : inactiveColor}
+ border
+ borderColor={this.bugFilter === b ? theme.palette.primary.main : inactiveBorderColor}
+ />
+ );
+ })}
+ </div>
+ </div>
<div className="issues">
{this.fetchingIssues ? (
<div style={{ flexGrow: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <Oval height={50} width={50} color={theme.palette.primary.main} visible={true} ariaLabel="oval-loading" secondaryColor={theme.palette.primary.main + 'b8'} strokeWidth={3} strokeWidthSecondary={3} />
+ <ReactLoading type="spin" color={theme.palette.primary.main} width={50} height={50} />
</div>
) : (
this.shownIssues
.filter(issue => issue.title.toLowerCase().includes(this.query))
+ .filter(issue => this.passesTagFilter(issue))
.map(issue => (
<IssueCard
key={issue.number}
@@ -353,10 +437,21 @@ export class ReportManager extends React.Component<{}> {
)}
</div>
</div>
- <div className="right">{this.selectedIssue ? <IssueView issue={this.selectedIssue} /> : <div>No issue selected</div>}</div>
- <IconButton sx={{ position: 'absolute', top: '4px', right: '4px' }} onClick={this.close}>
- <BiX size="16px" />
- </IconButton>
+ <div className="right">{this.selectedIssue ? <IssueView issue={this.selectedIssue} /> : <div>No issue selected</div>} </div>
+ <div style={{ position: 'absolute', top: '8px', right: '8px', display: 'flex', gap: '16px' }}>
+ <Tooltip title={this.rightExpanded ? 'Minimize right side' : 'Expand right side'}>
+ <IconButton
+ onClick={e => {
+ e.stopPropagation();
+ this.setRightExpanded(!this.rightExpanded);
+ }}>
+ {this.rightExpanded ? <BsArrowsAngleContract size="16px" /> : <BsArrowsAngleExpand size="16px" />}
+ </IconButton>
+ </Tooltip>
+ <IconButton onClick={this.close}>
+ <CgClose size="16px" />
+ </IconButton>
+ </div>
</div>
);
};
@@ -392,15 +487,18 @@ export class ReportManager extends React.Component<{}> {
<option value="" disabled selected>
Type
</option>
- <option className="report-opt" value="priority-low">
+ <option className="report-opt" value="bug">
Bug
</option>
- <option className="report-opt" value="priority-medium">
+ <option className="report-opt" value="cosmetic">
Poor Design or Cosmetic
</option>
- <option className="report-opt" value="priority-high">
+ <option className="report-opt" value="documentation">
Poor Documentation
</option>
+ <option className="report-opt" value="enhancement">
+ New feature or request
+ </option>
</select>
<select className="report-select" name="bigPriority" onChange={e => (this.bugPriority = e.target.value)}>
<option className="report-opt" value="" disabled selected>
@@ -438,10 +536,10 @@ export class ReportManager extends React.Component<{}> {
this.reportIssue();
}}>
Submit
- {this.submitting && <Oval height={20} width={20} color="#ffffff" visible={true} ariaLabel="oval-loading" secondaryColor="#ffffff87" strokeWidth={3} strokeWidthSecondary={3} />}
+ {this.submitting && <ReactLoading type="spin" color={theme.palette.primary.main} width={20} height={20} />}
</Button>
- <IconButton sx={{ position: 'absolute', top: '4px', right: '4px' }} onClick={this.close}>
- <BiX size={'16px'} />
+ <IconButton onClick={this.close} sx={{ position: 'absolute', top: '4px', right: '4px' }}>
+ <CgClose size={'16px'} />
</IconButton>
</div>
);
@@ -470,79 +568,3 @@ export class ReportManager extends React.Component<{}> {
);
}
}
-
-// Mini components to render issues
-
-interface IssueCardProps {
- issue: Issue;
- onSelect: () => void;
-}
-const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
- return (
- <div className="issue" onClick={onSelect}>
- <label className="issue-label">#{issue.number}</label>
- <h3 className="issue-title">{issue.title}</h3>
- </div>
- );
-};
-
-interface IssueViewProps {
- issue: Issue;
-}
-
-const IssueView = ({ issue }: IssueViewProps) => {
- const parseBody = (body: string) => {
- const imgTagRegex = /<img\b[^>]*\/?>/;
- const fileRegex = /https:\/\/browndash\.com\/files/;
- const parts = body.split('\n');
-
- const modifiedParts = parts.map(part => {
- if (imgTagRegex.test(part)) {
- return getLinkFromTag(part);
- } else if (fileRegex.test(part)) {
- return getTagFromUrl(part);
- } else {
- return part;
- }
- });
- return modifiedParts.join('\n');
- };
-
- const getLinkFromTag = (tag: string) => {
- const regex = /src="([^"]+)"/;
- let url = '';
- const match = tag.match(regex);
- if (match) {
- url = match[1];
- }
-
- return `\n${url}`;
- };
-
- const getTagFromUrl = (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)) {
- return `${url}\n<img width="100%" alt="Issue asset" src=${url} />`;
- } else if (videoRegex.test(url)) {
- return url;
- } else if (audioRegex.test(url)) {
- return `${url}\n<audio src=${url} controls />`;
- }
- };
-
- return (
- <div className="issue-view">
- <span className="issue-label">
- Issue{' '}
- <a className="issue-link" href={issue.html_url} target="_blank">
- #{issue.number}
- </a>
- </span>
- <h2 className="issue-title">{issue.title}</h2>
- <ReactMarkdown children={issue.body ? parseBody(issue.body as string) : ''} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
- </div>
- );
-};
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
new file mode 100644
index 000000000..1a4ddb3a3
--- /dev/null
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -0,0 +1,153 @@
+import * as React from 'react';
+import { Issue } from './reportManagerSchema';
+import { getLabelColors } from './reportManagerUtils';
+import ReactMarkdown from 'react-markdown';
+import rehypeRaw from 'rehype-raw';
+import remarkGfm from 'remark-gfm';
+
+// Mini components to render issues
+
+interface IssueCardProps {
+ issue: Issue;
+ onSelect: () => void;
+}
+export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
+ return (
+ <div className="issue-card" onClick={onSelect}>
+ <div className="issue-top">
+ <label className="issue-label">#{issue.number}</label>
+ <div className="issue-tags">
+ {issue.labels.map(label => {
+ const labelString = typeof label === 'string' ? label : label.name ?? '';
+ const colors = getLabelColors(labelString);
+ return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} />;
+ })}
+ </div>
+ </div>
+ <h3 className="issue-title">{issue.title}</h3>
+ </div>
+ );
+};
+
+interface IssueViewProps {
+ issue: Issue;
+}
+
+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;
+ }
+ };
+
+ const parseBody = async (body: string) => {
+ const imgTagRegex = /<img\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`;
+ } else if (fileRegex.test(part)) {
+ const tag = await getTagFromUrl(part);
+ return tag;
+ } else {
+ return part;
+ }
+ })
+ );
+
+ setIssueBody(modifiedParts.join('\n'));
+ };
+
+ React.useEffect(() => {
+ parseBody((issue.body as string) ?? '');
+ }, [issue]);
+
+ return (
+ <div className="issue-view">
+ <span className="issue-label">
+ Issue{' '}
+ <a className="issue-link" href={issue.html_url} target="_blank">
+ #{issue.number}
+ </a>
+ </span>
+ <h2 className="issue-title">{issue.title}</h2>
+ <div className="issue-date">
+ Opened on {new Date(issue.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} {issue.user?.login && `by ${issue.user?.login}`}
+ </div>
+ {issue.labels.length > 0 && (
+ <div>
+ <div className="issue-tags">
+ {issue.labels.map(label => {
+ const labelString = typeof label === 'string' ? label : label.name ?? '';
+ const colors = getLabelColors(labelString);
+ return <Tag key={labelString} text={labelString} backgroundColor={colors[0]} color={colors[1]} fontSize="12px" />;
+ })}
+ </div>
+ </div>
+ )}
+ <ReactMarkdown children={issueBody} className="issue-content" linkTarget={'_blank'} remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
+ </div>
+ );
+};
+
+interface TagProps {
+ text: string;
+ fontSize?: string;
+ color?: string;
+ backgroundColor?: string;
+ borderColor?: string;
+ border?: boolean;
+ onClick?: () => void;
+}
+
+export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
+ return (
+ <div
+ onClick={onClick ?? (() => {})}
+ className="report-tag"
+ style={{ color: color ?? '#ffffff', backgroundColor: backgroundColor ?? '#347bff', cursor: onClick ? 'pointer' : 'auto', fontSize: fontSize ?? '10px', border: border ? '1px solid' : 'none', borderColor: borderColor ?? '#94a3b8' }}>
+ {text}
+ </div>
+ );
+};
diff --git a/src/client/util/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
index 28ce6411d..56b9aa32b 100644
--- a/src/client/util/reportManagerSchema.ts
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -1,5 +1,5 @@
/**
- * Issues are a great way to keep track of tasks, enhancements, and bugs for your projects.
+ * Issue interface schema.
*/
export interface Issue {
active_lock_reason?: null | string;
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
new file mode 100644
index 000000000..3c919856a
--- /dev/null
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -0,0 +1,54 @@
+// Final file url reference: "https://browndash.com/files/images/upload_cb31bc0fda59c96ca14193ec494f80cf_o.jpg" />
+
+export enum ViewState {
+ VIEW,
+ CREATE,
+}
+
+export enum Priority {
+ HIGH = 'priority-high',
+ MEDIUM = 'priority-medium',
+ LOW = 'priority-low',
+}
+
+export enum BugType {
+ BUG = 'bug',
+ COSMETIC = 'cosmetic',
+ DOCUMENTATION = 'documentation',
+ ENHANCEMENT = 'enhancement',
+}
+
+// [bgColor, color]
+export const priorityColors: { [key: string]: string[] } = {
+ 'priority-low': ['#d4e0ff', '#000000'],
+ 'priority-medium': ['#6a91f6', '#ffffff'],
+ 'priority-high': ['#4476f7', '#ffffff'],
+};
+
+// [bgColor, color]
+export const bugColors: { [key: string]: string[] } = {
+ bug: ['#fe6d6d', '#ffffff'],
+ cosmetic: ['#c650f4', '#ffffff'],
+ documentation: ['#36acf0', '#ffffff'],
+ enhancement: ['#36d4f0', '#ffffff'],
+};
+
+export const prioritySet = new Set(Object.values(Priority));
+export const bugSet = new Set(Object.values(BugType));
+
+export const getLabelColors = (label: string): string[] => {
+ if (prioritySet.has(label as Priority)) {
+ return priorityColors[label];
+ } else if (bugSet.has(label as BugType)) {
+ return bugColors[label];
+ }
+ return ['#347bff', '#ffffff'];
+};
+
+export interface FileData {
+ _id: string;
+ file: File;
+}
+
+export const inactiveBorderColor = '#cbd5e1';
+export const inactiveColor = '#64748b';
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4557e5866..786154620 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -21,7 +21,7 @@ import { DocumentManager } from '../util/DocumentManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
-import { ReportManager } from '../util/ReportManager';
+import { ReportManager } from '../util/reportManager/ReportManager';
import { RTFMarkup } from '../util/RTFMarkup';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SelectionManager } from '../util/SelectionManager';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f1d98d22a..fb8ec93b2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -235,6 +235,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
bActive,
textX,
textY,
+ // pt1,
+ // pt2,
+ // this code adds space between links
pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
};
diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss
index 4c3b8dabe..d4a7e18f2 100644
--- a/src/client/views/nodes/LoadingBox.scss
+++ b/src/client/views/nodes/LoadingBox.scss
@@ -12,6 +12,10 @@
text-overflow: ellipsis;
max-width: 80%;
text-align: center;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: center;
}
}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 20cf563c1..f131032b5 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -11,7 +11,7 @@ import { StrCast } from '../../../fields/Types';
import { GetEffectiveAcl } from '../../../fields/util';
import { DocumentManager } from '../../util/DocumentManager';
import { PingManager } from '../../util/PingManager';
-import { ReportManager } from '../../util/ReportManager';
+import { ReportManager } from '../../util/reportManager/ReportManager';
import { ServerStats } from '../../util/ServerStats';
import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';