diff --git a/love/src/components/GeneralPurpose/DigitalClock/DigitalClock.jsx b/love/src/components/GeneralPurpose/DigitalClock/DigitalClock.jsx index be928aa39..5b873423c 100644 --- a/love/src/components/GeneralPurpose/DigitalClock/DigitalClock.jsx +++ b/love/src/components/GeneralPurpose/DigitalClock/DigitalClock.jsx @@ -21,15 +21,15 @@ DigitalClock.defaultProps = { } export default function DigitalClock ({ timestamp, hideDate }) { - const t = parseTimestamp(timestamp); + const t = timestamp === 0 ? 0 : parseTimestamp(timestamp); return (
- { t.toFormat('HH:mm:ss') } + { t ? t.toFormat('HH:mm:ss') : '--:--:--' }
{ !hideDate && (
- { t.toFormat('EEE, MMM dd yyyy') } + {t ? t.toFormat('EEE, MMM dd yyyy') : '---' }
)}
diff --git a/love/src/components/Layout/Layout.jsx b/love/src/components/Layout/Layout.jsx index ffdb98970..4673eb6cc 100644 --- a/love/src/components/Layout/Layout.jsx +++ b/love/src/components/Layout/Layout.jsx @@ -15,7 +15,7 @@ import MenuIcon from '../icons/MenuIcon/MenuIcon'; import HeartbeatIcon from '../icons/HeartbeatIcon/HeartbeatIcon'; import NotchCurve from './NotchCurve/NotchCurve'; import EditIcon from '../icons/EditIcon/EditIcon'; -import Clock from '../Time/Clock/Clock'; +import ClockContainer from '../Time/Clock/Clock.container'; import styles from './Layout.module.css'; import LabeledStatusTextContainer from '../GeneralPurpose/LabeledStatusText/LabeledStatusText.container'; import { HEARTBEAT_COMPONENTS } from '../../Config'; @@ -351,7 +351,7 @@ class Layout extends Component { this.props.mode === modes.EDIT && !this.state.toolbarOverflow ? styles.hidden : '', ].join(' ')} > - + diff --git a/love/src/components/Time/Clock/Clock.container.jsx b/love/src/components/Time/Clock/Clock.container.jsx index f344d6013..d23198a4a 100644 --- a/love/src/components/Time/Clock/Clock.container.jsx +++ b/love/src/components/Time/Clock/Clock.container.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { getTimeData } from '../../../redux/selectors'; +import { getClock } from '../../../redux/selectors'; import Clock from './Clock'; export const schema = { @@ -62,11 +62,12 @@ export const schema = { type: 'string', description: `Timezone string used to configure which UTC offset to use. - Null or empty if current should be used. 'UTC' for UTC. Null by default. + 'local' if current should be used. 'local' by default. The format for the timezone string can be a fixed string (for UTC or TAI); a fixed-offset string (e.g. UTC+5); or a location string in the format / (use camelcase with underscores instead of spaces, like America/New_York). For example: + - For local time use local - For UTC use UTC - For TAI use TAI - For Greenwich Sidereal Time use sidereal-greenwich @@ -78,7 +79,7 @@ export const schema = { Note that not every city is available, check the IANA DB documentation for more info: https://www.iana.org/time-zones See the default value as an example`, isPrivate: false, - default: null, + default: 'local', }, }, }; @@ -92,8 +93,8 @@ const ClockContainer = ({ ...props }) => { }; const mapStateToProps = (state) => { - const timeData = getTimeData(state); - return { timeData }; + const clock = getClock(state); + return { clock }; }; const mapDispatchToProps = (dispatch) => { diff --git a/love/src/components/Time/Clock/Clock.jsx b/love/src/components/Time/Clock/Clock.jsx index 5558df4b7..c0298c065 100644 --- a/love/src/components/Time/Clock/Clock.jsx +++ b/love/src/components/Time/Clock/Clock.jsx @@ -3,39 +3,11 @@ import PropTypes from 'prop-types'; import styles from './Clock.module.css'; import AnalogClock from '../../GeneralPurpose/AnalogClock/AnalogClock'; import DigitalClock from '../../GeneralPurpose/DigitalClock/DigitalClock'; -import { DateTime } from 'luxon'; -import { siderealSecond } from '../../../Utils'; - -/** - * Component that displays time, date and an analog clock, with options to display only some of those elements. - */ -// Clock.propTypes = { -// /** Optional name to display above the clock */ -// name: PropTypes.string, -// /** Date-able object or float, if float it must be in milliseconds */ -// timestamp: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), -// /** Flag to hide or not the analog clock, false by default */ -// hideAnalog: PropTypes.bool, -// /** Flag to hide or not the date, false by default */ -// hideDate: PropTypes.bool, -// /** Flag to hide or not the UTC offset besides the name, false by default */ -// hideOffset: PropTypes.bool, -// } - -// Clock.defaultProps = { -// name: null, -// timestamp: DateTime.local(), -// hideDate: false, -// hideAnalog: false, -// hideOffset: false, -// } export default class Clock extends React.Component { static propTypes = { /** Optional name to display above the clock */ name: PropTypes.string, - /** Date-able object or float, if float it must be in milliseconds. If null, an internal clock will be defined by the component*/ - timestamp: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), /** Flag to hide or not the analog clock, false by default */ hideAnalog: PropTypes.bool, /** Flag to hide or not the date, false by default */ @@ -45,9 +17,10 @@ export default class Clock extends React.Component { /** Locale string used to configure how to display the UTC Offset. en-GB by default (so it is displayed as GMT always). * Null or empty to use the browser locale */ locale: PropTypes.string, - /** The timezone to display the timestamps. Null or empty if current should be used 'UTC' for UTC. Null by default. + /** The timezone to display the timestamps. 'local' if current should be used. 'local' by default. * The format for this string must be: -. * For example: + * - For local time use local * - For UTC use UTC * - For TAI use TAI * - For 'La Serena' use 'America/Santiago' (yes America, not Chile) @@ -55,93 +28,76 @@ export default class Clock extends React.Component { * - For 'Illinois' use 'America/Chicago' */ timezone: PropTypes.string, - /** Time Data from the server */ - timeData: PropTypes.object, + /** + * Current time clocks from the server in the following format: + * { + utc: , + tai: , + mjd: , + sidereal_summit: , + sidereal_greenwich: , + } + */ + clock: PropTypes.shape({ + utc: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), + tai: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), + mjd: PropTypes.number, + sidereal_summit: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), + sidereal_greenwich: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), + }), }; static defaultProps = { name: null, - timestamp: null, hideDate: false, hideAnalog: false, hideOffset: false, locale: 'en-GB', timezone: null, - timeData: { - request_time: 0, - receive_time: 0, - server_time: { - utc: 0, - tai: 0, - mjd: 0, - sidereal_summit: 0, - sidereal_greenwhich: 0, - tai_to_utc: 0, - }, - }, - }; - - constructor(props) { - super(props); - this.state = { - timestamp: 0, - }; - } - - componentDidMount() { - if (this.props.timestamp) return; - this.timerID = setInterval(() => this.tick(), 1000); - } - - componentWillUnmount() { - if (this.props.timestamp) return; - clearInterval(this.timerID); - } - - tick() { - const diffLocalUtc = DateTime.utc().toSeconds() - (this.props.timeData.receive_time + this.props.timeData.request_time) / 2; - let timestamp = 0; - if (this.props.timezone === 'sidereal-summit') { - timestamp = DateTime.fromSeconds( - this.props.timeData.server_time.sidereal_summit * 3600 + siderealSecond * diffLocalUtc - ); - } else if (this.props.timezone === 'sidereal-greenwich') { - timestamp = DateTime.fromSeconds( - this.props.timeData.server_time.sidereal_greenwich * 3600 + siderealSecond * diffLocalUtc - ); - } else if (this.props.timezone === 'MJD') { - timestamp = this.props.timeData.server_time.mjd + diffLocalUtc / (3600 * 24); - } else { - timestamp = DateTime.fromSeconds(this.props.timeData.server_time.utc + diffLocalUtc); + clock: { + utc: 0, + tai: 0, + mjd: 0, + sidereal_summit: 0, + sidereal_greenwich: 0, } - if (timestamp !== this.state.timestamp) { - this.setState({ timestamp }); - } - } + }; render() { - let timestamp = this.props.timestamp ? this.props.timestamp : this.state.timestamp; let hideAnalog = this.props.hideAnalog; - let mjd = false; - let offset = timestamp.offsetNameShort; let hideDate = this.props.hideDate; - if (timestamp) { - if (this.props.timezone) { - if (this.props.timezone === 'TAI') { - timestamp = timestamp.setZone('UTC').minus({ seconds: this.props.timeData.server_time.tai_to_utc }); - offset = 'TAI'; - } else if (this.props.timezone === 'MJD') { - hideAnalog = true; - mjd = true; - offset = 'MJD'; - hideDate = true; - } else if (this.props.timezone === 'sidereal-summit' || this.props.timezone === 'sidereal-greenwich') { - timestamp = timestamp.setZone('UTC'); - offset = this.props.timezone === 'sidereal-greenwich' ? 'GAST' : 'Summit-AST'; - hideDate = true; - } else { - timestamp = timestamp.setZone(this.props.timezone); - } + let mjd = false; + let offset = null; + let timestamp = 0; + if (this.props.clock.utc !== 0) { + if (this.props.timezone === 'UTC') { + timestamp = this.props.clock.utc; + offset = 'UTC'; + } + else if (this.props.timezone === 'TAI') { + timestamp = this.props.clock.tai; + offset = 'TAI'; + } + else if (this.props.timezone === 'MJD') { + timestamp = this.props.clock.mjd; + hideAnalog = true; + mjd = true; + offset = 'MJD'; + hideDate = true; + } + else if (this.props.timezone === 'sidereal-summit') { + timestamp = this.props.clock.sidereal_summit; + offset = 'Summit-AST'; + hideDate = true; + } + else if (this.props.timezone === 'sidereal-greenwich') { + timestamp = this.props.clock.sidereal_greenwich; + offset = 'GAST'; + hideDate = true; + } + else { + timestamp = this.props.clock.utc.setZone(this.props.timezone); + offset = timestamp.offsetNameShort; } if (!mjd && this.props.locale) { timestamp = timestamp.setLocale(this.props.locale); diff --git a/love/src/components/Time/TimeDisplay.container.jsx b/love/src/components/Time/TimeDisplay.container.jsx index ac262e6be..bad041469 100644 --- a/love/src/components/Time/TimeDisplay.container.jsx +++ b/love/src/components/Time/TimeDisplay.container.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { getTimeData } from '../../redux/selectors'; +import { getClock } from '../../redux/selectors'; import TimeDisplay from './TimeDisplay'; export const schema = { @@ -34,7 +34,7 @@ export const schema = { isPrivate: false, default: 'en-GB', }, - clocks: { + clocks_layout: { type: 'array', description: `Layout of clocks in JSON format. @@ -45,11 +45,12 @@ export const schema = { 2. hideAnalog: (boolean = false) flag to hide the analog clock. 3. hideDate: (boolean = false) flag to hide the date. 4. hideOffset: (boolean = false) flag to hide the UTC offset, displayed at the right of the name - 5. timezone: timezone string used to configure which UTC offset to use. Null or empty if current should be used. Null by default. + 5. timezone: timezone string used to configure which UTC offset to use. 'local' if current should be used. 'local' by default. The format for the timezone string can be a fixed string (for UTC or TAI); a fixed-offset string (e.g. UTC+5); or a location string in the format / (use camelcase with underscores instead of spaces, like America/New_York) For example: + - For local time use local - For UTC use UTC - For TAI use TAI - For Greenwich Sidereal Time use sidereal-greenwich @@ -68,7 +69,7 @@ export const schema = { hideAnalog: false, hideDate: false, hideOffset: false, - timezone: null, + timezone: 'local', }, { name: 'Sidereal Time', @@ -140,8 +141,8 @@ const TimeDisplayContainer = ({ ...props }) => { }; const mapStateToProps = (state) => { - const timeData = getTimeData(state); - return { timeData }; + const clock = getClock(state); + return { clock }; }; const mapDispatchToProps = (dispatch) => { diff --git a/love/src/components/Time/TimeDisplay.jsx b/love/src/components/Time/TimeDisplay.jsx index e58c5da4a..fcfdd5571 100644 --- a/love/src/components/Time/TimeDisplay.jsx +++ b/love/src/components/Time/TimeDisplay.jsx @@ -2,86 +2,89 @@ import React from 'react'; import PropTypes from 'prop-types'; import Clock from './Clock/Clock'; import styles from './TimeDisplay.module.css'; -import { DateTime } from 'luxon'; -import { siderealSecond } from '../../Utils'; export default class TimeDisplay extends React.Component { static propTypes = { - /** Time Data from the server */ - timeData: PropTypes.object, /** Locale string used to configure how to display the UTC Offset. en-GB by default (so it is displayed as GMT always). * Null or empty to use the browser locale */ locale: PropTypes.string, - clocks: PropTypes.array, + /** + * Current time clocks from the server in the following format: + * { + utc: , + tai: , + mjd: , + sidereal_summit: , + sidereal_greenwich: , + } + */ + clock: PropTypes.shape({ + utc: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), + tai: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), + mjd: PropTypes.number, + sidereal_summit: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), + sidereal_greenwich: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), + }), + /** + * Layout of clocks in JSON format. + It is a list of horizontalGroups, each of which list of vertically-aligned elements. + Each clock has the following properties: + + 1. name: (string) name of the clock, to be displayed above it. + 2. hideAnalog: (boolean = false) flag to hide the analog clock. + 3. hideDate: (boolean = false) flag to hide the date. + 4. hideOffset: (boolean = false) flag to hide the UTC offset, displayed at the right of the name + 5. timezone: timezone string used to configure which UTC offset to use. 'local' if current should be used. 'local' by default. + + The format for the timezone string can be a fixed string (for UTC or TAI); a fixed-offset string (e.g. UTC+5); + or a location string in the format / (use camelcase with underscores instead of spaces, like America/New_York) + For example: + - For local time use local + - For UTC use UTC + - For TAI use TAI + - For Greenwich Sidereal Time use sidereal-greenwich + - For Summit Sidereal Time use sidereal-summit + - For a fixed offset (e.g. GMT+5) use this.tick(), 1000); - } - - componentWillUnmount() { - clearInterval(this.timerID); - } - - tick() { - const diffLocalUtc = DateTime.utc().toSeconds() - (this.props.timeData.receive_time + this.props.timeData.request_time) / 2; - this.setState({ - local: DateTime.fromSeconds(this.props.timeData.server_time.utc + diffLocalUtc), - sidereal_greenwich: DateTime.fromSeconds( - this.props.timeData.server_time.sidereal_greenwich * 3600 + siderealSecond * diffLocalUtc - ), - sidereal_summit: DateTime.fromSeconds( - this.props.timeData.server_time.sidereal_summit * 3600 + siderealSecond * diffLocalUtc - ), - mjd: this.props.timeData.server_time.mjd + diffLocalUtc / (3600 * 24), - }); - } + sidereal_greenwich: 0, + } + }; render() { return (
- {this.props.clocks.map((horizontalGroup, index) => ( + {this.props.clocks_layout.map((horizontalGroup, index) => (
{horizontalGroup.map((element, index) => { const verticalGroup = element instanceof Array ? element : [element]; return (
{verticalGroup.map((element, index) => { - let timestamp = this.state.local; + let timestamp = this.props.clock.utc; if (element.timezone === 'sidereal-greenwich') { - timestamp = this.state.sidereal_greenwich; + timestamp = this.props.clock.sidereal_greenwich; } else if (element.timezone === 'sidereal-summit') { - timestamp = this.state.sidereal_summit; + timestamp = this.props.clock.sidereal_summit; } else if (element.timezone === 'MJD') { - timestamp = this.state.mjd; + timestamp = this.props.clock.mjd; } - return ; + return ; })}
); diff --git a/love/src/redux/actions/actionTypes.js b/love/src/redux/actions/actionTypes.js index 71fa756cb..53894ae70 100644 --- a/love/src/redux/actions/actionTypes.js +++ b/love/src/redux/actions/actionTypes.js @@ -57,3 +57,6 @@ export const CHANGE_MODE = 'CHANGE_MODE'; export const RECEIVE_OBSERVING_LOG = 'RECEIVE_OBSERVING_LOG'; export const RECEIVE_TIME_DATA = 'RECEIVE_TIME_DATA'; +export const CLOCK_START = 'CLOCK_START'; +export const CLOCK_STOP = 'CLOCK_STOP'; +export const CLOCK_TICK = 'CLOCK_TICK'; diff --git a/love/src/redux/actions/auth.js b/love/src/redux/actions/auth.js index c1a2fa8d5..04f694176 100644 --- a/love/src/redux/actions/auth.js +++ b/love/src/redux/actions/auth.js @@ -15,7 +15,7 @@ import { requestViews } from './uif'; import ManagerInterface from '../../Utils'; import { getToken } from '../selectors'; import { openWebsocketConnection, closeWebsocketConnection } from './ws'; -import { receiveServerTime } from './time'; +import { receiveServerTime, clockStart } from './time'; export const requestToken = (username, password) => ({ type: REQUEST_TOKEN, username, password }); @@ -84,6 +84,7 @@ export function doReceiveToken(username, token, permissions, time_data, request_ dispatch(receiveToken(username, token, permissions)); dispatch(receiveServerTime(time_data, request_time)); dispatch(openWebsocketConnection()); + dispatch(clockStart()); localStorage.setItem('LOVE-TOKEN', token); }; } diff --git a/love/src/redux/actions/time.js b/love/src/redux/actions/time.js index 5bf3472f9..6c778883e 100644 --- a/love/src/redux/actions/time.js +++ b/love/src/redux/actions/time.js @@ -1,10 +1,58 @@ import { DateTime } from 'luxon'; -import { RECEIVE_TIME_DATA } from './actionTypes'; +import { + RECEIVE_TIME_DATA, + CLOCK_START, + CLOCK_STOP, + CLOCK_TICK, +} from './actionTypes'; +import { getAllTime } from '../selectors'; +import { siderealSecond } from '../../Utils'; export function receiveServerTime(time_data, request_time) { return (dispatch) => { const receive_time = DateTime.utc().toMillis() / 1000; dispatch({ type: RECEIVE_TIME_DATA, time_data, request_time, receive_time}); + dispatch({ type: CLOCK_START, time_data, request_time, receive_time}); }; +} + +export function tick() { + return (dispatch, getState) => { + const time = getAllTime(getState()); + const diffLocalUtc = DateTime.utc().toSeconds() - (time.receive_time + time.request_time) / 2; + dispatch({ + type: CLOCK_TICK, + clock: { + utc: DateTime.fromSeconds(time.server_time.utc + diffLocalUtc, { zone: 'utc' }), + tai: DateTime.fromSeconds(time.server_time.tai + diffLocalUtc, { zone: 'utc' }), + mjd: time.server_time.mjd + diffLocalUtc / (3600 * 24), + sidereal_summit: DateTime.fromSeconds( + time.server_time.sidereal_summit * 3600 + siderealSecond * diffLocalUtc, + { zone: 'utc' } + ), + sidereal_greenwich: DateTime.fromSeconds( + time.server_time.sidereal_greenwich * 3600 + siderealSecond * diffLocalUtc, + { zone: 'utc' } + ), + } + }); + } +}; + +let timerID = null; +export function clockStart() { + return (dispatch) => { + clearInterval(timerID); + timerID = setInterval(() => dispatch(tick()), 1000); + dispatch({ type: CLOCK_START }); + dispatch(tick()); + } +} + +export function clockStop() { + return (dispatch) => { + clearInterval(timerID); + dispatch({ type: CLOCK_STOP }); + } } \ No newline at end of file diff --git a/love/src/redux/reducers/time.js b/love/src/redux/reducers/time.js index f700ddd58..d2e7cfa7d 100644 --- a/love/src/redux/reducers/time.js +++ b/love/src/redux/reducers/time.js @@ -1,8 +1,17 @@ import { - RECEIVE_TIME_DATA + RECEIVE_TIME_DATA, + CLOCK_START, + CLOCK_STOP, + CLOCK_TICK, } from '../actions/actionTypes'; +export const clockStatuses = { + STARTED: 'STARTED', + STOPPED: 'STOPPED', +} + + const initialState = { request_time: 0, receive_time: 0, @@ -13,6 +22,14 @@ const initialState = { sidereal_summit: 0, sidereal_greenwich: 0, tai_to_utc: 0, + }, + clock_status: clockStatuses.STOPPED, + clock: { + utc: 0, + tai: 0, + mjd: 0, + sidereal_summit: 0, + sidereal_greenwich: 0, } }; /** @@ -36,6 +53,30 @@ export default function(state = initialState, action) { }, }); } + case CLOCK_START: + { + return Object.assign({}, state, { + clock_status: clockStatuses.STARTED, + }); + } + case CLOCK_STOP: + { + return Object.assign({}, state, { + clock_status: clockStatuses.STOPPED, + }); + } + case CLOCK_TICK: + { + return Object.assign({}, state, { + clock: { + utc: action.clock.utc, + tai: action.clock.tai, + mjd: action.clock.mjd, + sidereal_summit: action.clock.sidereal_summit, + sidereal_greenwich: action.clock.sidereal_greenwich, + } + }); + } default: return state; } diff --git a/love/src/redux/selectors/selectors.js b/love/src/redux/selectors/selectors.js index 2198c6196..0b5f3ac74 100644 --- a/love/src/redux/selectors/selectors.js +++ b/love/src/redux/selectors/selectors.js @@ -12,6 +12,10 @@ export const getServerTimeReceive = (state) => state.time.receive_time; export const getServerTime = (state) => ({...state.time.server_time}); +export const getAllTime = (state) => ({...state.time}); + +export const getClock = (state) => ({...state.time.clock}); + export const getTimeData = (state) => ({ receive_time: state.time.receive_time, request_time: state.time.request_time,