ha-frontend/src/panels/config/scene/ha-scene-dashboard.ts

363 lines
11 KiB
TypeScript

import {
mdiContentDuplicate,
mdiDelete,
mdiHelpCircle,
mdiInformationOutline,
mdiPencilOff,
mdiPlay,
mdiPlus,
} from "@mdi/js";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { differenceInDays } from "date-fns/esm";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/ha-button-related-filter-menu";
import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-state-icon";
import "../../../components/ha-svg-icon";
import "../../../components/ha-icon-overflow-menu";
import { forwardHaptic } from "../../../data/haptics";
import {
activateScene,
deleteScene,
getSceneConfig,
SceneEntity,
showSceneEditor,
} from "../../../data/scene";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config";
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time";
import { isUnavailableState } from "../../../data/entity";
type SceneItem = SceneEntity & {
name: string;
};
@customElement("ha-scene-dashboard")
class HaSceneDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
@property() public route!: Route;
@property() public scenes!: SceneEntity[];
@property() private _activeFilters?: string[];
@state() private _filteredScenes?: string[] | null;
@state() private _filterValue?;
private _scenes = memoizeOne(
(scenes: SceneEntity[], filteredScenes?: string[] | null): SceneItem[] => {
if (filteredScenes === null) {
return [];
}
return (
filteredScenes
? scenes.filter((scene) => filteredScenes!.includes(scene.entity_id))
: scenes
).map((scene) => ({
...scene,
name: computeStateName(scene),
}));
}
);
private _columns = memoizeOne(
(_language, narrow): DataTableColumnContainer => {
const columns: DataTableColumnContainer<SceneItem> = {
icon: {
title: "",
label: this.hass.localize(
"ui.panel.config.scene.picker.headers.state"
),
type: "icon",
template: (scene) => html`
<ha-state-icon .state=${scene}></ha-state-icon>
`,
},
name: {
title: this.hass.localize(
"ui.panel.config.scene.picker.headers.name"
),
main: true,
sortable: true,
filterable: true,
direction: "asc",
grows: true,
},
};
if (!narrow) {
columns.state = {
title: this.hass.localize(
"ui.panel.config.scene.picker.headers.last_activated"
),
sortable: true,
width: "30%",
template: (scene) => {
const lastActivated = scene.state;
if (!lastActivated || isUnavailableState(lastActivated)) {
return this.hass.localize("ui.components.relative_time.never");
}
const date = new Date(scene.state);
const now = new Date();
const dayDifference = differenceInDays(now, date);
return html`
${dayDifference > 3
? formatShortDateTime(date, this.hass.locale, this.hass.config)
: relativeTime(date, this.hass.locale)}
`;
},
};
}
columns.only_editable = {
title: "",
width: "56px",
template: (scene) =>
!scene.attributes.id
? html`
<simple-tooltip animation-delay="0" position="left">
${this.hass.localize(
"ui.panel.config.scene.picker.only_editable"
)}
</simple-tooltip>
<ha-svg-icon
.path=${mdiPencilOff}
style="color: var(--secondary-text-color)"
></ha-svg-icon>
`
: "",
};
columns.actions = {
title: "",
width: "72px",
type: "overflow-menu",
template: (scene) => html`
<ha-icon-overflow-menu
.hass=${this.hass}
narrow
.items=${[
{
path: mdiInformationOutline,
label: this.hass.localize(
"ui.panel.config.scene.picker.show_info"
),
action: () => this._showInfo(scene),
},
{
path: mdiPlay,
label: this.hass.localize(
"ui.panel.config.scene.picker.activate"
),
action: () => this._activateScene(scene),
},
{
divider: true,
},
{
path: mdiContentDuplicate,
label: this.hass.localize(
"ui.panel.config.scene.picker.duplicate"
),
action: () => this._duplicate(scene),
disabled: !scene.attributes.id,
},
{
label: this.hass.localize(
"ui.panel.config.scene.picker.delete"
),
path: mdiDelete,
action: () => this._deleteConfirm(scene),
warning: scene.attributes.id,
disabled: !scene.attributes.id,
},
]}
>
</ha-icon-overflow-menu>
`,
};
return columns;
}
);
protected render(): TemplateResult {
return html`
<hass-tabs-subpage-data-table
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.automations}
.columns=${this._columns(this.hass.locale, this.narrow)}
id="entity_id"
.data=${this._scenes(this.scenes, this._filteredScenes)}
.activeFilters=${this._activeFilters}
.noDataText=${this.hass.localize(
"ui.panel.config.scene.picker.no_scenes"
)}
@clear-filter=${this._clearFilter}
hasFab
clickable
@row-click=${this._handleRowClicked}
>
<ha-icon-button
slot="toolbar-icon"
@click=${this._showHelp}
.label=${this.hass.localize("ui.common.help")}
.path=${mdiHelpCircle}
></ha-icon-button>
<ha-button-related-filter-menu
slot="filter-menu"
.narrow=${this.narrow}
.hass=${this.hass}
.value=${this._filterValue}
exclude-domains='["scene"]'
@related-changed=${this._relatedFilterChanged}
>
</ha-button-related-filter-menu>
<a href="/config/scene/edit/new" slot="fab">
<ha-fab
.label=${this.hass.localize(
"ui.panel.config.scene.picker.add_scene"
)}
extended
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>
</a>
</hass-tabs-subpage-data-table>
`;
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const scene = this.scenes.find((a) => a.entity_id === ev.detail.id);
if (scene?.attributes.id) {
navigate(`/config/scene/edit/${scene?.attributes.id}`);
}
}
private _relatedFilterChanged(ev: CustomEvent) {
this._filterValue = ev.detail.value;
if (!this._filterValue) {
this._clearFilter();
return;
}
this._activeFilters = [ev.detail.filter];
this._filteredScenes = ev.detail.items.scene || null;
}
private _clearFilter() {
this._filteredScenes = undefined;
this._activeFilters = undefined;
this._filterValue = undefined;
}
private _showInfo(scene: SceneEntity) {
fireEvent(this, "hass-more-info", { entityId: scene.entity_id });
}
private _activateScene = async (scene: SceneEntity) => {
await activateScene(this.hass, scene.entity_id);
showToast(this, {
message: this.hass.localize("ui.panel.config.scene.activated", {
name: computeStateName(scene),
}),
});
forwardHaptic("light");
};
private _deleteConfirm(scene: SceneEntity): void {
showConfirmationDialog(this, {
title: this.hass!.localize(
"ui.panel.config.scene.picker.delete_confirm_title"
),
text: this.hass!.localize(
"ui.panel.config.scene.picker.delete_confirm_text",
{ name: computeStateName(scene) }
),
confirmText: this.hass!.localize("ui.common.delete"),
dismissText: this.hass!.localize("ui.common.cancel"),
confirm: () => this._delete(scene),
destructive: true,
});
}
private async _delete(scene: SceneEntity): Promise<void> {
if (scene.attributes.id) {
await deleteScene(this.hass, scene.attributes.id);
}
}
private async _duplicate(scene) {
if (scene.attributes.id) {
const config = await getSceneConfig(this.hass, scene.attributes.id);
showSceneEditor({
...config,
id: undefined,
name: `${config?.name} (${this.hass.localize(
"ui.panel.config.scene.picker.duplicate"
)})`,
});
}
}
private _showHelp() {
showAlertDialog(this, {
title: this.hass.localize("ui.panel.config.scene.picker.header"),
text: html`
${this.hass.localize("ui.panel.config.scene.picker.introduction")}
<p>
<a
href=${documentationUrl(this.hass, "/docs/scene/editor/")}
target="_blank"
rel="noreferrer"
>
${this.hass.localize("ui.panel.config.scene.picker.learn_more")}
</a>
</p>
`,
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
a {
text-decoration: none;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-scene-dashboard": HaSceneDashboard;
}
}