-
Notifications
You must be signed in to change notification settings - Fork 326
Include colors and theme when sharing timetable #3467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 3 commits
e02bea0
18cabe7
70a3744
97e2b14
24cc09c
d4e0adf
484b1bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,7 @@ import { | |
|
||
import { | ||
ColoredLesson, | ||
ColorIndex, | ||
HoverLesson, | ||
Lesson, | ||
ModuleLessonConfig, | ||
|
@@ -46,8 +47,9 @@ import { | |
TimetableDayFormat, | ||
} from 'types/timetables'; | ||
|
||
import { ModuleCodeMap, ModulesMap } from 'types/reducers'; | ||
import { ColorMapping, ModuleCodeMap, ModulesMap } from 'types/reducers'; | ||
import { ExamClashes } from 'types/views'; | ||
import { ThemeId } from 'types/settings'; | ||
|
||
import { getTimeAsDate } from './timify'; | ||
import { getModuleSemesterData, getModuleTimetable } from './modules'; | ||
|
@@ -72,10 +74,17 @@ export const LESSON_TYPE_ABBREV: lessonTypeAbbrev = { | |
// Reverse lookup map of LESSON_TYPE_ABBREV | ||
export const LESSON_ABBREV_TYPE: { [key: string]: LessonType } = invert(LESSON_TYPE_ABBREV); | ||
|
||
const COLOR_PARAM = 'Color'; | ||
const QUERY_PARAM_ABBREV: { [key: string]: string } = { | ||
...LESSON_TYPE_ABBREV, | ||
[COLOR_PARAM]: 'COL', | ||
}; | ||
const THEME_PARAM = 'THEME'; | ||
|
||
// Used for module config serialization - these must be query string safe | ||
// See: https://stackoverflow.com/a/31300627 | ||
export const LESSON_TYPE_SEP = ':'; | ||
export const LESSON_SEP = ','; | ||
export const QUERY_PARAM_VALUE_SEP = ':'; | ||
export const QUERY_PARAM_SEP = ','; | ||
|
||
const EMPTY_OBJECT = {}; | ||
|
||
|
@@ -358,20 +367,20 @@ export function getSemesterModules( | |
return values(pick(modules, Object.keys(timetable))); | ||
} | ||
|
||
function serializeModuleConfig(config: ModuleLessonConfig): string { | ||
// eg. { Lecture: 1, Laboratory: 2 } => LEC=1,LAB=2 | ||
return map(config, (classNo, lessonType) => | ||
[LESSON_TYPE_ABBREV[lessonType], encodeURIComponent(classNo)].join(LESSON_TYPE_SEP), | ||
).join(LESSON_SEP); | ||
function serializeModuleConfig(config: { [paramName: string]: string | ColorIndex }): string { | ||
// eg. { Lecture: 1, Laboratory: 2, Color: 3 } => LEC=1,LAB=2,COL=3 | ||
return map(config, (paramValue, paramName) => | ||
[QUERY_PARAM_ABBREV[paramName], encodeURIComponent(paramValue)].join(QUERY_PARAM_VALUE_SEP), | ||
).join(QUERY_PARAM_SEP); | ||
} | ||
|
||
function parseModuleConfig(serialized: string | string[] | null): ModuleLessonConfig { | ||
const config: ModuleLessonConfig = {}; | ||
if (!serialized) return config; | ||
|
||
castArray(serialized).forEach((serializedModule) => { | ||
serializedModule.split(LESSON_SEP).forEach((lesson) => { | ||
const [lessonTypeAbbr, classNo] = lesson.split(LESSON_TYPE_SEP); | ||
serializedModule.split(QUERY_PARAM_SEP).forEach((lesson) => { | ||
const [lessonTypeAbbr, classNo] = lesson.split(QUERY_PARAM_VALUE_SEP); | ||
const lessonType = LESSON_ABBREV_TYPE[lessonTypeAbbr]; | ||
// Ignore unparsable/invalid keys | ||
if (!lessonType) return; | ||
|
@@ -434,18 +443,68 @@ export function formatNumericWeeks(weeks: NumericWeeks): string | null { | |
// Converts a timetable config to query string | ||
// eg: | ||
// { | ||
// CS2104: { Lecture: '1', Tutorial: '2' }, | ||
// CS2107: { Lecture: '1', Tutorial: '8' }, | ||
// CS2104: { Lecture: '1', Tutorial: '2', Color: 3 }, | ||
// CS2107: { Lecture: '1', Tutorial: '8', Color: 4 }, | ||
// } | ||
// => CS2104=LEC:1,Tut:2&CS2107=LEC:1,Tut:8 | ||
export function serializeTimetable(timetable: SemTimetableConfig): string { | ||
// => CS2104=LEC:1,TUT:2,COL:3&CS2107=LEC:1,TUT:8,COL:4 | ||
export function serializeTimetable( | ||
timetable: SemTimetableConfig, | ||
colors?: ColorMapping | null, | ||
themeId?: ThemeId | null, | ||
): string { | ||
const timetableModuleParams = Object.fromEntries( | ||
Object.entries(timetable).map((module) => { | ||
const [moduleCode, moduleLessonConfig] = module; | ||
const moduleParams: { [param: string]: string | ColorIndex } = { ...moduleLessonConfig }; | ||
if (colors && moduleCode in colors) { | ||
leslieyip02 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
moduleParams[COLOR_PARAM] = colors[moduleCode]; | ||
} | ||
return [moduleCode, moduleParams]; | ||
}), | ||
); | ||
const timetableParams = { | ||
...mapValues(timetableModuleParams, serializeModuleConfig), | ||
...((themeId && { [THEME_PARAM]: themeId }) || EMPTY_OBJECT), | ||
}; | ||
leslieyip02 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// We are using query string safe characters, so this encoding is unnecessary | ||
return qs.stringify(mapValues(timetable, serializeModuleConfig), { encode: false }); | ||
return qs.stringify(timetableParams, { encode: false }); | ||
} | ||
|
||
// Extracts module configs from query string | ||
export function deserializeTimetable(serialized: string): SemTimetableConfig { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's best to rewrite this so that it parses from query string, then delegate to objects to extract individual parts instead of having each functions re-parse the query string. |
||
const params = qs.parse(serialized); | ||
return mapValues(params, parseModuleConfig); | ||
const moduleParams = Object.fromEntries( | ||
Object.entries(params).filter(([paramName]) => paramName !== THEME_PARAM), | ||
); | ||
return mapValues(moduleParams, parseModuleConfig); | ||
} | ||
|
||
// Extracts color mapping from query string | ||
export function deserializeTimetableColors(serialized: string): ColorMapping | null { | ||
const params = qs.parse(serialized); | ||
const colorMapping: ColorMapping = {}; | ||
Object.keys(params) | ||
.filter((param) => param !== THEME_PARAM) | ||
.forEach((moduleCode) => { | ||
castArray(params[moduleCode]).forEach((serializedModule) => { | ||
if (!serializedModule) return; | ||
serializedModule.split(QUERY_PARAM_SEP).forEach((param) => { | ||
const [paramName, paramValue] = param.split(QUERY_PARAM_VALUE_SEP); | ||
if (paramName === QUERY_PARAM_ABBREV[COLOR_PARAM]) { | ||
colorMapping[moduleCode] = parseInt(paramValue, 10); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since colours are bound to a sub-param inside each module code, this is better extracted as part of deserializeTimetable instead |
||
} | ||
}); | ||
}); | ||
}); | ||
if (isEqual(colorMapping, EMPTY_OBJECT)) return null; | ||
leslieyip02 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return colorMapping; | ||
} | ||
|
||
// Extracts themeId from query string | ||
export function deserializeTimetableThemeId(serialized: string): ThemeId | null { | ||
const params = qs.parse(serialized); | ||
if (!params || !(THEME_PARAM in params)) return null; | ||
return params[THEME_PARAM] as ThemeId; | ||
} | ||
|
||
export function isSameTimetableConfig(t1: SemTimetableConfig, t2: SemTimetableConfig): boolean { | ||
|
Uh oh!
There was an error while loading. Please reload this page.