mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 08:16:36 +00:00
Add last-changed date to more-info (#16509)
This commit is contained in:
parent
557fe33807
commit
75f080ee85
23
src/common/datetime/absolute_time.ts
Normal file
23
src/common/datetime/absolute_time.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { isSameDay, isSameYear } from "date-fns";
|
||||||
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
|
import {
|
||||||
|
formatShortDateTime,
|
||||||
|
formatShortDateTimeWithYear,
|
||||||
|
} from "./format_date_time";
|
||||||
|
import { formatTime } from "./format_time";
|
||||||
|
|
||||||
|
export const absoluteTime = (
|
||||||
|
from: Date,
|
||||||
|
locale: FrontendLocaleData,
|
||||||
|
to?: Date
|
||||||
|
): string => {
|
||||||
|
const _to = to ?? new Date();
|
||||||
|
|
||||||
|
if (isSameDay(from, _to)) {
|
||||||
|
return formatTime(from, locale);
|
||||||
|
}
|
||||||
|
if (isSameYear(from, _to)) {
|
||||||
|
return formatShortDateTime(from, locale);
|
||||||
|
}
|
||||||
|
return formatShortDateTimeWithYear(from, locale);
|
||||||
|
};
|
@ -24,6 +24,29 @@ const formatDateTimeMem = memoizeOne(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Aug 9, 2021, 8:23 AM
|
||||||
|
export const formatShortDateTimeWithYear = (
|
||||||
|
dateObj: Date,
|
||||||
|
locale: FrontendLocaleData
|
||||||
|
) => formatShortDateTimeWithYearMem(locale).format(dateObj);
|
||||||
|
|
||||||
|
const formatShortDateTimeWithYearMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(
|
||||||
|
locale.language === "en" && !useAmPm(locale)
|
||||||
|
? "en-u-hc-h23"
|
||||||
|
: locale.language,
|
||||||
|
{
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: useAmPm(locale),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// Aug 9, 8:23 AM
|
// Aug 9, 8:23 AM
|
||||||
export const formatShortDateTime = (
|
export const formatShortDateTime = (
|
||||||
dateObj: Date,
|
dateObj: Date,
|
||||||
|
76
src/components/ha-absolute-time.ts
Normal file
76
src/components/ha-absolute-time.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { addDays, differenceInMilliseconds, startOfDay } from "date-fns";
|
||||||
|
import { PropertyValues, ReactiveElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { absoluteTime } from "../common/datetime/absolute_time";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
const SAFE_MARGIN = 5 * 1000;
|
||||||
|
|
||||||
|
@customElement("ha-absolute-time")
|
||||||
|
class HaAbsoluteTime extends ReactiveElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public datetime?: string | Date;
|
||||||
|
|
||||||
|
private _timeout?: number;
|
||||||
|
|
||||||
|
public disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._clearTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (this.datetime) {
|
||||||
|
this._updateNextDay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this._updateAbsolute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected update(changedProps: PropertyValues) {
|
||||||
|
super.update(changedProps);
|
||||||
|
this._updateAbsolute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _clearTimeout(): void {
|
||||||
|
if (this._timeout) {
|
||||||
|
window.clearTimeout(this._timeout);
|
||||||
|
this._timeout = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateNextDay(): void {
|
||||||
|
this._clearTimeout();
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const nextDay = addDays(startOfDay(now), 1);
|
||||||
|
const ms = differenceInMilliseconds(nextDay, now) + SAFE_MARGIN;
|
||||||
|
|
||||||
|
this._timeout = window.setTimeout(() => {
|
||||||
|
this._updateNextDay();
|
||||||
|
this._updateAbsolute();
|
||||||
|
}, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateAbsolute(): void {
|
||||||
|
if (!this.datetime) {
|
||||||
|
this.innerHTML = this.hass.localize("ui.components.absolute_time.never");
|
||||||
|
} else {
|
||||||
|
this.innerHTML = absoluteTime(new Date(this.datetime), this.hass.locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-absolute-time": HaAbsoluteTime;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, TemplateResult, css, CSSResultGroup } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||||
|
import "../../../components/ha-absolute-time";
|
||||||
|
import "../../../components/ha-relative-time";
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
import { isUnavailableState } from "../../../data/entity";
|
||||||
import { LightEntity } from "../../../data/light";
|
import { LightEntity } from "../../../data/light";
|
||||||
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
|
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
|
||||||
@ -16,6 +18,8 @@ export class HaMoreInfoStateHeader extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public stateOverride?: string;
|
@property({ attribute: false }) public stateOverride?: string;
|
||||||
|
|
||||||
|
@state() private _absoluteTime = false;
|
||||||
|
|
||||||
private _computeStateDisplay(stateObj: HassEntity): TemplateResult | string {
|
private _computeStateDisplay(stateObj: HassEntity): TemplateResult | string {
|
||||||
if (
|
if (
|
||||||
stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP &&
|
stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP &&
|
||||||
@ -41,15 +45,32 @@ export class HaMoreInfoStateHeader extends LitElement {
|
|||||||
return stateDisplay;
|
return stateDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
private _toggleAbsolute() {
|
||||||
const name = this.stateObj.attributes.friendly_name;
|
this._absoluteTime = !this._absoluteTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
const stateDisplay =
|
const stateDisplay =
|
||||||
this.stateOverride ?? this._computeStateDisplay(this.stateObj);
|
this.stateOverride ?? this._computeStateDisplay(this.stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<p class="name">${name}</p>
|
|
||||||
<p class="state">${stateDisplay}</p>
|
<p class="state">${stateDisplay}</p>
|
||||||
|
<p class="last-changed" @click=${this._toggleAbsolute}>
|
||||||
|
${this._absoluteTime
|
||||||
|
? html`
|
||||||
|
<ha-absolute-time
|
||||||
|
.hass=${this.hass}
|
||||||
|
.datetime=${this.stateObj.last_changed}
|
||||||
|
></ha-absolute-time>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-relative-time
|
||||||
|
.hass=${this.hass}
|
||||||
|
.datetime=${this.stateObj.last_changed}
|
||||||
|
capitalize
|
||||||
|
></ha-relative-time>
|
||||||
|
`}
|
||||||
|
</p>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,20 +80,24 @@ export class HaMoreInfoStateHeader extends LitElement {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.name {
|
.state {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 28px;
|
font-size: 36px;
|
||||||
line-height: 36px;
|
line-height: 44px;
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
.state {
|
.last-changed {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
letter-spacing: 0.1px;
|
letter-spacing: 0.1px;
|
||||||
margin-bottom: 24px;
|
padding: 4px 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ import { HomeAssistant } from "../../types";
|
|||||||
import {
|
import {
|
||||||
computeShowHistoryComponent,
|
computeShowHistoryComponent,
|
||||||
computeShowLogBookComponent,
|
computeShowLogBookComponent,
|
||||||
computeShowNewMoreInfo,
|
|
||||||
DOMAINS_WITH_MORE_INFO,
|
DOMAINS_WITH_MORE_INFO,
|
||||||
EDITABLE_DOMAINS_WITH_ID,
|
EDITABLE_DOMAINS_WITH_ID,
|
||||||
EDITABLE_DOMAINS_WITH_UNIQUE_ID,
|
EDITABLE_DOMAINS_WITH_UNIQUE_ID,
|
||||||
@ -240,7 +239,6 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
const title = this._childView?.viewTitle ?? name;
|
const title = this._childView?.viewTitle ?? name;
|
||||||
|
|
||||||
const isInfoView = this._currView === "info" && !this._childView;
|
const isInfoView = this._currView === "info" && !this._childView;
|
||||||
const isNewMoreInfo = stateObj && computeShowNewMoreInfo(stateObj);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog open @closed=${this.closeDialog} .heading=${title} hideActions>
|
<ha-dialog open @closed=${this.closeDialog} .heading=${title} hideActions>
|
||||||
@ -265,13 +263,9 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
)}
|
)}
|
||||||
></ha-icon-button-prev>
|
></ha-icon-button-prev>
|
||||||
`}
|
`}
|
||||||
${!isInfoView || !isNewMoreInfo
|
|
||||||
? html`
|
|
||||||
<span slot="title" .title=${title} @click=${this._enlarge}>
|
<span slot="title" .title=${title} @click=${this._enlarge}>
|
||||||
${title}
|
${title}
|
||||||
</span>
|
</span>
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${isInfoView
|
${isInfoView
|
||||||
? html`
|
? html`
|
||||||
${this.shouldShowHistory(domain)
|
${this.shouldShowHistory(domain)
|
||||||
|
@ -509,6 +509,9 @@
|
|||||||
"relative_time": {
|
"relative_time": {
|
||||||
"never": "Never"
|
"never": "Never"
|
||||||
},
|
},
|
||||||
|
"absolute_time": {
|
||||||
|
"never": "[%key:ui::components::relative_time::never%]"
|
||||||
|
},
|
||||||
"history_charts": {
|
"history_charts": {
|
||||||
"history_disabled": "History integration disabled",
|
"history_disabled": "History integration disabled",
|
||||||
"loading_history": "Loading state history…",
|
"loading_history": "Loading state history…",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user