391 lines
12 KiB
TypeScript
391 lines
12 KiB
TypeScript
import "@material/mwc-button";
|
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
|
import {
|
|
mdiContentCopy,
|
|
mdiContentCut,
|
|
mdiContentDuplicate,
|
|
mdiDelete,
|
|
mdiDotsVertical,
|
|
mdiFileMoveOutline,
|
|
mdiMinus,
|
|
mdiPlus,
|
|
} from "@mdi/js";
|
|
import deepClone from "deep-clone-simple";
|
|
import {
|
|
CSSResultGroup,
|
|
LitElement,
|
|
PropertyValues,
|
|
TemplateResult,
|
|
css,
|
|
html,
|
|
nothing,
|
|
} from "lit";
|
|
import { customElement, property, queryAssignedNodes } from "lit/decorators";
|
|
import { storage } from "../../../common/decorators/storage";
|
|
import { fireEvent } from "../../../common/dom/fire_event";
|
|
import "../../../components/ha-button-menu";
|
|
import "../../../components/ha-icon-button";
|
|
import "../../../components/ha-list-item";
|
|
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
|
import { saveConfig } from "../../../data/lovelace/config/types";
|
|
import {
|
|
showAlertDialog,
|
|
showPromptDialog,
|
|
} from "../../../dialogs/generic/show-dialog-box";
|
|
import { haStyle } from "../../../resources/styles";
|
|
import { HomeAssistant } from "../../../types";
|
|
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
|
|
import { computeCardSize } from "../common/compute-card-size";
|
|
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
|
|
import {
|
|
addCard,
|
|
deleteCard,
|
|
moveCardToContainer,
|
|
moveCardToIndex,
|
|
} from "../editor/config-util";
|
|
import {
|
|
LovelaceCardPath,
|
|
findLovelaceCards,
|
|
getLovelaceContainerPath,
|
|
parseLovelaceCardPath,
|
|
} from "../editor/lovelace-path";
|
|
import { showSelectViewDialog } from "../editor/select-view/show-select-view-dialog";
|
|
import { Lovelace, LovelaceCard } from "../types";
|
|
|
|
@customElement("hui-card-options")
|
|
export class HuiCardOptions extends LitElement {
|
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
|
|
|
@property({ attribute: false }) public lovelace?: Lovelace;
|
|
|
|
@property({ type: Array }) public path?: LovelaceCardPath;
|
|
|
|
@queryAssignedNodes() private _assignedNodes?: NodeListOf<LovelaceCard>;
|
|
|
|
@property({ type: Boolean }) public hidePosition = false;
|
|
|
|
@storage({
|
|
key: "lovelaceClipboard",
|
|
state: false,
|
|
subscribe: false,
|
|
storage: "sessionStorage",
|
|
})
|
|
protected _clipboard?: LovelaceCardConfig;
|
|
|
|
public getCardSize() {
|
|
return this._assignedNodes ? computeCardSize(this._assignedNodes[0]) : 1;
|
|
}
|
|
|
|
protected updated(changedProps: PropertyValues) {
|
|
if (!changedProps.has("path") || !this.path) {
|
|
return;
|
|
}
|
|
const { viewIndex } = parseLovelaceCardPath(this.path);
|
|
this.classList.toggle(
|
|
"panel",
|
|
this.lovelace!.config.views[viewIndex].panel
|
|
);
|
|
}
|
|
|
|
private get _cards() {
|
|
const containerPath = getLovelaceContainerPath(this.path!);
|
|
return findLovelaceCards(this.lovelace!.config, containerPath)!;
|
|
}
|
|
|
|
protected render(): TemplateResult {
|
|
const { cardIndex } = parseLovelaceCardPath(this.path!);
|
|
|
|
return html`
|
|
<div class="card"><slot></slot></div>
|
|
<ha-card>
|
|
<div class="card-actions">
|
|
<mwc-button @click=${this._editCard}
|
|
>${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.edit"
|
|
)}</mwc-button
|
|
>
|
|
<div class="right">
|
|
<slot name="buttons"></slot>
|
|
${!this.hidePosition
|
|
? html`
|
|
<ha-icon-button
|
|
.label=${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.decrease_position"
|
|
)}
|
|
.path=${mdiMinus}
|
|
class="move-arrow"
|
|
@click=${this._decreaseCardPosiion}
|
|
?disabled=${cardIndex === 0}
|
|
></ha-icon-button>
|
|
<ha-icon-button
|
|
@click=${this._changeCardPosition}
|
|
.label=${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.change_position"
|
|
)}
|
|
>
|
|
<div class="position-badge">${cardIndex + 1}</div>
|
|
</ha-icon-button>
|
|
<ha-icon-button
|
|
.label=${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.increase_position"
|
|
)}
|
|
.path=${mdiPlus}
|
|
class="move-arrow"
|
|
@click=${this._increaseCardPosition}
|
|
.disabled=${this._cards!.length === cardIndex + 1}
|
|
></ha-icon-button>
|
|
`
|
|
: nothing}
|
|
<ha-button-menu @action=${this._handleAction}>
|
|
<ha-icon-button
|
|
slot="trigger"
|
|
.label=${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.options"
|
|
)}
|
|
.path=${mdiDotsVertical}
|
|
></ha-icon-button>
|
|
<ha-list-item graphic="icon">
|
|
<ha-svg-icon
|
|
slot="graphic"
|
|
.path=${mdiFileMoveOutline}
|
|
></ha-svg-icon>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.move"
|
|
)}
|
|
</ha-list-item>
|
|
<ha-list-item graphic="icon">
|
|
<ha-svg-icon
|
|
slot="graphic"
|
|
.path=${mdiContentDuplicate}
|
|
></ha-svg-icon>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.duplicate"
|
|
)}
|
|
</ha-list-item>
|
|
<ha-list-item graphic="icon">
|
|
<ha-svg-icon
|
|
slot="graphic"
|
|
.path=${mdiContentCopy}
|
|
></ha-svg-icon>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.copy"
|
|
)}
|
|
</ha-list-item>
|
|
<ha-list-item graphic="icon">
|
|
<ha-svg-icon
|
|
slot="graphic"
|
|
.path=${mdiContentCut}
|
|
></ha-svg-icon>
|
|
${this.hass!.localize("ui.panel.lovelace.editor.edit_card.cut")}
|
|
</ha-list-item>
|
|
<li divider role="separator"></li>
|
|
<ha-list-item class="warning" graphic="icon">
|
|
<ha-svg-icon
|
|
class="warning"
|
|
slot="graphic"
|
|
.path=${mdiDelete}
|
|
></ha-svg-icon>
|
|
${this.hass!.localize(
|
|
"ui.panel.lovelace.editor.edit_card.delete"
|
|
)}
|
|
</ha-list-item>
|
|
</ha-button-menu>
|
|
</div>
|
|
</div>
|
|
</ha-card>
|
|
`;
|
|
}
|
|
|
|
static get styles(): CSSResultGroup {
|
|
return [
|
|
haStyle,
|
|
css`
|
|
:host(:hover) {
|
|
outline: 2px solid var(--primary-color);
|
|
}
|
|
|
|
:host(:not(.panel)) ::slotted(*) {
|
|
display: block;
|
|
}
|
|
|
|
:host(.panel) .card {
|
|
height: calc(100% - 59px);
|
|
}
|
|
|
|
ha-card {
|
|
border-top-right-radius: 0;
|
|
border-top-left-radius: 0;
|
|
}
|
|
|
|
.card-actions {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.right {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.position-badge {
|
|
display: block;
|
|
width: 24px;
|
|
line-height: 24px;
|
|
box-sizing: border-box;
|
|
border-radius: 50%;
|
|
font-weight: 500;
|
|
text-align: center;
|
|
font-size: 14px;
|
|
background-color: var(--app-header-edit-background-color, #455a64);
|
|
color: var(--app-header-edit-text-color, white);
|
|
}
|
|
|
|
ha-icon-button {
|
|
color: var(--primary-text-color);
|
|
}
|
|
|
|
ha-icon-button.move-arrow[disabled] {
|
|
color: var(--disabled-text-color);
|
|
}
|
|
|
|
ha-list-item {
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
`,
|
|
];
|
|
}
|
|
|
|
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
|
switch (ev.detail.index) {
|
|
case 0:
|
|
this._moveCard();
|
|
break;
|
|
case 1:
|
|
this._duplicateCard();
|
|
break;
|
|
case 2:
|
|
this._copyCard();
|
|
break;
|
|
case 3:
|
|
this._cutCard();
|
|
break;
|
|
case 4:
|
|
this._deleteCard(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private _duplicateCard(): void {
|
|
const { cardIndex } = parseLovelaceCardPath(this.path!);
|
|
const containerPath = getLovelaceContainerPath(this.path!);
|
|
const cardConfig = this._cards![cardIndex];
|
|
showEditCardDialog(this, {
|
|
lovelaceConfig: this.lovelace!.config,
|
|
saveConfig: this.lovelace!.saveConfig,
|
|
path: containerPath,
|
|
cardConfig,
|
|
});
|
|
}
|
|
|
|
private _editCard(): void {
|
|
fireEvent(this, "ll-edit-card", { path: this.path! });
|
|
}
|
|
|
|
private _cutCard(): void {
|
|
this._copyCard();
|
|
this._deleteCard(false);
|
|
}
|
|
|
|
private _copyCard(): void {
|
|
const { cardIndex } = parseLovelaceCardPath(this.path!);
|
|
const cardConfig = this._cards[cardIndex];
|
|
this._clipboard = deepClone(cardConfig);
|
|
}
|
|
|
|
private _decreaseCardPosiion(): void {
|
|
const lovelace = this.lovelace!;
|
|
const path = this.path!;
|
|
const { cardIndex } = parseLovelaceCardPath(path);
|
|
lovelace.saveConfig(moveCardToIndex(lovelace.config, path, cardIndex - 1));
|
|
}
|
|
|
|
private _increaseCardPosition(): void {
|
|
const lovelace = this.lovelace!;
|
|
const path = this.path!;
|
|
const { cardIndex } = parseLovelaceCardPath(path);
|
|
lovelace.saveConfig(moveCardToIndex(lovelace.config, path, cardIndex + 1));
|
|
}
|
|
|
|
private async _changeCardPosition(): Promise<void> {
|
|
const lovelace = this.lovelace!;
|
|
const path = this.path!;
|
|
const { cardIndex } = parseLovelaceCardPath(path);
|
|
const positionString = await showPromptDialog(this, {
|
|
title: this.hass!.localize(
|
|
"ui.panel.lovelace.editor.change_position.title"
|
|
),
|
|
text: this.hass!.localize(
|
|
"ui.panel.lovelace.editor.change_position.text"
|
|
),
|
|
inputType: "number",
|
|
inputMin: "1",
|
|
placeholder: String(cardIndex + 1),
|
|
});
|
|
|
|
if (!positionString) return;
|
|
|
|
const position = parseInt(positionString);
|
|
|
|
if (isNaN(position)) return;
|
|
|
|
const newIndex = position - 1;
|
|
lovelace.saveConfig(moveCardToIndex(lovelace.config, path, newIndex));
|
|
}
|
|
|
|
private _moveCard(): void {
|
|
showSelectViewDialog(this, {
|
|
lovelaceConfig: this.lovelace!.config,
|
|
urlPath: this.lovelace!.urlPath,
|
|
allowDashboardChange: true,
|
|
header: this.hass!.localize("ui.panel.lovelace.editor.move_card.header"),
|
|
viewSelectedCallback: async (urlPath, selectedDashConfig, viewIndex) => {
|
|
if (urlPath === this.lovelace!.urlPath) {
|
|
this.lovelace!.saveConfig(
|
|
moveCardToContainer(this.lovelace!.config, this.path!, [viewIndex])
|
|
);
|
|
showSaveSuccessToast(this, this.hass!);
|
|
return;
|
|
}
|
|
try {
|
|
const { cardIndex } = parseLovelaceCardPath(this.path!);
|
|
await saveConfig(
|
|
this.hass!,
|
|
urlPath,
|
|
addCard(selectedDashConfig, [viewIndex], this._cards[cardIndex])
|
|
);
|
|
this.lovelace!.saveConfig(
|
|
deleteCard(this.lovelace!.config, this.path!)
|
|
);
|
|
showSaveSuccessToast(this, this.hass!);
|
|
} catch (err: any) {
|
|
showAlertDialog(this, {
|
|
text: `Moving failed: ${err.message}`,
|
|
});
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
private _deleteCard(confirm: boolean): void {
|
|
fireEvent(this, "ll-delete-card", { path: this.path!, confirm });
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"hui-card-options": HuiCardOptions;
|
|
}
|
|
}
|