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:
Paul Bottein 2024-10-30 13:27:12 +01:00 committed by GitHub
parent 3c8da03d66
commit 29d9b61319
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 358 additions and 154 deletions

View File

@ -34,6 +34,7 @@ export interface LovelaceSectionElement extends HTMLElement {
index?: number;
cards?: HuiCard[];
isStrategy: boolean;
importOnly?: boolean;
setConfig(config: LovelaceSectionConfig): void;
}

View File

@ -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 })}">
${this.noEdit
? html`
<div class="control">
<div class="control-overlay"></div>
<ha-svg-icon .path=${mdiCursorMove}> </ha-svg-icon>
</div>
`
: html`
<div
class="edit"
class="control"
@click=${this._handleOverlayClick}
@keydown=${this._handleOverlayClick}
tabindex="0"
>
<div class="edit-overlay"></div>
<ha-svg-icon class="edit" .path=${mdiPencil}> </ha-svg-icon>
<div class="control-overlay"></div>
<ha-svg-icon .path=${mdiPencil}> </ha-svg-icon>
</div>
`}
<ha-button-menu
class="more"
corner="BOTTOM_END"
@ -130,11 +146,28 @@ export class HuiCardEditMode extends LitElement {
>
<ha-icon-button slot="trigger" .path=${mdiDotsVertical}>
</ha-icon-button>
<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")}
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.edit"
)}
</ha-list-item>
<ha-list-item graphic="icon">
`}
${this.noDuplicate
? nothing
: html`
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"duplicate"}
>
<ha-svg-icon
slot="graphic"
.path=${mdiPlusCircleMultipleOutline}
@ -143,16 +176,30 @@ export class HuiCardEditMode extends LitElement {
"ui.panel.lovelace.editor.edit_card.duplicate"
)}
</ha-list-item>
<ha-list-item graphic="icon">
`}
<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);

View File

@ -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"
),
});
}
},

View File

@ -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,15 +229,28 @@ 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>
`
@ -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;
}

View File

@ -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
? ([
{

View File

@ -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);
},
});
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
`;
}
}

View File

@ -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",