mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-16 05:46: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/interaction": "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",
|
||||
"@material/chips": "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 { mdiArrowLeft, mdiClose } from "@mdi/js";
|
||||
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 { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import type {
|
||||
@ -13,7 +13,11 @@ import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-dialog";
|
||||
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";
|
||||
|
||||
@customElement("dialog-media-player-browse")
|
||||
@ -26,6 +30,8 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
|
||||
@state() private _params?: MediaPlayerBrowseDialogParams;
|
||||
|
||||
@query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse;
|
||||
|
||||
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
||||
this._params = params;
|
||||
this._navigateIds = params.navigateIds || [
|
||||
@ -80,6 +86,12 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
: this._currentItem.title}
|
||||
</span>
|
||||
|
||||
<ha-media-manage-button
|
||||
slot="actionItems"
|
||||
.hass=${this.hass}
|
||||
.currentItem=${this._currentItem}
|
||||
@media-refresh=${this._refreshMedia}
|
||||
></ha-media-manage-button>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||
.path=${mdiClose}
|
||||
@ -124,6 +136,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
return this._params!.action || "play";
|
||||
}
|
||||
|
||||
private _refreshMedia() {
|
||||
this._browser.refresh();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
@ -157,6 +173,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
flex-shrink: 0;
|
||||
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_type
|
||||
);
|
||||
// Update the parent with latest item.
|
||||
fireEvent(this, "media-browsed", {
|
||||
ids: this.navigateIds,
|
||||
current: this._currentItem,
|
||||
});
|
||||
} catch (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();
|
||||
};
|
||||
|
||||
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 { mdiAlertOutline } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/ha-switch";
|
||||
import "../../components/ha-textfield";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
@ -50,20 +51,22 @@ class DialogBox extends LitElement {
|
||||
?escapeKeyAction=${confirmPrompt}
|
||||
@closed=${this._dialogClosed}
|
||||
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.confirmation &&
|
||||
this.hass.localize("ui.dialogs.generic.default_confirmation_title")}
|
||||
this.hass.localize(
|
||||
"ui.dialogs.generic.default_confirmation_title"
|
||||
)}`}
|
||||
>
|
||||
<div>
|
||||
${this._params.text
|
||||
? html`
|
||||
<p
|
||||
class=${classMap({
|
||||
"no-bottom-padding": Boolean(this._params.prompt),
|
||||
warning: Boolean(this._params.warning),
|
||||
})}
|
||||
>
|
||||
<p class=${this._params.prompt ? "no-bottom-padding" : ""}>
|
||||
${this._params.text}
|
||||
</p>
|
||||
`
|
||||
@ -172,9 +175,6 @@ class DialogBox extends LitElement {
|
||||
/* Place above other dialogs */
|
||||
--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-toolbar/app-toolbar";
|
||||
import "@material/mwc-button";
|
||||
@ -15,10 +15,9 @@ import { LocalStorage } from "../../common/decorators/local-storage";
|
||||
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/media-player/ha-media-player-browse";
|
||||
import "../../components/media-player/ha-media-manage-button";
|
||||
import type {
|
||||
HaMediaPlayerBrowse,
|
||||
MediaPlayerItemId,
|
||||
@ -28,11 +27,7 @@ import {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerItem,
|
||||
} from "../../data/media-player";
|
||||
import {
|
||||
isLocalMediaSourceContentId,
|
||||
resolveMediaSource,
|
||||
uploadLocalMedia,
|
||||
} from "../../data/media_source";
|
||||
import { resolveMediaSource } from "../../data/media_source";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../types";
|
||||
@ -66,8 +61,6 @@ class PanelMediaBrowser extends LitElement {
|
||||
|
||||
@state() _currentItem?: MediaPlayerItem;
|
||||
|
||||
@state() _uploading = 0;
|
||||
|
||||
private _navigateIds: MediaPlayerItemId[] = [
|
||||
{
|
||||
media_content_id: undefined,
|
||||
@ -107,43 +100,11 @@ class PanelMediaBrowser extends LitElement {
|
||||
)
|
||||
: this._currentItem.title}
|
||||
</div>
|
||||
${this._currentItem &&
|
||||
isLocalMediaSourceContentId(
|
||||
this._currentItem.media_content_id || ""
|
||||
)
|
||||
? 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>
|
||||
`
|
||||
: ""}
|
||||
<ha-media-manage-button
|
||||
.hass=${this.hass}
|
||||
.currentItem=${this._currentItem}
|
||||
@media-refresh=${this._refreshMedia}
|
||||
></ha-media-manage-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<ha-media-player-browse
|
||||
@ -290,57 +251,16 @@ class PanelMediaBrowser extends LitElement {
|
||||
navigate(createMediaPanelUrl(entityId, this._navigateIds));
|
||||
}
|
||||
|
||||
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 () => {
|
||||
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();
|
||||
private _refreshMedia() {
|
||||
this._browser.refresh();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
app-toolbar mwc-button {
|
||||
app-toolbar {
|
||||
--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 {
|
||||
@ -357,11 +277,6 @@ class PanelMediaBrowser extends LitElement {
|
||||
left: 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 { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
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 "../../components/ha-card";
|
||||
import "../../components/ha-menu-button";
|
||||
@ -33,6 +33,7 @@ import "./ha-refresh-tokens-card";
|
||||
import "./ha-set-suspend-row";
|
||||
import "./ha-set-vibrate-row";
|
||||
|
||||
@customElement("ha-panel-profile")
|
||||
class HaPanelProfile extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@ -252,5 +253,8 @@ class HaPanelProfile extends LitElement {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-panel-profile", HaPanelProfile);
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-panel-profile": HaPanelProfile;
|
||||
}
|
||||
}
|
||||
|
@ -549,10 +549,17 @@
|
||||
"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.",
|
||||
"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",
|
||||
"upload_failed": "Upload failed: {reason}",
|
||||
"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": {
|
||||
"album": "Album",
|
||||
|
10
yarn.lock
10
yarn.lock
@ -1960,6 +1960,15 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 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/list": 5.9.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"
|
||||
"@material/chips": 14.0.0-canary.261f2db59.0
|
||||
"@material/data-table": 14.0.0-canary.261f2db59.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user