/* eslint-disable no-use-before-define */ import * as React from 'react'; 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'; /** * Mini helper components for the report component. */ interface FilterProps { items: T[]; activeValue: T | null; setActiveValue: (val: T | null) => void; } // filter ui for issues (horizontal list of tags) export function Filter({ items, activeValue, setActiveValue }: FilterProps) { // establishing theme const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)); const colors = darkMode ? darkColors : lightColors; const isTagDarkMode = isDarkMode(StrCast(Doc.UserDoc().userColor)); const activeTagTextColor = isTagDarkMode ? darkColors.text : lightColors.text; return (
{ setActiveValue(null); }} fontSize="12px" backgroundColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : 'transparent'} color={activeValue === null ? activeTagTextColor : colors.textGrey} borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border} border /> {items.map(item => ( { 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} /> ))}
); } interface IssueCardProps { issue: Issue; onSelect: () => void; } // Component for the issue cards list on the left export function IssueCard({ issue, onSelect }: IssueCardProps) { const [textColor, setTextColor] = React.useState(''); const [bgColor, setBgColor] = React.useState('transparent'); const [borderColor, setBorderColor] = React.useState('transparent'); const resetColors = () => { const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)); const colors = darkMode ? darkColors : lightColors; setTextColor(colors.text); setBorderColor(colors.border); setBgColor('transparent'); }; const handlePointerOver = () => { const darkMode = isDarkMode(StrCast(Doc.UserDoc().userColor)); setTextColor(darkMode ? darkColors.text : lightColors.text); setBorderColor(StrCast(Doc.UserDoc().userColor)); setBgColor(StrCast(Doc.UserDoc().userColor)); }; React.useEffect(() => { resetColors(); }, []); return (
{issue.labels.map(label => { const labelString = typeof label === 'string' ? label : (label.name ?? ''); const colors = getLabelColors(labelString); return ; })}

{issue.title}

); } interface IssueViewProps { issue: Issue; } // Detailed issue view that displays on the right 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) const parseBody = async (body: string) => { const imgTagRegex = /]*\/?>/; const videoTagRegex = /]*\/?>/; const audioTagRegex = /]*\/?>/; const fileRegex = /https:\/\/browndash\.com\/files/; const localRegex = /http:\/\/localhost:1050\/files/; const parts = body.split('\n'); const modifiedParts = await Promise.all( parts.map(async part => { if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) { return `\n${await parseFileTag(part)}\n`; } if (fileRegex.test(part)) { const tag = await parseDashFiles(part); return tag; } if (localRegex.test(part)) { const tag = await parseLocalFiles(part); return tag; } return part; }) ); 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 => { 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 getDisplayedFile(url, 'image'); // video case '.mp4': case '.mpeg': case '.webm': case '.mov': return getDisplayedFile(url, 'video'); // audio case '.mp3': case '.wav': case '.ogg': return getDisplayedFile(url, 'audio'); default: } 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 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 const parseLocalFiles = async (url: string) => { const imgRegex = /http:\/\/localhost:1050\/files[/\\]images/; const dashVideoRegex = /http:\/\/localhost:1050\.com\/files[/\\]videos/; const dashAudioRegex = /http:\/\/localhost:1050\.com\/files[/\\]audio/; if (imgRegex.test(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 => { switch (fileType) { case 'image': { const imgValid = await isImgValid(url); if (!imgValid) return `\n${url} (This image could not be loaded)\n`; return `\n${url}\nIssue asset\n`; } case 'video': { const videoValid = await isVideoValid(url); if (!videoValid) return `\n${url} (This video could not be loaded)\n`; return `\n${url}\n