✨ Option to show time for a city (#1236)
This commit is contained in:
@@ -19,13 +19,13 @@ import { IconAlertTriangle, IconPlaylistX, IconPlus } from '@tabler/icons-react'
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
import { FC, useState } from 'react';
|
||||
|
||||
import { InfoCard } from '../../../InfoCard/InfoCard';
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { useConfigStore } from '../../../../config/store';
|
||||
import { mapObject } from '../../../../tools/client/objects';
|
||||
import Widgets from '../../../../widgets';
|
||||
import type { IDraggableListInputValue, IWidgetOptionValue } from '../../../../widgets/widgets';
|
||||
import { IWidget } from '../../../../widgets/widgets';
|
||||
import { InfoCard } from '../../../InfoCard/InfoCard';
|
||||
import { DraggableList } from './Inputs/DraggableList';
|
||||
import { LocationSelection } from './Inputs/LocationSelection';
|
||||
import { StaticDraggableList } from './Inputs/StaticDraggableList';
|
||||
@@ -148,15 +148,17 @@ const WidgetOptionTypeSwitch: FC<{
|
||||
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
|
||||
{...option.inputProps}
|
||||
/>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
);
|
||||
case 'text':
|
||||
return (
|
||||
<Stack spacing={0}>
|
||||
<Group align="center" spacing="sm">
|
||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
<Text size="0.875rem" weight="500">
|
||||
{t(`descriptor.settings.${key}.label`)}
|
||||
</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
<TextInput
|
||||
value={value as string}
|
||||
@@ -169,8 +171,10 @@ const WidgetOptionTypeSwitch: FC<{
|
||||
return (
|
||||
<Stack spacing={0}>
|
||||
<Group align="center" spacing="sm">
|
||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
<Text size="0.875rem" weight="500">
|
||||
{t(`descriptor.settings.${key}.label`)}
|
||||
</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
<MultiSelect
|
||||
data={option.data}
|
||||
@@ -183,15 +187,26 @@ const WidgetOptionTypeSwitch: FC<{
|
||||
</Stack>
|
||||
);
|
||||
case 'select':
|
||||
const items = typeof option.data === 'function' ? option.data() : option.data;
|
||||
const data = items.map((dataType) => {
|
||||
return !dataType.label
|
||||
? {
|
||||
value: dataType.value,
|
||||
label: t(`descriptor.settings.${key}.data.${dataType.value}`),
|
||||
}
|
||||
: dataType;
|
||||
});
|
||||
return (
|
||||
<Stack spacing={0}>
|
||||
<Group align="center" spacing="sm">
|
||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
<Text size="0.875rem" weight="500">
|
||||
{t(`descriptor.settings.${key}.label`)}
|
||||
</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
<Select
|
||||
defaultValue={option.defaultValue}
|
||||
data={option.data}
|
||||
data={data}
|
||||
value={value as string}
|
||||
onChange={(v) => handleChange(key, v ?? option.defaultValue)}
|
||||
withinPortal
|
||||
@@ -203,8 +218,10 @@ const WidgetOptionTypeSwitch: FC<{
|
||||
return (
|
||||
<Stack spacing={0}>
|
||||
<Group align="center" spacing="sm">
|
||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
<Text size="0.875rem" weight="500">
|
||||
{t(`descriptor.settings.${key}.label`)}
|
||||
</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
<NumberInput
|
||||
value={value as number}
|
||||
@@ -217,8 +234,10 @@ const WidgetOptionTypeSwitch: FC<{
|
||||
return (
|
||||
<Stack spacing={0}>
|
||||
<Group align="center" spacing="sm">
|
||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
<Text size="0.875rem" weight="500">
|
||||
{t(`descriptor.settings.${key}.label`)}
|
||||
</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
<Slider
|
||||
label={value}
|
||||
@@ -270,7 +289,7 @@ const WidgetOptionTypeSwitch: FC<{
|
||||
<Stack spacing="xs">
|
||||
<Group align="center" spacing="sm">
|
||||
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
<StaticDraggableList
|
||||
value={typedVal}
|
||||
@@ -298,8 +317,10 @@ const WidgetOptionTypeSwitch: FC<{
|
||||
return (
|
||||
<Stack spacing={0}>
|
||||
<Group align="center" spacing="sm">
|
||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
<Text size="0.875rem" weight="500">
|
||||
{t(`descriptor.settings.${key}.label`)}
|
||||
</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
<MultiSelect
|
||||
data={value.map((name: any) => ({ value: name, label: name }))}
|
||||
@@ -324,7 +345,7 @@ const WidgetOptionTypeSwitch: FC<{
|
||||
<Stack spacing="xs">
|
||||
<Group align="center" spacing="sm">
|
||||
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
|
||||
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link} />}
|
||||
</Group>
|
||||
<DraggableList
|
||||
items={Array.from(value).map((v: any) => ({
|
||||
|
||||
@@ -8,12 +8,15 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
|
||||
import Consola from 'consola';
|
||||
import { getCookie } from 'cookies-next';
|
||||
import moment from 'moment-timezone';
|
||||
import { GetServerSidePropsContext } from 'next';
|
||||
import { appWithTranslation } from 'next-i18next';
|
||||
import { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import { useEffect, useState } from 'react';
|
||||
import 'video.js/dist/video-js.css';
|
||||
import { getLanguageByCode } from '~/tools/language';
|
||||
import { ConfigType } from '~/types/config';
|
||||
import { api } from '~/utils/api';
|
||||
|
||||
import nextI18nextConfig from '../../next-i18next.config';
|
||||
@@ -35,7 +38,6 @@ import {
|
||||
getServiceSidePackageAttributes,
|
||||
} from '../tools/server/getPackageVersion';
|
||||
import { theme } from '../tools/server/theme/theme';
|
||||
import { ConfigType } from '~/types/config';
|
||||
|
||||
function App(
|
||||
this: any,
|
||||
@@ -46,13 +48,24 @@ function App(
|
||||
defaultColorScheme: ColorScheme;
|
||||
config?: ConfigType;
|
||||
configName?: string;
|
||||
locale: string;
|
||||
}>
|
||||
) {
|
||||
const { Component, pageProps } = props;
|
||||
// TODO: make mapping from our locales to moment locales
|
||||
const language = getLanguageByCode(pageProps.locale);
|
||||
require('moment/locale/' + language.momentLocale);
|
||||
moment.locale(language.momentLocale);
|
||||
|
||||
const [primaryColor, setPrimaryColor] = useState<MantineTheme['primaryColor']>(props.pageProps.config?.settings.customization.colors.primary || 'red');
|
||||
const [secondaryColor, setSecondaryColor] = useState<MantineTheme['primaryColor']>(props.pageProps.config?.settings.customization.colors.secondary || 'orange');
|
||||
const [primaryShade, setPrimaryShade] = useState<MantineTheme['primaryShade']>(props.pageProps.config?.settings.customization.colors.shade || 6);
|
||||
const [primaryColor, setPrimaryColor] = useState<MantineTheme['primaryColor']>(
|
||||
props.pageProps.config?.settings.customization.colors.primary || 'red'
|
||||
);
|
||||
const [secondaryColor, setSecondaryColor] = useState<MantineTheme['primaryColor']>(
|
||||
props.pageProps.config?.settings.customization.colors.secondary || 'orange'
|
||||
);
|
||||
const [primaryShade, setPrimaryShade] = useState<MantineTheme['primaryShade']>(
|
||||
props.pageProps.config?.settings.customization.colors.shade || 6
|
||||
);
|
||||
const colorTheme = {
|
||||
primaryColor,
|
||||
secondaryColor,
|
||||
@@ -172,6 +185,7 @@ App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => {
|
||||
packageAttributes: getServiceSidePackageAttributes(),
|
||||
editModeEnabled: !disableEditMode,
|
||||
defaultColorScheme: colorScheme,
|
||||
locale: ctx.locale ?? 'en',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import { mediaRequestsRouter } from './routers/media-request';
|
||||
import { mediaServerRouter } from './routers/media-server';
|
||||
import { overseerrRouter } from './routers/overseerr';
|
||||
import { rssRouter } from './routers/rss';
|
||||
import { timezoneRouter } from './routers/timezone';
|
||||
import { usenetRouter } from './routers/usenet/router';
|
||||
import { weatherRouter } from './routers/weather';
|
||||
|
||||
@@ -22,18 +23,19 @@ import { weatherRouter } from './routers/weather';
|
||||
*/
|
||||
export const rootRouter = createTRPCRouter({
|
||||
app: appRouter,
|
||||
rss: rssRouter,
|
||||
calendar: calendarRouter,
|
||||
config: configRouter,
|
||||
docker: dockerRouter,
|
||||
icon: iconRouter,
|
||||
dashDot: dashDotRouter,
|
||||
dnsHole: dnsHoleRouter,
|
||||
docker: dockerRouter,
|
||||
download: downloadRouter,
|
||||
icon: iconRouter,
|
||||
mediaRequest: mediaRequestsRouter,
|
||||
mediaServer: mediaServerRouter,
|
||||
overseerr: overseerrRouter,
|
||||
rss: rssRouter,
|
||||
timezone: timezoneRouter,
|
||||
usenet: usenetRouter,
|
||||
calendar: calendarRouter,
|
||||
weather: weatherRouter,
|
||||
});
|
||||
|
||||
|
||||
17
src/server/api/routers/timezone.ts
Normal file
17
src/server/api/routers/timezone.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
import { find } from 'geo-tz'
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from '../trpc';
|
||||
|
||||
export const timezoneRouter = createTRPCRouter({
|
||||
at: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
longitude: z.number(),
|
||||
latitude: z.number(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
return find(input.latitude,input.longitude)[0];
|
||||
}),
|
||||
})
|
||||
@@ -1,16 +1,10 @@
|
||||
export class Language {
|
||||
export type Language = {
|
||||
shortName: string;
|
||||
originalName: string;
|
||||
translatedName: string;
|
||||
emoji: string;
|
||||
|
||||
constructor(shortName: string, originalName: string, translatedName: string, emoji: string) {
|
||||
this.shortName = shortName;
|
||||
this.originalName = originalName;
|
||||
this.translatedName = translatedName;
|
||||
this.emoji = emoji;
|
||||
}
|
||||
}
|
||||
momentLocale: string;
|
||||
};
|
||||
|
||||
export const languages: Language[] = [
|
||||
{
|
||||
@@ -18,12 +12,14 @@ export const languages: Language[] = [
|
||||
originalName: 'Deutsch',
|
||||
translatedName: 'German',
|
||||
emoji: '🇩🇪',
|
||||
momentLocale: 'de',
|
||||
},
|
||||
{
|
||||
shortName: 'en',
|
||||
originalName: 'English',
|
||||
translatedName: 'English',
|
||||
emoji: '🇬🇧',
|
||||
momentLocale: 'en-gb',
|
||||
},
|
||||
// Danish
|
||||
{
|
||||
@@ -31,6 +27,7 @@ export const languages: Language[] = [
|
||||
originalName: 'Dansk',
|
||||
translatedName: 'Danish',
|
||||
emoji: '🇩🇰',
|
||||
momentLocale: 'da',
|
||||
},
|
||||
// Hebrew
|
||||
{
|
||||
@@ -38,42 +35,49 @@ export const languages: Language[] = [
|
||||
originalName: 'עברית',
|
||||
translatedName: 'Hebrew',
|
||||
emoji: '🇮🇱',
|
||||
momentLocale: 'he',
|
||||
},
|
||||
{
|
||||
shortName: 'es',
|
||||
originalName: 'Español',
|
||||
translatedName: 'Spanish',
|
||||
emoji: '🇪🇸',
|
||||
momentLocale: 'es',
|
||||
},
|
||||
{
|
||||
shortName: 'fr',
|
||||
originalName: 'Français',
|
||||
translatedName: 'French',
|
||||
emoji: '🇫🇷',
|
||||
momentLocale: 'fr',
|
||||
},
|
||||
{
|
||||
shortName: 'it',
|
||||
originalName: 'Italiano',
|
||||
translatedName: 'Italian',
|
||||
emoji: '🇮🇹',
|
||||
momentLocale: 'it',
|
||||
},
|
||||
{
|
||||
shortName: 'ja',
|
||||
originalName: '日本語',
|
||||
translatedName: 'Japanese',
|
||||
emoji: '🇯🇵',
|
||||
momentLocale: 'ja',
|
||||
},
|
||||
{
|
||||
shortName: 'ko',
|
||||
originalName: '한국어',
|
||||
translatedName: 'Korean',
|
||||
emoji: '🇰🇷',
|
||||
momentLocale: 'ko',
|
||||
},
|
||||
{
|
||||
shortName: 'lol',
|
||||
originalName: 'LOLCAT',
|
||||
translatedName: 'LOLCAT',
|
||||
emoji: '🐱',
|
||||
momentLocale: 'en-gb',
|
||||
},
|
||||
// Norwegian
|
||||
{
|
||||
@@ -81,6 +85,7 @@ export const languages: Language[] = [
|
||||
originalName: 'Norsk',
|
||||
translatedName: 'Norwegian',
|
||||
emoji: '🇳🇴',
|
||||
momentLocale: 'nb',
|
||||
},
|
||||
// Slovak
|
||||
{
|
||||
@@ -88,36 +93,42 @@ export const languages: Language[] = [
|
||||
originalName: 'Slovenčina',
|
||||
translatedName: 'Slovak',
|
||||
emoji: '🇸🇰',
|
||||
momentLocale: 'sk',
|
||||
},
|
||||
{
|
||||
shortName: 'nl',
|
||||
originalName: 'Nederlands',
|
||||
translatedName: 'Dutch',
|
||||
emoji: '🇳🇱',
|
||||
momentLocale: 'nl',
|
||||
},
|
||||
{
|
||||
shortName: 'pl',
|
||||
originalName: 'Polski',
|
||||
translatedName: 'Polish',
|
||||
emoji: '🇵🇱',
|
||||
momentLocale: 'pl',
|
||||
},
|
||||
{
|
||||
shortName: 'pt',
|
||||
originalName: 'Português',
|
||||
translatedName: 'Portuguese',
|
||||
emoji: '🇵🇹',
|
||||
momentLocale: 'pt',
|
||||
},
|
||||
{
|
||||
shortName: 'ru',
|
||||
originalName: 'Русский',
|
||||
translatedName: 'Russian',
|
||||
emoji: '🇷🇺',
|
||||
momentLocale: 'ru',
|
||||
},
|
||||
{
|
||||
shortName: 'sl',
|
||||
originalName: 'Slovenščina',
|
||||
translatedName: 'Slovenian',
|
||||
emoji: '🇸🇮',
|
||||
momentLocale: 'sl',
|
||||
},
|
||||
|
||||
{
|
||||
@@ -125,12 +136,14 @@ export const languages: Language[] = [
|
||||
originalName: 'Svenska',
|
||||
translatedName: 'Swedish',
|
||||
emoji: '🇸🇪',
|
||||
momentLocale: 'sv',
|
||||
},
|
||||
{
|
||||
shortName: 'uk',
|
||||
originalName: 'Українська',
|
||||
translatedName: 'Ukrainian',
|
||||
emoji: '🇺🇦',
|
||||
momentLocale: 'uk',
|
||||
},
|
||||
// Vietnamese
|
||||
{
|
||||
@@ -138,37 +151,42 @@ export const languages: Language[] = [
|
||||
originalName: 'Tiếng Việt',
|
||||
translatedName: 'Vietnamese',
|
||||
emoji: '🇻🇳',
|
||||
momentLocale: 'vi',
|
||||
},
|
||||
{
|
||||
shortName: 'zh',
|
||||
originalName: '中文',
|
||||
translatedName: 'Chinese',
|
||||
emoji: '🇨🇳',
|
||||
momentLocale: 'zh-cn',
|
||||
},
|
||||
{
|
||||
shortName: 'el',
|
||||
originalName: 'Ελληνικά',
|
||||
translatedName: 'Greek',
|
||||
emoji: '🇬🇷',
|
||||
momentLocale: 'el',
|
||||
},
|
||||
{
|
||||
shortName: 'tr',
|
||||
originalName: 'Türkçe',
|
||||
translatedName: 'Turkish',
|
||||
emoji: '🇹🇷',
|
||||
momentLocale: 'tr',
|
||||
},
|
||||
{
|
||||
shortName: 'lv',
|
||||
originalName: 'Latvian',
|
||||
translatedName: 'Latvian',
|
||||
emoji: '🇱🇻',
|
||||
momentLocale: 'lv',
|
||||
},
|
||||
// Croatian
|
||||
{
|
||||
shortName: 'hr',
|
||||
originalName: 'Hrvatski',
|
||||
translatedName: 'Croatian',
|
||||
emoji: '🇭🇷',
|
||||
momentLocale: 'hr',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user