From 7d4c5d7be8b266b5f3caf2135c4f3fecb96d75be Mon Sep 17 00:00:00 2001 From: Samuel Gunter Date: Thu, 21 Mar 2024 19:20:03 -0500 Subject: [PATCH] feat: screenshot whole page, hide certain elements, screenshot fixed size (#180) * feat: screenshot whole page, hide certain elements, screenshot fixed size * refactor: use variants instead of groups and custom rules * feat: scaled header, smaller body, weird padding/margin changes * feat: consistent sizing & style regardless of zoom * feat: use downloadBlob instead of hand-rolled image saving * fix: be type safe is toBlob returns Promise * fix: revoke object url when it should be * fix: animation scheduling --------- Co-authored-by: Razboy20 --- src/shared/util/downloadBlob.ts | 2 +- .../calendar/CalendarBottomBar.stories.tsx | 2 - src/views/components/calendar/Calendar.tsx | 11 ++-- .../components/calendar/CalendarBottomBar.tsx | 13 +++-- .../calendar/CalendarCourseCell.tsx | 2 +- .../components/calendar/CalendarGrid.tsx | 2 +- .../components/calendar/CalenderHeader.tsx | 14 +++-- src/views/components/calendar/utils.ts | 57 ++++++++++++++----- src/views/components/common/LogoIcon.tsx | 2 +- .../common/ScheduleTotalHoursAndCourses.tsx | 2 +- unocss.config.ts | 11 +++- 11 files changed, 81 insertions(+), 37 deletions(-) diff --git a/src/shared/util/downloadBlob.ts b/src/shared/util/downloadBlob.ts index 8e654a34..c7a4db6c 100644 --- a/src/shared/util/downloadBlob.ts +++ b/src/shared/util/downloadBlob.ts @@ -18,7 +18,6 @@ export function downloadBlob(blobPart: BlobPart, type: MIMETypeKey, fileName: st link.download = fileName; link.addEventListener('click', () => { - URL.revokeObjectURL(url); resolve(); }); link.addEventListener('error', () => { @@ -26,5 +25,6 @@ export function downloadBlob(blobPart: BlobPart, type: MIMETypeKey, fileName: st reject(new Error('Download failed')); }); link.click(); + URL.revokeObjectURL(url); }); } diff --git a/src/stories/components/calendar/CalendarBottomBar.stories.tsx b/src/stories/components/calendar/CalendarBottomBar.stories.tsx index 578ec37c..91c5303f 100644 --- a/src/stories/components/calendar/CalendarBottomBar.stories.tsx +++ b/src/stories/components/calendar/CalendarBottomBar.stories.tsx @@ -96,7 +96,6 @@ export const Default: Story = { status: examplePsyCourse.status, }, ], - calendarRef: { current: null }, }, render: props => (
@@ -107,7 +106,6 @@ export const Default: Story = { export const Empty: Story = { args: { courses: [], - calendarRef: { current: null }, }, render: props => (
diff --git a/src/views/components/calendar/Calendar.tsx b/src/views/components/calendar/Calendar.tsx index b0429cc0..e47f5e61 100644 --- a/src/views/components/calendar/Calendar.tsx +++ b/src/views/components/calendar/Calendar.tsx @@ -9,7 +9,7 @@ import Divider from '@views/components/common/Divider'; import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule'; import { MessageListener } from 'chrome-extension-toolkit'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import CalendarFooter from './CalendarFooter'; import TeamLinks from './TeamLinks'; @@ -18,7 +18,6 @@ import TeamLinks from './TeamLinks'; * Calendar page component */ export default function Calendar(): JSX.Element { - const calendarRef = useRef(null); const { courseCells, activeSchedule } = useFlattenedCourseSchedule(); const [course, setCourse] = useState((): Course | null => { @@ -73,7 +72,7 @@ export default function Calendar(): JSX.Element { />
{showSidebar && ( -
+
@@ -84,11 +83,11 @@ export default function Calendar(): JSX.Element {
)} -
-
+
+
- +
diff --git a/src/views/components/calendar/CalendarBottomBar.tsx b/src/views/components/calendar/CalendarBottomBar.tsx index 9466809b..8438e410 100644 --- a/src/views/components/calendar/CalendarBottomBar.tsx +++ b/src/views/components/calendar/CalendarBottomBar.tsx @@ -13,17 +13,15 @@ import CalendarCourseBlock from './CalendarCourseCell'; type CalendarBottomBarProps = { courses?: CalendarCourseCellProps[]; - calendarRef: React.RefObject; }; /** * Renders the bottom bar of the calendar component. * * @param {Object[]} courses - The list of courses to display in the calendar. - * @param {React.RefObject} calendarRef - The reference to the calendar component. * @returns {JSX.Element} The rendered bottom bar component. */ -export default function CalendarBottomBar({ courses, calendarRef }: CalendarBottomBarProps): JSX.Element { +export default function CalendarBottomBar({ courses }: CalendarBottomBarProps): JSX.Element { const displayCourses = courses && courses.length > 0; return ( @@ -50,13 +48,18 @@ export default function CalendarBottomBar({ courses, calendarRef }: CalendarBott )}
-
+
{displayCourses && } -
diff --git a/src/views/components/calendar/CalendarCourseCell.tsx b/src/views/components/calendar/CalendarCourseCell.tsx index d96b29ba..c4bee432 100644 --- a/src/views/components/calendar/CalendarCourseCell.tsx +++ b/src/views/components/calendar/CalendarCourseCell.tsx @@ -93,7 +93,7 @@ export default function CalendarCourseCell({
{rightIcon && (
-
-
+
diff --git a/src/views/components/calendar/utils.ts b/src/views/components/calendar/utils.ts index 2535fc87..5d83252f 100644 --- a/src/views/components/calendar/utils.ts +++ b/src/views/components/calendar/utils.ts @@ -2,7 +2,7 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import type { UserSchedule } from '@shared/types/UserSchedule'; import { downloadBlob } from '@shared/util/downloadBlob'; import type { Serialized } from 'chrome-extension-toolkit'; -import { toPng } from 'html-to-image'; +import { toBlob } from 'html-to-image'; export const CAL_MAP = { Sunday: 'SU', @@ -91,17 +91,46 @@ export const saveAsCal = async () => { * * @param calendarRef - The reference to the calendar component. */ -export const saveCalAsPng = (calendarRef: React.RefObject) => { - if (calendarRef.current) { - toPng(calendarRef.current, { cacheBust: true }) - .then(dataUrl => { - const link = document.createElement('a'); - link.download = 'my-calendar.png'; - link.href = dataUrl; - link.click(); - }) - .catch(err => { - console.error(err); - }); - } +export const saveCalAsPng = () => { + const rootNode = document.createElement('div'); + rootNode.style.backgroundColor = 'white'; + rootNode.style.position = 'fixed'; + rootNode.style.zIndex = '1000'; + rootNode.style.top = '-10000px'; + rootNode.style.left = '-10000px'; + rootNode.style.width = '1165px'; + rootNode.style.height = '754px'; + document.body.appendChild(rootNode); + + const clonedNode = document.querySelector('#root')!.cloneNode(true) as HTMLDivElement; + clonedNode.style.backgroundColor = 'white'; + (clonedNode.firstChild as HTMLDivElement).classList.add('screenshot-in-progress'); + + return new Promise((resolve, reject) => { + requestAnimationFrame(async () => { + rootNode.appendChild(clonedNode); + + try { + const screenshotBlob = await toBlob(clonedNode, { + cacheBust: true, + canvasWidth: 1165 * 2, + canvasHeight: 754 * 2, + skipAutoScale: true, + pixelRatio: 2, + }); + + if (!screenshotBlob) { + throw new Error('Failed to create screenshot'); + } + + downloadBlob(screenshotBlob, 'IMAGE', 'my-calendar.png'); + } catch (e: unknown) { + console.error(e); + reject(e); + } finally { + document.body.removeChild(rootNode); + resolve(); + } + }); + }); }; diff --git a/src/views/components/common/LogoIcon.tsx b/src/views/components/common/LogoIcon.tsx index a6740de6..7345e1f4 100644 --- a/src/views/components/common/LogoIcon.tsx +++ b/src/views/components/common/LogoIcon.tsx @@ -46,7 +46,7 @@ export function LargeLogo({ className }: { className?: string }): JSX.Element { return (
-
+

UT Registration

Plus

diff --git a/src/views/components/common/ScheduleTotalHoursAndCourses.tsx b/src/views/components/common/ScheduleTotalHoursAndCourses.tsx index be03dd7f..f310e894 100644 --- a/src/views/components/common/ScheduleTotalHoursAndCourses.tsx +++ b/src/views/components/common/ScheduleTotalHoursAndCourses.tsx @@ -27,7 +27,7 @@ export default function ScheduleTotalHoursAndCourses({ {totalHours} {totalHours === 1 ? 'Hour' : 'Hours'} - + {totalCourses} {totalCourses === 1 ? 'Course' : 'Courses'} diff --git a/unocss.config.ts b/unocss.config.ts index 94f34783..a998c777 100644 --- a/unocss.config.ts +++ b/unocss.config.ts @@ -33,7 +33,16 @@ export default defineConfig({ }, colors, }, - + variants: [ + matcher => { + const search = 'screenshot:'; + if (!matcher.startsWith(search)) return matcher; + return { + matcher: matcher.slice(search.length), + selector: s => `.screenshot-in-progress ${s}`, + }; + }, + ], presets: [ presetUno(), presetWebFonts({