From fec9bfab49f274b7c9f790caeb3d61cfc294f6de Mon Sep 17 00:00:00 2001 From: Zhao Wei Liew Date: Fri, 3 Jan 2025 10:12:40 +0800 Subject: [PATCH] fix(website/export): Parse hidden courses (#3925) --- export/api/export/image.ts | 4 +-- export/api/export/pdf.ts | 4 +-- export/src/data.ts | 8 +++-- export/src/render-serverless.ts | 8 ++--- export/src/render.ts | 8 ++--- export/src/types.ts | 36 ++++++++++++++-------- website/src/entry/export/TimetableOnly.tsx | 12 +++++--- website/src/entry/export/main.tsx | 3 +- 8 files changed, 50 insertions(+), 33 deletions(-) diff --git a/export/api/export/image.ts b/export/api/export/image.ts index 486ec29faa..83fb3236b5 100644 --- a/export/api/export/image.ts +++ b/export/api/export/image.ts @@ -3,10 +3,10 @@ import _ from 'lodash'; import { validateExportData } from '../../src/data'; import { makeExportHandler } from '../../src/handler'; import * as render from '../../src/render-serverless'; -import type { PageData } from '../../src/types'; +import type { ExportData } from '../../src/types'; type Data = { - exportData: PageData; + exportData: ExportData; options: render.ViewportOptions; }; diff --git a/export/api/export/pdf.ts b/export/api/export/pdf.ts index a5631a274b..017fd90763 100644 --- a/export/api/export/pdf.ts +++ b/export/api/export/pdf.ts @@ -1,9 +1,9 @@ import { validateExportData } from '../../src/data'; import { makeExportHandler } from '../../src/handler'; import * as render from '../../src/render-serverless'; -import type { PageData } from '../../src/types'; +import type { ExportData } from '../../src/types'; -const handler = makeExportHandler( +const handler = makeExportHandler( (request) => { const exportData = JSON.parse(request.query.data as never); validateExportData(exportData); diff --git a/export/src/data.ts b/export/src/data.ts index adb1bc7b13..5cca25b6a3 100644 --- a/export/src/data.ts +++ b/export/src/data.ts @@ -6,7 +6,7 @@ import Joi from 'joi'; import type { Middleware } from 'koa'; import config from './config'; -import type { PageData, State } from './types'; +import type { ExportData, State } from './types'; async function fetchModule(moduleCode: string) { const fileName = `${moduleCode}.json`; @@ -58,7 +58,7 @@ export const parseExportData: Middleware = (ctx, next) => { return next(); }; -export function validateExportData(data: PageData) { +export function validateExportData(data: ExportData) { if (!_.isObject(data)) throw new Error('data should be an object'); const timetableSchema = Joi.object().pattern( @@ -73,8 +73,10 @@ export function validateExportData(data: PageData) { const pageDataSchema = Joi.object({ semester: Joi.number().integer().greater(0).less(5), timetable: timetableSchema, + colors: Joi.object().pattern(Joi.string(), Joi.number().integer().min(0)), + hidden: Joi.array().items(Joi.string()), settings: Joi.object({ - hiddenInTimeTable: Joi.array().items(Joi.string()), + colorScheme: Joi.string().valid('LIGHT_COLOR_SCHEME', 'DARK_COLOR_SCHEME'), }), theme: themeSchema, }); diff --git a/export/src/render-serverless.ts b/export/src/render-serverless.ts index 784201db39..78ad8d33b2 100644 --- a/export/src/render-serverless.ts +++ b/export/src/render-serverless.ts @@ -3,7 +3,7 @@ import puppeteer, { Page } from 'puppeteer-core'; import { getModules } from './data'; import config from './config'; -import type { PageData } from './types'; +import type { ExportData } from './types'; // Arbitrarily high number - just make sure it doesn't clip the timetable const VIEWPORT_HEIGHT = 2000; @@ -42,7 +42,7 @@ export async function open(url: string) { return page; } -async function injectData(page: Page, data: PageData) { +async function injectData(page: Page, data: ExportData) { const moduleCodes = Object.keys(data.timetable); const modules = await getModules(moduleCodes); @@ -59,7 +59,7 @@ async function injectData(page: Page, data: PageData) { return (await appEle.boundingBox()) || undefined; } -export async function image(page: Page, data: PageData, options: ViewportOptions = {}) { +export async function image(page: Page, data: ExportData, options: ViewportOptions = {}) { if (options.pixelRatio || (options.height && options.width)) { await setViewport(page, options); } @@ -70,7 +70,7 @@ export async function image(page: Page, data: PageData, options: ViewportOptions }); } -export async function pdf(page: Page, data: PageData) { +export async function pdf(page: Page, data: ExportData) { await injectData(page, data); await page.emulateMediaType('screen'); diff --git a/export/src/render.ts b/export/src/render.ts index 950b144240..a90ba15269 100644 --- a/export/src/render.ts +++ b/export/src/render.ts @@ -4,7 +4,7 @@ import type { Middleware } from 'koa'; import { getModules } from './data'; import config from './config'; -import type { PageData, State } from './types'; +import type { ExportData, State } from './types'; // Arbitrarily high number - just make sure it doesn't clip the timetable const VIEWPORT_HEIGHT = 2000; @@ -67,7 +67,7 @@ export const openPage: Middleware = async (ctx, next) => { await ctx.state.page.close(); }; -async function injectData(page: Page, data: PageData) { +async function injectData(page: Page, data: ExportData) { const moduleCodes = Object.keys(data.timetable); const modules = await getModules(moduleCodes); @@ -84,7 +84,7 @@ async function injectData(page: Page, data: PageData) { return (await appEle.boundingBox()) || undefined; } -export async function image(page: Page, data: PageData, options: ViewportOptions = {}) { +export async function image(page: Page, data: ExportData, options: ViewportOptions = {}) { if (options.pixelRatio || (options.height && options.width)) { await setViewport(page, options); } @@ -95,7 +95,7 @@ export async function image(page: Page, data: PageData, options: ViewportOptions }); } -export async function pdf(page: Page, data: PageData) { +export async function pdf(page: Page, data: ExportData) { await injectData(page, data); await page.emulateMediaType('screen'); diff --git a/export/src/types.ts b/export/src/types.ts index 976ac22239..5252b5191f 100644 --- a/export/src/types.ts +++ b/export/src/types.ts @@ -1,19 +1,31 @@ import type { Page } from 'puppeteer-core'; +// These types are duplicated from `website/`. +// TODO: Move these types to a shared package. export type TimetableOrientation = 'HORIZONTAL' | 'VERTICAL'; +export type Semester = number; +export type SemTimetableConfig = { + [moduleCode: string]: ModuleLessonConfig; +}; +export type ColorIndex = number; +export type ColorMapping = { [moduleCode: string]: ColorIndex }; +export type ModuleCode = string; +export type ThemeState = Readonly<{ + id: string; + timetableOrientation: TimetableOrientation; + showTitle: boolean; +}>; +export type ColorScheme = 'LIGHT_COLOR_SCHEME' | 'DARK_COLOR_SCHEME'; -export interface PageData { - readonly semester: number; - readonly timetable: { - [moduleCode: string]: ModuleLessonConfig; - }; +// `ExportData` is duplicated from `website/src/types/export.ts`. +export interface ExportData { + readonly semester: Semester; + readonly timetable: SemTimetableConfig; + readonly colors: ColorMapping; + readonly hidden: ModuleCode[]; + readonly theme: ThemeState; readonly settings: { - readonly hiddenInTimetable: string[]; - }; - readonly theme: { - id: string; - timetableOrientation: TimetableOrientation; - showTitle: boolean; + colorScheme: ColorScheme; }; } @@ -22,6 +34,6 @@ export interface ModuleLessonConfig { } export interface State { - data: PageData; + data: ExportData; page: Page; } diff --git a/website/src/entry/export/TimetableOnly.tsx b/website/src/entry/export/TimetableOnly.tsx index 1560300fa2..2e27bf5bf3 100644 --- a/website/src/entry/export/TimetableOnly.tsx +++ b/website/src/entry/export/TimetableOnly.tsx @@ -3,7 +3,7 @@ import { Component } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; -import { Semester } from 'types/modules'; +import { ModuleCode, Semester } from 'types/modules'; import { SemTimetableConfig } from 'types/timetables'; import { fillColorMapping } from 'utils/colors'; import TimetableContent from 'views/timetable/TimetableContent'; @@ -18,6 +18,7 @@ type State = { semester: Semester; timetable: SemTimetableConfig; colors: ColorMapping; + hidden: ModuleCode[]; }; export default class TimetableOnly extends Component { @@ -25,14 +26,15 @@ export default class TimetableOnly extends Component { semester: 1, timetable: {}, colors: {}, + hidden: [], }; override render() { const { store } = this.props; const theme = store.getState().theme.id; - const { semester, timetable, colors } = this.state; - const timetableColors = fillColorMapping(timetable, colors); + const { semester, timetable, colors, hidden } = this.state; + const filledColors = fillColorMapping(timetable, colors); return ( @@ -42,8 +44,8 @@ export default class TimetableOnly extends Component { header={null} semester={semester} timetable={timetable} - colors={timetableColors} - hiddenImportedModules={null} + colors={filledColors} + hiddenImportedModules={hidden} readOnly /> diff --git a/website/src/entry/export/main.tsx b/website/src/entry/export/main.tsx index 3654ee97ca..c526bb89e1 100644 --- a/website/src/entry/export/main.tsx +++ b/website/src/entry/export/main.tsx @@ -29,7 +29,7 @@ window.store = store; // For Puppeteer to import data const timetableRef = createRef(); window.setData = function setData(modules, data, callback) { - const { semester, timetable, colors } = data; + const { semester, timetable, colors, hidden } = data; if (document.body) { document.body.classList.toggle('mode-dark', data.settings.colorScheme === DARK_COLOR_SCHEME); @@ -43,6 +43,7 @@ window.setData = function setData(modules, data, callback) { semester, timetable, colors, + hidden, }, callback, );