mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-17 14:26:35 +00:00
Add media management dialog (#11787)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
aa988c758d
commit
df35496c6e
@ -46,6 +46,7 @@
|
|||||||
"@fullcalendar/daygrid": "5.9.0",
|
"@fullcalendar/daygrid": "5.9.0",
|
||||||
"@fullcalendar/interaction": "5.9.0",
|
"@fullcalendar/interaction": "5.9.0",
|
||||||
"@fullcalendar/list": "5.9.0",
|
"@fullcalendar/list": "5.9.0",
|
||||||
|
"@lit-labs/motion": "^1.0.2",
|
||||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
|
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
|
||||||
"@material/chips": "14.0.0-canary.261f2db59.0",
|
"@material/chips": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
||||||
|
337
src/components/media-player/dialog-media-manage.ts
Normal file
337
src/components/media-player/dialog-media-manage.ts
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
import { animate } from "@lit-labs/motion";
|
||||||
|
import "@material/mwc-list/mwc-check-list-item";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import "@material/mwc-list/mwc-list";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { mdiClose, mdiDelete } from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||||
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-header-bar";
|
||||||
|
import "../ha-dialog";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
import "../ha-circular-progress";
|
||||||
|
import "./ha-media-player-browse";
|
||||||
|
import "./ha-media-upload-button";
|
||||||
|
import type { MediaManageDialogParams } from "./show-media-manage-dialog";
|
||||||
|
import {
|
||||||
|
MediaClassBrowserSettings,
|
||||||
|
MediaPlayerItem,
|
||||||
|
} from "../../data/media-player";
|
||||||
|
import {
|
||||||
|
browseLocalMediaPlayer,
|
||||||
|
removeLocalMedia,
|
||||||
|
} from "../../data/media_source";
|
||||||
|
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
|
|
||||||
|
@customElement("dialog-media-manage")
|
||||||
|
class DialogMediaManage extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _currentItem?: MediaPlayerItem;
|
||||||
|
|
||||||
|
@state() private _params?: MediaManageDialogParams;
|
||||||
|
|
||||||
|
@state() private _uploading = false;
|
||||||
|
|
||||||
|
@state() private _deleting = false;
|
||||||
|
|
||||||
|
@state() private _selected = new Set<number>();
|
||||||
|
|
||||||
|
private _filesChanged = false;
|
||||||
|
|
||||||
|
public showDialog(params: MediaManageDialogParams): void {
|
||||||
|
this._params = params;
|
||||||
|
this._refreshMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
if (this._filesChanged && this._params!.onClose) {
|
||||||
|
this._params!.onClose();
|
||||||
|
}
|
||||||
|
this._params = undefined;
|
||||||
|
this._currentItem = undefined;
|
||||||
|
this._uploading = false;
|
||||||
|
this._deleting = false;
|
||||||
|
this._filesChanged = false;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._params) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const children =
|
||||||
|
this._currentItem?.children?.filter((child) => !child.can_expand) || [];
|
||||||
|
|
||||||
|
let fileIndex = 0;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
scrimClickAction
|
||||||
|
escapeKeyAction
|
||||||
|
hideActions
|
||||||
|
flexContent
|
||||||
|
.heading=${this._params.currentItem.title}
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
>
|
||||||
|
<ha-header-bar slot="heading">
|
||||||
|
${this._selected.size === 0
|
||||||
|
? html`
|
||||||
|
<span slot="title">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.title"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ha-media-upload-button
|
||||||
|
.disabled=${this._deleting}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.currentItem=${this._params.currentItem}
|
||||||
|
@uploading=${this._startUploading}
|
||||||
|
@media-refresh=${this._doneUploading}
|
||||||
|
slot="actionItems"
|
||||||
|
></ha-media-upload-button>
|
||||||
|
${this._uploading
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||||
|
.path=${mdiClose}
|
||||||
|
dialogAction="close"
|
||||||
|
slot="actionItems"
|
||||||
|
class="header_button"
|
||||||
|
dir=${computeRTLDirection(this.hass)}
|
||||||
|
></ha-icon-button>
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<mwc-button
|
||||||
|
class="danger"
|
||||||
|
slot="title"
|
||||||
|
.disabled=${this._deleting}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.components.media-browser.file_management.${
|
||||||
|
this._deleting ? "deleting" : "delete"
|
||||||
|
}`,
|
||||||
|
{ count: this._selected.size }
|
||||||
|
)}
|
||||||
|
@click=${this._handleDelete}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiDelete} slot="icon"></ha-svg-icon>
|
||||||
|
</mwc-button>
|
||||||
|
|
||||||
|
${this._deleting
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<mwc-button
|
||||||
|
slot="actionItems"
|
||||||
|
.label=${`Deselect all`}
|
||||||
|
@click=${this._handleDeselectAll}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiClose}
|
||||||
|
slot="icon"
|
||||||
|
></ha-svg-icon>
|
||||||
|
</mwc-button>
|
||||||
|
`}
|
||||||
|
`}
|
||||||
|
</ha-header-bar>
|
||||||
|
${!this._currentItem
|
||||||
|
? html`
|
||||||
|
<div class="refresh">
|
||||||
|
<ha-circular-progress active></ha-circular-progress>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: !children.length
|
||||||
|
? html`<div class="no-items">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.no_items"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
${this._currentItem?.children?.length
|
||||||
|
? html`<span class="folders"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.folders_not_supported"
|
||||||
|
)}</span
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
|
</div>`
|
||||||
|
: html`
|
||||||
|
<mwc-list multi @selected=${this._handleSelected}>
|
||||||
|
${repeat(
|
||||||
|
children,
|
||||||
|
(item) => item.media_content_id,
|
||||||
|
(item) => {
|
||||||
|
const icon = html`
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${MediaClassBrowserSettings[
|
||||||
|
item.media_class === "directory"
|
||||||
|
? item.children_media_class || item.media_class
|
||||||
|
: item.media_class
|
||||||
|
].icon}
|
||||||
|
></ha-svg-icon>
|
||||||
|
`;
|
||||||
|
return html`
|
||||||
|
<mwc-check-list-item
|
||||||
|
${animate({
|
||||||
|
id: item.media_content_id,
|
||||||
|
skipInitial: true,
|
||||||
|
})}
|
||||||
|
graphic="icon"
|
||||||
|
.disabled=${this._uploading || this._deleting}
|
||||||
|
.selected=${this._selected.has(fileIndex++)}
|
||||||
|
.item=${item}
|
||||||
|
>
|
||||||
|
${icon} ${item.title}
|
||||||
|
</mwc-check-list-item>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</mwc-list>
|
||||||
|
`}
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSelected(ev) {
|
||||||
|
this._selected = ev.detail.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _startUploading() {
|
||||||
|
this._uploading = true;
|
||||||
|
this._filesChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _doneUploading() {
|
||||||
|
this._uploading = false;
|
||||||
|
this._refreshMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDeselectAll() {
|
||||||
|
if (this._selected.size) {
|
||||||
|
this._selected = new Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleDelete() {
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.confirm_delete",
|
||||||
|
{ count: this._selected.size }
|
||||||
|
),
|
||||||
|
warning: true,
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._filesChanged = true;
|
||||||
|
this._deleting = true;
|
||||||
|
|
||||||
|
const toDelete: MediaPlayerItem[] = [];
|
||||||
|
let fileIndex = 0;
|
||||||
|
this._currentItem!.children!.forEach((item) => {
|
||||||
|
if (item.can_expand) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._selected.has(fileIndex++)) {
|
||||||
|
toDelete.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
toDelete.map(async (item) => {
|
||||||
|
await removeLocalMedia(this.hass, item.media_content_id);
|
||||||
|
this._currentItem = {
|
||||||
|
...this._currentItem!,
|
||||||
|
children: this._currentItem!.children!.filter((i) => i !== item),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this._deleting = false;
|
||||||
|
this._selected = new Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _refreshMedia() {
|
||||||
|
this._selected = new Set();
|
||||||
|
this._currentItem = undefined;
|
||||||
|
this._currentItem = await browseLocalMediaPlayer(
|
||||||
|
this.hass,
|
||||||
|
this._params!.currentItem.media_content_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--dialog-z-index: 8;
|
||||||
|
--dialog-content-padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 800px) {
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-max-width: 800px;
|
||||||
|
--dialog-surface-position: fixed;
|
||||||
|
--dialog-surface-top: 40px;
|
||||||
|
--mdc-dialog-max-height: calc(100vh - 72px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-header-bar {
|
||||||
|
--mdc-theme-on-primary: var(--primary-text-color);
|
||||||
|
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-media-upload-button,
|
||||||
|
mwc-button {
|
||||||
|
--mdc-theme-primary: var(--mdc-theme-on-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
--mdc-theme-primary: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-svg-icon[slot="icon"] {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh {
|
||||||
|
display: flex;
|
||||||
|
height: 200px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-items {
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.folders {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-media-manage": DialogMediaManage;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import "../ha-header-bar";
|
import "../ha-header-bar";
|
||||||
import { mdiArrowLeft, mdiClose } from "@mdi/js";
|
import { mdiArrowLeft, mdiClose } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
|
||||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||||
import type {
|
import type {
|
||||||
@ -13,7 +13,11 @@ import { haStyleDialog } from "../../resources/styles";
|
|||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-dialog";
|
import "../ha-dialog";
|
||||||
import "./ha-media-player-browse";
|
import "./ha-media-player-browse";
|
||||||
import type { MediaPlayerItemId } from "./ha-media-player-browse";
|
import "./ha-media-manage-button";
|
||||||
|
import type {
|
||||||
|
HaMediaPlayerBrowse,
|
||||||
|
MediaPlayerItemId,
|
||||||
|
} from "./ha-media-player-browse";
|
||||||
import { MediaPlayerBrowseDialogParams } from "./show-media-browser-dialog";
|
import { MediaPlayerBrowseDialogParams } from "./show-media-browser-dialog";
|
||||||
|
|
||||||
@customElement("dialog-media-player-browse")
|
@customElement("dialog-media-player-browse")
|
||||||
@ -26,6 +30,8 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
@state() private _params?: MediaPlayerBrowseDialogParams;
|
@state() private _params?: MediaPlayerBrowseDialogParams;
|
||||||
|
|
||||||
|
@query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse;
|
||||||
|
|
||||||
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._navigateIds = params.navigateIds || [
|
this._navigateIds = params.navigateIds || [
|
||||||
@ -80,6 +86,12 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
: this._currentItem.title}
|
: this._currentItem.title}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<ha-media-manage-button
|
||||||
|
slot="actionItems"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.currentItem=${this._currentItem}
|
||||||
|
@media-refresh=${this._refreshMedia}
|
||||||
|
></ha-media-manage-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
@ -124,6 +136,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
return this._params!.action || "play";
|
return this._params!.action || "play";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _refreshMedia() {
|
||||||
|
this._browser.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
@ -157,6 +173,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-media-manage-button {
|
||||||
|
--mdc-theme-primary: var(--mdc-theme-on-primary);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
69
src/components/media-player/ha-media-manage-button.ts
Normal file
69
src/components/media-player/ha-media-manage-button.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { mdiFolderEdit } from "@mdi/js";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { MediaPlayerItem } from "../../data/media-player";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
import { isLocalMediaSourceContentId } from "../../data/media_source";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { showMediaManageDialog } from "./show-media-manage-dialog";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"media-refresh": unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-media-manage-button")
|
||||||
|
class MediaManageButton extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() currentItem?: MediaPlayerItem;
|
||||||
|
|
||||||
|
@state() _uploading = 0;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (
|
||||||
|
!this.currentItem ||
|
||||||
|
!isLocalMediaSourceContentId(this.currentItem.media_content_id || "")
|
||||||
|
) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<mwc-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.manage"
|
||||||
|
)}
|
||||||
|
@click=${this._manage}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiFolderEdit} slot="icon"></ha-svg-icon>
|
||||||
|
</mwc-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _manage() {
|
||||||
|
showMediaManageDialog(this, {
|
||||||
|
currentItem: this.currentItem!,
|
||||||
|
onClose: () => fireEvent(this, "media-refresh"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
mwc-button {
|
||||||
|
/* We use icon + text to show disabled state */
|
||||||
|
--mdc-button-disabled-ink-color: --mdc-theme-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-svg-icon[slot="icon"],
|
||||||
|
ha-circular-progress[slot="icon"] {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-media-manage-button": MediaManageButton;
|
||||||
|
}
|
||||||
|
}
|
@ -131,6 +131,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
currentId.media_content_id,
|
currentId.media_content_id,
|
||||||
currentId.media_content_type
|
currentId.media_content_type
|
||||||
);
|
);
|
||||||
|
// Update the parent with latest item.
|
||||||
|
fireEvent(this, "media-browsed", {
|
||||||
|
ids: this.navigateIds,
|
||||||
|
current: this._currentItem,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._setError(err);
|
this._setError(err);
|
||||||
}
|
}
|
||||||
|
129
src/components/media-player/ha-media-upload-button.ts
Normal file
129
src/components/media-player/ha-media-upload-button.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { mdiUpload } from "@mdi/js";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { MediaPlayerItem } from "../../data/media-player";
|
||||||
|
import "../ha-circular-progress";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
import {
|
||||||
|
isLocalMediaSourceContentId,
|
||||||
|
uploadLocalMedia,
|
||||||
|
} from "../../data/media_source";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
uploading: unknown;
|
||||||
|
"media-refresh": unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-media-upload-button")
|
||||||
|
class MediaUploadButton extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() currentItem?: MediaPlayerItem;
|
||||||
|
|
||||||
|
@state() _uploading = 0;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (
|
||||||
|
!this.currentItem ||
|
||||||
|
!isLocalMediaSourceContentId(this.currentItem.media_content_id || "")
|
||||||
|
) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<mwc-button
|
||||||
|
.label=${this._uploading > 0
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.uploading",
|
||||||
|
{
|
||||||
|
count: this._uploading,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.add_media"
|
||||||
|
)}
|
||||||
|
.disabled=${this._uploading > 0}
|
||||||
|
@click=${this._startUpload}
|
||||||
|
>
|
||||||
|
${this._uploading > 0
|
||||||
|
? html`
|
||||||
|
<ha-circular-progress
|
||||||
|
size="tiny"
|
||||||
|
active
|
||||||
|
alt=""
|
||||||
|
slot="icon"
|
||||||
|
></ha-circular-progress>
|
||||||
|
`
|
||||||
|
: html` <ha-svg-icon .path=${mdiUpload} slot="icon"></ha-svg-icon> `}
|
||||||
|
</mwc-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _startUpload() {
|
||||||
|
if (this._uploading > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.accept = "audio/*,video/*,image/*";
|
||||||
|
input.multiple = true;
|
||||||
|
input.addEventListener(
|
||||||
|
"change",
|
||||||
|
async () => {
|
||||||
|
fireEvent(this, "uploading");
|
||||||
|
const files = input.files!;
|
||||||
|
document.body.removeChild(input);
|
||||||
|
const target = this.currentItem!.media_content_id!;
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
this._uploading = files.length - i;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await uploadLocalMedia(this.hass, target, files[i]);
|
||||||
|
} catch (err: any) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.upload_failed",
|
||||||
|
{
|
||||||
|
reason: err.message || err,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._uploading = 0;
|
||||||
|
fireEvent(this, "media-refresh");
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
// https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only
|
||||||
|
input.style.display = "none";
|
||||||
|
document.body.append(input);
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
mwc-button {
|
||||||
|
/* We use icon + text to show disabled state */
|
||||||
|
--mdc-button-disabled-ink-color: --mdc-theme-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-svg-icon[slot="icon"],
|
||||||
|
ha-circular-progress[slot="icon"] {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-media-upload-button": MediaUploadButton;
|
||||||
|
}
|
||||||
|
}
|
18
src/components/media-player/show-media-manage-dialog.ts
Normal file
18
src/components/media-player/show-media-manage-dialog.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { MediaPlayerItem } from "../../data/media-player";
|
||||||
|
|
||||||
|
export interface MediaManageDialogParams {
|
||||||
|
currentItem: MediaPlayerItem;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showMediaManageDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: MediaManageDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-media-manage",
|
||||||
|
dialogImport: () => import("./dialog-media-manage"),
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -49,3 +49,12 @@ export const uploadLocalMedia = async (
|
|||||||
}
|
}
|
||||||
return resp.json();
|
return resp.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeLocalMedia = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
media_content_id: string
|
||||||
|
) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "media_source/local_source/remove",
|
||||||
|
media_content_id,
|
||||||
|
});
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import { mdiAlertOutline } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../components/ha-dialog";
|
import "../../components/ha-dialog";
|
||||||
|
import "../../components/ha-svg-icon";
|
||||||
import "../../components/ha-switch";
|
import "../../components/ha-switch";
|
||||||
import "../../components/ha-textfield";
|
import "../../components/ha-textfield";
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
@ -50,20 +51,22 @@ class DialogBox extends LitElement {
|
|||||||
?escapeKeyAction=${confirmPrompt}
|
?escapeKeyAction=${confirmPrompt}
|
||||||
@closed=${this._dialogClosed}
|
@closed=${this._dialogClosed}
|
||||||
defaultAction="ignore"
|
defaultAction="ignore"
|
||||||
.heading=${this._params.title
|
.heading=${html`${this._params.warning
|
||||||
|
? html`<ha-svg-icon
|
||||||
|
.path=${mdiAlertOutline}
|
||||||
|
style="color: var(--warning-color)"
|
||||||
|
></ha-svg-icon> `
|
||||||
|
: ""}${this._params.title
|
||||||
? this._params.title
|
? this._params.title
|
||||||
: this._params.confirmation &&
|
: this._params.confirmation &&
|
||||||
this.hass.localize("ui.dialogs.generic.default_confirmation_title")}
|
this.hass.localize(
|
||||||
|
"ui.dialogs.generic.default_confirmation_title"
|
||||||
|
)}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
${this._params.text
|
${this._params.text
|
||||||
? html`
|
? html`
|
||||||
<p
|
<p class=${this._params.prompt ? "no-bottom-padding" : ""}>
|
||||||
class=${classMap({
|
|
||||||
"no-bottom-padding": Boolean(this._params.prompt),
|
|
||||||
warning: Boolean(this._params.warning),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
${this._params.text}
|
${this._params.text}
|
||||||
</p>
|
</p>
|
||||||
`
|
`
|
||||||
@ -172,9 +175,6 @@ class DialogBox extends LitElement {
|
|||||||
/* Place above other dialogs */
|
/* Place above other dialogs */
|
||||||
--dialog-z-index: 104;
|
--dialog-z-index: 104;
|
||||||
}
|
}
|
||||||
.warning {
|
|
||||||
color: var(--warning-color);
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { mdiArrowLeft, mdiUpload } from "@mdi/js";
|
import { mdiArrowLeft } from "@mdi/js";
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
@ -15,10 +15,9 @@ import { LocalStorage } from "../../common/decorators/local-storage";
|
|||||||
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
import "../../components/ha-circular-progress";
|
|
||||||
import "../../components/ha-icon-button";
|
import "../../components/ha-icon-button";
|
||||||
import "../../components/ha-svg-icon";
|
|
||||||
import "../../components/media-player/ha-media-player-browse";
|
import "../../components/media-player/ha-media-player-browse";
|
||||||
|
import "../../components/media-player/ha-media-manage-button";
|
||||||
import type {
|
import type {
|
||||||
HaMediaPlayerBrowse,
|
HaMediaPlayerBrowse,
|
||||||
MediaPlayerItemId,
|
MediaPlayerItemId,
|
||||||
@ -28,11 +27,7 @@ import {
|
|||||||
MediaPickedEvent,
|
MediaPickedEvent,
|
||||||
MediaPlayerItem,
|
MediaPlayerItem,
|
||||||
} from "../../data/media-player";
|
} from "../../data/media-player";
|
||||||
import {
|
import { resolveMediaSource } from "../../data/media_source";
|
||||||
isLocalMediaSourceContentId,
|
|
||||||
resolveMediaSource,
|
|
||||||
uploadLocalMedia,
|
|
||||||
} from "../../data/media_source";
|
|
||||||
import "../../layouts/ha-app-layout";
|
import "../../layouts/ha-app-layout";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../types";
|
import type { HomeAssistant, Route } from "../../types";
|
||||||
@ -66,8 +61,6 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
|
|
||||||
@state() _currentItem?: MediaPlayerItem;
|
@state() _currentItem?: MediaPlayerItem;
|
||||||
|
|
||||||
@state() _uploading = 0;
|
|
||||||
|
|
||||||
private _navigateIds: MediaPlayerItemId[] = [
|
private _navigateIds: MediaPlayerItemId[] = [
|
||||||
{
|
{
|
||||||
media_content_id: undefined,
|
media_content_id: undefined,
|
||||||
@ -107,43 +100,11 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
)
|
)
|
||||||
: this._currentItem.title}
|
: this._currentItem.title}
|
||||||
</div>
|
</div>
|
||||||
${this._currentItem &&
|
<ha-media-manage-button
|
||||||
isLocalMediaSourceContentId(
|
.hass=${this.hass}
|
||||||
this._currentItem.media_content_id || ""
|
.currentItem=${this._currentItem}
|
||||||
)
|
@media-refresh=${this._refreshMedia}
|
||||||
? html`
|
></ha-media-manage-button>
|
||||||
<mwc-button
|
|
||||||
.label=${this._uploading > 0
|
|
||||||
? this.hass.localize(
|
|
||||||
"ui.components.media-browser.file_management.uploading",
|
|
||||||
{
|
|
||||||
count: this._uploading,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
: this.hass.localize(
|
|
||||||
"ui.components.media-browser.file_management.add_media"
|
|
||||||
)}
|
|
||||||
.disabled=${this._uploading > 0}
|
|
||||||
@click=${this._startUpload}
|
|
||||||
>
|
|
||||||
${this._uploading > 0
|
|
||||||
? html`
|
|
||||||
<ha-circular-progress
|
|
||||||
size="tiny"
|
|
||||||
active
|
|
||||||
alt=""
|
|
||||||
slot="icon"
|
|
||||||
></ha-circular-progress>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiUpload}
|
|
||||||
slot="icon"
|
|
||||||
></ha-svg-icon>
|
|
||||||
`}
|
|
||||||
</mwc-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</app-toolbar>
|
</app-toolbar>
|
||||||
</app-header>
|
</app-header>
|
||||||
<ha-media-player-browse
|
<ha-media-player-browse
|
||||||
@ -290,57 +251,16 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
navigate(createMediaPanelUrl(entityId, this._navigateIds));
|
navigate(createMediaPanelUrl(entityId, this._navigateIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _startUpload() {
|
private _refreshMedia() {
|
||||||
if (this._uploading > 0) {
|
this._browser.refresh();
|
||||||
return;
|
|
||||||
}
|
|
||||||
const input = document.createElement("input");
|
|
||||||
input.type = "file";
|
|
||||||
input.accept = "audio/*,video/*,image/*";
|
|
||||||
input.multiple = true;
|
|
||||||
input.addEventListener(
|
|
||||||
"change",
|
|
||||||
async () => {
|
|
||||||
const files = input.files!;
|
|
||||||
document.body.removeChild(input);
|
|
||||||
const target = this._currentItem!.media_content_id!;
|
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
this._uploading = files.length - i;
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await uploadLocalMedia(this.hass, target, files[i]);
|
|
||||||
} catch (err: any) {
|
|
||||||
showAlertDialog(this, {
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.components.media-browser.file_management.upload_failed",
|
|
||||||
{
|
|
||||||
reason: err.message || err,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._uploading = 0;
|
|
||||||
await this._browser.refresh();
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
// https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only
|
|
||||||
input.style.display = "none";
|
|
||||||
document.body.append(input);
|
|
||||||
input.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
app-toolbar mwc-button {
|
app-toolbar {
|
||||||
--mdc-theme-primary: var(--app-header-text-color);
|
--mdc-theme-primary: var(--app-header-text-color);
|
||||||
/* We use icon + text to show disabled state */
|
|
||||||
--mdc-button-disabled-ink-color: var(--app-header-text-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-media-player-browse {
|
ha-media-player-browse {
|
||||||
@ -357,11 +277,6 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-svg-icon[slot="icon"],
|
|
||||||
ha-circular-progress[slot="icon"] {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import "@polymer/app-layout/app-header/app-header";
|
|||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../components/ha-card";
|
import "../../components/ha-card";
|
||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
@ -33,6 +33,7 @@ import "./ha-refresh-tokens-card";
|
|||||||
import "./ha-set-suspend-row";
|
import "./ha-set-suspend-row";
|
||||||
import "./ha-set-vibrate-row";
|
import "./ha-set-vibrate-row";
|
||||||
|
|
||||||
|
@customElement("ha-panel-profile")
|
||||||
class HaPanelProfile extends LitElement {
|
class HaPanelProfile extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@ -252,5 +253,8 @@ class HaPanelProfile extends LitElement {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
declare global {
|
||||||
customElements.define("ha-panel-profile", HaPanelProfile);
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-panel-profile": HaPanelProfile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -549,10 +549,17 @@
|
|||||||
"no_media_folder": "It looks like you have not yet created a media directory.",
|
"no_media_folder": "It looks like you have not yet created a media directory.",
|
||||||
"setup_local_help": "Check the {documentation} on how to setup local media.",
|
"setup_local_help": "Check the {documentation} on how to setup local media.",
|
||||||
"file_management": {
|
"file_management": {
|
||||||
|
"title": "Media Management",
|
||||||
|
"manage": "Manage",
|
||||||
|
"no_items": "No media items found",
|
||||||
|
"folders_not_supported": "Folders can not be managed via the UI.",
|
||||||
"highlight_button": "Click here to upload your first media",
|
"highlight_button": "Click here to upload your first media",
|
||||||
"upload_failed": "Upload failed: {reason}",
|
"upload_failed": "Upload failed: {reason}",
|
||||||
"add_media": "Add Media",
|
"add_media": "Add Media",
|
||||||
"uploading": "Uploading {count} {count, plural,\n one {file}\n other {files}\n}"
|
"uploading": "Uploading {count} {count, plural,\n one {file}\n other {files}\n}",
|
||||||
|
"confirm_delete": "Do you want to delete {count} {count, plural,\n one {file}\n other {files}\n}?",
|
||||||
|
"delete": "Delete {count}",
|
||||||
|
"deleting": "Deleting {count}"
|
||||||
},
|
},
|
||||||
"class": {
|
"class": {
|
||||||
"album": "Album",
|
"album": "Album",
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -1960,6 +1960,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@lit-labs/motion@npm:^1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "@lit-labs/motion@npm:1.0.2"
|
||||||
|
dependencies:
|
||||||
|
lit: ^2.0.0
|
||||||
|
checksum: 598e0be22a3f931ec971fa001e863c5a4dd82f572d8d0214211bde1d6403b00e3c8fafa92f30b0c02b7272bc12510ec40060bf2c8ab18151bdb264cf32f0ef71
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@lit-labs/virtualizer@0.7.0-pre.2":
|
"@lit-labs/virtualizer@0.7.0-pre.2":
|
||||||
version: 0.7.0-pre.2
|
version: 0.7.0-pre.2
|
||||||
resolution: "@lit-labs/virtualizer@npm:0.7.0-pre.2"
|
resolution: "@lit-labs/virtualizer@npm:0.7.0-pre.2"
|
||||||
@ -9131,6 +9140,7 @@ fsevents@^1.2.7:
|
|||||||
"@fullcalendar/interaction": 5.9.0
|
"@fullcalendar/interaction": 5.9.0
|
||||||
"@fullcalendar/list": 5.9.0
|
"@fullcalendar/list": 5.9.0
|
||||||
"@koa/cors": ^3.1.0
|
"@koa/cors": ^3.1.0
|
||||||
|
"@lit-labs/motion": ^1.0.2
|
||||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch"
|
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch"
|
||||||
"@material/chips": 14.0.0-canary.261f2db59.0
|
"@material/chips": 14.0.0-canary.261f2db59.0
|
||||||
"@material/data-table": 14.0.0-canary.261f2db59.0
|
"@material/data-table": 14.0.0-canary.261f2db59.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user