diff --git a/package.json b/package.json index 41c9ac50..f40c28dd 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@octokit/rest": "^21.1.1", "@phosphor-icons/react": "^2.1.7", "@sentry/react": "^8.55.0", + "@tanstack/react-query": "^5.69.0", "@unocss/vite": "^0.63.6", "@vitejs/plugin-react": "^4.3.4", "chrome-extension-toolkit": "^0.0.54", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37c32c91..4d594e33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,6 +46,9 @@ importers: '@sentry/react': specifier: ^8.55.0 version: 8.55.0(react@18.3.1) + '@tanstack/react-query': + specifier: ^5.69.0 + version: 5.69.0(react@18.3.1) '@unocss/vite': specifier: ^0.63.6 version: 0.63.6(patch_hash=9e2d2732a6e057a2ca90fba199730f252d8b4db8631b2c6ee0854fce7771bc95)(rollup@4.34.8)(typescript@5.7.3)(vite@5.4.14(@types/node@22.13.5)(sass@1.85.1)(terser@5.39.0)) @@ -1981,6 +1984,14 @@ packages: '@swc/types@0.1.18': resolution: {integrity: sha512-NZghLaQvF3eFdj2DUjGkpwaunbZYaRcxciHINnwA4n3FrLAI8hKFOBqs2wkcOiLQfWkIdfuG6gBkNFrkPNji5g==} + '@tanstack/query-core@5.69.0': + resolution: {integrity: sha512-Kn410jq6vs1P8Nm+ZsRj9H+U3C0kjuEkYLxbiCyn3MDEiYor1j2DGVULqAz62SLZtUZ/e9Xt6xMXiJ3NJ65WyQ==} + + '@tanstack/react-query@5.69.0': + resolution: {integrity: sha512-Ift3IUNQqTcaFa1AiIQ7WCb/PPy8aexZdq9pZWLXhfLcLxH0+PZqJ2xFImxCpdDZrFRZhLJrh76geevS5xjRhA==} + peerDependencies: + react: ^18 || ^19 + '@tanstack/react-virtual@3.13.2': resolution: {integrity: sha512-LceSUgABBKF6HSsHK2ZqHzQ37IKV/jlaWbHm+NyTa3/WNb/JZVcThDuTainf+PixltOOcFCYXwxbLpOX9sCx+g==} peerDependencies: @@ -8621,6 +8632,13 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@tanstack/query-core@5.69.0': {} + + '@tanstack/react-query@5.69.0(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.69.0 + react: 18.3.1 + '@tanstack/react-virtual@3.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/virtual-core': 3.13.2 diff --git a/src/shared/storage/OptionsStore.ts b/src/shared/storage/OptionsStore.ts index ce45c934..d1807dd4 100644 --- a/src/shared/storage/OptionsStore.ts +++ b/src/shared/storage/OptionsStore.ts @@ -18,6 +18,9 @@ export interface IOptionsStore { /** whether we should open the calendar in a new tab; default is to focus an existing calendar tab */ alwaysOpenCalendarInNewTab: boolean; + + /** whether the calendar sidebar should be shown when the calendar is opened */ + showCalendarSidebar: boolean; } export const OptionsStore = createSyncStore({ @@ -26,6 +29,7 @@ export const OptionsStore = createSyncStore({ enableScrollToLoad: true, enableDataRefreshing: false, alwaysOpenCalendarInNewTab: false, + showCalendarSidebar: true, }); /** @@ -40,6 +44,7 @@ export const initSettings = async () => enableScrollToLoad: await OptionsStore.get('enableScrollToLoad'), enableDataRefreshing: await OptionsStore.get('enableDataRefreshing'), alwaysOpenCalendarInNewTab: await OptionsStore.get('alwaysOpenCalendarInNewTab'), + showCalendarSidebar: await OptionsStore.get('showCalendarSidebar'), }) satisfies IOptionsStore; // Clothing retailer right diff --git a/src/views/components/calendar/Calendar.tsx b/src/views/components/calendar/Calendar.tsx index f2402e9c..5525646b 100644 --- a/src/views/components/calendar/Calendar.tsx +++ b/src/views/components/calendar/Calendar.tsx @@ -1,8 +1,10 @@ import { Sidebar } from '@phosphor-icons/react'; import type { CalendarTabMessages } from '@shared/messages/CalendarMessages'; +import { OptionsStore } from '@shared/storage/OptionsStore'; import type { Course } from '@shared/types/Course'; import { CRX_PAGES } from '@shared/types/CRXPages'; import { openReportWindow } from '@shared/util/openReportWindow'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import CalendarBottomBar from '@views/components/calendar/CalendarBottomBar'; import CalendarGrid from '@views/components/calendar/CalendarGrid'; import CalendarHeader from '@views/components/calendar/CalendarHeader/CalendarHeader'; @@ -16,6 +18,7 @@ import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSched import useWhatsNewPopUp from '@views/hooks/useWhatsNew'; import { MessageListener } from 'chrome-extension-toolkit'; import clsx from 'clsx'; +import type { ReactNode } from 'react'; import React, { useEffect, useState } from 'react'; import OutwardArrowIcon from '~icons/material-symbols/arrow-outward'; @@ -28,15 +31,31 @@ import CalendarFooter from './CalendarFooter'; /** * Calendar page component */ -export default function Calendar(): JSX.Element { +export default function Calendar(): ReactNode { const { courseCells, activeSchedule } = useFlattenedCourseSchedule(); const [course, setCourse] = useState(useCourseFromUrl()); const [showPopup, setShowPopup] = useState(course !== null); - const [showSidebar, setShowSidebar] = useState(true); const showWhatsNewDialog = useWhatsNewPopUp(); + const queryClient = useQueryClient(); + const { data: showSidebar, isPending: isSidebarStatePending } = useQuery({ + queryKey: ['settings', 'showCalendarSidebar'], + queryFn: () => OptionsStore.get('showCalendarSidebar'), + staleTime: Infinity, // Prevent loading state on refocus + }); + + const { mutate: setShowSidebar } = useMutation({ + mutationKey: ['settings', 'showCalendarSidebar'], + mutationFn: async (showSidebar: boolean) => { + OptionsStore.set('showCalendarSidebar', showSidebar); + }, + onSuccess: (_, showSidebar) => { + queryClient.setQueryData(['settings', 'showCalendarSidebar'], showSidebar); + }, + }); + useEffect(() => { const listener = new MessageListener({ async openCoursePopup({ data, sendResponse }) { @@ -61,6 +80,8 @@ export default function Calendar(): JSX.Element { if (course) setShowPopup(true); }, [course]); + if (isSidebarStatePending) return null; + return (
diff --git a/src/views/components/common/ExtensionRoot/ExtensionRoot.tsx b/src/views/components/common/ExtensionRoot/ExtensionRoot.tsx index 18bb5799..0936fb0a 100644 --- a/src/views/components/common/ExtensionRoot/ExtensionRoot.tsx +++ b/src/views/components/common/ExtensionRoot/ExtensionRoot.tsx @@ -1,6 +1,7 @@ // import '@unocss/reset/tailwind-compat.css'; import 'uno.css'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import clsx from 'clsx'; import React, { forwardRef } from 'react'; @@ -8,6 +9,8 @@ import styles from './ExtensionRoot.module.scss'; export const styleResetClass = styles.extensionRoot; +const queryClient = new QueryClient(); + /** * A wrapper component for the extension elements that adds some basic styling to them */ @@ -16,7 +19,9 @@ export default function ExtensionRoot(props: React.HTMLProps): J return ( -
+ +
+ ); }