fix: include logo in screenshot, fix screenshots on small/zoomed windows (#579)

* fix: include logo in screenshot

* fix: screenshots on small/zoomed windows, screenshots with no async/other

---------

Co-authored-by: Razboy20 <razboy20@gmail.com>
This commit is contained in:
Samuel Gunter
2025-04-05 11:21:37 -05:00
committed by GitHub
parent 70d4fecad6
commit 76b6aa7c15
3 changed files with 27 additions and 10 deletions

View File

@@ -33,6 +33,8 @@ import CalendarFooter from './CalendarFooter';
*/ */
export default function Calendar(): ReactNode { export default function Calendar(): ReactNode {
const { courseCells, activeSchedule } = useFlattenedCourseSchedule(); const { courseCells, activeSchedule } = useFlattenedCourseSchedule();
const asyncCourseCells = courseCells.filter(block => block.async);
const displayBottomBar = asyncCourseCells && asyncCourseCells.length > 0;
const [course, setCourse] = useState<Course | null>(useCourseFromUrl()); const [course, setCourse] = useState<Course | null>(useCourseFromUrl());
@@ -85,7 +87,7 @@ export default function Calendar(): ReactNode {
return ( return (
<CalendarContext.Provider value> <CalendarContext.Provider value>
<div className='h-full w-full flex flex-col'> <div className='h-full w-full flex flex-col'>
<div className='h-screen flex overflow-auto'> <div className='h-screen flex overflow-auto screenshot:calendar-target'>
<div <div
className={clsx( className={clsx(
'py-spacing-6 relative h-full min-h-screen w-full flex flex-none flex-col justify-between overflow-clip whitespace-nowrap border-r border-ut-offwhite/50 shadow-[2px_0_10px,rgba(214_210_196_/_.1)] motion-safe:duration-300 motion-safe:ease-out-expo motion-safe:transition-[max-width] screenshot:hidden', 'py-spacing-6 relative h-full min-h-screen w-full flex flex-none flex-col justify-between overflow-clip whitespace-nowrap border-r border-ut-offwhite/50 shadow-[2px_0_10px,rgba(214_210_196_/_.1)] motion-safe:duration-300 motion-safe:ease-out-expo motion-safe:transition-[max-width] screenshot:hidden',
@@ -169,7 +171,11 @@ export default function Calendar(): ReactNode {
setShowSidebar(!showSidebar); setShowSidebar(!showSidebar);
}} }}
/> />
<div className='min-h-2xl min-w-5xl flex-grow gap-0 pl-spacing-3 screenshot:min-h-xl'> <div
className={clsx('min-h-2xl min-w-5xl flex-grow gap-0 pl-spacing-3 screenshot:min-h-xl', {
'screenshot:flex-grow-0': displayBottomBar, // html-to-image seems to have a bug with flex-grow
})}
>
<CalendarGrid courseCells={courseCells} setCourse={setCourse} /> <CalendarGrid courseCells={courseCells} setCourse={setCourse} />
</div> </div>
<CalendarBottomBar courseCells={courseCells} setCourse={setCourse} /> <CalendarBottomBar courseCells={courseCells} setCourse={setCourse} />

View File

@@ -5,6 +5,7 @@ import { Button } from '@views/components/common/Button';
import DialogProvider from '@views/components/common/DialogProvider/DialogProvider'; import DialogProvider from '@views/components/common/DialogProvider/DialogProvider';
import Divider from '@views/components/common/Divider'; import Divider from '@views/components/common/Divider';
import { ExtensionRootWrapper, styleResetClass } from '@views/components/common/ExtensionRoot/ExtensionRoot'; import { ExtensionRootWrapper, styleResetClass } from '@views/components/common/ExtensionRoot/ExtensionRoot';
import { LargeLogo } from '@views/components/common/LogoIcon';
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses'; import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses';
import useSchedules from '@views/hooks/useSchedules'; import useSchedules from '@views/hooks/useSchedules';
import clsx from 'clsx'; import clsx from 'clsx';
@@ -39,6 +40,9 @@ export default function CalendarHeader({ sidebarOpen, onSidebarToggle }: Calenda
/> />
)} )}
<LargeLogo className='hidden! screenshot:flex!' />
<Divider className='self-center hidden! screenshot:block!' size='2.5rem' orientation='vertical' />
<div className='min-w-[11.5rem] screenshot:transform-origin-left screenshot:scale-120'> <div className='min-w-[11.5rem] screenshot:transform-origin-left screenshot:scale-120'>
<ScheduleTotalHoursAndCourses <ScheduleTotalHoursAndCourses
scheduleName={activeSchedule.name} scheduleName={activeSchedule.name}

View File

@@ -266,31 +266,38 @@ export const saveAsCal = async () => {
* @param calendarRef - The reference to the calendar component. * @param calendarRef - The reference to the calendar component.
*/ */
export const saveCalAsPng = () => { export const saveCalAsPng = () => {
const WIDTH_PX = 1165;
const HEIGHT_PX = 754;
const SCALE = 2;
const rootNode = document.createElement('div'); const rootNode = document.createElement('div');
rootNode.style.backgroundColor = 'white'; rootNode.style.backgroundColor = 'white';
rootNode.style.position = 'fixed'; rootNode.style.position = 'fixed';
rootNode.style.zIndex = '1000'; rootNode.style.zIndex = '1000';
rootNode.style.top = '-10000px'; rootNode.style.top = '-10000px';
rootNode.style.left = '-10000px'; rootNode.style.left = '-10000px';
rootNode.style.width = '1165px'; rootNode.style.width = `${WIDTH_PX}px`;
rootNode.style.height = '754px'; rootNode.style.height = `${HEIGHT_PX}px`;
document.body.appendChild(rootNode); document.body.appendChild(rootNode);
const clonedNode = document.querySelector('#root')!.cloneNode(true) as HTMLDivElement; const clonedNode = document.querySelector('#root')!.cloneNode(true) as HTMLDivElement;
clonedNode.style.backgroundColor = 'white'; clonedNode.style.backgroundColor = 'white';
(clonedNode.firstChild as HTMLDivElement).classList.add('screenshot-in-progress'); (clonedNode.firstChild as HTMLDivElement).classList.add('screenshot-in-progress');
return new Promise<void>((resolve, reject) => { const calendarTarget = clonedNode.querySelector('.screenshot\\:calendar-target') as HTMLDivElement;
requestAnimationFrame(async () => { calendarTarget.style.width = `${WIDTH_PX}px`;
rootNode.appendChild(clonedNode); calendarTarget.style.height = `${HEIGHT_PX}px`;
return new Promise<void>((resolve, reject) => {
rootNode.appendChild(clonedNode);
requestAnimationFrame(async () => {
try { try {
const screenshotBlob = await toBlob(clonedNode, { const screenshotBlob = await toBlob(clonedNode, {
cacheBust: true, cacheBust: true,
canvasWidth: 1165 * 2, canvasWidth: WIDTH_PX * SCALE,
canvasHeight: 754 * 2, canvasHeight: HEIGHT_PX * SCALE,
skipAutoScale: true, skipAutoScale: true,
pixelRatio: 2, pixelRatio: SCALE,
}); });
if (!screenshotBlob) { if (!screenshotBlob) {