mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +00:00
Edit sidebar in a dialog (#25532)
This commit is contained in:
parent
f0beef22d2
commit
32b3c83337
@ -25,6 +25,7 @@ export interface DisplayItem {
|
|||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
disableSorting?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DisplayValue {
|
export interface DisplayValue {
|
||||||
@ -50,6 +51,9 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "show-navigation-button" })
|
@property({ type: Boolean, attribute: "show-navigation-button" })
|
||||||
public showNavigationButton = false;
|
public showNavigationButton = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "dont-sort-visible" })
|
||||||
|
public dontSortVisible = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public value: DisplayValue = {
|
public value: DisplayValue = {
|
||||||
order: [],
|
order: [],
|
||||||
@ -122,9 +126,15 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
private _visibleItems = memoizeOne(
|
private _visibleItems = memoizeOne(
|
||||||
(items: DisplayItem[], hidden: string[], order: string[]) => {
|
(items: DisplayItem[], hidden: string[], order: string[]) => {
|
||||||
const compare = orderCompare(order);
|
const compare = orderCompare(order);
|
||||||
return items
|
|
||||||
.filter((item) => !hidden.includes(item.value))
|
const visibleItems = items.filter((item) => !hidden.includes(item.value));
|
||||||
.sort((a, b) => compare(a.value, b.value));
|
if (this.dontSortVisible) {
|
||||||
|
return visibleItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.sort((a, b) =>
|
||||||
|
a.disableSorting && !b.disableSorting ? -1 : compare(a.value, b.value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -160,7 +170,14 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
(item) => item.value,
|
(item) => item.value,
|
||||||
(item: DisplayItem, _idx) => {
|
(item: DisplayItem, _idx) => {
|
||||||
const isVisible = !this.value.hidden.includes(item.value);
|
const isVisible = !this.value.hidden.includes(item.value);
|
||||||
const { label, value, description, icon, iconPath } = item;
|
const {
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
iconPath,
|
||||||
|
disableSorting,
|
||||||
|
} = item;
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
type=${ifDefined(
|
type=${ifDefined(
|
||||||
@ -172,14 +189,14 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
.value=${value}
|
.value=${value}
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
hidden: !isVisible,
|
hidden: !isVisible,
|
||||||
draggable: isVisible,
|
draggable: isVisible && !disableSorting,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span slot="headline">${label}</span>
|
<span slot="headline">${label}</span>
|
||||||
${description
|
${description
|
||||||
? html`<span slot="supporting-text">${description}</span>`
|
? html`<span slot="supporting-text">${description}</span>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${isVisible
|
${isVisible && !disableSorting
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
class="handle"
|
class="handle"
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
|
||||||
import {
|
import {
|
||||||
mdiBell,
|
mdiBell,
|
||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
mdiCellphoneCog,
|
mdiCellphoneCog,
|
||||||
mdiChartBox,
|
mdiChartBox,
|
||||||
mdiClipboardList,
|
mdiClipboardList,
|
||||||
mdiClose,
|
|
||||||
mdiCog,
|
mdiCog,
|
||||||
mdiFormatListBulletedType,
|
mdiFormatListBulletedType,
|
||||||
mdiHammer,
|
mdiHammer,
|
||||||
@ -13,12 +11,11 @@ import {
|
|||||||
mdiMenu,
|
mdiMenu,
|
||||||
mdiMenuOpen,
|
mdiMenuOpen,
|
||||||
mdiPlayBoxMultiple,
|
mdiPlayBoxMultiple,
|
||||||
mdiPlus,
|
|
||||||
mdiTooltipAccount,
|
mdiTooltipAccount,
|
||||||
mdiViewDashboard,
|
mdiViewDashboard,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import type { CSSResult, CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
@ -29,7 +26,6 @@ import {
|
|||||||
} from "lit/decorators";
|
} from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { storage } from "../common/decorators/storage";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||||
import { stringCompare } from "../common/string/compare";
|
import { stringCompare } from "../common/string/compare";
|
||||||
@ -40,6 +36,7 @@ import { subscribeNotifications } from "../data/persistent_notification";
|
|||||||
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
||||||
import type { UpdateEntity } from "../data/update";
|
import type { UpdateEntity } from "../data/update";
|
||||||
import { updateCanInstall } from "../data/update";
|
import { updateCanInstall } from "../data/update";
|
||||||
|
import { showEditSidebarDialog } from "../dialogs/sidebar/show-dialog-edit-sidebar";
|
||||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
@ -49,8 +46,6 @@ import "./ha-icon-button";
|
|||||||
import "./ha-md-list";
|
import "./ha-md-list";
|
||||||
import "./ha-md-list-item";
|
import "./ha-md-list-item";
|
||||||
import type { HaMdListItem } from "./ha-md-list-item";
|
import type { HaMdListItem } from "./ha-md-list-item";
|
||||||
import "./ha-menu-button";
|
|
||||||
import "./ha-sortable";
|
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import "./user/ha-user-badge";
|
import "./user/ha-user-badge";
|
||||||
|
|
||||||
@ -67,7 +62,7 @@ const SORT_VALUE_URL_PATHS = {
|
|||||||
config: 11,
|
config: 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
const PANEL_ICONS = {
|
export const PANEL_ICONS = {
|
||||||
calendar: mdiCalendar,
|
calendar: mdiCalendar,
|
||||||
"developer-tools": mdiHammer,
|
"developer-tools": mdiHammer,
|
||||||
energy: mdiLightningBolt,
|
energy: mdiLightningBolt,
|
||||||
@ -140,7 +135,7 @@ const defaultPanelSorter = (
|
|||||||
return stringCompare(a.title!, b.title!, language);
|
return stringCompare(a.title!, b.title!, language);
|
||||||
};
|
};
|
||||||
|
|
||||||
const computePanels = memoizeOne(
|
export const computePanels = memoizeOne(
|
||||||
(
|
(
|
||||||
panels: HomeAssistant["panels"],
|
panels: HomeAssistant["panels"],
|
||||||
defaultPanel: HomeAssistant["defaultPanel"],
|
defaultPanel: HomeAssistant["defaultPanel"],
|
||||||
@ -192,8 +187,11 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
@property({ attribute: "always-expand", type: Boolean })
|
@property({ attribute: "always-expand", type: Boolean })
|
||||||
public alwaysExpand = false;
|
public alwaysExpand = false;
|
||||||
|
|
||||||
@property({ attribute: "edit-mode", type: Boolean })
|
@property({ attribute: false })
|
||||||
public editMode = false;
|
public panelOrder!: string[];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public hiddenPanels!: string[];
|
||||||
|
|
||||||
@state() private _notifications?: PersistentNotification[];
|
@state() private _notifications?: PersistentNotification[];
|
||||||
|
|
||||||
@ -207,26 +205,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _recentKeydownActiveUntil = 0;
|
private _recentKeydownActiveUntil = 0;
|
||||||
|
|
||||||
private _editStyleLoaded = false;
|
|
||||||
|
|
||||||
private _unsubPersistentNotifications: UnsubscribeFunc | undefined;
|
private _unsubPersistentNotifications: UnsubscribeFunc | undefined;
|
||||||
|
|
||||||
@state()
|
|
||||||
@storage({
|
|
||||||
key: "sidebarPanelOrder",
|
|
||||||
state: true,
|
|
||||||
subscribe: true,
|
|
||||||
})
|
|
||||||
private _panelOrder: string[] = [];
|
|
||||||
|
|
||||||
@state()
|
|
||||||
@storage({
|
|
||||||
key: "sidebarHiddenPanels",
|
|
||||||
state: true,
|
|
||||||
subscribe: true,
|
|
||||||
})
|
|
||||||
private _hiddenPanels: string[] = [];
|
|
||||||
|
|
||||||
@query(".tooltip") private _tooltip!: HTMLDivElement;
|
@query(".tooltip") private _tooltip!: HTMLDivElement;
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
@ -270,13 +250,12 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
changedProps.has("expanded") ||
|
changedProps.has("expanded") ||
|
||||||
changedProps.has("narrow") ||
|
changedProps.has("narrow") ||
|
||||||
changedProps.has("alwaysExpand") ||
|
changedProps.has("alwaysExpand") ||
|
||||||
changedProps.has("editMode") ||
|
|
||||||
changedProps.has("_externalConfig") ||
|
changedProps.has("_externalConfig") ||
|
||||||
changedProps.has("_updatesCount") ||
|
changedProps.has("_updatesCount") ||
|
||||||
changedProps.has("_issuesCount") ||
|
changedProps.has("_issuesCount") ||
|
||||||
changedProps.has("_notifications") ||
|
changedProps.has("_notifications") ||
|
||||||
changedProps.has("_hiddenPanels") ||
|
changedProps.has("hiddenPanels") ||
|
||||||
changedProps.has("_panelOrder")
|
changedProps.has("panelOrder")
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -322,9 +301,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
if (changedProps.has("alwaysExpand")) {
|
if (changedProps.has("alwaysExpand")) {
|
||||||
toggleAttribute(this, "expanded", this.alwaysExpand);
|
toggleAttribute(this, "expanded", this.alwaysExpand);
|
||||||
}
|
}
|
||||||
if (changedProps.has("editMode") && this.editMode) {
|
|
||||||
this._editModeActivated();
|
|
||||||
}
|
|
||||||
if (!changedProps.has("hass")) {
|
if (!changedProps.has("hass")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -374,8 +350,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
class="menu"
|
class="menu"
|
||||||
@action=${this._handleAction}
|
@action=${this._handleAction}
|
||||||
.actionHandler=${actionHandler({
|
.actionHandler=${actionHandler({
|
||||||
hasHold: !this.editMode,
|
hasHold: true,
|
||||||
disabled: this.editMode,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
${!this.narrow
|
${!this.narrow
|
||||||
@ -389,11 +364,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this.editMode
|
<div class="title">Home Assistant</div>
|
||||||
? html`<mwc-button outlined @click=${this._closeEditMode}>
|
|
||||||
${this.hass.localize("ui.sidebar.done")}
|
|
||||||
</mwc-button>`
|
|
||||||
: html`<div class="title">Home Assistant</div>`}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,14 +372,13 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
const [beforeSpacer, afterSpacer] = computePanels(
|
const [beforeSpacer, afterSpacer] = computePanels(
|
||||||
this.hass.panels,
|
this.hass.panels,
|
||||||
this.hass.defaultPanel,
|
this.hass.defaultPanel,
|
||||||
this._panelOrder,
|
this.panelOrder,
|
||||||
this._hiddenPanels,
|
this.hiddenPanels,
|
||||||
this.hass.locale
|
this.hass.locale
|
||||||
);
|
);
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return html`
|
return html`
|
||||||
<ha-sortable .disabled=${!this.editMode} draggable-selector=".draggable" @item-moved=${this._panelMoved}>
|
|
||||||
<ha-md-list
|
<ha-md-list
|
||||||
class="ha-scrollbar"
|
class="ha-scrollbar"
|
||||||
@focusin=${this._listboxFocusIn}
|
@focusin=${this._listboxFocusIn}
|
||||||
@ -416,22 +386,15 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
@scroll=${this._listboxScroll}
|
@scroll=${this._listboxScroll}
|
||||||
@keydown=${this._listboxKeydown}
|
@keydown=${this._listboxKeydown}
|
||||||
>
|
>
|
||||||
${this.editMode
|
${this._renderPanels(beforeSpacer, selectedPanel)}
|
||||||
? this._renderPanelsEdit(beforeSpacer, selectedPanel)
|
|
||||||
: this._renderPanels(beforeSpacer, selectedPanel)}
|
|
||||||
${this._renderSpacer()}
|
${this._renderSpacer()}
|
||||||
${this._renderPanels(afterSpacer, selectedPanel)}
|
${this._renderPanels(afterSpacer, selectedPanel)}
|
||||||
${this._renderExternalConfiguration()}
|
${this._renderExternalConfiguration()}
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
</ha-sortable>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderPanels(
|
private _renderPanels(panels: PanelInfo[], selectedPanel: string) {
|
||||||
panels: PanelInfo[],
|
|
||||||
selectedPanel: string,
|
|
||||||
sortable = false
|
|
||||||
) {
|
|
||||||
return panels.map((panel) =>
|
return panels.map((panel) =>
|
||||||
this._renderPanel(
|
this._renderPanel(
|
||||||
panel.url_path,
|
panel.url_path,
|
||||||
@ -444,36 +407,26 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
: panel.url_path in PANEL_ICONS
|
: panel.url_path in PANEL_ICONS
|
||||||
? PANEL_ICONS[panel.url_path]
|
? PANEL_ICONS[panel.url_path]
|
||||||
: undefined,
|
: undefined,
|
||||||
selectedPanel,
|
selectedPanel
|
||||||
sortable
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderPanelsEdit(beforeSpacer: PanelInfo[], selectedPanel: string) {
|
|
||||||
return html`
|
|
||||||
${this._renderPanels(beforeSpacer, selectedPanel, true)}
|
|
||||||
${this._renderSpacer()}${this._renderHiddenPanels()}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderPanel(
|
private _renderPanel(
|
||||||
urlPath: string,
|
urlPath: string,
|
||||||
title: string | null,
|
title: string | null,
|
||||||
icon: string | null | undefined,
|
icon: string | null | undefined,
|
||||||
iconPath: string | null | undefined,
|
iconPath: string | null | undefined,
|
||||||
selectedPanel: string,
|
selectedPanel: string
|
||||||
sortable = false
|
|
||||||
) {
|
) {
|
||||||
return urlPath === "config"
|
return urlPath === "config"
|
||||||
? this._renderConfiguration(title, selectedPanel)
|
? this._renderConfiguration(title, selectedPanel)
|
||||||
: html`
|
: html`
|
||||||
<ha-md-list-item
|
<ha-md-list-item
|
||||||
.href=${this.editMode ? undefined : `/${urlPath}`}
|
.href=${`/${urlPath}`}
|
||||||
type="link"
|
type="link"
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
selected: selectedPanel === urlPath,
|
selected: selectedPanel === urlPath,
|
||||||
draggable: this.editMode && sortable,
|
|
||||||
})}
|
})}
|
||||||
@mouseenter=${this._itemMouseEnter}
|
@mouseenter=${this._itemMouseEnter}
|
||||||
@mouseleave=${this._itemMouseLeave}
|
@mouseleave=${this._itemMouseLeave}
|
||||||
@ -482,81 +435,10 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
? html`<ha-svg-icon slot="start" .path=${iconPath}></ha-svg-icon>`
|
? html`<ha-svg-icon slot="start" .path=${iconPath}></ha-svg-icon>`
|
||||||
: html`<ha-icon slot="start" .icon=${icon}></ha-icon>`}
|
: html`<ha-icon slot="start" .icon=${icon}></ha-icon>`}
|
||||||
<span class="item-text" slot="headline">${title}</span>
|
<span class="item-text" slot="headline">${title}</span>
|
||||||
${this.editMode
|
|
||||||
? html`<ha-icon-button
|
|
||||||
.label=${this.hass.localize("ui.sidebar.hide_panel")}
|
|
||||||
.path=${mdiClose}
|
|
||||||
class="hide-panel"
|
|
||||||
.panel=${urlPath}
|
|
||||||
@click=${this._hidePanel}
|
|
||||||
slot="end"
|
|
||||||
></ha-icon-button>`
|
|
||||||
: nothing}
|
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _panelMoved(ev: CustomEvent) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const { oldIndex, newIndex } = ev.detail;
|
|
||||||
|
|
||||||
const [beforeSpacer] = computePanels(
|
|
||||||
this.hass.panels,
|
|
||||||
this.hass.defaultPanel,
|
|
||||||
this._panelOrder,
|
|
||||||
this._hiddenPanels,
|
|
||||||
this.hass.locale
|
|
||||||
);
|
|
||||||
|
|
||||||
const panelOrder = beforeSpacer.map((panel) => panel.url_path);
|
|
||||||
const panel = panelOrder.splice(oldIndex, 1)[0];
|
|
||||||
panelOrder.splice(newIndex, 0, panel);
|
|
||||||
|
|
||||||
this._panelOrder = panelOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderHiddenPanels() {
|
|
||||||
return html`${this._hiddenPanels.length
|
|
||||||
? html`${this._hiddenPanels.map((url) => {
|
|
||||||
const panel = this.hass.panels[url];
|
|
||||||
if (!panel) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return html`<ha-md-list-item
|
|
||||||
@click=${this._unhidePanel}
|
|
||||||
class="hidden-panel"
|
|
||||||
.panel=${url}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
${panel.url_path === this.hass.defaultPanel && !panel.icon
|
|
||||||
? html`<ha-svg-icon
|
|
||||||
slot="start"
|
|
||||||
.path=${PANEL_ICONS.lovelace}
|
|
||||||
></ha-svg-icon>`
|
|
||||||
: panel.url_path in PANEL_ICONS
|
|
||||||
? html`<ha-svg-icon
|
|
||||||
slot="start"
|
|
||||||
.path=${PANEL_ICONS[panel.url_path]}
|
|
||||||
></ha-svg-icon>`
|
|
||||||
: html`<ha-icon slot="start" .icon=${panel.icon}></ha-icon>`}
|
|
||||||
<span class="item-text" slot="headline"
|
|
||||||
>${panel.url_path === this.hass.defaultPanel
|
|
||||||
? this.hass.localize("panel.states")
|
|
||||||
: this.hass.localize(`panel.${panel.title}`) ||
|
|
||||||
panel.title}</span
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize("ui.sidebar.show_panel")}
|
|
||||||
.path=${mdiPlus}
|
|
||||||
class="show-panel"
|
|
||||||
slot="end"
|
|
||||||
></ha-icon-button>
|
|
||||||
</ha-md-list-item>`;
|
|
||||||
})}
|
|
||||||
${this._renderSpacer()}`
|
|
||||||
: ""}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderDivider() {
|
private _renderDivider() {
|
||||||
return html`<div class="divider"></div>`;
|
return html`<div class="divider"></div>`;
|
||||||
}
|
}
|
||||||
@ -677,48 +559,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(this, "hass-edit-sidebar", { editMode: true });
|
showEditSidebarDialog(this, {
|
||||||
|
saveCallback: this._saveSidebar,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _editModeActivated() {
|
private _saveSidebar = (order: string[], hidden: string[]) => {
|
||||||
await this._loadEditStyle();
|
fireEvent(this, "hass-edit-sidebar", {
|
||||||
}
|
order,
|
||||||
|
hidden,
|
||||||
private async _loadEditStyle() {
|
});
|
||||||
if (this._editStyleLoaded) return;
|
};
|
||||||
|
|
||||||
const editStylesImport = await import("../resources/ha-sidebar-edit-style");
|
|
||||||
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.innerHTML = (editStylesImport.sidebarEditStyle as CSSResult).cssText;
|
|
||||||
this.shadowRoot!.appendChild(style);
|
|
||||||
|
|
||||||
await this.updateComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _closeEditMode() {
|
|
||||||
fireEvent(this, "hass-edit-sidebar", { editMode: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _hidePanel(ev: Event) {
|
|
||||||
ev.preventDefault();
|
|
||||||
const panel = (ev.currentTarget as any).panel;
|
|
||||||
if (this._hiddenPanels.includes(panel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Make a copy for Memoize
|
|
||||||
this._hiddenPanels = [...this._hiddenPanels, panel];
|
|
||||||
// Remove it from the panel order
|
|
||||||
this._panelOrder = this._panelOrder.filter((order) => order !== panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _unhidePanel(ev: Event) {
|
|
||||||
ev.preventDefault();
|
|
||||||
const panel = (ev.currentTarget as any).panel;
|
|
||||||
this._hiddenPanels = this._hiddenPanels.filter(
|
|
||||||
(hidden) => hidden !== panel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _itemMouseEnter(ev: MouseEvent) {
|
private _itemMouseEnter(ev: MouseEvent) {
|
||||||
// On keypresses on the listbox, we're going to ignore mouse enter events
|
// On keypresses on the listbox, we're going to ignore mouse enter events
|
||||||
@ -875,12 +726,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
:host([expanded]) .title {
|
:host([expanded]) .title {
|
||||||
display: initial;
|
display: initial;
|
||||||
}
|
}
|
||||||
:host([expanded]) .menu mwc-button {
|
|
||||||
margin: 0 8px;
|
|
||||||
}
|
|
||||||
.menu mwc-button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.hidden-panel {
|
.hidden-panel {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
159
src/dialogs/sidebar/dialog-edit-sidebar.ts
Normal file
159
src/dialogs/sidebar/dialog-edit-sidebar.ts
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import "../../components/ha-dialog-header";
|
||||||
|
import "../../components/ha-icon-button";
|
||||||
|
import "../../components/ha-items-display-editor";
|
||||||
|
import type { DisplayValue } from "../../components/ha-items-display-editor";
|
||||||
|
import "../../components/ha-md-dialog";
|
||||||
|
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
||||||
|
import { computePanels, PANEL_ICONS } from "../../components/ha-sidebar";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import type { EditSidebarDialogParams } from "./show-dialog-edit-sidebar";
|
||||||
|
|
||||||
|
@customElement("dialog-edit-sidebar")
|
||||||
|
class DialogEditSidebar extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _open = false;
|
||||||
|
|
||||||
|
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||||
|
|
||||||
|
@state() private _order: string[] = [];
|
||||||
|
|
||||||
|
@state() private _hidden: string[] = [];
|
||||||
|
|
||||||
|
private _saveCallback?: (order: string[], hidden: string[]) => void;
|
||||||
|
|
||||||
|
public async showDialog(params: EditSidebarDialogParams): Promise<void> {
|
||||||
|
this._open = true;
|
||||||
|
|
||||||
|
const storedOrder = localStorage.getItem("sidebarPanelOrder");
|
||||||
|
const storedHidden = localStorage.getItem("sidebarHiddenPanels");
|
||||||
|
|
||||||
|
this._order = storedOrder ? JSON.parse(storedOrder) : this._order;
|
||||||
|
this._hidden = storedHidden ? JSON.parse(storedHidden) : this._hidden;
|
||||||
|
this._saveCallback = params.saveCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dialogClosed(): void {
|
||||||
|
this._open = false;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this._dialog?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _panels = memoizeOne((panels: HomeAssistant["panels"]) =>
|
||||||
|
panels ? Object.values(panels) : []
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._open) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogTitle = this.hass.localize("ui.sidebar.edit_sidebar");
|
||||||
|
|
||||||
|
const panels = this._panels(this.hass.panels);
|
||||||
|
|
||||||
|
const [beforeSpacer, afterSpacer] = computePanels(
|
||||||
|
this.hass.panels,
|
||||||
|
this.hass.defaultPanel,
|
||||||
|
this._order,
|
||||||
|
this._hidden,
|
||||||
|
this.hass.locale
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
...beforeSpacer,
|
||||||
|
...panels.filter((panel) => this._hidden.includes(panel.url_path)),
|
||||||
|
...afterSpacer.filter((panel) => panel.url_path !== "config"),
|
||||||
|
].map((panel) => ({
|
||||||
|
value: panel.url_path,
|
||||||
|
label:
|
||||||
|
panel.url_path === this.hass.defaultPanel
|
||||||
|
? panel.title || this.hass.localize("panel.states")
|
||||||
|
: this.hass.localize(`panel.${panel.title}`) || panel.title || "?",
|
||||||
|
icon: panel.icon || undefined,
|
||||||
|
iconPath:
|
||||||
|
panel.url_path === this.hass.defaultPanel && !panel.icon
|
||||||
|
? PANEL_ICONS.lovelace
|
||||||
|
: panel.url_path in PANEL_ICONS
|
||||||
|
? PANEL_ICONS[panel.url_path]
|
||||||
|
: undefined,
|
||||||
|
disableSorting: panel.url_path === "developer-tools",
|
||||||
|
}));
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-md-dialog open @closed=${this._dialogClosed}>
|
||||||
|
<ha-dialog-header slot="headline">
|
||||||
|
<ha-icon-button
|
||||||
|
slot="navigationIcon"
|
||||||
|
.label=${this.hass.localize("ui.common.close") ?? "Close"}
|
||||||
|
.path=${mdiClose}
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
></ha-icon-button>
|
||||||
|
<span slot="title" .title=${dialogTitle}> ${dialogTitle} </span>
|
||||||
|
</ha-dialog-header>
|
||||||
|
<div slot="content" class="content">
|
||||||
|
<ha-items-display-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${{
|
||||||
|
order: this._order,
|
||||||
|
hidden: this._hidden,
|
||||||
|
}}
|
||||||
|
.items=${items}
|
||||||
|
@value-changed=${this._changed}
|
||||||
|
dont-sort-visible
|
||||||
|
>
|
||||||
|
</ha-items-display-editor>
|
||||||
|
</div>
|
||||||
|
<div slot="actions">
|
||||||
|
<ha-button @click=${this.closeDialog}>
|
||||||
|
${this.hass.localize("ui.common.cancel")}
|
||||||
|
</ha-button>
|
||||||
|
<ha-button @click=${this._save}>
|
||||||
|
${this.hass.localize("ui.common.save")}
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
|
</ha-md-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _changed(ev: CustomEvent<{ value: DisplayValue }>): void {
|
||||||
|
const { order = [], hidden = [] } = ev.detail.value;
|
||||||
|
this._order = [...order];
|
||||||
|
this._hidden = [...hidden];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _save(): void {
|
||||||
|
this._saveCallback?.(this._order ?? [], this._hidden ?? []);
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-md-dialog {
|
||||||
|
min-width: 600px;
|
||||||
|
max-height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 600px), all and (max-height: 500px) {
|
||||||
|
ha-md-dialog {
|
||||||
|
--md-dialog-container-shape: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-edit-sidebar": DialogEditSidebar;
|
||||||
|
}
|
||||||
|
}
|
18
src/dialogs/sidebar/show-dialog-edit-sidebar.ts
Normal file
18
src/dialogs/sidebar/show-dialog-edit-sidebar.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface EditSidebarDialogParams {
|
||||||
|
saveCallback: (order: string[], hidden: string[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadEditSidebarDialog = () => import("./dialog-edit-sidebar");
|
||||||
|
|
||||||
|
export const showEditSidebarDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: EditSidebarDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-edit-sidebar",
|
||||||
|
dialogImport: loadEditSidebarDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -10,6 +10,7 @@ import { showNotificationDrawer } from "../dialogs/notifications/show-notificati
|
|||||||
import type { HomeAssistant, Route } from "../types";
|
import type { HomeAssistant, Route } from "../types";
|
||||||
import "./partial-panel-resolver";
|
import "./partial-panel-resolver";
|
||||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||||
|
import { storage } from "../common/decorators/storage";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -25,7 +26,8 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EditSideBarEvent {
|
interface EditSideBarEvent {
|
||||||
editMode: boolean;
|
order: string[];
|
||||||
|
hidden: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("home-assistant-main")
|
@customElement("home-assistant-main")
|
||||||
@ -42,6 +44,22 @@ export class HomeAssistantMain extends LitElement {
|
|||||||
|
|
||||||
@state() private _drawerOpen = false;
|
@state() private _drawerOpen = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
@storage({
|
||||||
|
key: "sidebarPanelOrder",
|
||||||
|
state: true,
|
||||||
|
subscribe: true,
|
||||||
|
})
|
||||||
|
private _panelOrder: string[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
|
@storage({
|
||||||
|
key: "sidebarHiddenPanels",
|
||||||
|
state: true,
|
||||||
|
subscribe: true,
|
||||||
|
})
|
||||||
|
private _hiddenPanels: string[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
listenMediaQuery("(max-width: 870px)", (matches) => {
|
listenMediaQuery("(max-width: 870px)", (matches) => {
|
||||||
@ -63,7 +81,8 @@ export class HomeAssistantMain extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${sidebarNarrow}
|
.narrow=${sidebarNarrow}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.editMode=${this._sidebarEditMode}
|
.panelOrder=${this._panelOrder}
|
||||||
|
.hiddenPanels=${this._hiddenPanels}
|
||||||
.alwaysExpand=${sidebarNarrow || this.hass.dockedSidebar === "docked"}
|
.alwaysExpand=${sidebarNarrow || this.hass.dockedSidebar === "docked"}
|
||||||
></ha-sidebar>
|
></ha-sidebar>
|
||||||
<partial-panel-resolver
|
<partial-panel-resolver
|
||||||
@ -90,17 +109,8 @@ export class HomeAssistantMain extends LitElement {
|
|||||||
this.addEventListener(
|
this.addEventListener(
|
||||||
"hass-edit-sidebar",
|
"hass-edit-sidebar",
|
||||||
(ev: HASSDomEvent<EditSideBarEvent>) => {
|
(ev: HASSDomEvent<EditSideBarEvent>) => {
|
||||||
this._sidebarEditMode = ev.detail.editMode;
|
this._panelOrder = ev.detail.order;
|
||||||
|
this._hiddenPanels = ev.detail.hidden;
|
||||||
if (this._sidebarEditMode) {
|
|
||||||
if (this._sidebarNarrow) {
|
|
||||||
this._drawerOpen = true;
|
|
||||||
} else {
|
|
||||||
fireEvent(this, "hass-dock-sidebar", {
|
|
||||||
dock: "docked",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import { isExternal } from "../../data/external";
|
|||||||
import type { CoreFrontendUserData } from "../../data/frontend";
|
import type { CoreFrontendUserData } from "../../data/frontend";
|
||||||
import { subscribeFrontendUserData } from "../../data/frontend";
|
import { subscribeFrontendUserData } from "../../data/frontend";
|
||||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
|
import { showEditSidebarDialog } from "../../dialogs/sidebar/show-dialog-edit-sidebar";
|
||||||
import "../../layouts/hass-tabs-subpage";
|
import "../../layouts/hass-tabs-subpage";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../types";
|
import type { HomeAssistant, Route } from "../../types";
|
||||||
@ -247,9 +248,18 @@ class HaProfileSectionGeneral extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _customizeSidebar() {
|
private _customizeSidebar() {
|
||||||
fireEvent(this, "hass-edit-sidebar", { editMode: true });
|
showEditSidebarDialog(this, {
|
||||||
|
saveCallback: this._saveSidebar,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _saveSidebar = (order: string[], hidden: string[]) => {
|
||||||
|
fireEvent(this, "hass-edit-sidebar", {
|
||||||
|
order,
|
||||||
|
hidden,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private _handleLogOut() {
|
private _handleLogOut() {
|
||||||
showConfirmationDialog(this, {
|
showConfirmationDialog(this, {
|
||||||
title: this.hass.localize("ui.panel.profile.logout_title"),
|
title: this.hass.localize("ui.panel.profile.logout_title"),
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import { css } from "lit";
|
|
||||||
|
|
||||||
export const sidebarEditStyle = css`
|
|
||||||
ha-sortable ha-md-list-item.draggable:nth-child(2n) {
|
|
||||||
animation-name: keyframes1;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
transform-origin: 50% 10%;
|
|
||||||
animation-delay: -0.75s;
|
|
||||||
animation-duration: 0.25s;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-sortable ha-md-list-item.draggable:nth-child(2n-1) {
|
|
||||||
animation-name: keyframes2;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
animation-direction: alternate;
|
|
||||||
transform-origin: 30% 5%;
|
|
||||||
animation-delay: -0.5s;
|
|
||||||
animation-duration: 0.33s;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-sortable ha-md-list-item.draggable {
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden-panel {
|
|
||||||
display: flex !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes keyframes1 {
|
|
||||||
0% {
|
|
||||||
transform: rotate(-1deg);
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: rotate(1.5deg);
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes keyframes2 {
|
|
||||||
0% {
|
|
||||||
transform: rotate(1deg);
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: rotate(-1.5deg);
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-panel,
|
|
||||||
.hide-panel {
|
|
||||||
display: none;
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([expanded]) .hide-panel {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([expanded]) .show-panel {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-md-list-item.hidden-panel,
|
|
||||||
ha-md-list-item.hidden-panel span,
|
|
||||||
ha-md-list-item.hidden-panel ha-icon[slot="start"] {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
@ -2021,9 +2021,7 @@
|
|||||||
"sidebar": {
|
"sidebar": {
|
||||||
"external_app_configuration": "App settings",
|
"external_app_configuration": "App settings",
|
||||||
"sidebar_toggle": "Sidebar toggle",
|
"sidebar_toggle": "Sidebar toggle",
|
||||||
"done": "Done",
|
"edit_sidebar": "Edit sidebar"
|
||||||
"hide_panel": "Hide panel",
|
|
||||||
"show_panel": "Show panel"
|
|
||||||
},
|
},
|
||||||
"panel": {
|
"panel": {
|
||||||
"my": {
|
"my": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user