diff --git a/website/src/actions/__snapshots__/theme.test.ts.snap b/website/src/actions/__snapshots__/theme.test.ts.snap index 670e33f8cd..8f0967c0ea 100644 --- a/website/src/actions/__snapshots__/theme.test.ts.snap +++ b/website/src/actions/__snapshots__/theme.test.ts.snap @@ -16,7 +16,11 @@ exports[`theme should dispatch a cycle of theme 2`] = ` exports[`theme should dispatch a select of theme 1`] = ` { - "payload": "test", + "payload": { + "id": "test", + "name": "Test", + "numOfColors": 8, + }, "type": "SELECT_THEME", } `; diff --git a/website/src/actions/theme.test.ts b/website/src/actions/theme.test.ts index 37a33e5f3e..1a8bfb7ecc 100644 --- a/website/src/actions/theme.test.ts +++ b/website/src/actions/theme.test.ts @@ -1,8 +1,13 @@ import * as actions from 'actions/theme'; +import { Theme } from 'types/settings'; describe('theme', () => { test('should dispatch a select of theme', () => { - const theme = 'test'; + const theme: Theme = { + id: 'test', + name: 'Test', + numOfColors: 8, + }; expect(actions.selectTheme(theme)).toMatchSnapshot(); }); diff --git a/website/src/actions/theme.ts b/website/src/actions/theme.ts index 3e9a311575..c4d17b1d24 100644 --- a/website/src/actions/theme.ts +++ b/website/src/actions/theme.ts @@ -1,5 +1,7 @@ +import { Theme } from 'types/settings'; + export const SELECT_THEME = 'SELECT_THEME' as const; -export function selectTheme(theme: string) { +export function selectTheme(theme: Theme) { return { type: SELECT_THEME, payload: theme, diff --git a/website/src/actions/timetables.ts b/website/src/actions/timetables.ts index 1d1debbd54..5733885708 100644 --- a/website/src/actions/timetables.ts +++ b/website/src/actions/timetables.ts @@ -33,13 +33,19 @@ export const Internal = { }; }, - addModule(semester: Semester, moduleCode: ModuleCode, moduleLessonConfig: ModuleLessonConfig) { + addModule( + semester: Semester, + moduleCode: ModuleCode, + moduleLessonConfig: ModuleLessonConfig, + numOfColors: number, + ) { return { type: ADD_MODULE, payload: { semester, moduleCode, moduleLessonConfig, + numOfColors, }, }; }, @@ -67,8 +73,9 @@ export function addModule(semester: Semester, moduleCode: ModuleCode) { const lessons = getModuleTimetable(module, semester); const moduleLessonConfig = randomModuleLessonConfig(lessons); + const { numOfColors } = getState().theme; - dispatch(Internal.addModule(semester, moduleCode, moduleLessonConfig)); + dispatch(Internal.addModule(semester, moduleCode, moduleLessonConfig, numOfColors)); }); } @@ -238,6 +245,16 @@ export function selectModuleColor( }; } +export const REASSIGN_ALL_MODULES_COLOR = 'REASSIGN_ALL_MODULES_COLOR' as const; +export function reassignAllModulesColor(numOfColors: number) { + return { + type: REASSIGN_ALL_MODULES_COLOR, + payload: { + numOfColors, + }, + }; +} + export const HIDE_LESSON_IN_TIMETABLE = 'HIDE_LESSON_IN_TIMETABLE' as const; export function hideLessonInTimetable(semester: Semester, moduleCode: ModuleCode) { return { diff --git a/website/src/data/themes.json b/website/src/data/themes.json index b3f82ec90d..38dc21a8da 100644 --- a/website/src/data/themes.json +++ b/website/src/data/themes.json @@ -1,50 +1,67 @@ [ { "id": "ashes", - "name": " Ashes" + "name": " Ashes", + "numOfColors": 8 }, { "id": "chalk", - "name": "Chalk" + "name": "Chalk", + "numOfColors": 8 }, { "id": "eighties", - "name": "Eighties" + "name": "Eighties", + "numOfColors": 8 }, { "id": "google", - "name": "Google" + "name": "Google", + "numOfColors": 8 }, { "id": "mocha", - "name": "Mocha" + "name": "Mocha", + "numOfColors": 8 }, { "id": "monokai", - "name": "Monokai" + "name": "Monokai", + "numOfColors": 8 }, { "id": "ocean", - "name": "Ocean" + "name": "Ocean", + "numOfColors": 8 }, { "id": "oceanic-next", - "name": "Oceanic Next" + "name": "Oceanic Next", + "numOfColors": 8 }, { "id": "paraiso", - "name": "Paraiso" + "name": "Paraiso", + "numOfColors": 8 }, { "id": "railscasts", - "name": "Railscasts" + "name": "Railscasts", + "numOfColors": 8 + }, + { + "id": "tequila", + "name": "Tequila", + "numOfColors": 10 }, { "id": "tomorrow", - "name": "Tomorrow" + "name": "Tomorrow", + "numOfColors": 8 }, { "id": "twilight", - "name": "Twilight" + "name": "Twilight", + "numOfColors": 8 } ] diff --git a/website/src/entry/export/TimetableOnly.tsx b/website/src/entry/export/TimetableOnly.tsx index 1560300fa2..a15c0a0ead 100644 --- a/website/src/entry/export/TimetableOnly.tsx +++ b/website/src/entry/export/TimetableOnly.tsx @@ -29,15 +29,15 @@ export default class TimetableOnly extends Component { override render() { const { store } = this.props; - const theme = store.getState().theme.id; + const { theme } = store.getState(); const { semester, timetable, colors } = this.state; - const timetableColors = fillColorMapping(timetable, colors); + const timetableColors = fillColorMapping(timetable, colors, theme.numOfColors); return ( -
+
{ expect(state.theme).toEqual({ id: 'google', + numOfColors: 8, timetableOrientation: VERTICAL, showTitle: true, }); diff --git a/website/src/reducers/theme.test.ts b/website/src/reducers/theme.test.ts index 9fc7049919..390abb42f1 100644 --- a/website/src/reducers/theme.test.ts +++ b/website/src/reducers/theme.test.ts @@ -2,10 +2,15 @@ import { ThemeState, VERTICAL } from 'types/reducers'; import * as actions from 'actions/theme'; import reducer, { defaultThemeState, themeIds } from 'reducers/theme'; +import { Theme } from 'types/settings'; const themeInitialState: ThemeState = defaultThemeState; -const googleTheme = 'google'; -const themeWithEighties: ThemeState = { ...themeInitialState, id: googleTheme }; +const googleTheme: Theme = { + id: 'google', + name: 'Google', + numOfColors: 8, +}; +const themeWithEighties: ThemeState = { ...themeInitialState, id: googleTheme.id }; const themeWithFirstTheme: ThemeState = { ...themeInitialState, id: themeIds[0] }; const themeWithLastTheme: ThemeState = { ...themeInitialState, id: themeIds[themeIds.length - 1] }; const themeWithVerticalOrientation: ThemeState = { diff --git a/website/src/reducers/theme.ts b/website/src/reducers/theme.ts index 2f243be7d3..93f741bdce 100644 --- a/website/src/reducers/theme.ts +++ b/website/src/reducers/theme.ts @@ -15,31 +15,34 @@ import { DIMENSIONS, withTracker } from 'bootstrapping/matomo'; export const defaultThemeState: ThemeState = { // Available themes are defined in `themes.scss` id: 'eighties', + numOfColors: 8, timetableOrientation: HORIZONTAL, showTitle: false, }; export const themeIds = themes.map((obj: Theme) => obj.id); function theme(state: ThemeState = defaultThemeState, action: Actions): ThemeState { - function setTheme(newTheme: string): ThemeState { + function setTheme(newTheme: Theme): ThemeState { // Update theme analytics info - withTracker((tracker) => tracker.setCustomDimension(DIMENSIONS.theme, newTheme)); + withTracker((tracker) => tracker.setCustomDimension(DIMENSIONS.theme, newTheme.id)); return { ...state, - id: newTheme, + id: newTheme.id, + numOfColors: newTheme.numOfColors, }; } switch (action.type) { case SELECT_THEME: + // Reassign all modules' color when changing theme return setTheme(action.payload); case CYCLE_THEME: { const newThemeIndex = (themeIds.indexOf(state.id) + themeIds.length + action.payload) % themeIds.length; - return setTheme(themeIds[newThemeIndex]); + return setTheme(themes[newThemeIndex]); } case TOGGLE_TIMETABLE_ORIENTATION: return { diff --git a/website/src/reducers/timetables.test.ts b/website/src/reducers/timetables.test.ts index 907384080c..0ba4880c9a 100644 --- a/website/src/reducers/timetables.test.ts +++ b/website/src/reducers/timetables.test.ts @@ -14,6 +14,9 @@ import { import { TimetablesState } from 'types/reducers'; import config from 'config'; +// use 8 different colors for testing +const NUM_DIFFERENT_COLORS = 8; + const initialState = defaultTimetableState; jest.mock('config'); @@ -28,6 +31,7 @@ describe('color reducers', () => { semester: 1, moduleCode: 'CS1010S', moduleLessonConfig: {}, + numOfColors: NUM_DIFFERENT_COLORS, }, }).colors, ).toHaveProperty('1.CS1010S'); @@ -39,6 +43,7 @@ describe('color reducers', () => { semester: 2, moduleCode: 'CS3216', moduleLessonConfig: {}, + numOfColors: NUM_DIFFERENT_COLORS, }, }).colors, ).toHaveProperty('2.CS3216'); diff --git a/website/src/reducers/timetables.ts b/website/src/reducers/timetables.ts index 036905a110..c0f70a10b8 100644 --- a/website/src/reducers/timetables.ts +++ b/website/src/reducers/timetables.ts @@ -5,7 +5,7 @@ import { createMigrate } from 'redux-persist'; import { PersistConfig } from 'storage/persistReducer'; import { ModuleCode } from 'types/modules'; import { ModuleLessonConfig, SemTimetableConfig } from 'types/timetables'; -import { ColorMapping, TimetablesState } from 'types/reducers'; +import { ColorMapping, SemesterColorMap, TimetablesState } from 'types/reducers'; import config from 'config'; import { @@ -13,6 +13,7 @@ import { CHANGE_LESSON, HIDDEN_IMPORTED_SEM, HIDE_LESSON_IN_TIMETABLE, + REASSIGN_ALL_MODULES_COLOR, REMOVE_MODULE, RESET_TIMETABLE, SELECT_MODULE_COLOR, @@ -134,7 +135,7 @@ function semColors(state: ColorMapping = DEFAULT_SEM_COLOR_MAP, action: Actions) case ADD_MODULE: return { ...state, - [moduleCode]: getNewColor(values(state)), + [moduleCode]: getNewColor(values(state), action.payload.numOfColors), }; case REMOVE_MODULE: @@ -151,6 +152,18 @@ function semColors(state: ColorMapping = DEFAULT_SEM_COLOR_MAP, action: Actions) } } +function recolor(curSemesterColorMap: SemesterColorMap, numOfColors: number): SemesterColorMap { + const newSemesterColorMap: SemesterColorMap = {}; + Object.entries(curSemesterColorMap).forEach(([semester, colorMapping]) => { + const newColorMapping: ColorMapping = {}; + Object.entries(colorMapping).forEach(([moduleCode, colorIndex]) => { + newColorMapping[moduleCode] = colorIndex % numOfColors; + }); + newSemesterColorMap[semester] = newColorMapping; + }); + return newSemesterColorMap; +} + // Map of semester to list of hidden modules const DEFAULT_HIDDEN_STATE: ModuleCode[] = []; function semHiddenModules(state = DEFAULT_HIDDEN_STATE, action: Actions) { @@ -236,6 +249,11 @@ function timetables( hidden: { [semester]: hidden }, }; } + case REASSIGN_ALL_MODULES_COLOR: + return { + ...state, + colors: recolor(state.colors, action.payload.numOfColors), + }; case SET_HIDDEN_IMPORTED: { const { semester, hiddenModules } = action.payload; diff --git a/website/src/styles/constants.scss b/website/src/styles/constants.scss index c1bc53a1af..952bc37dd1 100644 --- a/website/src/styles/constants.scss +++ b/website/src/styles/constants.scss @@ -145,6 +145,18 @@ $nusmods-theme-colors: ( #b6b3eb, #bc9458 ), + tequila: ( + #e7553e, + #ff9f1e, + #ffc7c7, + #f1baa1, + #ffd700, + #8febdb, + #a9daf5, + #80b9f3, + #b9848c, + #b249f2, + ), tomorrow: ( #c66, #de935f, diff --git a/website/src/types/reducers.ts b/website/src/types/reducers.ts index 5418bb7d6f..7fb49b984f 100644 --- a/website/src/types/reducers.ts +++ b/website/src/types/reducers.ts @@ -79,6 +79,7 @@ export const HORIZONTAL: TimetableOrientation = 'HORIZONTAL'; export type ThemeState = Readonly<{ id: string; + numOfColors: number; timetableOrientation: TimetableOrientation; showTitle: boolean; }>; diff --git a/website/src/types/settings.ts b/website/src/types/settings.ts index c3a0679c10..dd9299e383 100644 --- a/website/src/types/settings.ts +++ b/website/src/types/settings.ts @@ -7,6 +7,7 @@ export type ThemeId = string; export type Theme = { readonly id: ThemeId; readonly name: string; + readonly numOfColors: number; }; /** diff --git a/website/src/utils/colors.test.ts b/website/src/utils/colors.test.ts index 6ad2c01488..3af472542a 100644 --- a/website/src/utils/colors.test.ts +++ b/website/src/utils/colors.test.ts @@ -3,18 +3,23 @@ import { range, uniq, without } from 'lodash'; import { ColorMapping } from 'types/reducers'; import { ColorIndex, Lesson, SemTimetableConfig } from 'types/timetables'; -import { colorLessonsByKey, fillColorMapping, getNewColor, NUM_DIFFERENT_COLORS } from './colors'; +import { colorLessonsByKey, fillColorMapping, getNewColor } from './colors'; + +// use 8 different colors for testing +const NUM_DIFFERENT_COLORS = 8; describe(getNewColor, () => { test('it should get color without randomization', () => { // When there are no current colors - expect(getNewColor([], false)).toBe(0); + expect(getNewColor([], NUM_DIFFERENT_COLORS, false)).toBe(0); // When there are colors that have not been picked - expect(getNewColor([0, 1], false)).toBe(2); + expect(getNewColor([0, 1], NUM_DIFFERENT_COLORS, false)).toBe(2); // When all the colors have been picked once - expect(getNewColor(range(NUM_DIFFERENT_COLORS), false)).toBe(0); + expect(getNewColor(range(NUM_DIFFERENT_COLORS), NUM_DIFFERENT_COLORS, false)).toBe(0); // When all the colors have been picked once or more - expect(getNewColor([...range(NUM_DIFFERENT_COLORS), 0, 1], false)).toBe(2); + expect(getNewColor([...range(NUM_DIFFERENT_COLORS), 0, 1], NUM_DIFFERENT_COLORS, false)).toBe( + 2, + ); }); test('it should get random color', () => { @@ -23,7 +28,7 @@ describe(getNewColor, () => { // in [0, NUM_DIFFERENT_COLORS] AND not in unexpectedColors function expectValidIndex(unexpectedColors: ColorIndex[], currentColors: ColorIndex[]) { expect(without(range(NUM_DIFFERENT_COLORS), ...unexpectedColors)).toContain( - getNewColor(currentColors, true), + getNewColor(currentColors, NUM_DIFFERENT_COLORS, true), ); } @@ -61,7 +66,7 @@ describe(colorLessonsByKey, () => { lessons.push(newLesson); }); - const coloredLessons = colorLessonsByKey(lessons, 'venue'); + const coloredLessons = colorLessonsByKey(lessons, 'venue', NUM_DIFFERENT_COLORS); range(NUM_DIFFERENT_COLORS * 2).forEach((i) => { const coloredLesson = coloredLessons[i]; @@ -75,12 +80,17 @@ describe(colorLessonsByKey, () => { describe(fillColorMapping, () => { test('should return color map with colors for all modules', () => { - expect(Object.keys(fillColorMapping({ CS1010S: {}, CS3216: {} }, {}))).toEqual([ - 'CS1010S', - 'CS3216', - ]); + expect( + Object.keys(fillColorMapping({ CS1010S: {}, CS3216: {} }, {}, NUM_DIFFERENT_COLORS)), + ).toEqual(['CS1010S', 'CS3216']); - expect(fillColorMapping({ CS1010S: {}, CS3216: {} }, { CS1010S: 0, CS3216: 1 })).toEqual({ + expect( + fillColorMapping( + { CS1010S: {}, CS3216: {} }, + { CS1010S: 0, CS3216: 1 }, + NUM_DIFFERENT_COLORS, + ), + ).toEqual({ CS1010S: 0, CS3216: 1, }); @@ -89,13 +99,20 @@ describe(fillColorMapping, () => { fillColorMapping( { CS1010S: {}, CS3216: {} }, { CS1010S: 0, CS3216: 1, CS1101S: 1, CS2105: 0, CS1231: 2 }, + NUM_DIFFERENT_COLORS, ), ).toEqual({ CS1010S: 0, CS3216: 1, }); - expect(fillColorMapping({ CS1010S: {}, CS3216: {} }, { CS1010S: 0, CS3216: 0 })).toEqual({ + expect( + fillColorMapping( + { CS1010S: {}, CS3216: {} }, + { CS1010S: 0, CS3216: 0 }, + NUM_DIFFERENT_COLORS, + ), + ).toEqual({ CS1010S: 0, CS3216: 0, }); @@ -114,7 +131,7 @@ describe(fillColorMapping, () => { }; const uniqueColors = (timetable: SemTimetableConfig, colors: ColorMapping) => - uniq(Object.values(fillColorMapping(timetable, colors))); + uniq(Object.values(fillColorMapping(timetable, colors, NUM_DIFFERENT_COLORS))); expect(uniqueColors(FILLED_TIMETABLE, {})).toHaveLength(8); expect(uniqueColors(FILLED_TIMETABLE, { CS3216: 1, CS1101S: 0 })).toHaveLength(8); diff --git a/website/src/utils/colors.ts b/website/src/utils/colors.ts index 62bd0ba016..83df637ea2 100644 --- a/website/src/utils/colors.ts +++ b/website/src/utils/colors.ts @@ -4,21 +4,23 @@ import { ColorIndex, SemTimetableConfig } from 'types/timetables'; import { ColorMapping } from 'types/reducers'; import { ModuleCode } from 'types/modules'; -export const NUM_DIFFERENT_COLORS = 8; - -function generateInitialColors(): ColorIndex[] { - return range(NUM_DIFFERENT_COLORS); +function generateInitialColors(numOfColors: number): ColorIndex[] { + return range(numOfColors); } // Returns a new index that is not present in the current color index. -// If there are more than NUM_DIFFERENT_COLORS modules already present, +// If there are more than numOfColors modules already present, // will try to balance the color distribution if randomize === true. -export function getNewColor(currentColors: ColorIndex[], randomize = true): ColorIndex { - let availableColors = generateInitialColors(); +export function getNewColor( + currentColors: ColorIndex[], + numOfColors: number, + randomize = true, +): ColorIndex { + let availableColors = generateInitialColors(numOfColors); currentColors.forEach((index: ColorIndex) => { availableColors = without(availableColors, index); if (availableColors.length === 0) { - availableColors = generateInitialColors(); + availableColors = generateInitialColors(numOfColors); } }); @@ -34,13 +36,14 @@ export function getNewColor(currentColors: ColorIndex[], randomize = true): Colo export function colorLessonsByKey( lessons: T[], key: keyof T, + numOfColors: number, ): (T & { colorIndex: ColorIndex })[] { const colorMap = new Map(); return lessons.map((lesson) => { let colorIndex = colorMap.get(lesson[key]); if (!colorMap.has(lesson[key])) { - colorIndex = getNewColor(Array.from(colorMap.values()), false); + colorIndex = getNewColor(Array.from(colorMap.values()), numOfColors, false); colorMap.set(lesson[key], colorIndex); } @@ -54,6 +57,7 @@ export function colorLessonsByKey( export function fillColorMapping( timetable: SemTimetableConfig, original: ColorMapping, + numOfColors: number, ): ColorMapping { const colorMap: ColorMapping = {}; const colorsUsed: ColorIndex[] = []; @@ -71,7 +75,7 @@ export function fillColorMapping( // Assign the modules without colors withoutColors.forEach((moduleCode) => { - const color = getNewColor(colorsUsed, false); + const color = getNewColor(colorsUsed, numOfColors, false); colorMap[moduleCode] = color; colorsUsed.push(color); }); diff --git a/website/src/views/components/ColorPicker.test.tsx b/website/src/views/components/ColorPicker.test.tsx index d4dfe6724e..1a25c67de0 100644 --- a/website/src/views/components/ColorPicker.test.tsx +++ b/website/src/views/components/ColorPicker.test.tsx @@ -1,16 +1,31 @@ import { mount, ReactWrapper } from 'enzyme'; -import ColorPicker from 'views/components/ColorPicker'; +import { PropsWithChildren } from 'react'; +import { Provider } from 'react-redux'; +import reducers from 'reducers'; import { ColorIndex } from 'types/timetables'; import { expectColor } from 'test-utils/theme'; +import { initAction } from 'test-utils/redux'; +import configureStore from 'bootstrapping/configure-store'; +import ColorPicker from 'views/components/ColorPicker'; import styles from './ColorPicker.scss'; function makeColorPicker(color: ColorIndex = 0) { const onChooseColor = jest.fn(); + const initialState = reducers(undefined, initAction()); + const { store } = configureStore(initialState); + + const ProviderWrapper = ({ children }: PropsWithChildren>) => ( + {children} + ); + return { onChooseColor, wrapper: mount( , + { + wrappingComponent: ProviderWrapper, + }, ), }; } diff --git a/website/src/views/components/ColorPicker.tsx b/website/src/views/components/ColorPicker.tsx index 12e5f5efc5..2af728830e 100644 --- a/website/src/views/components/ColorPicker.tsx +++ b/website/src/views/components/ColorPicker.tsx @@ -3,9 +3,10 @@ import classnames from 'classnames'; import Downshift, { ChildrenFunction } from 'downshift'; import _ from 'lodash'; +import { useSelector } from 'react-redux'; +import { State } from 'types/state'; import { ColorIndex } from 'types/timetables'; -import { NUM_DIFFERENT_COLORS } from 'utils/colors'; import styles from './ColorPicker.scss'; type Props = { @@ -21,6 +22,8 @@ type Props = { * For use in places like changing module colors */ const ColorPicker = memo((props) => { + const theme = useSelector((state: State) => state.theme); + const renderColorPicker: ChildrenFunction = ({ getToggleButtonProps, getItemProps, @@ -44,7 +47,7 @@ const ColorPicker = memo((props) => { className={classnames(styles.palette, { [styles.isClosed]: !isOpen })} {...getMenuProps()} > - {_.range(NUM_DIFFERENT_COLORS).map((index: ColorIndex) => ( + {_.range(theme.numOfColors).map((index: ColorIndex) => (
@@ -305,6 +311,7 @@ const connectedSettings = connect(mapStateToProps, { selectTheme, selectFaculty, selectColorScheme, + reassignAllModulesColor, toggleBetaTesting, setLoadDisqusManually, toggleModRegNotificationGlobally, diff --git a/website/src/views/settings/ThemeOption.scss b/website/src/views/settings/ThemeOption.scss index f240f9440b..bcfdfc3675 100644 --- a/website/src/views/settings/ThemeOption.scss +++ b/website/src/views/settings/ThemeOption.scss @@ -25,13 +25,8 @@ $theme-option-gap: 0.7rem; } .colorList { - $number-colors: 8; - .colorItem { - $size: 12.5%; // 100% / $number-colors - display: inline-block; - width: $size; - padding-bottom: $size; + padding-bottom: 12.5%; } } diff --git a/website/src/views/settings/ThemeOption.tsx b/website/src/views/settings/ThemeOption.tsx index 7b34a9bc71..5a35042c6b 100644 --- a/website/src/views/settings/ThemeOption.tsx +++ b/website/src/views/settings/ThemeOption.tsx @@ -1,15 +1,14 @@ import * as React from 'react'; import { range } from 'lodash'; import classnames from 'classnames'; -import { Theme, ThemeId } from 'types/settings'; +import { Theme } from 'types/settings'; -import { NUM_DIFFERENT_COLORS } from 'utils/colors'; import styles from './ThemeOption.scss'; type Props = { theme: Theme; isSelected: boolean; - onSelectTheme: (themeId: ThemeId) => void; + onSelectTheme: () => void; className?: string; }; @@ -22,14 +21,20 @@ const ThemeOption: React.FC = (props) => { className={classnames(className, styles.option, `theme-${theme.id}`, { [styles.isSelected]: isSelected, })} - onClick={() => onSelectTheme(theme.id)} + onClick={onSelectTheme} >
{theme.name}
    - {range(NUM_DIFFERENT_COLORS).map((index) => ( -
  • + {range(theme.numOfColors).map((index) => ( +
  • ))}
diff --git a/website/src/views/timetable/TimetableContainer.tsx b/website/src/views/timetable/TimetableContainer.tsx index 32264ffd93..ea48f9e3a6 100644 --- a/website/src/views/timetable/TimetableContainer.tsx +++ b/website/src/views/timetable/TimetableContainer.tsx @@ -145,6 +145,7 @@ export const TimetableContainerComponent: FC = () => { const getModule = useSelector(getModuleCondensed); const modules = useSelector(({ moduleBank }: State) => moduleBank.modules); const activeSemester = useSelector(({ app }: State) => app.activeSemester); + const numOfColors = useSelector(({ theme }: State) => theme.numOfColors); const location = useLocation(); const [importedTimetable, setImportedTimetable] = useState(() => @@ -178,8 +179,8 @@ export const TimetableContainerComponent: FC = () => { const displayedTimetable = importedTimetable || timetable; const filledColors = useMemo( - () => fillColorMapping(displayedTimetable, colors), - [colors, displayedTimetable], + () => fillColorMapping(displayedTimetable, colors, numOfColors), + [colors, numOfColors, displayedTimetable], ); const readOnly = displayedTimetable === importedTimetable; diff --git a/website/src/views/venues/VenueDetails.tsx b/website/src/views/venues/VenueDetails.tsx index 3d7a93d01c..4132567f5c 100644 --- a/website/src/views/venues/VenueDetails.tsx +++ b/website/src/views/venues/VenueDetails.tsx @@ -1,10 +1,12 @@ import { FC, memo, useCallback, useMemo } from 'react'; import { Link, useHistory } from 'react-router-dom'; +import { useSelector } from 'react-redux'; import { ChevronLeft, ChevronRight } from 'react-feather'; import classnames from 'classnames'; import { flatMap } from 'lodash'; import type { DayAvailability, TimePeriod, Venue } from 'types/venues'; +import { State } from 'types/state'; import type { Lesson } from 'types/timetables'; import { colorLessonsByKey } from 'utils/colors'; @@ -33,6 +35,8 @@ const VenueDetailsComponent: FC = ({ availability, highlightPeriod, }) => { + const numOfColors = useSelector(({ theme }: State) => theme.numOfColors); + const arrangedLessons = useMemo(() => { const lessons: Lesson[] = flatMap(availability, (day) => day.classes).map((venueLesson) => ({ ...venueLesson, @@ -40,9 +44,9 @@ const VenueDetailsComponent: FC = ({ isModifiable: true, venue: '', })); - const coloredLessons = colorLessonsByKey(lessons, 'moduleCode'); + const coloredLessons = colorLessonsByKey(lessons, 'moduleCode', numOfColors); return arrangeLessonsForWeek(coloredLessons); - }, [availability]); + }, [availability, numOfColors]); const history = useHistory(); const navigateToLesson = useCallback(