247 lines
7.2 KiB
TypeScript
247 lines
7.2 KiB
TypeScript
import { mdiPlus } from "@mdi/js";
|
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
|
import { property, state } from "lit/decorators";
|
|
import { classMap } from "lit/directives/class-map";
|
|
import { repeat } from "lit/directives/repeat";
|
|
import { styleMap } from "lit/directives/style-map";
|
|
import { fireEvent } from "../../../common/dom/fire_event";
|
|
import type { HaSortableOptions } from "../../../components/ha-sortable";
|
|
import { LovelaceSectionElement } from "../../../data/lovelace";
|
|
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
|
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
|
import { haStyle } from "../../../resources/styles";
|
|
import type { HomeAssistant } from "../../../types";
|
|
import { HuiErrorCard } from "../cards/hui-error-card";
|
|
import "../components/hui-card-edit-mode";
|
|
import { moveCard } from "../editor/config-util";
|
|
import type { Lovelace, LovelaceCard } from "../types";
|
|
|
|
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
|
delay: 200,
|
|
delayOnTouchOnly: true,
|
|
direction: "vertical",
|
|
invertedSwapThreshold: 0.7,
|
|
} as HaSortableOptions;
|
|
|
|
export class GridSection extends LitElement implements LovelaceSectionElement {
|
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
|
|
@property({ attribute: false }) public lovelace?: Lovelace;
|
|
|
|
@property({ type: Number }) public index?: number;
|
|
|
|
@property({ type: Number }) public viewIndex?: number;
|
|
|
|
@property({ type: Boolean }) public isStrategy = false;
|
|
|
|
@property({ attribute: false }) public cards: Array<
|
|
LovelaceCard | HuiErrorCard
|
|
> = [];
|
|
|
|
@state() _config?: LovelaceSectionConfig;
|
|
|
|
@state() _dragging = false;
|
|
|
|
public setConfig(config: LovelaceSectionConfig): void {
|
|
this._config = config;
|
|
}
|
|
|
|
private _cardConfigKeys = new WeakMap<LovelaceCardConfig, string>();
|
|
|
|
private _getKey(cardConfig: LovelaceCardConfig) {
|
|
if (!this._cardConfigKeys.has(cardConfig)) {
|
|
this._cardConfigKeys.set(cardConfig, Math.random().toString());
|
|
}
|
|
return this._cardConfigKeys.get(cardConfig)!;
|
|
}
|
|
|
|
render() {
|
|
if (!this.cards || !this._config) return nothing;
|
|
|
|
const cardsConfig = this._config?.cards ?? [];
|
|
|
|
const editMode = Boolean(this.lovelace?.editMode && !this.isStrategy);
|
|
|
|
return html`
|
|
${this._config.title || this.lovelace?.editMode
|
|
? html`
|
|
<h2
|
|
class="title ${classMap({
|
|
placeholder: !this._config.title,
|
|
})}"
|
|
>
|
|
${this._config.title ||
|
|
this.hass.localize(
|
|
"ui.panel.lovelace.editor.section.unnamed_section"
|
|
)}
|
|
</h2>
|
|
`
|
|
: nothing}
|
|
<ha-sortable
|
|
.disabled=${!editMode}
|
|
@item-moved=${this._cardMoved}
|
|
@drag-start=${this._dragStart}
|
|
@drag-end=${this._dragEnd}
|
|
group="card"
|
|
draggable-selector=".card"
|
|
.path=${[this.viewIndex, this.index]}
|
|
.rollback=${false}
|
|
.options=${CARD_SORTABLE_OPTIONS}
|
|
invert-swap
|
|
>
|
|
<div class="container ${classMap({ "edit-mode": editMode })}">
|
|
${repeat(
|
|
cardsConfig,
|
|
(cardConfig) => this._getKey(cardConfig),
|
|
(_cardConfig, idx) => {
|
|
const card = this.cards![idx];
|
|
(card as any).editMode = editMode;
|
|
const size = card && (card as any).getGridSize?.();
|
|
return html`
|
|
<div
|
|
class="card"
|
|
style=${styleMap({
|
|
"--column-size": size?.[0],
|
|
"--row-size": size?.[1],
|
|
})}
|
|
>
|
|
${editMode
|
|
? html`
|
|
<hui-card-edit-mode
|
|
.hass=${this.hass}
|
|
.lovelace=${this.lovelace}
|
|
.path=${[this.viewIndex, this.index, idx]}
|
|
.hiddenOverlay=${this._dragging}
|
|
>
|
|
${card}
|
|
</hui-card-edit-mode>
|
|
`
|
|
: card}
|
|
</div>
|
|
`;
|
|
}
|
|
)}
|
|
${editMode
|
|
? html`
|
|
<button
|
|
class="add"
|
|
@click=${this._addCard}
|
|
aria-label=${this.hass.localize(
|
|
"ui.panel.lovelace.editor.section.add_card"
|
|
)}
|
|
.title=${this.hass.localize(
|
|
"ui.panel.lovelace.editor.section.add_card"
|
|
)}
|
|
>
|
|
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
|
|
</button>
|
|
`
|
|
: nothing}
|
|
</div>
|
|
</ha-sortable>
|
|
`;
|
|
}
|
|
|
|
private _cardMoved(ev) {
|
|
ev.stopPropagation();
|
|
const { oldIndex, newIndex, oldPath, newPath } = ev.detail;
|
|
const newConfig = moveCard(
|
|
this.lovelace!.config,
|
|
[...oldPath, oldIndex] as [number, number, number],
|
|
[...newPath, newIndex] as [number, number, number]
|
|
);
|
|
this.lovelace!.saveConfig(newConfig);
|
|
}
|
|
|
|
private _dragStart() {
|
|
this._dragging = true;
|
|
}
|
|
|
|
private _dragEnd() {
|
|
this._dragging = false;
|
|
}
|
|
|
|
private _addCard() {
|
|
fireEvent(this, "ll-create-card", { suggested: ["tile"] });
|
|
}
|
|
|
|
static get styles(): CSSResultGroup {
|
|
return [
|
|
haStyle,
|
|
css`
|
|
:host {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
.container {
|
|
--column-count: 4;
|
|
display: grid;
|
|
grid-template-columns: repeat(var(--column-count), minmax(0, 1fr));
|
|
grid-auto-rows: minmax(66px, auto);
|
|
gap: 8px;
|
|
padding: 0;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.container.edit-mode {
|
|
padding: 8px;
|
|
border-radius: var(--ha-card-border-radius, 12px);
|
|
border: 2px dashed var(--divider-color);
|
|
min-height: 66px;
|
|
}
|
|
|
|
.title {
|
|
color: var(--primary-text-color);
|
|
font-size: 20px;
|
|
font-weight: normal;
|
|
margin: 0px;
|
|
letter-spacing: 0.1px;
|
|
line-height: 32px;
|
|
min-height: 32px;
|
|
display: block;
|
|
padding: 24px 10px 10px;
|
|
}
|
|
|
|
.title.placeholder {
|
|
color: var(--secondary-text-color);
|
|
font-style: italic;
|
|
}
|
|
|
|
.card {
|
|
border-radius: var(--ha-card-border-radius, 12px);
|
|
position: relative;
|
|
grid-row: span var(--row-size, 1);
|
|
grid-column: span var(--column-size, 4);
|
|
}
|
|
|
|
.add {
|
|
outline: none;
|
|
grid-row: span var(--row-size, 1);
|
|
grid-column: span var(--column-size, 2);
|
|
background: none;
|
|
cursor: pointer;
|
|
border-radius: var(--ha-card-border-radius, 12px);
|
|
border: 2px dashed var(--primary-color);
|
|
height: 66px;
|
|
order: 1;
|
|
}
|
|
.add:focus {
|
|
border-style: solid;
|
|
}
|
|
.sortable-ghost {
|
|
border-radius: var(--ha-card-border-radius, 12px);
|
|
}
|
|
`,
|
|
];
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"hui-grid-section": GridSection;
|
|
}
|
|
}
|
|
|
|
customElements.define("hui-grid-section", GridSection);
|