Compare commits

..

2 Commits

Author SHA1 Message Date
697e81b7c2 Safely commenting it out 2024-02-17 17:42:48 -06:00
f0329f33aa Idk why this is erroring out 2024-02-17 17:22:39 -06:00
8 changed files with 152 additions and 193 deletions

View File

@@ -14,17 +14,5 @@
1. Clone this repo 1. Clone this repo
2. Run `pnpm install` to install and patch all the required dependencies 2. Run `pnpm install` to install and patch all the required dependencies
3. Run `pnpm run dev` to start the development server
- If you want to run the development build: 4. Run `pnpm build` to build the extension for production
- Run `pnpm run dev`
- If you want to build the extension for production:
- Run `pnpm build`
You may have to rename the `__uno.css.js` to `uno.css.js` in dist
Go to chrome://extensions, ensure you have "Developer Mode" enabled, and click 'Load unpacked'
Navigate to the 'dist' folder and click 'select' to import the extension

View File

@@ -24,7 +24,7 @@
"clsx": "^2.1.0", "clsx": "^2.1.0",
"highcharts": "^11.3.0", "highcharts": "^11.3.0",
"highcharts-react-official": "^3.2.1", "highcharts-react-official": "^3.2.1",
"html-to-image": "^1.11.11", "html2canvas": "^1.4.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-devtools-core": "^5.0.0", "react-devtools-core": "^5.0.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

37
pnpm-lock.yaml generated
View File

@@ -34,9 +34,9 @@ dependencies:
highcharts-react-official: highcharts-react-official:
specifier: ^3.2.1 specifier: ^3.2.1
version: 3.2.1(highcharts@11.3.0)(react@18.2.0) version: 3.2.1(highcharts@11.3.0)(react@18.2.0)
html-to-image: html2canvas:
specifier: ^1.11.11 specifier: ^1.4.1
version: 1.11.11 version: 1.4.1
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
@@ -5856,6 +5856,11 @@ packages:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true dev: true
/base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
dev: false
/base64-js@1.5.1: /base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: true dev: true
@@ -6548,6 +6553,12 @@ packages:
postcss: 8.4.35 postcss: 8.4.35
dev: true dev: true
/css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
dependencies:
utrie: 1.0.2
dev: false
/css-select@5.1.0: /css-select@5.1.0:
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
dependencies: dependencies:
@@ -8652,8 +8663,12 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/html-to-image@1.11.11: /html2canvas@1.4.1:
resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==} resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
dev: false dev: false
/htmlparser2@8.0.2: /htmlparser2@8.0.2:
@@ -12415,6 +12430,12 @@ packages:
minimatch: 3.1.2 minimatch: 3.1.2
dev: true dev: true
/text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
dependencies:
utrie: 1.0.2
dev: false
/text-table@0.2.0: /text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true dev: true
@@ -12957,6 +12978,12 @@ packages:
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
dev: true dev: true
/utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
dependencies:
base64-arraybuffer: 1.0.2
dev: false
/uuid@9.0.1: /uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true hasBin: true

View File

@@ -13,11 +13,10 @@ const meta = {
}, },
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
department: { control: { type: 'text' } }, courseDeptAndInstr: { control: { type: 'text' } },
courseNumber: { control: { type: 'text' } }, className: { control: { type: 'text' } },
instructorLastName: { control: { type: 'text' } },
status: { control: { type: 'select', options: Object.values(Status) } }, status: { control: { type: 'select', options: Object.values(Status) } },
meetingTime: { control: { type: 'text' } }, timeAndLocation: { control: { type: 'text' } },
colors: { control: { type: 'object' } }, colors: { control: { type: 'object' } },
}, },
render: (args: any) => ( render: (args: any) => (
@@ -26,11 +25,10 @@ const meta = {
</div> </div>
), ),
args: { args: {
department: exampleCourse.department, courseDeptAndInstr: exampleCourse.department,
courseNumber: exampleCourse.number, className: exampleCourse.number,
instructorLastName: exampleCourse.instructors[0].lastName,
status: exampleCourse.status, status: exampleCourse.status,
meetingTime: exampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }), timeAndLocation: exampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }),
colors: getCourseColors('emerald', 500), colors: getCourseColors('emerald', 500),
}, },
@@ -46,22 +44,69 @@ export const Variants: Story = {
<div className='grid grid-cols-2 h-40 max-w-60 w-90vw gap-x-4 gap-y-2'> <div className='grid grid-cols-2 h-40 max-w-60 w-90vw gap-x-4 gap-y-2'>
<CalendarCourseCell <CalendarCourseCell
{...props} {...props}
course={new Course({ ...exampleCourse, status: Status.OPEN })} // course={new Course({ ...exampleCourse, status: Status.OPEN })}
// Course = new Course({
// courseName: 'PRINCIPLES OF COMPUTER SYSTEMS',
// creditHours: 3,
// department: 'C S',
// description: [
// 'Restricted to computer science majors.',
// 'An introduction to computer systems software abstractions with an emphasis on the connection of these abstractions to underlying computer hardware. Key abstractions include threads, virtual memory, protection, and I/O. Requires writing of synchronized multithreaded programs and pieces of an operating system.',
// 'Computer Science 439 and 439H may not both be counted.',
// 'Prerequisite: Computer Science 429, or 429H with a grade of at least C-.',
// 'May be counted toward the Independent Inquiry flag requirement.',
// ],
// flags: ['Independent Inquiry'],
// fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
// instructionMode: 'In Person',
// instructors: [
// new Instructor({
// firstName: 'Allison',
// lastName: 'Norman',
// fullName: 'Allison Norman',
// }),
// ],
// isReserved: false,
// number: '439',
// schedule: {
// meetings: [
// new CourseMeeting({
// days: ['Tuesday', 'Thursday'],
// startTime: 930,
// endTime: 1050,
// }),
// new CourseMeeting({
// days: ['Friday'],
// startTime: 600,
// endTime: 720,
// }),
// ],
// },
// semester: {
// code: '12345',
// season: 'Spring',
// year: 2024,
// },
// status: Status.WAITLISTED,
// uniqueId: 67890,
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
// });
colors={getCourseColors('green', 500)} colors={getCourseColors('green', 500)}
/> />
<CalendarCourseCell <CalendarCourseCell
{...props} {...props}
course={new Course({ ...exampleCourse, status: Status.CLOSED })} // course={new Course({ ...exampleCourse, status: Status.CLOSED })}
colors={getCourseColors('teal', 400)} colors={getCourseColors('teal', 400)}
/> />
<CalendarCourseCell <CalendarCourseCell
{...props} {...props}
course={new Course({ ...exampleCourse, status: Status.WAITLISTED })} // course={new Course({ ...exampleCourse, status: Status.WAITLISTED })}
colors={getCourseColors('indigo', 400)} colors={getCourseColors('indigo', 400)}
/> />
<CalendarCourseCell <CalendarCourseCell
{...props} {...props}
course={new Course({ ...exampleCourse, status: Status.CANCELLED })} // course={new Course({ ...exampleCourse, status: Status.CANCELLED })}
colors={getCourseColors('red', 500)} colors={getCourseColors('red', 500)}
/> />
</div> </div>

View File

@@ -1,96 +1,5 @@
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning'; import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning';
import { Course, Status } from 'src/shared/types/Course';
import { CourseMeeting } from 'src/shared/types/CourseMeeting';
import Instructor from 'src/shared/types/Instructor';
export const ExampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
creditHours: 3,
department: 'C S',
description: [
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.',
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.',
'May be counted toward the Quantitative Reasoning flag requirement.',
'Designed to accommodate 100 or more students.',
'Taught as a Web-based course.',
],
flags: ['Quantitative Reasoning'],
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
instructionMode: 'Online',
instructors: [
new Instructor({
firstName: 'Bevo',
lastName: 'Bevo',
fullName: 'Bevo Bevo',
}),
],
isReserved: false,
number: '303E',
schedule: {
meetings: [
new CourseMeeting({
days: ['Tuesday', 'Thursday'],
endTime: 660,
startTime: 570,
}),
],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.WAITLISTED,
uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
export const ExampleCourse2: Course = new Course({
courseName: 'PRINCIPLES OF COMPUTER SYSTEMS',
creditHours: 3,
department: 'C S',
description: [
'Restricted to computer science majors.',
'An introduction to computer systems software abstractions with an emphasis on the connection of these abstractions to underlying computer hardware. Key abstractions include threads, virtual memory, protection, and I/O. Requires writing of synchronized multithreaded programs and pieces of an operating system.',
'Computer Science 439 and 439H may not both be counted.',
'Prerequisite: Computer Science 429, or 429H with a grade of at least C-.',
'May be counted toward the Independent Inquiry flag requirement.',
],
flags: ['Independent Inquiry'],
fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS',
instructionMode: 'In Person',
instructors: [
new Instructor({
firstName: 'Allison',
lastName: 'Norman',
fullName: 'Allison Norman',
}),
],
isReserved: false,
number: '439',
schedule: {
meetings: [
new CourseMeeting({
days: ['Tuesday', 'Thursday'],
startTime: 930,
endTime: 1050,
}),
new CourseMeeting({
days: ['Friday'],
startTime: 600,
endTime: 720,
}),
],
},
semester: {
code: '12345',
season: 'Spring',
year: 2024,
},
status: Status.WAITLISTED,
uniqueId: 67890,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
});
const meta = { const meta = {
title: 'Components/Common/ConflictsWithWarning', title: 'Components/Common/ConflictsWithWarning',
@@ -100,10 +9,8 @@ const meta = {
}, },
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
conflicts: { control: 'object' }, ConflictingCourse: { control: 'string' },
}, SectionNumber: { control: 'string' },
args: {
conflicts: [ExampleCourse, ExampleCourse2],
}, },
} satisfies Meta<typeof ConflictsWithWarning>; } satisfies Meta<typeof ConflictsWithWarning>;
export default meta; export default meta;
@@ -112,6 +19,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
args: { args: {
conflicts: [ExampleCourse, ExampleCourse2], ConflictingCourse: 'BVO 311C',
SectionNumber: '47280',
}, },
}; };

View File

@@ -1,5 +1,5 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import * as htmlToImage from 'html-to-image'; import html2canvas from 'html2canvas';
import { DAY_MAP } from 'src/shared/types/CourseMeeting'; import { DAY_MAP } from 'src/shared/types/CourseMeeting';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule'; import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
import calIcon from 'src/assets/icons/cal.svg'; import calIcon from 'src/assets/icons/cal.svg';
@@ -38,36 +38,15 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
const calendarRef = useRef(null); // Create a ref for the calendar grid const calendarRef = useRef(null); // Create a ref for the calendar grid
const saveAsPNG = () => { const saveAsPNG = () => {
htmlToImage if (calendarRef.current) {
.toPng(calendarRef.current, { html2canvas(calendarRef.current).then(canvas => {
backgroundColor: 'white', // Create an a element to trigger download
style: { const a = document.createElement('a');
background: 'white', a.href = canvas.toDataURL('image/png');
marginTop: '20px', a.download = 'calendar.png';
marginBottom: '20px', a.click();
marginRight: '20px',
marginLeft: '20px',
},
})
.then(dataUrl => {
let img = new Image();
img.src = dataUrl;
fetch(dataUrl)
.then(response => response.blob())
.then(blob => {
const href = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.download = 'my-schedule.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch(error => console.error('Error downloading file:', error));
})
.catch(error => {
console.error('oops, something went wrong!', error);
}); });
}
}; };
return ( return (

View File

@@ -1,14 +1,12 @@
import React from 'react'; import React from 'react';
import { Course } from 'src/shared/types/Course';
import clsx from 'clsx';
import Text from '../Text/Text'; import Text from '../Text/Text';
/** /**
* Props for ConflictWithWarningProps * Props for ConflictWithWarningProps
*/ */
export interface ConflictsWithWarningProps { export interface ConflictsWithWarningProps {
className?: string; ConflictingCourse: string;
conflicts: Course[]; SectionNumber: string;
} }
/** /**
@@ -17,21 +15,29 @@ export interface ConflictsWithWarningProps {
* *
* @param props ConflictsWithWarningProps * @param props ConflictsWithWarningProps
*/ */
export default function ConflictsWithWarning({ className, conflicts }: ConflictsWithWarningProps): JSX.Element { export default function ConflictsWithWarning( { ConflictingCourse, SectionNumber }: ConflictsWithWarningProps): JSX.Element {
const UniqueCourseConflictText = `${ConflictingCourse} (${SectionNumber})`;
return (
<div className="min-w-21 w-21 flex flex-col items-start gap-2.5 rounded bg-[#AF2E2D] p-2.5">
<ConflictsWithoutWarningText>
Conflicts With:
</ConflictsWithoutWarningText>
<ConflictsWithoutWarningText>
{UniqueCourseConflictText}
</ConflictsWithoutWarningText>
</div>
);
}
function ConflictsWithoutWarningText( {children}: {children: string} ) {
return ( return (
<Text <Text
variant='mini' variant='mini'
className={clsx( as='span'
className, className='text-white'
'min-w-21 w-21 flex flex-col items-start gap-2.5 rounded bg-[#AF2E2D] p-2.5 text-white'
)}
> >
<div>Conflicts With:</div> {children}
{conflicts.map(course => (
<div>
{`${course.department} ${course.number} (${course.uniqueId})`}
</div>
))}
</Text> </Text>
); );
} }

View File

@@ -3,9 +3,9 @@ import { UserSchedule } from '@shared/types/UserSchedule';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Button } from '../../common/Button/Button'; import { Button } from '../../common/Button/Button';
import Icon from '../../common/Icon/Icon';
import Text from '../../common/Text/Text';
import styles from './TableRow.module.scss'; import styles from './TableRow.module.scss';
import ConflictsWithWarning from '../../common/ConflictsWithWarning/ConflictsWithWarning';
import AddIcon from '~icons/material-symbols/add-circle';
interface Props { interface Props {
isSelected: boolean; isSelected: boolean;
@@ -54,7 +54,7 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
return () => { return () => {
element.classList.remove(styles.inActiveSchedule); element.classList.remove(styles.inActiveSchedule);
}; };
}, [activeSchedule, course, element.classList]); }, [activeSchedule, element.classList]);
useEffect(() => { useEffect(() => {
if (!activeSchedule || !course) { if (!activeSchedule || !course) {
@@ -84,14 +84,20 @@ export default function TableRow({ row, isSelected, activeSchedule, onClick }: P
return ReactDOM.createPortal( return ReactDOM.createPortal(
<> <>
<Button <Button className={styles.rowButton} onClick={onClick} variant='secondary'>
icon={AddIcon} <Icon name='bar_chart' color='white' size='medium' />
className={styles.rowButton} </Button>
color='ut-burntorange' {conflicts.length > 0 && (
onClick={onClick} <div className={styles.conflictTooltip}>
variant='single' <div className={styles.body}>
/> {conflicts.map(c => (
{conflicts.length > 0 && <ConflictsWithWarning className={styles.conflictTooltip} conflicts={conflicts} />} <Text /* size='small' */ key={c.uniqueId}>
{c.department} {c.number} ({c.uniqueId})
</Text>
))}
</div>
</div>
)}
</>, </>,
container container
); );