Skip to content

Commit

Permalink
Added Theme Selection for Timetable
Browse files Browse the repository at this point in the history
  • Loading branch information
ImJustChew committed Oct 29, 2023
1 parent deb8764 commit 9c416c4
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 14 deletions.
37 changes: 36 additions & 1 deletion src/app/[lang]/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
'use client';
import useDictionary from "@/dictionaries/useDictionary";
import { timetableColors } from "@/helpers/timetable";
import { useSettings } from "@/hooks/contexts/settings";
import { Divider, Option, Select, Switch } from "@mui/joy";
import { useEffect, useState } from "react";
import { useEffect, useState } from "react";

const TimetableThemePreview = ({ theme, onClick = () => {}, selected = false }: { theme: string, selected?: boolean, onClick?: () => void}) => {
return <div
onClick={onClick}
className={`flex flex-col rounded-lg p-3 hover:dark:bg-neutral-800 hover:bg-gray-100 transition cursor-pointer space-y-2 ${selected? "bg-gray-100 dark: bg-neutral-800":""}`}>
<div className="flex flex-row">
{timetableColors[theme].map((color, index) => (
<div className="flex-1 h-6 w-6" style={{background: color}} key={index}/>
))}
</div>
<span className="text-sm">{theme}</span>
</div>
}

const TimetableThemeList = () => {
const { timetableTheme, setTimetableTheme } = useSettings();
return <div className="flex flex-row flex-wrap gap-2">
{
Object.keys(timetableColors).map((theme, index) => (
<TimetableThemePreview key={index} theme={theme} onClick={() => setTimetableTheme(theme)} selected={timetableTheme == theme} />
))
}
</div>

}


const SettingsPage = () => {
Expand Down Expand Up @@ -44,6 +70,15 @@ const SettingsPage = () => {
</Select>
</div>
</div>
<Divider/>
<div className="flex flex-col gap-4 py-4">
<div className="flex flex-col flex-1">
<h2 className="font-semibold text-xl text-gray-600 dark:text-gray-400 pb-2">{dict.settings.theme.title}</h2>
<p className="text-gray-600 dark:text-gray-400">{dict.settings.theme.description}</p>
</div>
{/* TODO: Timetable Preview */}
<TimetableThemeList/>
</div>
</div>
)
};
Expand Down
3 changes: 2 additions & 1 deletion src/app/[lang]/timetable/calendar.ics/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { zonedTimeToUtc } from 'date-fns-tz'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const courses_ids = searchParams.get('semester_1121')?.split(',')!;
const theme = searchParams.get('theme') || 'tsinghuarian';

try {
let { data = [], error } = await supabase.from('courses').select("*").in('raw_id', courses_ids);
if (error) throw error;
else {
const timetableData = createTimetableFromCourses(data!);
const timetableData = createTimetableFromCourses(data!, theme);
const icss = ics.createEvents(timetableData.map(course => {
const start = zonedTimeToUtc(parse(
scheduleTimeSlots[course.startTime]!.start,
Expand Down
6 changes: 4 additions & 2 deletions src/app/[lang]/timetable/view/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { useSearchParams } from 'next/navigation'
import supabase from "@/config/supabase";
import useSWR from "swr";
import { createTimetableFromCourses, timetableColors } from "@/helpers/timetable";
import { useSettings } from "@/hooks/contexts/settings";

const ViewTimetablePage: NextPage = () => {
const router = useRouter();
const searchParams = useSearchParams();
const courseCodes = searchParams.get('semester_1121')?.split(',');
const { timetableTheme } = useSettings();

if(!courseCodes) router.back();

Expand All @@ -22,15 +24,15 @@ const ViewTimetablePage: NextPage = () => {
return data;
})

const timetableData = courses? createTimetableFromCourses(courses) : [];
const timetableData = courses? createTimetableFromCourses(courses, timetableTheme) : [];

return (
<div className="grid grid-cols-1 grid-rows-2 md:grid-rows-1 md:grid-cols-[3fr_2fr] px-1 py-4 md:p-4">
<Timetable timetableData={timetableData} />
<div className="flex flex-col gap-4 px-4">
{courses && courses.map((course, index) => (
<div key={index} className="flex flex-row gap-4 items-center">
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: timetableColors['tsinghuarian'][index] }}></div>
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: timetableColors[timetableTheme][index] }}></div>
<div className="flex flex-col flex-1">
<span className="text-sm">{course.name_zh}</span>
<span className="text-xs">{course.name_en}</span>
Expand Down
37 changes: 37 additions & 0 deletions src/components/Alerts/ThemeChangableAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import useDictionary from "@/dictionaries/useDictionary";
import { Alert, Button, IconButton } from "@mui/joy"
import Link from "next/link";
import React from "react";
import { Info, X } from "react-feather"
import { useLocalStorage } from "usehooks-ts"

const ThemeChangableAlert = () => {
const [open, setOpen] = useLocalStorage('theme_changable_alert', true);
const dict = useDictionary();

if(!open) return <></>;

return <Alert
variant="outlined"
color="success"
startDecorator={
<Info/>
}
endDecorator={
<React.Fragment>
<Link href="/settings">
<Button variant="plain" color="success" sx={{ mr: 1 }} onClick={() => setOpen(false)}>
{dict.alerts.TimetableCourseList.action}
</Button>
</Link>
<IconButton variant="soft" color="success" onClick={() => setOpen(false)}>
<X />
</IconButton>
</React.Fragment>
}
>
{dict.alerts.TimetableCourseList.text}
</Alert>
}

export default ThemeChangableAlert
6 changes: 4 additions & 2 deletions src/components/Timetable/TimetableCourseList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, ButtonGroup, DialogContent, DialogTitle, IconButton, Input, ModalClose, ModalDialog } from '@mui/joy';
import { Alert, Button, ButtonGroup, DialogContent, DialogTitle, IconButton, Input, ModalClose, ModalDialog } from '@mui/joy';
import { Calendar, Download, EyeOff, Image, Mail, Search, Share, Trash } from 'react-feather';
import { QRCodeSVG } from 'qrcode.react';
import { useSettings } from '@/hooks/contexts/settings';
Expand All @@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation';
import { useModal } from '@/hooks/contexts/useModal';
import CourseSearchbar from './CourseSearchbar';
import { timetableColors } from '@/helpers/timetable';
import ThemeChangableAlert from '../Alerts/ThemeChangableAlert';

const TimetableCourseList = () => {
const { courses, setCourses } = useSettings();
Expand Down Expand Up @@ -54,7 +55,7 @@ const TimetableCourseList = () => {
<Button
component="a"
// Subject: Here is My Timetable, Body: My Timetable can be found on NTHUMODS at {shareLink}
href={`mailto:?subject=Here is My Timetable&body=<div style='padding: 0;'>My Timetable can be found on NTHUMODS at <${shareLink}></div>`}
href={`mailto:?subject=Here is My Timetable&body=My Timetable can be found on NTHUMODS at ${shareLink}`}
target='_blank'
variant="outlined"
startDecorator={<Mail className="w-4 h-4" />}
Expand Down Expand Up @@ -138,6 +139,7 @@ const TimetableCourseList = () => {
</div>
</div>
)}
<ThemeChangableAlert />
<div className="grid grid-cols-2 grid-rows-2 gap-2">
<Button variant="outlined" startDecorator={<Download className="w-4 h-4" />} onClick={handleDownloadDialog}>Download</Button>
<Button variant="outlined" startDecorator={<Share className="w-4 h-4" />} onClick={handleShowShareDialog}>Share/Sync</Button>
Expand Down
10 changes: 10 additions & 0 deletions src/dictionaries/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,19 @@
"language": {
"title": "Language",
"description": "Select your preferred language."
},
"theme": {
"title": "Theme",
"description": "Personalize your timetable to make it yours."
}
},
"venues": {
"placeholder": "Select a Venue from the list"
},
"alerts": {
"TimetableCourseList": {
"action": "Go Settings",
"text": "You can change your theme in the settings page!"
}
}
}
10 changes: 10 additions & 0 deletions src/dictionaries/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,19 @@
"language": {
"title": "語言 Language",
"description": "選擇您想要的語言。"
},
"theme": {
"title": "時間表顔色",
"description": "客制化你的時間表的顔色,讓你的時間表更有個性!"
}
},
"venues": {
"placeholder": "Select a Venue from the list"
},
"alerts": {
"TimetableCourseList": {
"action": "Go Settings",
"text": "You can change your theme in the settings page!"
}
}
}
114 changes: 111 additions & 3 deletions src/helpers/timetable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,115 @@ import { CourseDefinition } from "@/config/supabase";
import { scheduleTimeSlots } from "@/const/timetable";
import { CourseTimeslotData } from "@/types/timetable";

export const timetableColors = {
export const timetableColors: { [theme: string]: string[] } = {
'harmonyBlossom': [
"#855EBE", // Orchid Whisper
"#D65DB1", // Pink Dahlia
"#FF6F91", // Blossom Pink
"#FF9573", // Coral Charm
"#FFC85B", // Sunlit Petal
"#A9BC5D", // Meadow Glow
"#5DA671", // Fresh Fern
"#22907B", // Turquoise Dream
"#1C6873", // Deep Lagoon
"#2F4959", // Midnight Sapphire
],
'autumnSunset': [
"#844C2A", // Maple Brown
"#D65631", // Fiery Pumpkin
"#FF7F3D", // Sunset Blaze
"#FFA354", // Harvest Gold
"#FFCE6C", // Amber Glow
"#A8B964", // Olive Grove
"#5C9053", // Forest Green
"#207A69", // Teal Twilight
"#1C6472", // Twilight Blue
"#2F4A59", // Dusk Navy
],
'oceanBreeze': [
"#2A758C", // Deep Sea Blue
"#316ED6", // Azure Waters
"#3D88FF", // Oceanic Blue
"#54A0FF", // Calm Surf
"#6CBEFF", // Gentle Breeze
"#64B9A8", // Coastal Green
"#53A061", // Seafoam Delight
"#427F27", // Emerald Shore
"#355A22", // Seaside Moss
"#2A472F", // Midnight Tide
],
'springMeadow': [
"#688456", // Fresh Grass
"#6ED663", // Zesty Lime
"#80FF70", // Spring Green
"#A2FF8E", // Vibrant Meadow
"#C9FFAB", // Lush Pasture
"#B3C35E", // Sunny Field
"#94A72F", // Meadowland
"#7B8E1E", // Golden Sun
"#68721A", // Honeydew Gold
"#4A5A2F", // Mossy Path
],
'sunsetWarmth': [
"#8C5F2A", // Amber Haze
"#D68431", // Tangerine Sunset
"#FF9B3D", // Warm Embrace
"#FFB854", // Sunlit Copper
"#FFD06C", // Golden Radiance
"#BCAB60", // Amber Fields
"#7E762F", // Sunflower Gold
"#55782D", // Olive Harvest
"#48671A", // Burnished Bronze
"#3C4D2F", // Rustic Ember
],
'roseGarden': [
"#8C5E61", // Dusty Rose
"#D65D75", // Rose Petal
"#FF6F8F", // Blush Pink
"#FF969D", // Rosy Cheeks
"#FFC76C", // Sun-kissed Rose
"#B99D61", // Antique Rose
"#7E625E", // Mauve Dream
"#8E4C75", // Orchid Mist
"#7A3673", // Violet Haze
"#5D2D59", // Midnight Rose
],
'sapphireTwilight': [
"#59618C", // Twilight Blue
"#734DD6", // Sapphire Gem
"#9181FF", // Evening Sky
"#A89EFF", // Starry Horizon
"#C6C5FF", // Lavender Dusk
"#B5B4A8", // Misty Lake
"#7F8F61", // Enchanted Forest
"#4E7627", // Emerald Moon
"#2F712A", // Pine Grove
"#292F47", // Midnight Sapphire
],
'goldenHarbor': [
"#8C752A", // Sunlit Sand
"#D68F31", // Golden Shores
"#FFA63D", // Amber Harbor
"#FFBC54", // Glistening Gold
"#FFD36C", // Radiant Bay
"#B8A461", // Coastal Pebble
"#8E842F", // Lighthouse Glow
"#6D7427", // Warm Marina
"#6F591A", // Sunlit Deck
"#4F432F", // Nautical Bronze
],
'plumElegance': [
"#8C5E81", // Plum Charm
"#D65D96", // Lavender Mist
"#FF6FAA", // Radiant Orchid
"#FF96B9", // Velvet Blossom
"#FFC76C", // Golden Plum
"#B88761", // Peach Blossom
"#8E5C6E", // Mauve Serenity
"#753B8E", // Twilight Rose
"#6B1A87", // Royal Amethyst
"#4F2D4A", // Midnight Plum
],
'pastelColors': [
"#ffffed", // lavender
"#fff5ee", // seashell
Expand Down Expand Up @@ -30,7 +138,7 @@ export const timetableColors = {
}


export const createTimetableFromCourses = (data: CourseDefinition[]) => {
export const createTimetableFromCourses = (data: CourseDefinition[], theme = 'tsinghuarian') => {
const newTimetableData: CourseTimeslotData[] = [];
data!.forEach(course => {
//get unique days first
Expand All @@ -46,7 +154,7 @@ export const createTimetableFromCourses = (data: CourseDefinition[]) => {
const startTime = Math.min(...times);
const endTime = Math.max(...times);
//get the color
const color = timetableColors['tsinghuarian'][data!.indexOf(course)];
const color = timetableColors[theme][data!.indexOf(course)];
//push to scheduleData
newTimetableData.push({
course: course,
Expand Down
10 changes: 8 additions & 2 deletions src/hooks/contexts/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { FC, PropsWithChildren, createContext, useContext, useEffect, useMemo, u
import { useLocalStorage } from 'usehooks-ts';
import { useCookies } from 'react-cookie';
import { Language, SettingsType } from "@/types/settings";
import type { timetableColors } from "@/helpers/timetable";

const settingsContext = createContext<ReturnType<typeof useSettingsProvider>>({
language: "zh",
darkMode: false,
courses: [],
timetableTheme: "tsinghuarian",
setSettings: () => {},
setCourses: () => {},
setLanguage: () => {},
setDarkMode: () => {}
setDarkMode: () => {},
setTimetableTheme: () => {}
});

const useSettingsProvider = () => {
Expand All @@ -21,6 +24,7 @@ const useSettingsProvider = () => {
const pathname = usePathname();
const [cookies, setCookie, removeCookie] = useCookies(['theme']);
const [courses, setCourses] = useLocalStorage<string[]>("semester_1121", []);
const [timetableTheme, setTimetableTheme] = useLocalStorage<string>("timetable_theme", "tsinghuarian");

const setSettings = (settings: SettingsType) => {
setCourses(settings.courses);
Expand Down Expand Up @@ -61,10 +65,12 @@ const useSettingsProvider = () => {
language,
darkMode,
courses,
timetableTheme,
setSettings,
setCourses,
setLanguage,
setDarkMode
setDarkMode,
setTimetableTheme
};
}

Expand Down
Loading

0 comments on commit 9c416c4

Please sign in to comment.