mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
Allow an external sidebar (#11347)
This commit is contained in:
parent
bbcec38450
commit
6c12a5a4b1
@ -9,7 +9,6 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
import { getExternalConfig } from "../external_app/external_config";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-alert";
|
||||
|
||||
@ -91,18 +90,9 @@ class HaHLSPlayer extends LitElement {
|
||||
this._startHls();
|
||||
}
|
||||
|
||||
private async _getUseExoPlayer(): Promise<boolean> {
|
||||
if (!this.hass!.auth.external || !this.allowExoPlayer) {
|
||||
return false;
|
||||
}
|
||||
const externalConfig = await getExternalConfig(this.hass!.auth.external);
|
||||
return externalConfig && externalConfig.hasExoPlayer;
|
||||
}
|
||||
|
||||
private async _startHls(): Promise<void> {
|
||||
this._error = undefined;
|
||||
|
||||
const useExoPlayerPromise = this._getUseExoPlayer();
|
||||
const masterPlaylistPromise = fetch(this.url);
|
||||
|
||||
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min"))
|
||||
@ -126,7 +116,8 @@ class HaHLSPlayer extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const useExoPlayer = await useExoPlayerPromise;
|
||||
const useExoPlayer =
|
||||
this.allowExoPlayer && this.hass.auth.external?.config.hasExoPlayer;
|
||||
const masterPlaylist = await (await masterPlaylistPromise).text();
|
||||
|
||||
if (!this.isConnected) {
|
||||
|
@ -44,10 +44,6 @@ import {
|
||||
PersistentNotification,
|
||||
subscribeNotifications,
|
||||
} from "../data/persistent_notification";
|
||||
import {
|
||||
ExternalConfig,
|
||||
getExternalConfig,
|
||||
} from "../external_app/external_config";
|
||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||
@ -192,8 +188,6 @@ class HaSidebar extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public editMode = false;
|
||||
|
||||
@state() private _externalConfig?: ExternalConfig;
|
||||
|
||||
@state() private _notifications?: PersistentNotification[];
|
||||
|
||||
@state() private _renderEmptySortable = false;
|
||||
@ -270,13 +264,6 @@ class HaSidebar extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
if (this.hass && this.hass.auth.external) {
|
||||
getExternalConfig(this.hass.auth.external).then((conf) => {
|
||||
this._externalConfig = conf;
|
||||
});
|
||||
}
|
||||
|
||||
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||
this._notifications = notifications;
|
||||
});
|
||||
@ -559,8 +546,7 @@ class HaSidebar extends LitElement {
|
||||
|
||||
private _renderExternalConfiguration() {
|
||||
return html`${!this.hass.user?.is_admin &&
|
||||
this._externalConfig &&
|
||||
this._externalConfig.hasSettingsScreen
|
||||
this.hass.auth.external?.config.hasSettingsScreen
|
||||
? html`
|
||||
<a
|
||||
role="option"
|
||||
|
52
src/external_app/external_app_entrypoint.ts
Normal file
52
src/external_app/external_app_entrypoint.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
All commands that do UI stuff need to be loaded from the app bundle as UI stuff
|
||||
in core bundle slows things down and causes duplicate registration.
|
||||
|
||||
This is the entry point for providing external app stuff from app entrypoint.
|
||||
*/
|
||||
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistantMain } from "../layouts/home-assistant-main";
|
||||
import type { EMExternalMessageCommands } from "./external_messaging";
|
||||
|
||||
export const attachExternalToApp = (hassMainEl: HomeAssistantMain) => {
|
||||
window.addEventListener("haptic", (ev) =>
|
||||
hassMainEl.hass.auth.external!.fireMessage({
|
||||
type: "haptic",
|
||||
payload: { hapticType: ev.detail },
|
||||
})
|
||||
);
|
||||
|
||||
hassMainEl.hass.auth.external!.addCommandHandler((msg) =>
|
||||
handleExternalMessage(hassMainEl, msg)
|
||||
);
|
||||
};
|
||||
|
||||
const handleExternalMessage = (
|
||||
hassMainEl: HomeAssistantMain,
|
||||
msg: EMExternalMessageCommands
|
||||
): boolean => {
|
||||
const bus = hassMainEl.hass.auth.external!;
|
||||
|
||||
if (msg.command === "restart") {
|
||||
hassMainEl.hass.connection.reconnect(true);
|
||||
bus.fireMessage({
|
||||
id: msg.id,
|
||||
type: "result",
|
||||
success: true,
|
||||
result: null,
|
||||
});
|
||||
} else if (msg.command === "notifications/show") {
|
||||
fireEvent(hassMainEl, "hass-show-notifications");
|
||||
bus.fireMessage({
|
||||
id: msg.id,
|
||||
type: "result",
|
||||
success: true,
|
||||
result: null,
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
@ -128,14 +128,14 @@ export class ExternalAuth extends Auth {
|
||||
}
|
||||
}
|
||||
|
||||
export const createExternalAuth = (hassUrl: string) => {
|
||||
export const createExternalAuth = async (hassUrl: string) => {
|
||||
const auth = new ExternalAuth(hassUrl);
|
||||
if (
|
||||
(window.externalApp && window.externalApp.externalBus) ||
|
||||
(window.webkit && window.webkit.messageHandlers.externalBus)
|
||||
) {
|
||||
auth.external = new ExternalMessaging();
|
||||
auth.external.attach();
|
||||
await auth.external.attach();
|
||||
}
|
||||
return auth;
|
||||
};
|
||||
|
@ -1,18 +0,0 @@
|
||||
import { ExternalMessaging } from "./external_messaging";
|
||||
|
||||
export interface ExternalConfig {
|
||||
hasSettingsScreen: boolean;
|
||||
canWriteTag: boolean;
|
||||
hasExoPlayer: boolean;
|
||||
}
|
||||
|
||||
export const getExternalConfig = (
|
||||
bus: ExternalMessaging
|
||||
): Promise<ExternalConfig> => {
|
||||
if (!bus.cache.cfg) {
|
||||
bus.cache.cfg = bus.sendMessage<ExternalConfig>({
|
||||
type: "config/get",
|
||||
});
|
||||
}
|
||||
return bus.cache.cfg;
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
import { ExternalMessaging } from "./external_messaging";
|
||||
|
||||
export const externalForwardConnectionEvents = (bus: ExternalMessaging) => {
|
||||
window.addEventListener("connection-status", (ev) =>
|
||||
bus.fireMessage({
|
||||
type: "connection-status",
|
||||
payload: { event: ev.detail },
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const externalForwardHaptics = (bus: ExternalMessaging) =>
|
||||
window.addEventListener("haptic", (ev) =>
|
||||
bus.fireMessage({ type: "haptic", payload: { hapticType: ev.detail } })
|
||||
);
|
@ -1,9 +1,3 @@
|
||||
import { Connection } from "home-assistant-js-websocket";
|
||||
import {
|
||||
externalForwardConnectionEvents,
|
||||
externalForwardHaptics,
|
||||
} from "./external_events_forwarder";
|
||||
|
||||
const CALLBACK_EXTERNAL_BUS = "externalBus";
|
||||
|
||||
interface CommandInFlight {
|
||||
@ -42,24 +36,54 @@ interface EMExternalMessageRestart {
|
||||
command: "restart";
|
||||
}
|
||||
|
||||
interface EMExternMessageShowNotifications {
|
||||
id: number;
|
||||
type: "command";
|
||||
command: "notifications/show";
|
||||
}
|
||||
|
||||
export type EMExternalMessageCommands =
|
||||
| EMExternalMessageRestart
|
||||
| EMExternMessageShowNotifications;
|
||||
|
||||
type ExternalMessage =
|
||||
| EMMessageResultSuccess
|
||||
| EMMessageResultError
|
||||
| EMExternalMessageRestart;
|
||||
| EMExternalMessageCommands;
|
||||
|
||||
type ExternalMessageHandler = (msg: EMExternalMessageCommands) => boolean;
|
||||
|
||||
export interface ExternalConfig {
|
||||
hasSettingsScreen: boolean;
|
||||
hasSidebar: boolean;
|
||||
canWriteTag: boolean;
|
||||
hasExoPlayer: boolean;
|
||||
}
|
||||
|
||||
export class ExternalMessaging {
|
||||
public config!: ExternalConfig;
|
||||
|
||||
public commands: { [msgId: number]: CommandInFlight } = {};
|
||||
|
||||
public connection?: Connection;
|
||||
|
||||
public cache: Record<string, any> = {};
|
||||
|
||||
public msgId = 0;
|
||||
|
||||
public attach() {
|
||||
externalForwardConnectionEvents(this);
|
||||
externalForwardHaptics(this);
|
||||
private _commandHandler?: ExternalMessageHandler;
|
||||
|
||||
public async attach() {
|
||||
window[CALLBACK_EXTERNAL_BUS] = (msg) => this.receiveMessage(msg);
|
||||
window.addEventListener("connection-status", (ev) =>
|
||||
this.fireMessage({
|
||||
type: "connection-status",
|
||||
payload: { event: ev.detail },
|
||||
})
|
||||
);
|
||||
this.config = await this.sendMessage<ExternalConfig>({
|
||||
type: "config/get",
|
||||
});
|
||||
}
|
||||
|
||||
public addCommandHandler(handler: ExternalMessageHandler) {
|
||||
this._commandHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,36 +121,25 @@ export class ExternalMessaging {
|
||||
}
|
||||
|
||||
if (msg.type === "command") {
|
||||
if (!this.connection) {
|
||||
if (!this._commandHandler || !this._commandHandler(msg)) {
|
||||
let code: string;
|
||||
let message: string;
|
||||
if (this._commandHandler) {
|
||||
code = "not_ready";
|
||||
message = "Command handler not ready";
|
||||
} else {
|
||||
code = "unknown_command";
|
||||
message = `Unknown command ${msg.command}`;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Received command without having connection set", msg);
|
||||
console.warn(message, msg);
|
||||
this.fireMessage({
|
||||
id: msg.id,
|
||||
type: "result",
|
||||
success: false,
|
||||
error: {
|
||||
code: "commands_not_init",
|
||||
message: `Commands connection not set`,
|
||||
},
|
||||
});
|
||||
} else if (msg.command === "restart") {
|
||||
this.connection.reconnect(true);
|
||||
this.fireMessage({
|
||||
id: msg.id,
|
||||
type: "result",
|
||||
success: true,
|
||||
result: null,
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Received unknown command", msg.command, msg);
|
||||
this.fireMessage({
|
||||
id: msg.id,
|
||||
type: "result",
|
||||
success: false,
|
||||
error: {
|
||||
code: "unknown_command",
|
||||
message: `Unknown command ${msg.command}`,
|
||||
code,
|
||||
message,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ interface EditSideBarEvent {
|
||||
}
|
||||
|
||||
@customElement("home-assistant-main")
|
||||
class HomeAssistantMain extends LitElement {
|
||||
export class HomeAssistantMain extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public route?: Route;
|
||||
@ -47,6 +47,8 @@ class HomeAssistantMain extends LitElement {
|
||||
|
||||
@state() private _sidebarEditMode = false;
|
||||
|
||||
@state() private _externalSidebar = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
listenMediaQuery("(max-width: 870px)", (matches) => {
|
||||
@ -56,11 +58,12 @@ class HomeAssistantMain extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const hass = this.hass;
|
||||
const sidebarNarrow = this._sidebarNarrow;
|
||||
const sidebarNarrow = this._sidebarNarrow || this._externalSidebar;
|
||||
const disableSwipe =
|
||||
this._sidebarEditMode ||
|
||||
!sidebarNarrow ||
|
||||
NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1;
|
||||
NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1 ||
|
||||
this._externalSidebar;
|
||||
|
||||
// Style block in render because of the mixin that is not supported
|
||||
return html`
|
||||
@ -107,6 +110,14 @@ class HomeAssistantMain extends LitElement {
|
||||
protected firstUpdated() {
|
||||
import(/* webpackPreload: true */ "../components/ha-sidebar");
|
||||
|
||||
if (this.hass.auth.external) {
|
||||
this._externalSidebar =
|
||||
this.hass.auth.external.config.hasSidebar === true;
|
||||
import("../external_app/external_app_entrypoint").then((mod) =>
|
||||
mod.attachExternalToApp(this)
|
||||
);
|
||||
}
|
||||
|
||||
this.addEventListener(
|
||||
"hass-edit-sidebar",
|
||||
(ev: HASSDomEvent<EditSideBarEvent>) => {
|
||||
@ -129,6 +140,12 @@ class HomeAssistantMain extends LitElement {
|
||||
if (this._sidebarEditMode) {
|
||||
return;
|
||||
}
|
||||
if (this._externalSidebar) {
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "sidebar/show",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this._sidebarNarrow) {
|
||||
if (this.drawer.opened) {
|
||||
this.drawer.close();
|
||||
|
@ -3,15 +3,8 @@ import "@material/mwc-list/mwc-list-item";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
@ -24,10 +17,6 @@ import {
|
||||
SupervisorAvailableUpdates,
|
||||
} from "../../../data/supervisor/root";
|
||||
import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar";
|
||||
import {
|
||||
ExternalConfig,
|
||||
getExternalConfig,
|
||||
} from "../../../external_app/external_config";
|
||||
import "../../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -54,18 +43,6 @@ class HaConfigDashboard extends LitElement {
|
||||
|
||||
@property() public showAdvanced!: boolean;
|
||||
|
||||
@state() private _externalConfig?: ExternalConfig;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
if (this.hass && this.hass.auth.external) {
|
||||
getExternalConfig(this.hass.auth.external).then((conf) => {
|
||||
this._externalConfig = conf;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-app-layout>
|
||||
@ -143,7 +120,6 @@ class HaConfigDashboard extends LitElement {
|
||||
<ha-config-navigation
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.externalConfig=${this._externalConfig}
|
||||
.showAdvanced=${this.showAdvanced}
|
||||
.pages=${configSections.dashboard}
|
||||
></ha-config-navigation>
|
||||
|
@ -6,7 +6,6 @@ import { canShowPage } from "../../../common/config/can_show_page";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
|
||||
import { ExternalConfig } from "../../../external_app/external_config";
|
||||
import { PageNavigation } from "../../../layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
@ -20,14 +19,12 @@ class HaConfigNavigation extends LitElement {
|
||||
|
||||
@property() public pages!: PageNavigation[];
|
||||
|
||||
@property() public externalConfig?: ExternalConfig;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.pages.map((page) =>
|
||||
(
|
||||
page.path === "#external-app-configuration"
|
||||
? this.externalConfig?.hasSettingsScreen
|
||||
? this.hass.auth.external?.config.hasSettingsScreen
|
||||
: canShowPage(this.hass, page)
|
||||
)
|
||||
? html`
|
||||
|
@ -28,7 +28,6 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { getExternalConfig } from "../../../external_app/external_config";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@ -53,14 +52,12 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _tags: Tag[] = [];
|
||||
|
||||
@state() private _canWriteTags = false;
|
||||
private get _canWriteTags() {
|
||||
return this.hass.auth.external?.config.canWriteTag;
|
||||
}
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(
|
||||
narrow: boolean,
|
||||
canWriteTags: boolean,
|
||||
_language
|
||||
): DataTableColumnContainer => {
|
||||
(narrow: boolean, _language): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer = {
|
||||
icon: {
|
||||
title: "",
|
||||
@ -103,7 +100,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
`,
|
||||
};
|
||||
}
|
||||
if (canWriteTags) {
|
||||
if (this._canWriteTags) {
|
||||
columns.write = {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
@ -152,11 +149,6 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this._fetchTags();
|
||||
if (this.hass && this.hass.auth.external) {
|
||||
getExternalConfig(this.hass.auth.external).then((conf) => {
|
||||
this._canWriteTags = conf.canWriteTag;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected hassSubscribe() {
|
||||
@ -181,11 +173,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.tags}
|
||||
.columns=${this._columns(
|
||||
this.narrow,
|
||||
this._canWriteTags,
|
||||
this.hass.language
|
||||
)}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._data(this._tags)}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")}
|
||||
hasFab
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { Constructor } from "../types";
|
||||
import { HassBaseEl } from "./hass-base-mixin";
|
||||
|
||||
export const ExternalMixin = <T extends Constructor<HassBaseEl>>(
|
||||
superClass: T
|
||||
) =>
|
||||
class extends superClass {
|
||||
protected hassConnected() {
|
||||
super.hassConnected();
|
||||
|
||||
if (this.hass!.auth.external) {
|
||||
this.hass!.auth.external.connection = this.hass!.connection;
|
||||
}
|
||||
}
|
||||
};
|
@ -6,7 +6,6 @@ import DisconnectToastMixin from "./disconnect-toast-mixin";
|
||||
import { hapticMixin } from "./haptic-mixin";
|
||||
import { HassBaseEl } from "./hass-base-mixin";
|
||||
import { loggingMixin } from "./logging-mixin";
|
||||
import { ExternalMixin } from "./external-mixin";
|
||||
import MoreInfoMixin from "./more-info-mixin";
|
||||
import NotificationMixin from "./notification-mixin";
|
||||
import { panelTitleMixin } from "./panel-title-mixin";
|
||||
@ -32,5 +31,4 @@ export class HassElement extends ext(HassBaseEl, [
|
||||
hapticMixin,
|
||||
panelTitleMixin,
|
||||
loggingMixin,
|
||||
ExternalMixin,
|
||||
]) {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user