mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Events Page: Added option to override browser time format and style (#5538)
* use12hour optional config * use12hour config arg * added use12HourFormat arg to format function * dateStyle & timeStyle option * moved timezone & locales to format function * added dateStyle & timeStyle * re-formatted * added strftime_fmt config entry * strftime package * added strftime option * underscore instead of camelCase * underscore props instead of camelCase
This commit is contained in:
parent
fbf29667d4
commit
3611e874ca
@ -66,12 +66,29 @@ class LiveModeEnum(str, Enum):
|
|||||||
webrtc = "webrtc"
|
webrtc = "webrtc"
|
||||||
|
|
||||||
|
|
||||||
|
class DateTimeStyleEnum(str, Enum):
|
||||||
|
full = "full"
|
||||||
|
long = "long"
|
||||||
|
medium = "medium"
|
||||||
|
short = "short"
|
||||||
|
|
||||||
|
|
||||||
class UIConfig(FrigateBaseModel):
|
class UIConfig(FrigateBaseModel):
|
||||||
live_mode: LiveModeEnum = Field(
|
live_mode: LiveModeEnum = Field(
|
||||||
default=LiveModeEnum.mse, title="Default Live Mode."
|
default=LiveModeEnum.mse, title="Default Live Mode."
|
||||||
)
|
)
|
||||||
timezone: Optional[str] = Field(title="Override UI timezone.")
|
timezone: Optional[str] = Field(title="Override UI timezone.")
|
||||||
use_experimental: bool = Field(default=False, title="Experimental UI")
|
use_experimental: bool = Field(default=False, title="Experimental UI")
|
||||||
|
use12hour: Optional[bool] = Field(title="Override UI time format.")
|
||||||
|
date_style: DateTimeStyleEnum = Field(
|
||||||
|
default=DateTimeStyleEnum.short, title="Override UI dateStyle."
|
||||||
|
)
|
||||||
|
time_style: DateTimeStyleEnum = Field(
|
||||||
|
default=DateTimeStyleEnum.medium, title="Override UI timeStyle."
|
||||||
|
)
|
||||||
|
strftime_fmt: Optional[str] = Field(
|
||||||
|
default=None, title="Override date and time format using strftime syntax."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TelemetryConfig(FrigateBaseModel):
|
class TelemetryConfig(FrigateBaseModel):
|
||||||
|
41
web/package-lock.json
generated
41
web/package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cycjimmy/jsmpeg-player": "^6.0.5",
|
"@cycjimmy/jsmpeg-player": "^6.0.5",
|
||||||
"axios": "^1.2.2",
|
"axios": "^1.2.2",
|
||||||
|
"copy-to-clipboard": "3.3.3",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"idb-keyval": "^6.2.0",
|
"idb-keyval": "^6.2.0",
|
||||||
"immer": "^9.0.16",
|
"immer": "^9.0.16",
|
||||||
@ -19,6 +20,7 @@
|
|||||||
"preact-router": "^4.1.0",
|
"preact-router": "^4.1.0",
|
||||||
"react": "npm:@preact/compat@^17.1.2",
|
"react": "npm:@preact/compat@^17.1.2",
|
||||||
"react-dom": "npm:@preact/compat@^17.1.2",
|
"react-dom": "npm:@preact/compat@^17.1.2",
|
||||||
|
"strftime": "^0.10.1",
|
||||||
"swr": "^1.3.0",
|
"swr": "^1.3.0",
|
||||||
"video.js": "^7.20.3",
|
"video.js": "^7.20.3",
|
||||||
"videojs-playlist": "^5.0.0",
|
"videojs-playlist": "^5.0.0",
|
||||||
@ -3369,6 +3371,14 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/copy-to-clipboard": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
|
||||||
|
"dependencies": {
|
||||||
|
"toggle-selection": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-js": {
|
"node_modules/core-js": {
|
||||||
"version": "3.26.0",
|
"version": "3.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
|
||||||
@ -8547,6 +8557,14 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strftime": {
|
||||||
|
"version": "0.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.1.tgz",
|
||||||
|
"integrity": "sha512-nVvH6JG8KlXFPC0f8lojLgEsPA18lRpLZ+RrJh/NkQV2tqOgZfbas8gcU8SFgnnqR3rWzZPYu6N2A3xzs/8rQg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/strict-event-emitter": {
|
"node_modules/strict-event-emitter": {
|
||||||
"version": "0.2.8",
|
"version": "0.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz",
|
||||||
@ -8901,6 +8919,11 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/toggle-selection": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
||||||
|
},
|
||||||
"node_modules/totalist": {
|
"node_modules/totalist": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz",
|
||||||
@ -12151,6 +12174,14 @@
|
|||||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"copy-to-clipboard": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
|
||||||
|
"requires": {
|
||||||
|
"toggle-selection": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "3.26.0",
|
"version": "3.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
|
||||||
@ -15868,6 +15899,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"strftime": {
|
||||||
|
"version": "0.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.1.tgz",
|
||||||
|
"integrity": "sha512-nVvH6JG8KlXFPC0f8lojLgEsPA18lRpLZ+RrJh/NkQV2tqOgZfbas8gcU8SFgnnqR3rWzZPYu6N2A3xzs/8rQg=="
|
||||||
|
},
|
||||||
"strict-event-emitter": {
|
"strict-event-emitter": {
|
||||||
"version": "0.2.8",
|
"version": "0.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz",
|
||||||
@ -16154,6 +16190,11 @@
|
|||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"toggle-selection": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
|
||||||
|
},
|
||||||
"totalist": {
|
"totalist": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz",
|
||||||
|
@ -18,17 +18,18 @@
|
|||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"idb-keyval": "^6.2.0",
|
"idb-keyval": "^6.2.0",
|
||||||
"immer": "^9.0.16",
|
"immer": "^9.0.16",
|
||||||
|
"monaco-yaml": "^4.0.2",
|
||||||
"preact": "^10.11.3",
|
"preact": "^10.11.3",
|
||||||
"preact-async-route": "^2.2.1",
|
"preact-async-route": "^2.2.1",
|
||||||
"preact-router": "^4.1.0",
|
"preact-router": "^4.1.0",
|
||||||
"react": "npm:@preact/compat@^17.1.2",
|
"react": "npm:@preact/compat@^17.1.2",
|
||||||
"react-dom": "npm:@preact/compat@^17.1.2",
|
"react-dom": "npm:@preact/compat@^17.1.2",
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"strftime": "^0.10.1",
|
||||||
"monaco-yaml": "^4.0.2",
|
|
||||||
"swr": "^1.3.0",
|
"swr": "^1.3.0",
|
||||||
"video.js": "^7.20.3",
|
"video.js": "^7.20.3",
|
||||||
"videojs-playlist": "^5.0.0",
|
"videojs-playlist": "^5.0.0",
|
||||||
"videojs-seek-buttons": "^3.0.1"
|
"videojs-seek-buttons": "^3.0.1",
|
||||||
|
"vite-plugin-monaco-editor": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@preact/preset-vite": "^2.5.0",
|
"@preact/preset-vite": "^2.5.0",
|
||||||
|
@ -287,9 +287,6 @@ export default function Events({ path, ...props }) {
|
|||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timezone = config.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
||||||
const locale = window.navigator?.language || 'en-US';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 p-2 px-4 w-full">
|
<div className="space-y-4 p-2 px-4 w-full">
|
||||||
<Heading>Events</Heading>
|
<Heading>Events</Heading>
|
||||||
@ -508,7 +505,7 @@ export default function Events({ path, ...props }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-sm flex">
|
<div className="text-sm flex">
|
||||||
<Clock className="h-5 w-5 mr-2 inline" />
|
<Clock className="h-5 w-5 mr-2 inline" />
|
||||||
{formatUnixTimestampToDateTime(event.start_time, locale, timezone)}
|
{formatUnixTimestampToDateTime(event.start_time, { ...config.ui })}
|
||||||
<div className="hidden md:inline">
|
<div className="hidden md:inline">
|
||||||
<span className="m-1">-</span>
|
<span className="m-1">-</span>
|
||||||
<TimeAgo time={event.start_time * 1000} dense />
|
<TimeAgo time={event.start_time * 1000} dense />
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import strftime from 'strftime';
|
||||||
|
import { fromUnixTime, intervalToDuration, formatDuration } from 'date-fns';
|
||||||
export const longToDate = (long: number): Date => new Date(long * 1000);
|
export const longToDate = (long: number): Date => new Date(long * 1000);
|
||||||
export const epochToLong = (date: number): number => date / 1000;
|
export const epochToLong = (date: number): number => date / 1000;
|
||||||
export const dateToLong = (date: Date): number => epochToLong(date.getTime());
|
export const dateToLong = (date: Date): number => epochToLong(date.getTime());
|
||||||
import { fromUnixTime, intervalToDuration, formatDuration } from 'date-fns';
|
|
||||||
|
|
||||||
const getDateTimeYesterday = (dateTime: Date): Date => {
|
const getDateTimeYesterday = (dateTime: Date): Date => {
|
||||||
const twentyFourHoursInMilliseconds = 24 * 60 * 60 * 1000;
|
const twentyFourHoursInMilliseconds = 24 * 60 * 60 * 1000;
|
||||||
@ -17,28 +18,53 @@ export const getNowYesterdayInLong = (): number => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function takes in a unix timestamp, locale, timezone,
|
* This function takes in a Unix timestamp, configuration options for date/time display, and an optional strftime format string,
|
||||||
* and returns a dateTime string.
|
* and returns a formatted date/time string.
|
||||||
* If unixTimestamp is not provided, it returns 'Invalid time'
|
*
|
||||||
* @param unixTimestamp: number
|
* If the Unix timestamp is not provided, it returns "Invalid time".
|
||||||
* @param locale: string
|
*
|
||||||
* @param timezone: string
|
* The configuration options determine how the date and time are formatted.
|
||||||
* @returns string - dateTime or 'Invalid time' if unixTimestamp is not provided
|
* The `timezone` option allows you to specify a specific timezone for the output, otherwise the user's browser timezone will be used.
|
||||||
|
* The `use12hour` option allows you to display time in a 12-hour format if true, and 24-hour format if false.
|
||||||
|
* The `dateStyle` and `timeStyle` options allow you to specify pre-defined formats for displaying the date and time.
|
||||||
|
* The `strftime_fmt` option allows you to specify a custom format using the strftime syntax.
|
||||||
|
*
|
||||||
|
* If both `strftime_fmt` and `dateStyle`/`timeStyle` are provided, `strftime_fmt` takes precedence.
|
||||||
|
*
|
||||||
|
* @param unixTimestamp The Unix timestamp to format
|
||||||
|
* @param config An object containing the configuration options for date/time display
|
||||||
|
* @returns The formatted date/time string, or "Invalid time" if the Unix timestamp is not provided or invalid.
|
||||||
*/
|
*/
|
||||||
export const formatUnixTimestampToDateTime = (unixTimestamp: number, locale: string, timezone: string): string => {
|
interface DateTimeStyle {
|
||||||
|
timezone: string;
|
||||||
|
use12hour: boolean | undefined;
|
||||||
|
date_style: 'full' | 'long' | 'medium' | 'short';
|
||||||
|
time_style: 'full' | 'long' | 'medium' | 'short';
|
||||||
|
strftime_fmt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatUnixTimestampToDateTime = (unixTimestamp: number, config: DateTimeStyle): string => {
|
||||||
|
const { timezone, use12hour, date_style, time_style, strftime_fmt } = config;
|
||||||
|
const locale = window.navigator?.language || 'en-US';
|
||||||
|
|
||||||
if (isNaN(unixTimestamp)) {
|
if (isNaN(unixTimestamp)) {
|
||||||
return 'Invalid time';
|
return 'Invalid time';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const date = new Date(unixTimestamp * 1000);
|
const date = new Date(unixTimestamp * 1000);
|
||||||
|
|
||||||
|
// use strftime_fmt if defined in config file
|
||||||
|
if (strftime_fmt) {
|
||||||
|
const strftime_locale = strftime.localizeByIdentifier(locale);
|
||||||
|
return strftime_locale(strftime_fmt, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// else use Intl.DateTimeFormat
|
||||||
const formatter = new Intl.DateTimeFormat(locale, {
|
const formatter = new Intl.DateTimeFormat(locale, {
|
||||||
day: '2-digit',
|
dateStyle: date_style,
|
||||||
month: '2-digit',
|
timeStyle: time_style,
|
||||||
year: 'numeric',
|
timeZone: timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
hour: '2-digit',
|
hour12: use12hour !== null ? use12hour : undefined,
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit',
|
|
||||||
timeZone: timezone,
|
|
||||||
});
|
});
|
||||||
return formatter.format(date);
|
return formatter.format(date);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user