Allow an external sidebar (#11347)

This commit is contained in:
Paulus Schoutsen 2022-01-24 09:08:35 -08:00 committed by GitHub
parent bbcec38450
commit 6c12a5a4b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 137 additions and 167 deletions

View File

@ -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) {

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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