Option to show time for a city (#1236)

This commit is contained in:
Tagaishi
2023-08-09 20:33:17 +02:00
committed by GitHub
parent 6460e433a5
commit ffa850b081
10 changed files with 444 additions and 71 deletions

View File

@@ -1,8 +1,11 @@
import { Stack, Text, Title } from '@mantine/core';
import { Stack, Text, createStyles } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconClock } from '@tabler/icons-react';
import dayjs from 'dayjs';
import moment from 'moment-timezone';
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';
import { getLanguageByCode } from '~/tools/language';
import { api } from '~/utils/api';
import { useSetSafeInterval } from '../../hooks/useSetSafeInterval';
import { defineWidget } from '../helper';
@@ -16,6 +19,39 @@ const definition = defineWidget({
type: 'switch',
defaultValue: false,
},
dateFormat: {
type: 'select',
defaultValue: 'dddd, MMMM D',
data: () => [
{ value: 'hide' },
{ value: 'dddd, MMMM D', label: moment().format('dddd, MMMM D') },
{ value: 'dddd, D MMMM', label: moment().format('dddd, D MMMM') },
{ value: 'MMM D', label: moment().format('MMM D') },
{ value: 'D MMM', label: moment().format('D MMM') },
{ value: 'DD/MM/YYYY', label: moment().format('DD/MM/YYYY') },
{ value: 'MM/DD/YYYY', label: moment().format('MM/DD/YYYY') },
{ value: 'DD/MM', label: moment().format('DD/MM') },
{ value: 'MM/DD', label: moment().format('MM/DD') },
],
},
enableTimezone: {
type: 'switch',
defaultValue: false,
},
timezoneLocation: {
type: 'location',
defaultValue: {
name: 'Paris',
latitude: 48.85341,
longitude: 2.3488,
},
},
titleState: {
type: 'select',
defaultValue: 'both',
data: [{ value: 'both' }, { value: 'city' }, { value: 'none' }],
info: true,
},
},
gridstack: {
minWidth: 1,
@@ -33,52 +69,102 @@ interface DateTileProps {
}
function DateTile({ widget }: DateTileProps) {
const date = useDateState();
const date = useDateState(
widget.properties.enableTimezone ? widget.properties.timezoneLocation : undefined
);
const formatString = widget.properties.display24HourFormat ? 'HH:mm' : 'h:mm A';
const { width, ref } = useElementSize();
const { ref, width } = useElementSize();
const { cx, classes } = useStyles();
return (
<Stack ref={ref} spacing="xs" justify="space-around" align="center" style={{ height: '100%' }}>
<Title>{dayjs(date).format(formatString)}</Title>
{width > 200 && <Text size="lg">{dayjs(date).format('dddd, MMMM D')}</Text>}
<Stack ref={ref} className={cx(classes.wrapper, 'dashboard-tile-clock-wrapper')}>
{widget.properties.enableTimezone && widget.properties.titleState !== 'none' && (
<Text
size={width < 150 ? 'sm' : 'lg'}
className={cx(classes.extras, 'dashboard-tile-clock-city')}
>
{widget.properties.timezoneLocation.name}
{widget.properties.titleState === 'both' && moment(date).format(' (z)')}
</Text>
)}
<Text className={cx(classes.clock, 'dashboard-tile-clock-hour')}>
{moment(date).format(formatString)}
</Text>
{!widget.properties.dateFormat.includes('hide') && (
<Text
size={width < 150 ? 'sm' : 'lg'}
pt="0.2rem"
className={cx(classes.extras, 'dashboard-tile-clock-date')}
>
{moment(date).format(widget.properties.dateFormat)}
</Text>
)}
</Stack>
);
}
const useStyles = createStyles(()=>({
wrapper:{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-evenly',
alignItems: 'center',
height: '100%',
gap: 0,
},
clock:{
lineHeight: '1',
whiteSpace: 'nowrap',
fontWeight: 700,
fontSize: '2.125rem',
},
extras:{
lineHeight: '1',
whiteSpace: 'nowrap',
}
}))
/**
* State which updates when the minute is changing
* @returns current date updated every new minute
*/
const useDateState = () => {
const [date, setDate] = useState(new Date());
const useDateState = (location?: { latitude: number; longitude: number }) => {
//Gets a timezone from user input location. If location is undefined, then it means it's a local timezone so keep undefined
const { data: timezone } = api.timezone.at.useQuery(location!, {
enabled: location !== undefined,
});
const { locale } = useRouter();
const [date, setDate] = useState(getNewDate(timezone));
const setSafeInterval = useSetSafeInterval();
const timeoutRef = useRef<NodeJS.Timeout>(); // reference for initial timeout until first minute change
useEffect(() => {
timeoutRef.current = setTimeout(() => {
setDate(new Date());
// Starts intervall which update the date every minute
setSafeInterval(() => {
setDate(new Date());
}, 1000 * 60);
}, getMsUntilNextMinute());
const language = getLanguageByCode(locale ?? 'en');
moment.locale(language.momentLocale);
setDate(getNewDate(timezone));
timeoutRef.current = setTimeout(
() => {
setDate(getNewDate(timezone));
// Starts interval which update the date every minute
setSafeInterval(() => {
setDate(getNewDate(timezone));
}, 1000 * 60);
//1 minute - current seconds and milliseconds count
},
1000 * 60 - (1000 * moment().seconds() + moment().milliseconds())
);
return () => timeoutRef.current && clearTimeout(timeoutRef.current);
}, []);
}, [timezone, locale]);
return date;
};
// calculates the amount of milliseconds until next minute starts.
const getMsUntilNextMinute = () => {
const now = new Date();
const nextMinute = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
now.getHours(),
now.getMinutes() + 1
);
return nextMinute.getTime() - now.getTime();
//Returns a local date if no inputs or returns date from input zone
const getNewDate = (timezone?: string) => {
if (timezone) {
return moment().tz(timezone);
}
return moment();
};
export default definition;

View File

@@ -42,18 +42,19 @@ export type IWidgetOptionValue = (
| IDraggableEditableListInputValue<any>
| IMultipleTextInputOptionValue
| ILocationOptionValue
) & ICommonWidgetOptions;
) &
ICommonWidgetOptions;
// Interface for data type
interface DataType {
label: string;
label?: string;
value: string;
}
interface ICommonWidgetOptions {
interface ICommonWidgetOptions {
info?: boolean;
infoLink?: string;
};
}
// will show a multi-select with specified data
export type IMultiSelectOptionValue = {
@@ -67,7 +68,7 @@ export type IMultiSelectOptionValue = {
export type ISelectOptionValue = {
type: 'select';
defaultValue: string;
data: DataType[];
data: DataType[] | (() => DataType[]);
inputProps?: Partial<SelectProps>;
};