mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Allow to convert a view to sections view (#22594)
* 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
This commit is contained in:
parent
3c8da03d66
commit
29d9b61319
@ -34,6 +34,7 @@ export interface LovelaceSectionElement extends HTMLElement {
|
||||
index?: number;
|
||||
cards?: HuiCard[];
|
||||
isStrategy: boolean;
|
||||
importOnly?: boolean;
|
||||
setConfig(config: LovelaceSectionConfig): void;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import "@material/mwc-button";
|
||||
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import {
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiCursorMove,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiPencil,
|
||||
@ -10,7 +10,7 @@ import {
|
||||
} 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";
|
||||
@ -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;
|
||||
@ -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"
|
||||
@ -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"
|
||||
@ -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;
|
||||
}
|
||||
@ -262,7 +309,7 @@ export class HuiCardEditMode extends LitElement {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.edit {
|
||||
.control {
|
||||
outline: none !important;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
@ -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;
|
||||
@ -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);
|
||||
|
@ -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,
|
||||
@ -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 {
|
||||
@ -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, {
|
||||
@ -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 {
|
||||
@ -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"
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -13,6 +13,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { navigate } from "../../../../common/navigate";
|
||||
import { deepEqual } from "../../../../common/util/deep-equal";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-circular-progress";
|
||||
import "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
@ -57,8 +58,10 @@ export class HuiDialogEditView extends LitElement {
|
||||
|
||||
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
|
||||
|
||||
@state() private _currentType = getViewType();
|
||||
|
||||
get _type(): string {
|
||||
return getViewType(this._config!);
|
||||
return getViewType(this._config);
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
@ -76,7 +79,6 @@ export class HuiDialogEditView extends LitElement {
|
||||
if (this._params.viewIndex === undefined) {
|
||||
this._config = {
|
||||
type: SECTIONS_VIEW_LAYOUT,
|
||||
sections: [generateDefaultSection(this.hass!.localize)],
|
||||
};
|
||||
this._dirty = false;
|
||||
return;
|
||||
@ -89,6 +91,7 @@ export class HuiDialogEditView extends LitElement {
|
||||
return;
|
||||
}
|
||||
this._config = view;
|
||||
this._currentType = this._type;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@ -159,12 +162,14 @@ export class HuiDialogEditView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
const isCompatibleViewType =
|
||||
this._config?.type === SECTIONS_VIEW_LAYOUT
|
||||
? this._config?.type === SECTIONS_VIEW_LAYOUT &&
|
||||
!this._config?.cards?.length
|
||||
: this._config?.type !== SECTIONS_VIEW_LAYOUT &&
|
||||
!this._config?.sections?.length;
|
||||
const convertToSection =
|
||||
this._type === SECTIONS_VIEW_LAYOUT &&
|
||||
this._currentType !== SECTIONS_VIEW_LAYOUT &&
|
||||
this._config?.cards?.length;
|
||||
const convertNotSupported =
|
||||
this._type !== SECTIONS_VIEW_LAYOUT &&
|
||||
this._currentType === SECTIONS_VIEW_LAYOUT &&
|
||||
this._config?.sections?.length;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
@ -224,16 +229,29 @@ export class HuiDialogEditView extends LitElement {
|
||||
: ``}
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
${!isCompatibleViewType
|
||||
${convertToSection
|
||||
? html`
|
||||
<ha-alert class="incompatible" alert-type="warning">
|
||||
${this._config?.type === SECTIONS_VIEW_LAYOUT
|
||||
? this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.type_warning_sections"
|
||||
)
|
||||
: this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.type_warning_others"
|
||||
)}
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.card_to_section_convert"
|
||||
)}
|
||||
<ha-button
|
||||
slot="action"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.convert_view"
|
||||
)}
|
||||
@click=${this._convertToSection}
|
||||
>
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
${convertNotSupported
|
||||
? html`
|
||||
<ha-alert alert-type="warning">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.section_to_card_not_supported"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
@ -258,7 +276,7 @@ export class HuiDialogEditView extends LitElement {
|
||||
${content}
|
||||
${this._params.viewIndex !== undefined
|
||||
? html`
|
||||
<mwc-button
|
||||
<ha-button
|
||||
class="warning"
|
||||
slot="secondaryAction"
|
||||
@click=${this._deleteConfirm}
|
||||
@ -266,15 +284,16 @@ export class HuiDialogEditView extends LitElement {
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.delete"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<mwc-button
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
?disabled=${!this._config ||
|
||||
this._saving ||
|
||||
!this._dirty ||
|
||||
!isCompatibleViewType}
|
||||
convertToSection ||
|
||||
convertNotSupported}
|
||||
@click=${this._save}
|
||||
>
|
||||
${this._saving
|
||||
@ -284,7 +303,7 @@ export class HuiDialogEditView extends LitElement {
|
||||
aria-label="Saving"
|
||||
></ha-circular-progress>`
|
||||
: nothing}
|
||||
${this.hass!.localize("ui.common.save")}</mwc-button
|
||||
${this.hass!.localize("ui.common.save")}</ha-button
|
||||
>
|
||||
</ha-dialog>
|
||||
`;
|
||||
@ -303,6 +322,54 @@ export class HuiDialogEditView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _convertToSection() {
|
||||
if (!this._params || !this._config) {
|
||||
return;
|
||||
}
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.convert_view_title"
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.convert_view_text"
|
||||
),
|
||||
confirmText: this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.convert_view_action"
|
||||
),
|
||||
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newConfig = {
|
||||
...this._config,
|
||||
};
|
||||
newConfig.type = SECTIONS_VIEW_LAYOUT;
|
||||
newConfig.sections = [generateDefaultSection(this.hass!.localize)];
|
||||
newConfig.path = undefined;
|
||||
const lovelace = this._params!.lovelace!;
|
||||
|
||||
try {
|
||||
await lovelace.saveConfig(
|
||||
addView(this.hass!, lovelace.config, newConfig)
|
||||
);
|
||||
if (this._params.saveCallback) {
|
||||
this._params.saveCallback(lovelace.config.views.length, newConfig);
|
||||
}
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
text: `${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.saving_failed"
|
||||
)}: ${err.message}`,
|
||||
});
|
||||
} finally {
|
||||
this._saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _delete(): Promise<void> {
|
||||
if (!this._params) {
|
||||
return;
|
||||
@ -366,7 +433,7 @@ export class HuiDialogEditView extends LitElement {
|
||||
const viewConf = {
|
||||
...this._config,
|
||||
};
|
||||
|
||||
// Ensure we have at least one section if we are in sections view
|
||||
if (viewConf.type === SECTIONS_VIEW_LAYOUT && !viewConf.sections?.length) {
|
||||
viewConf.sections = [generateDefaultSection(this.hass!.localize)];
|
||||
} else if (!viewConf.cards?.length) {
|
||||
@ -386,7 +453,7 @@ export class HuiDialogEditView extends LitElement {
|
||||
viewConf
|
||||
)
|
||||
);
|
||||
if (this._params.saveCallback) {
|
||||
if (this._params.saveCallback && this._creatingView) {
|
||||
this._params.saveCallback(
|
||||
this._params.viewIndex || lovelace.config.views.length,
|
||||
viewConf
|
||||
@ -479,7 +546,7 @@ export class HuiDialogEditView extends LitElement {
|
||||
text-transform: uppercase;
|
||||
padding: 0 20px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
ha-button.warning {
|
||||
margin-right: auto;
|
||||
margin-inline-end: auto;
|
||||
margin-inline-start: initial;
|
||||
@ -494,7 +561,10 @@ export class HuiDialogEditView extends LitElement {
|
||||
color: var(--error-color);
|
||||
border-bottom: 1px solid var(--error-color);
|
||||
}
|
||||
.incompatible {
|
||||
ha-alert {
|
||||
display: block;
|
||||
}
|
||||
ha-alert ha-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,26 @@ export class HuiViewEditor extends LitElement {
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc, viewType: string) =>
|
||||
[
|
||||
{
|
||||
name: "type",
|
||||
selector: {
|
||||
select: {
|
||||
options: (
|
||||
[
|
||||
SECTIONS_VIEW_LAYOUT,
|
||||
MASONRY_VIEW_LAYOUT,
|
||||
SIDEBAR_VIEW_LAYOUT,
|
||||
PANEL_VIEW_LAYOUT,
|
||||
] as const
|
||||
).map((type) => ({
|
||||
value: type,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.edit_view.types.${type}`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
name: "icon",
|
||||
@ -55,26 +75,6 @@ export class HuiViewEditor extends LitElement {
|
||||
boolean: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
selector: {
|
||||
select: {
|
||||
options: (
|
||||
[
|
||||
SECTIONS_VIEW_LAYOUT,
|
||||
SIDEBAR_VIEW_LAYOUT,
|
||||
PANEL_VIEW_LAYOUT,
|
||||
MASONRY_VIEW_LAYOUT,
|
||||
] as const
|
||||
).map((type) => ({
|
||||
value: type,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.edit_view.types.${type}`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
...(viewType === SECTIONS_VIEW_LAYOUT
|
||||
? ([
|
||||
{
|
||||
|
@ -833,6 +833,10 @@ class HUIRoot extends LitElement {
|
||||
showEditViewDialog(this, {
|
||||
lovelace: this.lovelace!,
|
||||
viewIndex: this._curView as number,
|
||||
saveCallback: (viewIndex: number, viewConfig: LovelaceViewConfig) => {
|
||||
const path = viewConfig.path || viewIndex;
|
||||
this._navigateToView(path);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,18 @@ const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
||||
delayOnTouchOnly: true,
|
||||
direction: "vertical",
|
||||
invertedSwapThreshold: 0.7,
|
||||
group: "card",
|
||||
} as HaSortableOptions;
|
||||
|
||||
const IMPORT_MODE_CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
||||
...CARD_SORTABLE_OPTIONS,
|
||||
sort: false,
|
||||
group: {
|
||||
name: "card",
|
||||
put: false,
|
||||
},
|
||||
};
|
||||
|
||||
export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@ -40,6 +50,8 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
|
||||
@property({ attribute: false }) public cards: HuiCard[] = [];
|
||||
|
||||
@property({ attribute: false }) public importOnly = false;
|
||||
|
||||
@state() _config?: LovelaceSectionConfig;
|
||||
|
||||
@state() _dragging = false;
|
||||
@ -67,21 +79,29 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
|
||||
const editMode = Boolean(this.lovelace?.editMode && !this.isStrategy);
|
||||
|
||||
const sortableOptions = this.importOnly
|
||||
? IMPORT_MODE_CARD_SORTABLE_OPTIONS
|
||||
: CARD_SORTABLE_OPTIONS;
|
||||
|
||||
return html`
|
||||
<ha-sortable
|
||||
.disabled=${!editMode}
|
||||
@drag-start=${this._dragStart}
|
||||
@drag-end=${this._dragEnd}
|
||||
group="card"
|
||||
draggable-selector=".card"
|
||||
.rollback=${false}
|
||||
.options=${CARD_SORTABLE_OPTIONS}
|
||||
.options=${sortableOptions}
|
||||
@item-moved=${this._cardMoved}
|
||||
@item-added=${this._cardAdded}
|
||||
@item-removed=${this._cardRemoved}
|
||||
invert-swap
|
||||
>
|
||||
<div class="container ${classMap({ "edit-mode": editMode })}">
|
||||
<div
|
||||
class="container ${classMap({
|
||||
"edit-mode": editMode,
|
||||
"import-only": this.importOnly,
|
||||
})}"
|
||||
>
|
||||
${repeat(
|
||||
cardsConfig,
|
||||
(cardConfig) => this._getKey(cardConfig),
|
||||
@ -117,6 +137,8 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
.lovelace=${this.lovelace!}
|
||||
.path=${cardPath}
|
||||
.hiddenOverlay=${this._dragging}
|
||||
.noEdit=${this.importOnly}
|
||||
.noDuplicate=${this.importOnly}
|
||||
>
|
||||
${card}
|
||||
</hui-card-edit-mode>
|
||||
@ -126,7 +148,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
`;
|
||||
}
|
||||
)}
|
||||
${editMode
|
||||
${editMode && !this.importOnly
|
||||
? html`
|
||||
<button
|
||||
class="add"
|
||||
@ -216,11 +238,14 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||
.container.edit-mode {
|
||||
padding: 8px;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
border-start-end-radius: 0px;
|
||||
border: 2px dashed var(--divider-color);
|
||||
min-height: var(--row-height);
|
||||
}
|
||||
|
||||
.container.edit-mode:not(.import-only) {
|
||||
border-start-end-radius: 0px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
position: relative;
|
||||
|
@ -43,6 +43,9 @@ export class HuiSection extends ReactiveElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public preview = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "import-only" })
|
||||
public importOnly = false;
|
||||
|
||||
@property({ type: Number }) public index!: number;
|
||||
|
||||
@property({ type: Number }) public viewIndex!: number;
|
||||
@ -128,6 +131,9 @@ export class HuiSection extends ReactiveElement {
|
||||
element.preview = this.preview;
|
||||
});
|
||||
}
|
||||
if (changedProperties.has("importOnly")) {
|
||||
this._layoutElement.importOnly = this.importOnly;
|
||||
}
|
||||
if (changedProperties.has("_cards")) {
|
||||
this._layoutElement.cards = this._cards;
|
||||
}
|
||||
|
@ -6,17 +6,20 @@ import { customElement, 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 memoizeOne from "memoize-one";
|
||||
import { clamp } from "../../../common/number/clamp";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-sortable";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-ripple";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HuiBadge } from "../badges/hui-badge";
|
||||
import "../badges/hui-view-badges";
|
||||
import type { HuiCard } from "../cards/hui-card";
|
||||
import "../components/hui-badge-edit-mode";
|
||||
import { addSection, deleteSection, moveSection } from "../editor/config-util";
|
||||
import { findLovelaceContainer } from "../editor/lovelace-path";
|
||||
@ -42,6 +45,8 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
@property({ attribute: false }) public badges: HuiBadge[] = [];
|
||||
|
||||
@property({ attribute: false }) public cards: HuiCard[] = [];
|
||||
|
||||
@state() private _config?: LovelaceViewConfig;
|
||||
|
||||
@state() private _sectionColumnCount = 0;
|
||||
@ -235,11 +240,46 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
</button>
|
||||
`
|
||||
: nothing}
|
||||
${editMode && this._config?.cards?.length
|
||||
? html`
|
||||
<div class="section imported-cards">
|
||||
<div class="imported-card-header">
|
||||
<p class="title">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.imported_cards_title"
|
||||
)}
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.imported_cards_description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<hui-section
|
||||
.lovelace=${this.lovelace}
|
||||
.hass=${this.hass}
|
||||
.config=${this._importedCardSectionConfig(
|
||||
this._config.cards
|
||||
)}
|
||||
.viewIndex=${this.index}
|
||||
preview
|
||||
import-only
|
||||
></hui-section>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
`;
|
||||
}
|
||||
|
||||
private _importedCardSectionConfig = memoizeOne(
|
||||
(cards: LovelaceCardConfig[]) => ({
|
||||
type: "grid",
|
||||
cards,
|
||||
})
|
||||
);
|
||||
|
||||
private _createSection(): void {
|
||||
const newConfig = addSection(this.lovelace!.config, this.index!, {
|
||||
type: "grid",
|
||||
@ -432,6 +472,33 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.imported-cards {
|
||||
--column-span: var(--column-count);
|
||||
--row-span: 1;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.imported-card-header {
|
||||
margin-top: 24px;
|
||||
padding: 16px 8px;
|
||||
border-top: 2px dashed var(--divider-color);
|
||||
}
|
||||
|
||||
.imported-card-header .title {
|
||||
margin: 0;
|
||||
color: var(--primary-text-color);
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
}
|
||||
.imported-card-header .subtitle {
|
||||
margin: 0;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -5611,14 +5611,18 @@
|
||||
"visibility": {
|
||||
"select_users": "Select which users should see this view in the navigation"
|
||||
},
|
||||
"type": "View type",
|
||||
"type_warning_sections": "You can not change your view to use the 'sections' view type because migration is not supported yet. Start from scratch with a new view if you want to experiment with the 'sections' view.",
|
||||
"type_warning_others": "You can not change your view to an other type because migration is not supported yet. Start from scratch with a new view if you want to use another view type.",
|
||||
"type": "Layout",
|
||||
"convert_view": "Convert",
|
||||
"convert_view_title": "Convert view layout",
|
||||
"convert_view_text": "It will create a new view using sections. This current view will stay untouched. All your cards will be imported so you can rearrange them freely.",
|
||||
"convert_view_action": "Create",
|
||||
"card_to_section_convert": "Convert your view to a section view.",
|
||||
"section_to_card_not_supported": "You can not change your section view to an other type. Start from scratch with a new view if you want to use another view type.",
|
||||
"types": {
|
||||
"sections": "Sections (default)",
|
||||
"masonry": "Masonry",
|
||||
"sidebar": "Sidebar",
|
||||
"panel": "Panel (single card)",
|
||||
"sections": "Sections"
|
||||
"panel": "Panel (single card)"
|
||||
},
|
||||
"subview": "Subview",
|
||||
"max_columns": "Max number of sections wide",
|
||||
@ -5704,7 +5708,9 @@
|
||||
"move_card": {
|
||||
"header": "Choose a view to move the card to",
|
||||
"strategy_error_title": "Impossible to move the card",
|
||||
"strategy_error_text_strategy": "Moving a card to an auto generated view is not supported."
|
||||
"strategy_error_text_strategy": "Moving a card to an auto generated view is not supported.",
|
||||
"success": "Card moved successfully",
|
||||
"error": "Error while moving card"
|
||||
},
|
||||
"change_position": {
|
||||
"title": "Change card position",
|
||||
@ -5723,8 +5729,8 @@
|
||||
"add_card": "[%key:ui::panel::lovelace::editor::edit_card::add%]",
|
||||
"create_section": "Create section",
|
||||
"default_section_title": "New section",
|
||||
"imported_card_section_title_view": "Imported cards from ''{view_title}'' view",
|
||||
"imported_card_section_title_default": "Imported cards from another view"
|
||||
"imported_cards_title": "Imported cards",
|
||||
"imported_cards_description": "These cards are imported from another view. They will only be displayed in edit mode. Move them into sections to display them in your view."
|
||||
},
|
||||
"delete_section": {
|
||||
"title": "Delete section",
|
||||
|
Loading…
x
Reference in New Issue
Block a user