POC
This commit is contained in:
parent
6234f7b7d2
commit
539472bd96
|
@ -109,6 +109,7 @@
|
|||
"element-internals-polyfill": "1.3.10",
|
||||
"fuse.js": "7.0.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gridstack": "10.0.1",
|
||||
"hls.js": "1.5.1",
|
||||
"home-assistant-js-websocket": "9.1.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
|
|
|
@ -25,7 +25,6 @@ import "../ha-dialog";
|
|||
import "../ha-dialog-header";
|
||||
import "../ha-svg-icon";
|
||||
import "../ha-tip";
|
||||
import "./ha-media-player-browse";
|
||||
import "./ha-media-upload-button";
|
||||
import type { MediaManageDialogParams } from "./show-media-manage-dialog";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
|
|
|
@ -73,6 +73,7 @@ class HuiGridCard extends HuiStackCard<GridCardConfig> {
|
|||
super.sharedStyles,
|
||||
css`
|
||||
#root {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
var(--grid-card-column-count, ${DEFAULT_COLUMNS}),
|
||||
|
|
|
@ -453,12 +453,14 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||
.secondary=${localizedState}
|
||||
></ha-tile-info>
|
||||
</div>
|
||||
<hui-card-features
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
.color=${this._config.color}
|
||||
.features=${this._config.features}
|
||||
></hui-card-features>
|
||||
${this._config.features?.length
|
||||
? html`<hui-card-features
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
.color=${this._config.color}
|
||||
.features=${this._config.features}
|
||||
></hui-card-features>`
|
||||
: nothing}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
@ -481,6 +483,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||
transition:
|
||||
box-shadow 180ms ease-in-out,
|
||||
border-color 180ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
ha-card.active {
|
||||
--tile-color: var(--state-icon-color);
|
||||
|
|
|
@ -9,6 +9,7 @@ const ALWAYS_LOADED_LAYOUTS = new Set(["masonry"]);
|
|||
const LAZY_LOAD_LAYOUTS = {
|
||||
panel: () => import("../views/hui-panel-view"),
|
||||
sidebar: () => import("../views/hui-sidebar-view"),
|
||||
manual: () => import("../views/hui-manual-view"),
|
||||
};
|
||||
|
||||
export const createViewElement = (
|
||||
|
|
|
@ -212,6 +212,7 @@ export class HuiCreateDialogCard
|
|||
|
||||
showEditCardDialog(this, {
|
||||
lovelaceConfig: this._params!.lovelaceConfig,
|
||||
preSaveConfig: this._params!.preSaveConfig,
|
||||
saveConfig: this._params!.saveConfig,
|
||||
path: this._params!.path,
|
||||
cardConfig: config,
|
||||
|
|
|
@ -367,17 +367,20 @@ export class HuiDialogEditCard
|
|||
return;
|
||||
}
|
||||
this._saving = true;
|
||||
const cardConfig = this._params?.preSaveConfig
|
||||
? await this._params.preSaveConfig(this._cardConfig!)
|
||||
: this._cardConfig!;
|
||||
await this._params!.saveConfig(
|
||||
this._params!.path.length === 1
|
||||
? addCard(
|
||||
this._params!.lovelaceConfig,
|
||||
this._params!.path as [number],
|
||||
this._cardConfig!
|
||||
cardConfig
|
||||
)
|
||||
: replaceCard(
|
||||
this._params!.lovelaceConfig,
|
||||
this._params!.path as [number, number],
|
||||
this._cardConfig!
|
||||
cardConfig
|
||||
)
|
||||
);
|
||||
this._saving = false;
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
|
||||
export interface CreateCardDialogParams {
|
||||
lovelaceConfig: LovelaceConfig;
|
||||
preSaveConfig?: (
|
||||
config: LovelaceCardConfig
|
||||
) => LovelaceCardConfig | Promise<LovelaceCardConfig>;
|
||||
saveConfig: (config: LovelaceConfig) => void;
|
||||
path: [number] | [number, number];
|
||||
entities?: string[]; // We can pass entity id's that will be added to the config when a card is picked
|
||||
|
|
|
@ -4,6 +4,9 @@ import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
|||
|
||||
export interface EditCardDialogParams {
|
||||
lovelaceConfig: LovelaceConfig;
|
||||
preSaveConfig?: (
|
||||
config: LovelaceCardConfig
|
||||
) => LovelaceCardConfig | Promise<LovelaceCardConfig>;
|
||||
saveConfig: (config: LovelaceConfig) => void;
|
||||
path: [number] | [number, number];
|
||||
cardConfig?: LovelaceCardConfig;
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { SchemaUnion } from "../../../../components/ha-form/types";
|
|||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
DEFAULT_VIEW_LAYOUT,
|
||||
MANUAL_VIEW_LAYOUT,
|
||||
PANEL_VIEW_LAYOUT,
|
||||
SIDEBAR_VIEW_LAYOUT,
|
||||
} from "../../views/const";
|
||||
|
@ -53,6 +54,7 @@ export class HuiViewEditor extends LitElement {
|
|||
DEFAULT_VIEW_LAYOUT,
|
||||
SIDEBAR_VIEW_LAYOUT,
|
||||
PANEL_VIEW_LAYOUT,
|
||||
MANUAL_VIEW_LAYOUT,
|
||||
] as const
|
||||
).map((type) => ({
|
||||
value: type,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const DEFAULT_VIEW_LAYOUT = "masonry";
|
||||
export const PANEL_VIEW_LAYOUT = "panel";
|
||||
export const SIDEBAR_VIEW_LAYOUT = "sidebar";
|
||||
export const MANUAL_VIEW_LAYOUT = "manual";
|
||||
export const VIEWS_NO_BADGE_SUPPORT = [PANEL_VIEW_LAYOUT, SIDEBAR_VIEW_LAYOUT];
|
||||
|
|
|
@ -0,0 +1,358 @@
|
|||
import { mdiCursorMove, mdiDelete, mdiPencil, mdiPlus } from "@mdi/js";
|
||||
import { GridStack, GridStackWidget } from "gridstack";
|
||||
import gridStackStyleExtra from "gridstack/dist/gridstack-extra.min.css";
|
||||
import gridStackStyle from "gridstack/dist/gridstack.min.css";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
unsafeCSS,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/entity/ha-state-label-badge";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HuiErrorCard } from "../cards/hui-error-card";
|
||||
import { createCardElement } from "../custom-card-helpers";
|
||||
import { replaceView } from "../editor/config-util";
|
||||
import type { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
|
||||
|
||||
@customElement("hui-manual-view")
|
||||
export class ManualView extends LitElement implements LovelaceViewElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public lovelace?: Lovelace;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Number }) public index?: number;
|
||||
|
||||
@property({ type: Boolean }) public isStrategy = false;
|
||||
|
||||
@property({ attribute: false }) public cards: Array<
|
||||
LovelaceCard | HuiErrorCard
|
||||
> = [];
|
||||
|
||||
@property({ attribute: false }) public badges: LovelaceBadge[] = [];
|
||||
|
||||
public setConfig(_config: LovelaceViewConfig): void {}
|
||||
|
||||
private _grid?: GridStack;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._setupGrid();
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._grid?.destroy(false);
|
||||
this._grid = undefined;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.badges.length > 0
|
||||
? html`<div class="badges">${this.badges}</div>`
|
||||
: ""}
|
||||
<div class="grid-stack">
|
||||
${this.cards.map(
|
||||
(card, i) =>
|
||||
html`<div class="grid-stack-item" gs-id=${i}>
|
||||
${this.lovelace?.editMode
|
||||
? html` <div class="controls">
|
||||
<ha-svg-icon
|
||||
class="handle"
|
||||
.path=${mdiCursorMove}
|
||||
></ha-svg-icon>
|
||||
<ha-icon-button
|
||||
@click=${this._editCard}
|
||||
.path=${mdiPencil}
|
||||
.index=${i}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
@click=${this._deleteCard}
|
||||
.index=${i}
|
||||
class="warning"
|
||||
.path=${mdiDelete}
|
||||
></ha-icon-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
<div class="grid-stack-item-content">${card}</div>
|
||||
</div>`
|
||||
)}
|
||||
</div>
|
||||
${this.lovelace?.editMode
|
||||
? html`
|
||||
<ha-fab
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.add"
|
||||
)}
|
||||
extended
|
||||
@click=${this._addCard}
|
||||
class=${classMap({
|
||||
rtl: computeRTL(this.hass!),
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated(changed) {
|
||||
super.firstUpdated(changed);
|
||||
this._setupGrid();
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues) {
|
||||
if (
|
||||
changedProperties.has("lovelace") &&
|
||||
this.lovelace?.editMode !== changedProperties.get("lovelace")?.editMode
|
||||
) {
|
||||
if (this.lovelace?.editMode) {
|
||||
this._grid!.setStatic(false);
|
||||
this._grid!.setAnimation(true);
|
||||
// this.grid.addWidget(
|
||||
// '<div class="grid-stack-item"><div class="grid-stack-item-content">hello</div></div>',
|
||||
// { w: 3 }
|
||||
// );
|
||||
} else {
|
||||
this._grid!.setStatic(true);
|
||||
this._grid!.setAnimation(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("cards") &&
|
||||
changedProperties.get("cards") &&
|
||||
!this.lovelace?.editMode
|
||||
) {
|
||||
this._grid!.load(
|
||||
(
|
||||
this.lovelace?.config.views[this.index!] as LovelaceViewConfig
|
||||
).cards?.map((card, i) => ({
|
||||
id: i.toString(),
|
||||
...card.view_layout,
|
||||
})) || [],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues) {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (
|
||||
changedProperties.has("lovelace") &&
|
||||
this.lovelace?.editMode !== changedProperties.get("lovelace")?.editMode
|
||||
) {
|
||||
if (this.lovelace?.editMode) {
|
||||
import("./default-view-editable");
|
||||
} else if (this._grid) {
|
||||
this._saveLayout();
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProperties.has("hass")) {
|
||||
const oldHass = changedProperties.get("hass") as
|
||||
| HomeAssistant
|
||||
| undefined;
|
||||
|
||||
if (this.hass!.dockedSidebar !== oldHass?.dockedSidebar) {
|
||||
// this._updateColumns();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProperties.has("narrow")) {
|
||||
// this._updateColumns();
|
||||
return;
|
||||
}
|
||||
|
||||
const oldLovelace = changedProperties.get("lovelace") as
|
||||
| Lovelace
|
||||
| undefined;
|
||||
|
||||
if (
|
||||
changedProperties.has("cards") ||
|
||||
(changedProperties.has("lovelace") &&
|
||||
oldLovelace &&
|
||||
(oldLovelace.config !== this.lovelace!.config ||
|
||||
oldLovelace.editMode !== this.lovelace!.editMode))
|
||||
) {
|
||||
// this._createColumns();
|
||||
}
|
||||
}
|
||||
|
||||
private async _editCard(ev): Promise<void> {
|
||||
const index = ev.target.index;
|
||||
fireEvent(this, "ll-edit-card", { path: [this.index!, index] });
|
||||
}
|
||||
|
||||
private async _deleteCard(ev): Promise<void> {
|
||||
const index = ev.target.index;
|
||||
fireEvent(this, "ll-delete-card", {
|
||||
path: [this.index!, index],
|
||||
confirm: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async _addCard(): Promise<void> {
|
||||
fireEvent(this, "ll-create-card", {
|
||||
preSaveConfig: async (config) => {
|
||||
const card = createCardElement(config);
|
||||
const height = await card.getCardSize();
|
||||
const add = this._grid!.addWidget({
|
||||
w: 3,
|
||||
h: height,
|
||||
content: "",
|
||||
});
|
||||
return {
|
||||
...config,
|
||||
view_layout: {
|
||||
x: add.gridstackNode!.x,
|
||||
y: add.gridstackNode!.y,
|
||||
w: add.gridstackNode!.w,
|
||||
h: add.gridstackNode!.h,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _saveLayout(): Promise<void> {
|
||||
if (!this._grid || !this.lovelace?.editMode) {
|
||||
return;
|
||||
}
|
||||
const layouts = this._grid.save(false) as GridStackWidget[];
|
||||
layouts
|
||||
.sort((a, b) => Number(a.id!) - Number(b.id!))
|
||||
.forEach((layout) => {
|
||||
delete layout.id;
|
||||
});
|
||||
const cardConfigs = (
|
||||
this.lovelace?.config.views[this.index!] as LovelaceViewConfig
|
||||
).cards?.map((card, i) => ({
|
||||
...card,
|
||||
view_layout: layouts[i],
|
||||
}));
|
||||
await this.lovelace!.saveConfig(
|
||||
replaceView(this.hass!, this.lovelace!.config, this.index!, {
|
||||
...this.lovelace!.config.views[this.index!],
|
||||
cards: cardConfigs,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _setupGrid(): void {
|
||||
this._grid = GridStack.init(
|
||||
{
|
||||
cellHeight: 60,
|
||||
animate: false,
|
||||
columnOpts: {
|
||||
layout: "moveScale",
|
||||
// breakpointForWindow: true, // test window vs grid size
|
||||
breakpoints: [
|
||||
{ w: 700, c: 1 },
|
||||
{ w: 850, c: 4 },
|
||||
{ w: 950, c: 8 },
|
||||
{ w: 1100, c: 12 },
|
||||
],
|
||||
},
|
||||
minRow: 5,
|
||||
sizeToContent: false,
|
||||
handleClass: "handle",
|
||||
staticGrid: true,
|
||||
margin: 4,
|
||||
},
|
||||
this.shadowRoot!.querySelector(".grid-stack") as HTMLElement
|
||||
);
|
||||
this._grid.load(
|
||||
(
|
||||
this.lovelace?.config.views[this.index!] as LovelaceViewConfig
|
||||
).cards?.map((card, i) => ({
|
||||
id: i.toString(),
|
||||
...card.view_layout,
|
||||
})) || [],
|
||||
false
|
||||
);
|
||||
this._grid.on("dragstop resizestop", () => {
|
||||
this._saveLayout();
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(gridStackStyle)}
|
||||
${unsafeCSS(gridStackStyleExtra)}
|
||||
:host {
|
||||
display: block;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.grid-stack {
|
||||
height: 100vh;
|
||||
margin: 4px;
|
||||
}
|
||||
.grid-stack-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: none;
|
||||
z-index: 999;
|
||||
= }
|
||||
|
||||
.grid-stack-item:hover .controls {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.handle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab !important;
|
||||
}
|
||||
|
||||
|
||||
.badges {
|
||||
margin: 8px 16px;
|
||||
font-size: 85%;
|
||||
text-align: center;
|
||||
}
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
ha-fab.rtl {
|
||||
right: auto;
|
||||
left: calc(16px + env(safe-area-inset-left));
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-manual-view": ManualView;
|
||||
}
|
||||
}
|
|
@ -35,7 +35,13 @@ import {
|
|||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"ll-create-card": undefined;
|
||||
"ll-create-card":
|
||||
| {
|
||||
preSaveConfig?: (
|
||||
config: LovelaceCardConfig
|
||||
) => LovelaceCardConfig | Promise<LovelaceCardConfig>;
|
||||
}
|
||||
| undefined;
|
||||
"ll-edit-card": { path: [number] | [number, number] };
|
||||
"ll-delete-card": { path: [number] | [number, number]; confirm: boolean };
|
||||
}
|
||||
|
@ -236,9 +242,10 @@ export class HUIView extends ReactiveElement {
|
|||
private _createLayoutElement(config: LovelaceViewConfig): void {
|
||||
this._layoutElement = createViewElement(config) as LovelaceViewElement;
|
||||
this._layoutElementType = config.type;
|
||||
this._layoutElement.addEventListener("ll-create-card", () => {
|
||||
this._layoutElement.addEventListener("ll-create-card", (ev) => {
|
||||
showCreateCardDialog(this, {
|
||||
lovelaceConfig: this.lovelace.config,
|
||||
preSaveConfig: ev.detail.preSaveConfig,
|
||||
saveConfig: this.lovelace.saveConfig,
|
||||
path: [this.index],
|
||||
});
|
||||
|
|
|
@ -4984,7 +4984,8 @@
|
|||
"types": {
|
||||
"masonry": "Masonry (default)",
|
||||
"sidebar": "Sidebar",
|
||||
"panel": "Panel (1 card)"
|
||||
"panel": "Panel (1 card)",
|
||||
"manual": "Manual"
|
||||
},
|
||||
"subview": "Subview",
|
||||
"subview_helper": "Subviews don't appear in tabs and have a back button.",
|
||||
|
|
|
@ -9235,6 +9235,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gridstack@npm:10.0.1":
|
||||
version: 10.0.1
|
||||
resolution: "gridstack@npm:10.0.1"
|
||||
checksum: 5310d1e299f01bba68162d1cf69725248a3a4baee2088276927597f74894da6a432981d2e1879464faee91f0011fc4f6cd8b71dceb812a175bb7cbcf4ab7ea8b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gulp-cli@npm:^2.2.0":
|
||||
version: 2.3.0
|
||||
resolution: "gulp-cli@npm:2.3.0"
|
||||
|
@ -9613,6 +9620,7 @@ __metadata:
|
|||
fuse.js: "npm:7.0.0"
|
||||
glob: "npm:10.3.10"
|
||||
google-timezones-json: "npm:1.2.0"
|
||||
gridstack: "npm:10.0.1"
|
||||
gulp: "npm:4.0.2"
|
||||
gulp-flatmap: "npm:1.0.2"
|
||||
gulp-json-transform: "npm:0.4.8"
|
||||
|
|
Loading…
Reference in New Issue