Compare commits

..

8 Commits

Author SHA1 Message Date
DhruvArora-03
70c75ff481 add button to the rows, use new ConflictsWithWarning component 2024-02-19 11:29:24 -06:00
DhruvArora-03
7f76af7ab3 fix ConflictsWithWarning 2024-02-19 11:28:33 -06:00
DhruvArora-03
a538f20aad Merge remote-tracking branch 'origin/hackathon' into feat-conflict-row 2024-02-19 11:11:37 -06:00
Lukas Zenick
0acd0b722c html2canvas -> htmlToImage
also fixed derick's bugs
2024-02-18 12:46:35 -06:00
ae32d0b645 feat: Derek/export png (#95)
* Attempting to use more lightweight version

* Did not work.

* This is not what I wanted

* The image saves correctly. Needs padding

* Padding !!

* Removed downloadjs

* Padding more
2024-02-17 19:09:41 -06:00
DhruvArora-03
f214ed7c01 Merge remote-tracking branch 'origin/hackathon' into feat-conflict-row 2024-02-17 18:21:39 -06:00
Lukas Zenick
e44b0c0e45 FIX README 2024-02-17 17:24:27 -06:00
Lukas Zenick
206c97c5b5 fixed README 2024-02-17 17:23:04 -06:00
8 changed files with 193 additions and 152 deletions

View File

@@ -2,17 +2,29 @@
## Built Using
- React 18
- TypeScript
- Vite 5
- ESLint
- Prettier
- Semantic-Release
- Custom Messaging & Storage Wrappers
- React 18
- TypeScript
- Vite 5
- ESLint
- Prettier
- Semantic-Release
- Custom Messaging & Storage Wrappers
## Getting Started
1. Clone this repo
2. Run `pnpm install` to install and patch all the required dependencies
3. Run `pnpm run dev` to start the development server
4. Run `pnpm build` to build the extension for production
- If you want to run the development build:
- 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",
"highcharts": "^11.3.0",
"highcharts-react-official": "^3.2.1",
"html2canvas": "^1.4.1",
"html-to-image": "^1.11.11",
"react": "^18.2.0",
"react-devtools-core": "^5.0.0",
"react-dom": "^18.2.0",

37
pnpm-lock.yaml generated
View File

@@ -34,9 +34,9 @@ dependencies:
highcharts-react-official:
specifier: ^3.2.1
version: 3.2.1(highcharts@11.3.0)(react@18.2.0)
html2canvas:
specifier: ^1.4.1
version: 1.4.1
html-to-image:
specifier: ^1.11.11
version: 1.11.11
react:
specifier: ^18.2.0
version: 18.2.0
@@ -5856,11 +5856,6 @@ packages:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
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:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: true
@@ -6553,12 +6548,6 @@ packages:
postcss: 8.4.35
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:
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
dependencies:
@@ -8663,12 +8652,8 @@ packages:
engines: {node: '>=8'}
dev: true
/html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
/html-to-image@1.11.11:
resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==}
dev: false
/htmlparser2@8.0.2:
@@ -12430,12 +12415,6 @@ packages:
minimatch: 3.1.2
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:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true
@@ -12978,12 +12957,6 @@ packages:
engines: {node: '>= 0.4.0'}
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:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true

View File

@@ -13,10 +13,11 @@ const meta = {
},
tags: ['autodocs'],
argTypes: {
courseDeptAndInstr: { control: { type: 'text' } },
className: { control: { type: 'text' } },
department: { control: { type: 'text' } },
courseNumber: { control: { type: 'text' } },
instructorLastName: { control: { type: 'text' } },
status: { control: { type: 'select', options: Object.values(Status) } },
timeAndLocation: { control: { type: 'text' } },
meetingTime: { control: { type: 'text' } },
colors: { control: { type: 'object' } },
},
render: (args: any) => (
@@ -25,10 +26,11 @@ const meta = {
</div>
),
args: {
courseDeptAndInstr: exampleCourse.department,
className: exampleCourse.number,
department: exampleCourse.department,
courseNumber: exampleCourse.number,
instructorLastName: exampleCourse.instructors[0].lastName,
status: exampleCourse.status,
timeAndLocation: exampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }),
meetingTime: exampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }),
colors: getCourseColors('emerald', 500),
},
@@ -44,69 +46,22 @@ export const Variants: Story = {
<div className='grid grid-cols-2 h-40 max-w-60 w-90vw gap-x-4 gap-y-2'>
<CalendarCourseCell
{...props}
// 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/',
// });
course={new Course({ ...exampleCourse, status: Status.OPEN })}
colors={getCourseColors('green', 500)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.CLOSED })}
course={new Course({ ...exampleCourse, status: Status.CLOSED })}
colors={getCourseColors('teal', 400)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.WAITLISTED })}
course={new Course({ ...exampleCourse, status: Status.WAITLISTED })}
colors={getCourseColors('indigo', 400)}
/>
<CalendarCourseCell
{...props}
// course={new Course({ ...exampleCourse, status: Status.CANCELLED })}
course={new Course({ ...exampleCourse, status: Status.CANCELLED })}
colors={getCourseColors('red', 500)}
/>
</div>

View File

@@ -1,5 +1,96 @@
import { Meta, StoryObj } from '@storybook/react';
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 = {
title: 'Components/Common/ConflictsWithWarning',
@@ -9,8 +100,10 @@ const meta = {
},
tags: ['autodocs'],
argTypes: {
ConflictingCourse: { control: 'string' },
SectionNumber: { control: 'string' },
conflicts: { control: 'object' },
},
args: {
conflicts: [ExampleCourse, ExampleCourse2],
},
} satisfies Meta<typeof ConflictsWithWarning>;
export default meta;
@@ -19,7 +112,6 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
ConflictingCourse: 'BVO 311C',
SectionNumber: '47280',
conflicts: [ExampleCourse, ExampleCourse2],
},
};
};

View File

@@ -1,5 +1,5 @@
import React, { useRef } from 'react';
import html2canvas from 'html2canvas';
import * as htmlToImage from 'html-to-image';
import { DAY_MAP } from 'src/shared/types/CourseMeeting';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
import calIcon from 'src/assets/icons/cal.svg';
@@ -38,15 +38,36 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
const calendarRef = useRef(null); // Create a ref for the calendar grid
const saveAsPNG = () => {
if (calendarRef.current) {
html2canvas(calendarRef.current).then(canvas => {
// Create an a element to trigger download
const a = document.createElement('a');
a.href = canvas.toDataURL('image/png');
a.download = 'calendar.png';
a.click();
htmlToImage
.toPng(calendarRef.current, {
backgroundColor: 'white',
style: {
background: 'white',
marginTop: '20px',
marginBottom: '20px',
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 (

View File

@@ -1,43 +1,37 @@
import React from 'react';
import { Course } from 'src/shared/types/Course';
import clsx from 'clsx';
import Text from '../Text/Text';
/**
* Props for ConflictWithWarningProps
*/
export interface ConflictsWithWarningProps {
ConflictingCourse: string;
SectionNumber: string;
className?: string;
conflicts: Course[];
}
/**
* The ConflictsWithWarning component is used to display a warning message when a course conflicts
* The ConflictsWithWarning component is used to display a warning message when a course conflicts
* with another course as part of the labels and details section
*
* @param props ConflictsWithWarningProps
*/
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} ) {
export default function ConflictsWithWarning({ className, conflicts }: ConflictsWithWarningProps): JSX.Element {
return (
<Text
variant='mini'
as='span'
className='text-white'
className={clsx(
className,
'min-w-21 w-21 flex flex-col items-start gap-2.5 rounded bg-[#AF2E2D] p-2.5 text-white'
)}
>
{children}
<div>Conflicts With:</div>
{conflicts.map(course => (
<div>
{`${course.department} ${course.number} (${course.uniqueId})`}
</div>
))}
</Text>
);
}

View File

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