diff --git a/components/Header.tsx b/components/Header.tsx index 84c1645..0d7d87c 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -17,6 +17,7 @@ import { Badge, useToast, } from "@chakra-ui/react" +import Head from "next/head" import { EditIcon, Icon, InfoIcon, SettingsIcon } from "@chakra-ui/icons" import { FaShareSquare } from "react-icons/fa" import { useAppContext } from "../src/SqrlContext" @@ -83,11 +84,11 @@ const Header = ({ setSidebarOpen }: { setSidebarOpen: any }) => { const router = useRouter() - const { allowedToEdit, updateName } = useTimetable({ + const { allowedToEdit } = useTimetable({ id: (router.query.id as string) || "", }) - const { name } = useSections() + const { name, updateName } = useSections() const keydownListener = useCallback( (e: KeyboardEvent) => { @@ -162,6 +163,12 @@ const Header = ({ setSidebarOpen }: { setSidebarOpen: any }) => { return ( + + Sqrl Planner | {name} + + + + diff --git a/pages/index.tsx b/pages/index.tsx index 91b8c01..d21c6bc 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -38,6 +38,31 @@ import { useTranslation } from "next-i18next" import AboutModal from "../components/AboutModal" import Layout from "../components/Layout" import { FaMoon, FaSun } from "react-icons/fa" +import { IconType } from "react-icons" +import { theme } from "./_app" + +/** + * Darkens a color by a percentage + * @param hexColor A hex color with a hashtag, like #ABCDEF + * @param percentage A positive real number + * Values from 0-1 make the color darker + * A value of 1 keeps the color the same + * Values greater than 1 make the color brighter, up to FFFFFF + */ +function changeColor(hexColor: string, percentage: number): string { + const changeChannel = (twoDigitHex: string): string => { + const base10Number = Math.floor(parseInt(twoDigitHex, 16) * percentage) + const constrained = Math.min(Math.max(0, base10Number), 255) // Put in [0, 255] range + const stringRep = constrained.toString(16) + return stringRep.length === 1 ? "0" + stringRep : stringRep + } + + const red = changeChannel(hexColor.slice(1, 3)) + const green = changeChannel(hexColor.slice(3, 5)) + const blue = changeChannel(hexColor.slice(5, 7)) + + return "#" + red + green + blue +} const Dashboard = () => { const router = useRouter() @@ -167,6 +192,29 @@ const Dashboard = () => { const { t } = useTranslation("index") + // Differences in the UI between light and dark mode + // Mostly for icons/hover + interface ModeConfig { + tLabel: string + icon: IconType + iconId: string + hoverFactor: number + } + const modeConfig: ModeConfig = { + light: { + tLabel: "light-mode", + icon: FaSun, + iconId: "sun-icon", + hoverFactor: 0.9, + }, + dark: { + tLabel: "dark-mode", + icon: FaMoon, + iconId: "moon-icon", + hoverFactor: 1.1, + }, + }[colorMode] + return ( @@ -286,9 +334,16 @@ const Dashboard = () => { px={8} minH="2xs" fontWeight="medium" + transition="background 75ms linear" position="relative" opacity={!!pageLoading ? "0.5" : "1"} cursor={!!pageLoading ? "not-allowed" : "pointer"} + _hover={{ + background: changeColor( + theme.colors.blue[600], + modeConfig.hoverFactor + ), // blue[700] doesn't work here + }} onClick={() => { if (pageLoading) return setPageLoading(id) @@ -382,37 +437,20 @@ const Dashboard = () => { {t("common:about")} - {colorMode === "light" ? ( - - - - {" "} - {t("dark-mode")} - - ) : ( - - - - {" "} - {t("light-mode")} - - )} + + + + {" "} + {t(modeConfig.tLabel)} + diff --git a/pages/timetable/[id].tsx b/pages/timetable/[id].tsx index 7e1a452..9f1a2d1 100644 --- a/pages/timetable/[id].tsx +++ b/pages/timetable/[id].tsx @@ -1,8 +1,7 @@ import { ApolloClient, InMemoryCache } from "@apollo/client" import type { NextPage } from "next" import { serverSideTranslations } from "next-i18next/serverSideTranslations" -import Head from "next/head" -import React, { useEffect, useState } from "react" +import React from "react" import Layout from "../../components/Layout" import { GET_TIMETABLE_BY_ID } from "../../operations/queries/getTimetableById" import { apolloClientParams } from "../../src/apollo-client" @@ -12,26 +11,14 @@ import { AppContextProvider } from "../../src/SqrlContext" import { SectionsProvider } from "../../src/useSections" export const TimetableView: NextPage = (props: any) => { - const [sharePrefix, setSharePrefix] = useState("") - - useEffect(() => { - setSharePrefix( - `${window.location.protocol}//${window.location.host}/timetable/` - ) - }, []) - return ( - - Sqrl Planner | {props.name} - - - - - + + + diff --git a/src/useSections.tsx b/src/useSections.tsx index 53c30f7..919f0a3 100644 --- a/src/useSections.tsx +++ b/src/useSections.tsx @@ -4,6 +4,8 @@ import { createContext, useContext, useEffect, useState } from "react" import { REMOVE_COURSE_TIMETABLE } from "../operations/mutations/removeCourseTimetable" import { SET_SECTIONS_TIMETABLE } from "../operations/mutations/setSectionsTimetable" import { GET_TIMETABLE_BY_ID } from "../operations/queries/getTimetableById" +import { SET_TIMETABLE_NAME } from "../operations/mutations/setTimetableName" + import useTimetable from "./useTimetable" import { constructSectionsFromMeetings, @@ -24,6 +26,7 @@ const SectionsContext = createContext< | { sections: { [key: string]: Array } name: string + updateName: Function setSections: Function removeCourse: Function } @@ -71,7 +74,7 @@ export const SectionsProvider = ({ }, [data, id]) const [setSectionsTimetable] = useMutation(SET_SECTIONS_TIMETABLE) - + const [setTimetableName] = useMutation(SET_TIMETABLE_NAME) const [removeCourse] = useMutation(REMOVE_COURSE_TIMETABLE) const removeTimetableCourse = ({ courseId }: RemoveTimetableCourseProps) => { @@ -115,11 +118,38 @@ export const SectionsProvider = ({ }) } + const updateName = (name: string, cb?: Function) => { + if (!key) return + if (!id) return + + // Set the name in the client (contexts) + setName(name) + + // Set the new name in LS (persistence) + setTimetableName({ + variables: { id, key, name }, + onCompleted: (data) => { + const prevLsTimetablesJSON = localStorage.getItem("timetables") + let prevLsTimetables: { [key: string]: { key: string; name: string } } = + {} + if (prevLsTimetablesJSON) + prevLsTimetables = JSON.parse(prevLsTimetablesJSON) + + prevLsTimetables[id].name = data.setTimetableName.timetable.name + + localStorage.setItem("timetables", JSON.stringify(prevLsTimetables)) + + if (cb) cb() + }, + }) + } + return ( { } }, [allowedToEdit]) - const [setTimetableName] = useMutation(SET_TIMETABLE_NAME) - - const updateName = (name: string, cb?: Function) => { - if (!key) return - if (!id) return - - setTimetableName({ - variables: { id, key, name }, - onCompleted: (data) => { - const prevLsTimetablesJSON = localStorage.getItem("timetables") - let prevLsTimetables: { [key: string]: { key: string; name: string } } = - {} - if (prevLsTimetablesJSON) - prevLsTimetables = JSON.parse(prevLsTimetablesJSON) - - prevLsTimetables[id].name = data.setTimetableName.timetable.name - - localStorage.setItem("timetables", JSON.stringify(prevLsTimetables)) - - if (cb) cb() - }, - }) - } - - return { allowedToEdit, updateName, key } + return { allowedToEdit, key } } export default useTimetable