diff --git a/src/features/time/Clock.jsx b/src/features/time/Clock.jsx index ac675ca2..42f02376 100644 --- a/src/features/time/Clock.jsx +++ b/src/features/time/Clock.jsx @@ -1,177 +1,55 @@ -import { PureComponent } from 'react'; - -import { convertTimezone } from 'utils/date'; +import { useState, useEffect } from 'react'; import { AnalogClock } from './components/AnalogClock'; import { VerticalClock } from './components/VerticalClock'; +import { DigitalClock } from './components/DigitalClock'; +import { PercentageClock } from './components/PercentageClock'; import EventBus from 'utils/eventbus'; import defaults from './options/default'; +import 'react-clock/dist/Clock.css'; import './clock.scss'; -export default class Clock extends PureComponent { - constructor() { - super(); +const Clock = () => { + const [timeType, setTimeType] = useState(localStorage.getItem('timeType') || defaults.timeType); - this.timer = undefined; - this.state = { - timeType: localStorage.getItem('timeType'), - time: '', - finalHour: '', - finalMinute: '', - finalSeconds: '', - ampm: '', - nowGlobal: new Date(), - }; - } - - startTime( - time = localStorage.getItem('seconds') === 'true' || - localStorage.getItem('timeType') === 'analogue' - ? 1000 - (Date.now() % 1000) - : 60000 - (Date.now() % 60000), - ) { - this.timer = setTimeout(() => { - let now = new Date(); - const timezone = localStorage.getItem('timezone'); - if (timezone && timezone !== 'auto') { - now = convertTimezone(now, timezone); - } - - switch (localStorage.getItem('timeType')) { - case 'percentageComplete': - this.setState({ - time: (now.getHours() / 24).toFixed(2).replace('0.', '') + '%', - ampm: '', - }); - break; - case 'analogue': - // load analog clock css - import('react-clock/dist/Clock.css'); - - this.setState({ - time: now, - }); - break; - default: - // Default clock - let time, - sec = ''; - const zero = localStorage.getItem('zero'); - - if (localStorage.getItem('seconds') === 'true') { - sec = `:${('00' + now.getSeconds()).slice(-2)}`; - this.setState({ finalSeconds: `${('00' + now.getSeconds()).slice(-2)}` }); - } - - if (localStorage.getItem('timeformat') === 'twentyfourhour') { - if (zero === 'false') { - time = `${now.getHours()}:${('00' + now.getMinutes()).slice(-2)}:${sec}`; - this.setState({ - finalHour: `${now.getHours()}`, - finalMinute: `${('00' + now.getMinutes()).slice(-2)}`, - }); - } else { - time = `${('00' + now.getHours()).slice(-2)}:${('00' + now.getMinutes()).slice( - -2, - )}${sec}`; - this.setState({ - finalHour: `${('00' + now.getHours()).slice(-2)}`, - finalMinute: `${('00' + now.getMinutes()).slice(-2)}`, - }); - } - - this.setState({ - time, - ampm: '', - }); - } else { - // 12 hour - let hours = now.getHours(); - - if (hours > 12) { - hours -= 12; - } else if (hours === 0) { - hours = 12; - } - - if (zero === 'false') { - time = `${hours}:${('00' + now.getMinutes()).slice(-2)}${sec}`; - this.setState({ - finalHour: `${hours}`, - finalMinute: `${('00' + now.getMinutes()).slice(-2)}`, - }); - } else { - time = `${('00' + hours).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`; - this.setState({ - finalHour: `${('00' + hours).slice(-2)}`, - finalMinute: `${('00' + now.getMinutes()).slice(-2)}`, - }); - } - - this.setState({ - time, - ampm: now.getHours() > 11 ? 'PM' : 'AM', - }); - } - break; - } - - this.startTime(); - }, time); - } - - componentDidMount() { - EventBus.on('refresh', (data) => { + useEffect(() => { + const handleRefresh = (data) => { if (data === 'clock' || data === 'timezone') { const element = document.querySelector('.clock-container'); - if (localStorage.getItem('time') === 'false') { - return (element.style.display = 'none'); + element.style.display = 'none'; + return; } - this.timer = null; - this.startTime(0); - element.style.display = 'block'; - element.style.fontSize = `${ - 4 * Number((localStorage.getItem('zoomClock') || defaults.zoomClock) / 100) - }em`; + setTimeType(localStorage.getItem('timeType')); + + // Handle zoom updates + const zoomClock = localStorage.getItem('zoomClock') || defaults.zoomClock; + if (localStorage.getItem('timeType') !== 'analogue') { + element.style.fontSize = `${4 * Number(zoomClock / 100)}em`; + } } - }); + }; - if (localStorage.getItem('timeType') !== 'analogue') { - document.querySelector('.clock-container').style.fontSize = `${ - 4 * Number((localStorage.getItem('zoomClock') || defaults.zoomClock) / 100) - }em`; + EventBus.on('refresh', handleRefresh); + return () => EventBus.off('refresh', handleRefresh); + }, []); + + const renderClock = () => { + switch (timeType) { + case 'analogue': + return ; + case 'verticalClock': + return ; + case 'percentageComplete': + return ; + default: + return ; } + }; - this.startTime(0); - } + return renderClock(); +}; - componentWillUnmount() { - EventBus.off('refresh'); - } - - render() { - if (localStorage.getItem('timeType') === 'analogue') { - return ; - } - - if (localStorage.getItem('timeType') === 'verticalClock') { - return ( - - ); - } - - return ( - - {this.state.time} - {this.state.ampm} - - ); - } -} +export default Clock; diff --git a/src/features/time/components/AnalogClock.jsx b/src/features/time/components/AnalogClock.jsx index 4b819a60..f8042483 100644 --- a/src/features/time/components/AnalogClock.jsx +++ b/src/features/time/components/AnalogClock.jsx @@ -1,20 +1,37 @@ -import { Suspense, lazy } from 'react'; +import { useState, Suspense, lazy } from 'react'; +import { useTimeUpdate } from '../hooks/useTimeUpdate'; +import { useClockZoom } from '../hooks/useClockZoom'; +import { convertTimezone } from 'utils/date'; import defaults from '../options/default'; -const Analog = lazy(() => import('react-clock')); +const Clock = lazy(() => import('react-clock')); -function AnalogClock({ time }) { - const enabled = (setting) => { - return localStorage.getItem(setting) === 'true'; +export const AnalogClock = () => { + const [time, setTime] = useState(new Date()); + const { elementRef, updateZoom } = useClockZoom(); + + const updateTime = () => { + let now = new Date(); + const timezone = localStorage.getItem('timezone'); + + if (timezone && timezone !== 'auto') { + now = convertTimezone(now, timezone); + } + setTime(now); }; + useTimeUpdate(updateTime); + + const enabled = (setting) => localStorage.getItem(setting) === 'true'; + const zoomClock = Number(localStorage.getItem('zoomClock') || defaults.zoomClock); + return ( }> -
- + ); -} - -export { AnalogClock as default, AnalogClock }; +}; diff --git a/src/features/time/components/DigitalClock.jsx b/src/features/time/components/DigitalClock.jsx new file mode 100644 index 00000000..e7a6d7be --- /dev/null +++ b/src/features/time/components/DigitalClock.jsx @@ -0,0 +1,55 @@ +import React, { useState } from 'react'; +import { useTimeUpdate } from '../hooks/useTimeUpdate'; +import { useClockZoom } from '../hooks/useClockZoom'; +import { convertTimezone } from 'utils/date'; + +export const DigitalClock = () => { + const [time, setTime] = useState(''); + const [ampm, setAmpm] = useState(''); + const { elementRef } = useClockZoom(); + + const updateTime = () => { + let now = new Date(); + const timezone = localStorage.getItem('timezone'); + const zero = localStorage.getItem('zero'); + const showSeconds = localStorage.getItem('seconds') === 'true'; + + if (timezone && timezone !== 'auto') { + now = convertTimezone(now, timezone); + } + + let timeStr = ''; + let hours = now.getHours(); + const minutes = ('00' + now.getMinutes()).slice(-2); + const seconds = showSeconds ? `:${('00' + now.getSeconds()).slice(-2)}` : ''; + + if (localStorage.getItem('timeformat') === 'twentyfourhour') { + timeStr = + zero === 'false' + ? `${hours}:${minutes}${seconds}` + : `${('00' + hours).slice(-2)}:${minutes}${seconds}`; + setAmpm(''); + } else { + if (hours > 12) { + hours -= 12; + } else if (hours === 0) { + hours = 12; + } + timeStr = + zero === 'false' + ? `${hours}:${minutes}${seconds}` + : `${('00' + hours).slice(-2)}:${minutes}${seconds}`; + setAmpm(now.getHours() > 11 ? 'PM' : 'AM'); + } + setTime(timeStr); + }; + + useTimeUpdate(updateTime); + + return ( + + {time} + {ampm} + + ); +}; diff --git a/src/features/time/components/PercentageClock.jsx b/src/features/time/components/PercentageClock.jsx new file mode 100644 index 00000000..45c87e67 --- /dev/null +++ b/src/features/time/components/PercentageClock.jsx @@ -0,0 +1,22 @@ +import { useState, useEffect } from 'react'; +import { useTimeUpdate } from '../hooks/useTimeUpdate'; +import { convertTimezone } from 'utils/date'; + +export const PercentageClock = () => { + const [percentage, setPercentage] = useState(''); + + const updateTime = () => { + let now = new Date(); + const timezone = localStorage.getItem('timezone'); + + if (timezone && timezone !== 'auto') { + now = convertTimezone(now, timezone); + } + + setPercentage((now.getHours() / 24).toFixed(2).replace('0.', '') + '%'); + }; + + useTimeUpdate(updateTime); + + return {percentage}; +}; diff --git a/src/features/time/components/VerticalClock.jsx b/src/features/time/components/VerticalClock.jsx index 84c10ea9..e1c68c5c 100644 --- a/src/features/time/components/VerticalClock.jsx +++ b/src/features/time/components/VerticalClock.jsx @@ -1,23 +1,62 @@ +import React, { useState } from 'react'; +import { useTimeUpdate } from '../hooks/useTimeUpdate'; +import { useClockZoom } from '../hooks/useClockZoom'; +import { convertTimezone } from 'utils/date'; import defaults from '../options/default'; -function VerticalClock({ finalHour, finalMinute, finalSeconds }) { - const hourColour = localStorage.getItem('hourColour') || defaults.time.hourColour; - const minuteColour = localStorage.getItem('minuteColour') || defaults.time.minuteColour; - const secondColour = localStorage.getItem('secondColour') || defaults.time.secondColour; +export const VerticalClock = () => { + const [hour, setHour] = useState(''); + const [minute, setMinute] = useState(''); + const [seconds, setSeconds] = useState(''); + const { elementRef, updateZoom } = useClockZoom(); + + const updateTime = () => { + let now = new Date(); + const timezone = localStorage.getItem('timezone'); + const zero = localStorage.getItem('zero'); + const showSeconds = localStorage.getItem('seconds') === 'true'; + + if (timezone && timezone !== 'auto') { + now = convertTimezone(now, timezone); + } + + let hours = now.getHours(); + const minutes = ('00' + now.getMinutes()).slice(-2); + + if (localStorage.getItem('timeformat') !== 'twentyfourhour') { + if (hours > 12) { + hours -= 12; + } else if (hours === 0) { + hours = 12; + } + } + + setHour(zero === 'false' ? `${hours}` : ('00' + hours).slice(-2)); + setMinute(minutes); + if (showSeconds) { + setSeconds(('00' + now.getSeconds()).slice(-2)); + } + }; + + useTimeUpdate(updateTime); + + const hourColour = localStorage.getItem('hourColour') || defaults.hourColour; + const minuteColour = localStorage.getItem('minuteColour') || defaults.minuteColour; + const secondColour = localStorage.getItem('secondColour') || defaults.secondColour; return ( - +
- {finalHour} + {hour}
- {finalMinute} -
-
- {finalSeconds} + {minute}
+ {localStorage.getItem('seconds') === 'true' && ( +
+ {seconds} +
+ )}
); -} - -export { VerticalClock as default, VerticalClock }; +}; diff --git a/src/features/time/hooks/useClockZoom.js b/src/features/time/hooks/useClockZoom.js new file mode 100644 index 00000000..1f25db83 --- /dev/null +++ b/src/features/time/hooks/useClockZoom.js @@ -0,0 +1,19 @@ +import { useEffect, useRef } from 'react'; +import defaults from '../options/default'; + +export const useClockZoom = () => { + const elementRef = useRef(null); + + const updateZoom = () => { + if (elementRef.current) { + const zoomClock = localStorage.getItem('zoomClock') || defaults.zoomClock; + elementRef.current.style.fontSize = `${4 * Number(zoomClock / 100)}em`; + } + }; + + useEffect(() => { + updateZoom(); + }, []); + + return { elementRef, updateZoom }; +}; diff --git a/src/features/time/hooks/useTimeUpdate.js b/src/features/time/hooks/useTimeUpdate.js new file mode 100644 index 00000000..997e934b --- /dev/null +++ b/src/features/time/hooks/useTimeUpdate.js @@ -0,0 +1,28 @@ +import { useEffect, useRef } from 'react'; + +export const useTimeUpdate = (updateFn) => { + const timerRef = useRef(null); + + useEffect(() => { + const startTime = (initialDelay = null) => { + const showSeconds = localStorage.getItem('seconds') === 'true'; + const delay = + initialDelay ?? (showSeconds ? 1000 - (Date.now() % 1000) : 60000 - (Date.now() % 60000)); + + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + updateFn(); + timerRef.current = setTimeout(() => startTime(), delay); + }; + + startTime(0); + + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + }; + }, [updateFn]); +}; diff --git a/src/features/time/options/ClockPreview.jsx b/src/features/time/options/ClockPreview.jsx new file mode 100644 index 00000000..05c23a0e --- /dev/null +++ b/src/features/time/options/ClockPreview.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import Clock from 'features/time/Clock'; + +const ClockPreview = ({ zoomLevel = 100 }) => { + return ( +
+ +
+ ); +}; + +export { ClockPreview, ClockPreview as default }; diff --git a/src/features/time/options/TimeOptions.jsx b/src/features/time/options/TimeOptions.jsx index f74d6a4f..2a8a99b4 100644 --- a/src/features/time/options/TimeOptions.jsx +++ b/src/features/time/options/TimeOptions.jsx @@ -4,8 +4,7 @@ import React, { useState } from 'react'; import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings'; import { Checkbox, Dropdown, Radio } from 'components/Form/Settings'; import { Hero, Preview, Controls } from 'components/Layout/Settings/Hero'; -import Clock from 'features/time/Clock'; - +import { ClockPreview } from './ClockPreview'; import { MdRefresh } from 'react-icons/md'; import defaults from './default'; @@ -251,7 +250,7 @@ const TimeOptions = () => { />*/} - +