Skip to content

Commit

Permalink
Allow to convert a view to sections view (#22594)
Browse files Browse the repository at this point in the history
* Add imported cards in section view

* Add convert logic

* Improve editor

* Fix type

* Use imported container for individual card move

* Fix type import

* Add missing translations

* Feedback
  • Loading branch information
piitaya authored Oct 30, 2024
1 parent 3c8da03 commit 29d9b61
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 150 deletions.
1 change: 1 addition & 0 deletions src/data/lovelace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface LovelaceSectionElement extends HTMLElement {
index?: number;
cards?: HuiCard[];
isStrategy: boolean;
importOnly?: boolean;
setConfig(config: LovelaceSectionConfig): void;
}

Expand Down
123 changes: 85 additions & 38 deletions src/panels/lovelace/components/hui-card-edit-mode.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import "@material/mwc-button";
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import {
mdiContentCopy,
mdiContentCut,
mdiCursorMove,
mdiDelete,
mdiDotsVertical,
mdiPencil,
mdiPlusCircleMultipleOutline,
} from "@mdi/js";
import deepClone from "deep-clone-simple";
import type { CSSResultGroup, TemplateResult } from "lit";
import { LitElement, css, html } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { storage } from "../../../common/decorators/storage";
Expand Down Expand Up @@ -39,7 +39,14 @@ export class HuiCardEditMode extends LitElement {

@property({ type: Array }) public path!: LovelaceCardPath;

@property({ type: Boolean }) public hiddenOverlay = false;
@property({ type: Boolean, attribute: "hidden-overlay" })
public hiddenOverlay = false;

@property({ type: Boolean, attribute: "no-edit" })
public noEdit = false;

@property({ type: Boolean, attribute: "no-duplicate" })
public noDuplicate = false;

@state()
public _menuOpened: boolean = false;
Expand Down Expand Up @@ -110,15 +117,24 @@ export class HuiCardEditMode extends LitElement {
return html`
<div class="card-wrapper" inert><slot></slot></div>
<div class="card-overlay ${classMap({ visible: showOverlay })}">
<div
class="edit"
@click=${this._handleOverlayClick}
@keydown=${this._handleOverlayClick}
tabindex="0"
>
<div class="edit-overlay"></div>
<ha-svg-icon class="edit" .path=${mdiPencil}> </ha-svg-icon>
</div>
${this.noEdit
? html`
<div class="control">
<div class="control-overlay"></div>
<ha-svg-icon .path=${mdiCursorMove}> </ha-svg-icon>
</div>
`
: html`
<div
class="control"
@click=${this._handleOverlayClick}
@keydown=${this._handleOverlayClick}
tabindex="0"
>
<div class="control-overlay"></div>
<ha-svg-icon .path=${mdiPencil}> </ha-svg-icon>
</div>
`}
<ha-button-menu
class="more"
corner="BOTTOM_END"
Expand All @@ -130,29 +146,60 @@ export class HuiCardEditMode extends LitElement {
>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<ha-list-item graphic="icon">
<ha-svg-icon slot="graphic" .path=${mdiPencil}></ha-svg-icon>
${this.hass.localize("ui.panel.lovelace.editor.edit_card.edit")}
</ha-list-item>
<ha-list-item graphic="icon">
<ha-svg-icon
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.duplicate"
)}
</ha-list-item>
<ha-list-item graphic="icon">
${this.noEdit
? nothing
: html`
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"edit"}
>
<ha-svg-icon slot="graphic" .path=${mdiPencil}></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.edit"
)}
</ha-list-item>
`}
${this.noDuplicate
? nothing
: html`
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"duplicate"}
>
<ha-svg-icon
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.duplicate"
)}
</ha-list-item>
`}
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"copy"}
>
<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-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"cut"}
>
<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 graphic="icon" class="warning">
<ha-list-item
graphic="icon"
class="warning"
@click=${this._handleAction}
.action=${"delete"}
>
${this.hass.localize("ui.panel.lovelace.editor.edit_card.delete")}
<ha-svg-icon
class="warning"
Expand Down Expand Up @@ -185,21 +232,21 @@ export class HuiCardEditMode extends LitElement {
this._editCard();
}

private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
private _handleAction(ev) {
switch (ev.target.action) {
case "edit":
this._editCard();
break;
case 1:
case "duplicate":
this._duplicateCard();
break;
case 2:
case "copy":
this._copyCard();
break;
case 3:
case "cut":
this._cutCard();
break;
case 4:
case "delete":
this._deleteCard();
break;
}
Expand Down Expand Up @@ -262,7 +309,7 @@ export class HuiCardEditMode extends LitElement {
z-index: 0;
}
.edit {
.control {
outline: none !important;
cursor: pointer;
position: absolute;
Expand All @@ -273,7 +320,7 @@ export class HuiCardEditMode extends LitElement {
border-radius: var(--ha-card-border-radius, 12px);
z-index: 0;
}
.edit-overlay {
.control-overlay {
position: absolute;
inset: 0;
opacity: 0.8;
Expand All @@ -282,7 +329,7 @@ export class HuiCardEditMode extends LitElement {
border-radius: var(--ha-card-border-radius, 12px);
z-index: 0;
}
.edit ha-svg-icon {
.control ha-svg-icon {
display: flex;
position: relative;
color: var(--primary-text-color);
Expand Down
90 changes: 34 additions & 56 deletions src/panels/lovelace/components/hui-card-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,17 @@ import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import { saveConfig } from "../../../data/lovelace/config/types";
import {
isStrategyView,
type LovelaceViewConfig,
} from "../../../data/lovelace/config/view";
import { isStrategyView } from "../../../data/lovelace/config/view";
import {
showAlertDialog,
showPromptDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../resources/styles";
import type { 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,
addSection,
deleteCard,
moveCardToContainer,
moveCardToIndex,
Expand All @@ -50,8 +45,6 @@ import {
} from "../editor/lovelace-path";
import { showSelectViewDialog } from "../editor/select-view/show-select-view-dialog";
import type { Lovelace, LovelaceCard } from "../types";
import { SECTIONS_VIEW_LAYOUT } from "../views/const";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";

@customElement("hui-card-options")
export class HuiCardOptions extends LitElement {
Expand Down Expand Up @@ -352,9 +345,13 @@ export class HuiCardOptions extends LitElement {
allowDashboardChange: true,
header: this.hass!.localize("ui.panel.lovelace.editor.move_card.header"),
viewSelectedCallback: async (urlPath, selectedDashConfig, viewIndex) => {
const fromView = selectedDashConfig.views[this.path![0]];
let toView = selectedDashConfig.views[viewIndex];
let newConfig = selectedDashConfig;
if (!this.lovelace) return;
const toView = selectedDashConfig.views[viewIndex];
const newConfig = selectedDashConfig;

const undoAction = async () => {
this.lovelace!.saveConfig(selectedDashConfig);
};

if (isStrategyView(toView)) {
showAlertDialog(this, {
Expand All @@ -369,53 +366,22 @@ export class HuiCardOptions extends LitElement {
return;
}

const isSectionsView = toView.type === SECTIONS_VIEW_LAYOUT;

let toPath: LovelaceContainerPath = [viewIndex];

// If the view is a section view and has no "imported cards" section, adds a default section.
if (isSectionsView) {
const importedCardHeading = fromView.title
? this.hass!.localize(
"ui.panel.lovelace.editor.section.imported_card_section_title_view",
{ view_title: fromView.title }
)
: this.hass!.localize(
"ui.panel.lovelace.editor.section.imported_card_section_title_default"
);

let sectionIndex = toView.sections
? toView.sections.findIndex(
(s) =>
"cards" in s &&
s.cards?.some(
(c) =>
c.type === "heading" && c.heading === importedCardHeading
)
)
: -1;
if (sectionIndex === -1) {
const newSection: LovelaceSectionConfig = {
type: "grid",
cards: [
{
type: "heading",
heading: importedCardHeading,
},
],
};
newConfig = addSection(selectedDashConfig, viewIndex, newSection);
toView = newConfig.views[viewIndex] as LovelaceViewConfig;
sectionIndex = toView.sections!.length - 1;
}
toPath = [viewIndex, sectionIndex];
}
const toPath: LovelaceContainerPath = [viewIndex];

if (urlPath === this.lovelace!.urlPath) {
this.lovelace!.saveConfig(
moveCardToContainer(newConfig, this.path!, toPath)
);
showSaveSuccessToast(this, this.hass!);
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_card.success"
),
duration: 4000,
action: {
action: undoAction,
text: this.hass!.localize("ui.common.undo"),
},
});
return;
}
try {
Expand All @@ -429,10 +395,22 @@ export class HuiCardOptions extends LitElement {
this.lovelace!.saveConfig(
deleteCard(this.lovelace!.config, this.path!)
);
showSaveSuccessToast(this, this.hass!);

this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_card.success"
),
duration: 4000,
action: {
action: undoAction,
text: this.hass!.localize("ui.common.undo"),
},
});
} catch (err: any) {
showAlertDialog(this, {
text: `Moving failed: ${err.message}`,
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_card.error"
),
});
}
},
Expand Down
Loading

0 comments on commit 29d9b61

Please sign in to comment.