Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

20200930.0 #7174

Merged
merged 10 commits into from
Sep 30, 2020
4 changes: 3 additions & 1 deletion src/components/entity/ha-entity-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export class HaEntityPicker extends LitElement {

@property() public entityFilter?: HaEntityPickerEntityFilterFunc;

@property({ type: Boolean }) public hideClearIcon = false;

@property({ type: Boolean }) private _opened = false;

@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
Expand Down Expand Up @@ -204,7 +206,7 @@ export class HaEntityPicker extends LitElement {
autocorrect="off"
spellcheck="false"
>
${this.value
${this.value && !this.hideClearIcon
? html`
<ha-icon-button
aria-label=${this.hass.localize(
Expand Down
57 changes: 34 additions & 23 deletions src/components/ha-hls-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
import { nextRender } from "../common/util/render-status";
import { getExternalConfig } from "../external_app/external_config";
import type { HomeAssistant } from "../types";

type HLSModule = typeof import("hls.js");
Expand Down Expand Up @@ -93,39 +94,49 @@ class HaHLSPlayer extends LitElement {
}

private async _getUseExoPlayer(): Promise<boolean> {
return false;
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> {
let hls: any;
const videoEl = this._videoEl;
this._useExoPlayer = await this._getUseExoPlayer();
if (!this._useExoPlayer) {
hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any)
.default as HLSModule;
let hlsSupported = hls.isSupported();

if (!hlsSupported) {
hlsSupported =
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
}

if (!hlsSupported) {
this._videoEl.innerHTML = this.hass.localize(
"ui.components.media-browser.video_not_supported"
);
return;
}
const playlist_url = this.url.replace("master_playlist", "playlist");
const useExoPlayerPromise = this._getUseExoPlayer();
const masterPlaylistPromise = fetch(this.url);

const hls = ((await import(
/* webpackChunkName: "hls.js" */ "hls.js"
)) as any).default as HLSModule;
let hlsSupported = hls.isSupported();

if (!hlsSupported) {
hlsSupported =
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
}

const url = this.url;
if (!hlsSupported) {
this._videoEl.innerHTML = this.hass.localize(
"ui.components.media-browser.video_not_supported"
);
return;
}

this._useExoPlayer = await useExoPlayerPromise;
let hevcRegexp: RegExp;
let masterPlaylist: string;
if (this._useExoPlayer) {
this._renderHLSExoPlayer(url);
hevcRegexp = /CODECS=".*?((hev1)|(hvc1))\..*?"/;
masterPlaylist = await (await masterPlaylistPromise).text();
}
if (this._useExoPlayer && hevcRegexp!.test(masterPlaylist!)) {
this._renderHLSExoPlayer(playlist_url);
} else if (hls.isSupported()) {
this._renderHLSPolyfill(videoEl, hls, url);
this._renderHLSPolyfill(videoEl, hls, playlist_url);
} else {
this._renderHLSNative(videoEl, url);
this._renderHLSNative(videoEl, playlist_url);
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/components/ha-icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ interface DeprecatedIcon {
};
}

const mdiDeprecatedIcons: DeprecatedIcon = {
scooter: { removeIn: "117", newName: "human-scooter" },
};
const mdiDeprecatedIcons: DeprecatedIcon = {};

const chunks: Chunks = {};

Expand Down
1 change: 1 addition & 0 deletions src/data/lovelace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface LovelaceViewElement extends HTMLElement {
index?: number;
cards?: Array<LovelaceCard | HuiErrorCard>;
badges?: LovelaceBadge[];
setConfig(config: LovelaceViewConfig): void;
}

export interface ShowViewConfig {
Expand Down
1 change: 1 addition & 0 deletions src/panels/config/ha-panel-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
translationKey: "ui.panel.config.users.caption",
iconPath: mdiBadgeAccountHorizontal,
core: true,
advancedOnly: true,
},
],
general: [
Expand Down
191 changes: 178 additions & 13 deletions src/panels/config/person/dialog-person-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
import "../../../components/ha-formfield";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import {
User,
SYSTEM_GROUP_ID_ADMIN,
deleteUser,
SYSTEM_GROUP_ID_USER,
updateUser,
} from "../../../data/user";
import {
showAlertDialog,
showPromptDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { adminChangePassword } from "../../../data/auth";
import { showAddUserDialog } from "../users/show-dialog-add-user";

const includeDomains = ["device_tracker"];

Expand All @@ -39,6 +55,10 @@ class DialogPersonDetail extends LitElement {

@internalProperty() private _userId?: string;

@internalProperty() private _user?: User;

@internalProperty() private _isAdmin?: boolean;

@internalProperty() private _deviceTrackers!: string[];

@internalProperty() private _picture!: string | null;
Expand All @@ -64,9 +84,15 @@ class DialogPersonDetail extends LitElement {
this._userId = this._params.entry.user_id || undefined;
this._deviceTrackers = this._params.entry.device_trackers || [];
this._picture = this._params.entry.picture || null;
this._user = this._userId
? this._params.users.find((user) => user.id === this._userId)
: undefined;
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
} else {
this._name = "";
this._userId = undefined;
this._user = undefined;
this._isAdmin = undefined;
this._deviceTrackers = [];
this._picture = null;
}
Expand Down Expand Up @@ -115,15 +141,37 @@ class DialogPersonDetail extends LitElement {
@change=${this._pictureChanged}
></ha-picture-upload>
<ha-user-picker
label="${this.hass!.localize(
"ui.panel.config.person.detail.linked_user"
)}"
.hass=${this.hass}
.value=${this._userId}
.users=${this._params.users}
@value-changed=${this._userChanged}
></ha-user-picker>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.person.detail.allow_login"
)}
>
<ha-switch
@change=${this._allowLoginChanged}
.disabled=${this._user &&
(this._user.id === this.hass.user?.id ||
this._user.system_generated ||
this._user.is_owner)}
.checked=${this._userId}
></ha-switch>
</ha-formfield>
${this._user
? html`<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.person.detail.admin"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${this._user.system_generated ||
this._user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>
</ha-switch>
</ha-formfield>`
: ""}
${this._deviceTrackersAvailable(this.hass)
? html`
<p>
Expand Down Expand Up @@ -185,10 +233,21 @@ class DialogPersonDetail extends LitElement {
slot="secondaryAction"
class="warning"
@click="${this._deleteEntry}"
.disabled=${this._submitting}
.disabled=${(this._user && this._user.is_owner) ||
this._submitting}
>
${this.hass!.localize("ui.panel.config.person.detail.delete")}
</mwc-button>
${this._user && this.hass.user?.is_owner
? html`<mwc-button
slot="secondaryAction"
@click=${this._changePassword}
>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
: ""}
`
: html``}
<mwc-button
Expand All @@ -213,9 +272,47 @@ class DialogPersonDetail extends LitElement {
this._name = ev.detail.value;
}

private _userChanged(ev: PolymerChangedEvent<string>) {
this._error = undefined;
this._userId = ev.detail.value;
private async _adminChanged(ev): Promise<void> {
this._isAdmin = ev.target.checked;
}

private async _allowLoginChanged(ev): Promise<void> {
const target = ev.target;
if (target.checked) {
target.checked = false;
showAddUserDialog(this, {
userAddedCallback: async (user?: User) => {
if (user) {
target.checked = true;
this._user = user;
this._userId = user.id;
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
this._params?.refreshUsers();
}
},
name: this._name,
});
} else if (this._userId) {
if (
!(await showConfirmationDialog(this, {
text: this.hass!.localize(
"ui.panel.config.person.detail.confirm_delete_user",
"name",
this._name
),
confirmText: this.hass!.localize(
"ui.panel.config.person.detail.delete"
),
dismissText: this.hass!.localize("ui.common.cancel"),
}))
) {
target.checked = true;
return;
}
await deleteUser(this.hass, this._userId);
this._params?.refreshUsers();
this._userId = undefined;
}
}

private _deviceTrackersChanged(ev: PolymerChangedEvent<string[]>) {
Expand All @@ -228,9 +325,70 @@ class DialogPersonDetail extends LitElement {
this._picture = (ev.target as HaPictureUpload).value;
}

private async _changePassword() {
if (!this._user) {
return;
}
const credential = this._user.credentials.find(
(cred) => cred.type === "homeassistant"
);
if (!credential) {
showAlertDialog(this, {
title: "No Home Assistant credentials found.",
});
return;
}
const newPassword = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.users.editor.change_password"),
inputType: "password",
inputLabel: this.hass.localize(
"ui.panel.config.users.editor.new_password"
),
});
if (!newPassword) {
return;
}
const confirmPassword = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.users.editor.change_password"),
inputType: "password",
inputLabel: this.hass.localize(
"ui.panel.config.users.add_user.password_confirm"
),
});
if (!confirmPassword) {
return;
}
if (newPassword !== confirmPassword) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.users.add_user.password_not_match"
),
});
return;
}
await adminChangePassword(this.hass, this._user.id, newPassword);
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.users.editor.password_changed"
),
});
}

private async _updateEntry() {
this._submitting = true;
try {
if (
(this._userId && this._name !== this._params!.entry?.name) ||
this._isAdmin !== this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN)
) {
await updateUser(this.hass!, this._userId!, {
name: this._name.trim(),
group_ids: [
this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER,
],
});
this._params?.refreshUsers();
}
const values: PersonMutableParams = {
name: this._name.trim(),
device_trackers: this._deviceTrackers,
Expand All @@ -254,6 +412,9 @@ class DialogPersonDetail extends LitElement {
this._submitting = true;
try {
if (await this._params!.removeEntry()) {
if (this._params!.entry!.user_id) {
deleteUser(this.hass, this._params!.entry!.user_id);
}
this._params = undefined;
}
} finally {
Expand All @@ -275,6 +436,10 @@ class DialogPersonDetail extends LitElement {
ha-picture-upload {
display: block;
}
ha-formfield {
display: block;
padding: 16px 0;
}
ha-user-picker {
margin-top: 16px;
}
Expand Down
Loading