From 52c871dd0d798bcd27fa86df399abb066361f5d8 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Fri, 21 May 2021 13:20:05 +0000 Subject: [PATCH 01/13] Refresh snapshot create/restore dialogs --- .../components/supervisor-snapshot-content.ts | 384 ++++++++++++++++++ .../snapshot/dialog-hassio-create-snapshot.ts | 287 ++----------- .../snapshot/dialog-hassio-snapshot.ts | 361 ++++------------ src/components/ha-button-menu.ts | 3 + src/translations/en.json | 7 +- 5 files changed, 521 insertions(+), 521 deletions(-) create mode 100644 hassio/src/components/supervisor-snapshot-content.ts diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts new file mode 100644 index 000000000000..8e3ca5b24a88 --- /dev/null +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -0,0 +1,384 @@ +import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js"; +import { PaperInputElement } from "@polymer/paper-input/paper-input"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { atLeastVersion } from "../../../src/common/config/version"; +import { formatDateTime } from "../../../src/common/datetime/format_date_time"; +import "../../../src/components/ha-checkbox"; +import "../../../src/components/ha-formfield"; +import "../../../src/components/ha-radio"; +import type { HaRadio } from "../../../src/components/ha-radio"; +import { HassioSnapshotDetail } from "../../../src/data/hassio/snapshot"; +import { Supervisor } from "../../../src/data/supervisor/supervisor"; +import { PolymerChangedEvent } from "../../../src/polymer-types"; +import { HomeAssistant } from "../../../src/types"; + +interface CheckboxItem { + slug: string; + checked: boolean; + name: string; +} + +interface AddonCheckboxItem extends CheckboxItem { + version: string; +} + +const _computeFolders = (folders): CheckboxItem[] => { + const list: CheckboxItem[] = []; + if (folders.includes("homeassistant")) { + list.push({ + slug: "homeassistant", + name: "Home Assistant configuration", + checked: false, + }); + } + if (folders.includes("ssl")) { + list.push({ slug: "ssl", name: "SSL", checked: false }); + } + if (folders.includes("share")) { + list.push({ slug: "share", name: "Share", checked: false }); + } + if (folders.includes("addons/local")) { + list.push({ slug: "addons/local", name: "Local add-ons", checked: false }); + } + return list.sort((a, b) => (a.name > b.name ? 1 : -1)); +}; + +const _computeAddons = (addons): AddonCheckboxItem[] => + addons + .map((addon) => ({ + slug: addon.slug, + name: addon.name, + version: addon.version, + checked: false, + })) + .sort((a, b) => (a.name > b.name ? 1 : -1)); + +@customElement("supervisor-snapshot-content") +export class SupervisorSnapshotContent extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public supervisor?: Supervisor; + + @property({ attribute: false }) public snapshot?: HassioSnapshotDetail; + + @property() public snapshotType: HassioSnapshotDetail["type"] = "full"; + + @property({ attribute: false }) public folders?: CheckboxItem[]; + + @property({ attribute: false }) public addons?: AddonCheckboxItem[]; + + @property({ type: Boolean }) public homeAssistant = false; + + @property({ type: Boolean }) public snapshotHasPassword = false; + + @property() public snapshotName = ""; + + @property() public snapshotPassword = ""; + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + this.folders = _computeFolders( + this.snapshot + ? this.snapshot.folders + : ["homeassistant", "ssl", "share", "media", "addons/local"] + ); + this.addons = _computeAddons( + this.snapshot ? this.snapshot.addons : this.supervisor?.supervisor.addons + ); + this.snapshotType = this.snapshot?.type || "full"; + this.snapshotName = this.snapshot?.name || ""; + this.snapshotHasPassword = this.snapshot?.protected || false; + } + + protected render(): TemplateResult { + if (!this.supervisor) { + return html``; + } + return html` + ${this.snapshot + ? html`
+ ${this.snapshot.type === "full" + ? this.supervisor.localize("snapshot.full_snapshot") + : this.supervisor.localize("snapshot.partial_snapshot")} + (${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})
+ ${formatDateTime(new Date(this.snapshot.date), this.hass.locale)} +
` + : html` + `} + ${!this.snapshot || this.snapshot.type === "full" + ? html`
+
+ ${!this.snapshot + ? this.supervisor.localize("snapshot.type") + : this.supervisor.localize("snapshot.select_type")} +
+ + + + + + + + +
` + : ""} + ${this.snapshot && this.snapshotType === "partial" + ? html` + ${this.snapshot.homeassistant + ? html`
{ + this.homeAssistant = !this.homeAssistant; + }} + > + + + + Home Assistant + + (${this.snapshot.homeassistant}) + +
` + : ""} + ` + : ""} + ${this.snapshotType === "partial" + ? html` + ${this.folders?.length + ? html` +
+ this._toggleSection("folders", this.folders!)} + > + + + + ${this.supervisor.localize("snapshot.folders")} +
+
+ ${this.folders.map( + (item) => html` +
this._updateFolder(item)} + > + + + + ${item.name} +
+ ` + )} +
+ ` + : ""} + ${this.addons?.length + ? html` +
this._toggleSection("addons", this.addons!)} + > + + + + ${this.supervisor.localize("snapshot.addons")} +
+
+ ${this.addons.map( + (item) => html` +
this._updateAddon(item)} + > + +
+ ${atLeastVersion( + this.hass.config.version, + 0, + 105 + ) && + this.supervisor?.addon.addons.find( + (addon) => addon.slug === item.slug + )?.icon + ? html`` + : html` + `} + ${item.name} + (${item.version}) +
+
+ ` + )} +
+ ` + : ""} + ` + : ""} + ${!this.snapshot + ? html`
+ + + + ${this.supervisor.localize("snapshot.password_protection")} + + +
` + : ""} + ${this.snapshotHasPassword + ? html` + + + ` + : ""} + `; + } + + static get styles(): CSSResultGroup { + return css` + ha-checkbox { + --mdc-checkbox-touch-target-size: 16px; + display: block; + margin: 4px 12px 8px 0; + } + .details, + .item-version { + color: var(--secondary-text-color); + } + .icon { + max-height: 22px; + max-width: 22px; + margin-right: 8px; + } + .checkbox-row { + display: flex; + align-items: center; + font-size: 0.875rem; + cursor: pointer; + } + .item-name { + margin-right: 4px; + } + .section-content { + display: flex; + flex-direction: column; + margin-left: 16px; + } + .addon { + display: inline-flex; + align-items: center; + } + .security { + margin-top: 16px; + } + paper-input[type="password"] { + display: block; + margin: 4px 0 4px 16px; + } + `; + } + + private _handleRadioValueChanged(ev: CustomEvent) { + const input = ev.currentTarget as HaRadio; + this[input.name] = input.value; + } + + private _handleTextValueChanged(ev: PolymerChangedEvent) { + const input = ev.currentTarget as PaperInputElement; + this[input.name!] = ev.detail.value; + } + + private _toggleHasPassword(): void { + this.snapshotHasPassword = !this.snapshotHasPassword; + } + + private _sectionCheked(items: CheckboxItem[] | AddonCheckboxItem[]): boolean { + return !items.some((item) => !item.checked); + } + + private _sectionIndeterminate( + items: CheckboxItem[] | AddonCheckboxItem[] + ): boolean { + const checked = items.filter((item) => item.checked); + return checked.length !== 0 && checked.length !== items.length; + } + + private _toggleSection(section: "addons" | "folders", items): void { + const shouldCheck = + this._sectionIndeterminate(items) || !this._sectionCheked(items); + + items = items.map((item) => ({ ...item, checked: shouldCheck })); + + this[section] = items.map((item) => ({ + ...item, + checked: shouldCheck, + })); + } + + private _updateFolder(item: CheckboxItem): void { + this.folders = this.folders?.map((folder) => { + if (folder.slug === item.slug) { + folder.checked = !folder.checked; + } + return folder; + }); + } + + private _updateAddon(item: AddonCheckboxItem): void { + this.addons = this.addons?.map((addon) => { + if (addon.slug === item.slug) { + addon.checked = !addon.checked; + } + return addon; + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "supervisor-snapshot-content": SupervisorSnapshotContent; + } +} diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts index 4afbf0acae45..4cab7e141eff 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts @@ -1,91 +1,45 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; -import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { formatDate } from "../../../../src/common/datetime/format_date"; import { fireEvent } from "../../../../src/common/dom/fire_event"; -import { compare } from "../../../../src/common/string/compare"; import "../../../../src/components/buttons/ha-progress-button"; -import "../../../../src/components/ha-checkbox"; -import type { HaCheckbox } from "../../../../src/components/ha-checkbox"; import { createCloseHeading } from "../../../../src/components/ha-dialog"; -import "../../../../src/components/ha-formfield"; -import "../../../../src/components/ha-radio"; -import type { HaRadio } from "../../../../src/components/ha-radio"; -import "../../../../src/components/ha-settings-row"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { createHassioFullSnapshot, createHassioPartialSnapshot, HassioFullSnapshotCreateParams, HassioPartialSnapshotCreateParams, - HassioSnapshot, } from "../../../../src/data/hassio/snapshot"; import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box"; -import { PolymerChangedEvent } from "../../../../src/polymer-types"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { HomeAssistant } from "../../../../src/types"; +import "../../components/supervisor-snapshot-content"; +import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content"; import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot"; -interface CheckboxItem { - slug: string; - checked: boolean; - name?: string; - version?: string; -} - -const folderList = () => [ - { - slug: "homeassistant", - checked: true, - }, - { slug: "ssl", checked: true }, - { slug: "share", checked: true }, - { slug: "media", checked: true }, - { slug: "addons/local", checked: true }, -]; - @customElement("dialog-hassio-create-snapshot") class HassioCreateSnapshotDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @state() private _snapshotName = ""; - - @state() private _snapshotPassword = ""; - - @state() private _snapshotHasPassword = false; - - @state() private _snapshotType: HassioSnapshot["type"] = "full"; - @state() private _dialogParams?: HassioCreateSnapshotDialogParams; - @state() private _addonList: CheckboxItem[] = []; + @state() private _error?: string; - @state() private _folderList: CheckboxItem[] = folderList(); + @state() private _creatingSnapshot = false; - @state() private _error = ""; + @query("supervisor-snapshot-content", true) + private _snapshotContent!: SupervisorSnapshotContent; public showDialog(params: HassioCreateSnapshotDialogParams) { this._dialogParams = params; - this._addonList = this._dialogParams.supervisor.supervisor.addons - .map((addon) => ({ - slug: addon.slug, - name: addon.name, - version: addon.version, - checked: true, - })) - .sort((a, b) => compare(a.name, b.name)); - this._snapshotType = "full"; - this._error = ""; - this._folderList = folderList(); - this._snapshotHasPassword = false; - this._snapshotPassword = ""; - this._snapshotName = ""; + this._creatingSnapshot = false; } public closeDialog() { this._dialogParams = undefined; + this._creatingSnapshot = false; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -102,173 +56,29 @@ class HassioCreateSnapshotDialog extends LitElement { this._dialogParams.supervisor.localize("snapshot.create_snapshot") )} > - - -
-
- ${this._dialogParams.supervisor.localize("snapshot.type")}: -
- - - - - ` + : html` - - - -
- - ${ - this._snapshotType === "full" - ? undefined - : html` - ${this._dialogParams.supervisor.localize("snapshot.folders")}: -
- ${this._folderList.map( - (folder, idx) => html` -
- - - - ${this._dialogParams!.supervisor.localize( - `snapshot.folder.${folder.slug}` - )} - -
- ` - )} -
- - ${this._dialogParams.supervisor.localize("snapshot.addons")}: -
- ${this._addonList.map( - (addon, idx) => html` -
- - - - ${addon.name} - (${addon.version}) - - -
- ` - )} -
- ` - } - ${this._dialogParams.supervisor.localize("snapshot.security")}: -
-
- - - - ${this._dialogParams.supervisor.localize( - "snapshot.password_protection" - )} - - -
-
- - ${ - this._snapshotHasPassword - ? html` - - - ` - : undefined - } - ${ - this._error !== "" - ? html`

${this._error}

` - : undefined - } + `} + ${this._error ? html`

Error: ${this._error}

` : ""} ${this._dialogParams.supervisor.localize("common.close")} - + ${this._dialogParams.supervisor.localize("snapshot.create")} - + `; } - private _handleTextValueChanged(ev: PolymerChangedEvent) { - const input = ev.currentTarget as PaperInputElement; - this[`_${input.name}`] = ev.detail.value; - } - - private _handleCheckboxValueChanged(ev: CustomEvent) { - const input = ev.currentTarget as HaCheckbox; - this._snapshotHasPassword = input.checked; - } - - private _handleRadioValueChanged(ev: CustomEvent) { - const input = ev.currentTarget as HaRadio; - this[`_${input.name}`] = input.value; - } - - private _folderChecked(ev) { - const { idx, checked } = ev.currentTarget!; - this._folderList = this._folderList.map((folder, curIdx) => - curIdx === idx ? { ...folder, checked } : folder - ); - } - - private _addonChecked(ev) { - const { idx, checked } = ev.currentTarget!; - this._addonList = this._addonList.map((addon, curIdx) => - curIdx === idx ? { ...addon, checked } : addon - ); - } - - private async _createSnapshot(ev: CustomEvent): Promise { + private async _createSnapshot(): Promise { if (this._dialogParams!.supervisor.info.state !== "running") { showAlertDialog(this, { title: this._dialogParams!.supervisor.localize( @@ -282,38 +92,42 @@ class HassioCreateSnapshotDialog extends LitElement { }); return; } - const button = ev.currentTarget as any; - button.progress = true; + this._creatingSnapshot = true; this._error = ""; - if (this._snapshotHasPassword && !this._snapshotPassword.length) { + if ( + this._snapshotContent.snapshotHasPassword && + !this._snapshotContent.snapshotPassword.length + ) { this._error = this._dialogParams!.supervisor.localize( "snapshot.enter_password" ); - button.progress = false; + this._creatingSnapshot = false; return; } - const name = this._snapshotName || formatDate(new Date(), this.hass.locale); + const name = + this._snapshotContent.snapshotName || + formatDate(new Date(), this.hass.locale); try { - if (this._snapshotType === "full") { + if (this._snapshotContent.snapshotType === "full") { const data: HassioFullSnapshotCreateParams = { name }; - if (this._snapshotHasPassword) { - data.password = this._snapshotPassword; + if (this._snapshotContent.snapshotHasPassword) { + data.password = this._snapshotContent.snapshotPassword; } await createHassioFullSnapshot(this.hass, data); } else { const data: HassioPartialSnapshotCreateParams = { name, - folders: this._folderList - .filter((folder) => folder.checked) + folders: this._snapshotContent + .folders!.filter((folder) => folder.checked) .map((folder) => folder.slug), - addons: this._addonList - .filter((addon) => addon.checked) + addons: this._snapshotContent + .addons!.filter((addon) => addon.checked) .map((addon) => addon.slug), }; - if (this._snapshotHasPassword) { - data.password = this._snapshotPassword; + if (this._snapshotContent.snapshotHasPassword) { + data.password = this._snapshotContent.snapshotPassword; } await createHassioPartialSnapshot(this.hass, data); } @@ -323,7 +137,7 @@ class HassioCreateSnapshotDialog extends LitElement { } catch (err) { this._error = extractApiErrorMessage(err); } - button.progress = false; + this._creatingSnapshot = false; } static get styles(): CSSResultGroup { @@ -331,22 +145,9 @@ class HassioCreateSnapshotDialog extends LitElement { haStyle, haStyleDialog, css` - .error { - color: var(--error-color); - } - paper-input[type="password"] { + ha-circular-progress { display: block; - margin: 4px 0 4px 16px; - } - span.version { - color: var(--secondary-text-color); - } - .checkbox-section { - display: grid; - } - .checkbox-line { - display: inline-flex; - align-items: center; + text-align: center; } `, ]; diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts index 81f21bb90867..571863a47458 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts @@ -1,13 +1,12 @@ -import "@material/mwc-button"; -import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js"; -import "@polymer/paper-checkbox/paper-checkbox"; -import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox"; -import "@polymer/paper-input/paper-input"; +import { ActionDetail } from "@material/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import { mdiDotsVertical } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { formatDateTime } from "../../../../src/common/datetime/format_date_time"; +import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../../../src/common/dom/fire_event"; -import "../../../../src/components/ha-header-bar"; +import "../../../../src/components/buttons/ha-progress-button"; +import "../../../../src/components/ha-button-menu"; +import { createCloseHeading } from "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-svg-icon"; import { getSignedPath } from "../../../../src/data/auth"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; @@ -15,106 +14,44 @@ import { fetchHassioSnapshotInfo, HassioSnapshotDetail, } from "../../../../src/data/hassio/snapshot"; -import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { showAlertDialog, showConfirmationDialog, } from "../../../../src/dialogs/generic/show-dialog-box"; import { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; -import { PolymerChangedEvent } from "../../../../src/polymer-types"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { HomeAssistant } from "../../../../src/types"; +import "../../components/supervisor-snapshot-content"; +import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content"; import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot"; -const _computeFolders = (folders) => { - const list: Array<{ slug: string; name: string; checked: boolean }> = []; - if (folders.includes("homeassistant")) { - list.push({ - slug: "homeassistant", - name: "Home Assistant configuration", - checked: true, - }); - } - if (folders.includes("ssl")) { - list.push({ slug: "ssl", name: "SSL", checked: true }); - } - if (folders.includes("share")) { - list.push({ slug: "share", name: "Share", checked: true }); - } - if (folders.includes("addons/local")) { - list.push({ slug: "addons/local", name: "Local add-ons", checked: true }); - } - return list; -}; - -const _computeAddons = (addons) => - addons.map((addon) => ({ - slug: addon.slug, - name: addon.name, - version: addon.version, - checked: true, - })); - -interface AddonItem { - slug: string; - name: string; - version: string; - checked: boolean | null | undefined; -} - -interface FolderItem { - slug: string; - name: string; - checked: boolean | null | undefined; -} - @customElement("dialog-hassio-snapshot") class HassioSnapshotDialog extends LitElement implements HassDialog { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) public supervisor?: Supervisor; - @state() private _error?: string; - @state() private _onboarding = false; - @state() private _snapshot?: HassioSnapshotDetail; - @state() private _folders!: FolderItem[]; - - @state() private _addons!: AddonItem[]; - @state() private _dialogParams?: HassioSnapshotDialogParams; - @state() private _snapshotPassword!: string; + @state() private _restoringSnapshot = false; - @state() private _restoreHass = true; + @query("supervisor-snapshot-content", true) + private _snapshotContent!: SupervisorSnapshotContent; public async showDialog(params: HassioSnapshotDialogParams) { this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); - this._folders = _computeFolders( - this._snapshot?.folders - ).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1)); - this._addons = _computeAddons( - this._snapshot?.addons - ).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1)); - this._dialogParams = params; - this._onboarding = params.onboarding ?? false; - this.supervisor = params.supervisor; - if (!this._snapshot.homeassistant) { - this._restoreHass = false; - } + this._restoringSnapshot = false; } public closeDialog() { - this._dialogParams = undefined; this._snapshot = undefined; - this._snapshotPassword = ""; - this._folders = []; - this._addons = []; + this._dialogParams = undefined; + this._restoringSnapshot = false; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -123,121 +60,40 @@ class HassioSnapshotDialog return html``; } return html` - -
- - ${this._computeName} - - - - -
-
- ${this._snapshot.type === "full" - ? "Full snapshot" - : "Partial snapshot"} - (${this._computeSize})
- ${formatDateTime(new Date(this._snapshot.date), this.hass.locale)} -
- ${this._snapshot.homeassistant - ? html`
Home Assistant:
- - Home Assistant - (${this._snapshot.homeassistant}) - ` - : ""} - ${this._folders.length - ? html` -
Folders:
- - ${this._folders.map( - (item) => html` - - ${item.name} - - ` - )} - - ` - : ""} - ${this._addons.length - ? html` -
Add-on:
- - ${this._addons.map( - (item) => html` - - ${item.name} - (${item.version}) - - ` - )} - - ` - : ""} - ${this._snapshot.protected - ? html` - - ` - : ""} - ${this._error ? html`

Error: ${this._error}

` : ""} - -
- - - Restore Selected - - ${!this._onboarding - ? html` - - - - Delete Snapshot - - ` - : ""} -
-
- ${this._snapshot.type === "full" - ? html` - - - Restore Everything - - ` - : ""} - ${!this._onboarding - ? html` - - Download Snapshot - ` - : ""} -
+ + ${this._restoringSnapshot + ? html` ` + : html` + `} + ${this._error ? html`

Error: ${this._error}

` : ""} + + + Restore + + + + + + + Download Snapshot + Delete Snapshot +
`; } @@ -247,83 +103,42 @@ class HassioSnapshotDialog haStyle, haStyleDialog, css` - paper-checkbox { - display: block; - margin: 4px; - } - mwc-button ha-svg-icon { - margin-right: 4px; - } - .button-row { - display: grid; - gap: 8px; - margin-right: 8px; - } - .details { - color: var(--secondary-text-color); - } - .warning, - .error { - color: var(--error-color); - } - .buttons li { - list-style-type: none; - } - .buttons .icon { - margin-right: 16px; - } - .no-margin-top { - margin-top: 0; - } - span.version { - color: var(--secondary-text-color); - } - ha-header-bar { - --mdc-theme-on-primary: var(--primary-text-color); - --mdc-theme-primary: var(--mdc-theme-surface); - flex-shrink: 0; - } - /* overrule the ha-style-dialog max-height on small screens */ - @media all and (max-width: 450px), all and (max-height: 500px) { - ha-header-bar { - --mdc-theme-primary: var(--app-header-background-color); - --mdc-theme-on-primary: var(--app-header-text-color, white); - } + ha-svg-icon { + color: var(--primary-text-color); } `, ]; } - private _updateFolders(item: FolderItem, value: boolean | null | undefined) { - this._folders = this._folders.map((folder) => { - if (folder.slug === item.slug) { - folder.checked = value; - } - return folder; - }); - } - - private _updateAddons(item: AddonItem, value: boolean | null | undefined) { - this._addons = this._addons.map((addon) => { - if (addon.slug === item.slug) { - addon.checked = value; - } - return addon; - }); + private _handleMenuAction(ev: CustomEvent) { + switch (ev.detail.index) { + case 0: + this._downloadClicked(); + break; + case 1: + this._deleteClicked(); + break; + } } - private _passwordInput(ev: PolymerChangedEvent) { - this._snapshotPassword = ev.detail.value; + private async _restoreClicked() { + this._restoringSnapshot = true; + if (this._snapshotContent.snapshotType === "full") { + await this._fullRestoreClicked(); + } else { + await this._partialRestoreClicked(); + } + this._restoringSnapshot = false; } private async _partialRestoreClicked() { if ( - this.supervisor !== undefined && - this.supervisor.info.state !== "running" + this._dialogParams?.supervisor !== undefined && + this._dialogParams?.supervisor.info.state !== "running" ) { await showAlertDialog(this, { title: "Could not restore snapshot", - text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`, + text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, }); return; } @@ -337,12 +152,12 @@ class HassioSnapshotDialog return; } - const addons = this._addons - .filter((addon) => addon.checked) + const addons = this._snapshotContent + .addons!.filter((addon) => addon.checked) .map((addon) => addon.slug); - const folders = this._folders - .filter((folder) => folder.checked) + const folders = this._snapshotContent + .folders!.filter((folder) => folder.checked) .map((folder) => folder.slug); const data: { @@ -351,16 +166,16 @@ class HassioSnapshotDialog folders: any; password?: string; } = { - homeassistant: this._restoreHass, + homeassistant: this._snapshotContent.homeAssistant, addons, folders, }; if (this._snapshot!.protected) { - data.password = this._snapshotPassword; + data.password = this._snapshotContent.snapshotPassword; } - if (!this._onboarding) { + if (!this._dialogParams?.onboarding) { this.hass .callApi( "POST", @@ -370,7 +185,6 @@ class HassioSnapshotDialog ) .then( () => { - alert("Snapshot restored!"); this.closeDialog(); }, (error) => { @@ -389,12 +203,12 @@ class HassioSnapshotDialog private async _fullRestoreClicked() { if ( - this.supervisor !== undefined && - this.supervisor.info.state !== "running" + this._dialogParams?.supervisor !== undefined && + this._dialogParams?.supervisor.info.state !== "running" ) { await showAlertDialog(this, { title: "Could not restore snapshot", - text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`, + text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, }); return; } @@ -410,9 +224,9 @@ class HassioSnapshotDialog } const data = this._snapshot!.protected - ? { password: this._snapshotPassword } + ? { password: this._snapshotContent.snapshotPassword } : undefined; - if (!this._onboarding) { + if (!this._dialogParams?.onboarding) { this.hass .callApi( "POST", @@ -421,7 +235,6 @@ class HassioSnapshotDialog ) .then( () => { - alert("Snapshot restored!"); this.closeDialog(); }, (error) => { @@ -473,7 +286,9 @@ class HassioSnapshotDialog `/api/hassio/snapshots/${this._snapshot!.slug}/download` ); } catch (err) { - alert(`Error: ${extractApiErrorMessage(err)}`); + await showAlertDialog(this, { + text: extractApiErrorMessage(err), + }); return; } @@ -504,10 +319,6 @@ class HassioSnapshotDialog ? this._snapshot.name || this._snapshot.slug : "Unnamed snapshot"; } - - private get _computeSize() { - return Math.ceil(this._snapshot!.size * 10) / 10 + " MB"; - } } declare global { diff --git a/src/components/ha-button-menu.ts b/src/components/ha-button-menu.ts index 52e27af49ec6..8223b68d1cc7 100644 --- a/src/components/ha-button-menu.ts +++ b/src/components/ha-button-menu.ts @@ -12,6 +12,8 @@ export class HaButtonMenu extends LitElement { @property({ type: Boolean }) public disabled = false; + @property({ type: Boolean }) public fixed = false; + @query("mwc-menu", true) private _menu?: Menu; public get items() { @@ -29,6 +31,7 @@ export class HaButtonMenu extends LitElement { diff --git a/src/translations/en.json b/src/translations/en.json index 739a5e9b8316..f9f8acaa0a77 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3896,14 +3896,15 @@ "create_snapshot": "Create snapshot", "create": "Create", "created": "Created", - "name": "Name", - "type": "Type", + "name": "Snapshot name", + "type": "Snapshot type", + "select_type": "Select what to restore", "security": "Security", "full_snapshot": "Full snapshot", "partial_snapshot": "Partial snapshot", "addons": "Add-ons", "folders": "Folders", - "password": "Password", + "password": "Snapshot password", "password_protection": "Password protection", "password_protected": "password protected", "enter_password": "Please enter a password.", From c5072f13c4223930f97aca85f154571170db62a8 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Fri, 21 May 2021 13:35:02 +0000 Subject: [PATCH 02/13] Clear _errror --- hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts | 1 + hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts index 4cab7e141eff..a81fd5befabf 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts @@ -40,6 +40,7 @@ class HassioCreateSnapshotDialog extends LitElement { public closeDialog() { this._dialogParams = undefined; this._creatingSnapshot = false; + this._error = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts index 571863a47458..28eb526c53fe 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts @@ -52,6 +52,7 @@ class HassioSnapshotDialog this._snapshot = undefined; this._dialogParams = undefined; this._restoringSnapshot = false; + this._error = undefined; fireEvent(this, "dialog-closed", { dialog: this.localName }); } @@ -106,6 +107,10 @@ class HassioSnapshotDialog ha-svg-icon { color: var(--primary-text-color); } + ha-circular-progress { + display: block; + text-align: center; + } `, ]; } From 98ac4c429b88e10829f61bca27d5995fb9f1f804 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 07:55:45 +0000 Subject: [PATCH 03/13] update --- .../components/supervisor-snapshot-content.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index 8e3ca5b24a88..dfd3d8082a14 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -76,19 +76,23 @@ export class SupervisorSnapshotContent extends LitElement { @property() public snapshotPassword = ""; - protected firstUpdated(changedProps) { - super.firstUpdated(changedProps); - this.folders = _computeFolders( - this.snapshot - ? this.snapshot.folders - : ["homeassistant", "ssl", "share", "media", "addons/local"] - ); - this.addons = _computeAddons( - this.snapshot ? this.snapshot.addons : this.supervisor?.supervisor.addons - ); - this.snapshotType = this.snapshot?.type || "full"; - this.snapshotName = this.snapshot?.name || ""; - this.snapshotHasPassword = this.snapshot?.protected || false; + public willUpdate(changedProps) { + super.willUpdate(changedProps); + if (!this.hasUpdated) { + this.folders = _computeFolders( + this.snapshot + ? this.snapshot.folders + : ["homeassistant", "ssl", "share", "media", "addons/local"] + ); + this.addons = _computeAddons( + this.snapshot + ? this.snapshot.addons + : this.supervisor?.supervisor.addons + ); + this.snapshotType = this.snapshot?.type || "full"; + this.snapshotName = this.snapshot?.name || ""; + this.snapshotHasPassword = this.snapshot?.protected || false; + } } protected render(): TemplateResult { @@ -172,7 +176,7 @@ export class SupervisorSnapshotContent extends LitElement { this._toggleSection("folders", this.folders!)} > @@ -203,7 +207,7 @@ export class SupervisorSnapshotContent extends LitElement { @click=${() => this._toggleSection("addons", this.addons!)} > From 0132e82225b3f0a824c1ee49a9cd49217b3a2b3e Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 09:20:23 +0000 Subject: [PATCH 04/13] Add supervisor-formfield-label --- .../components/supervisor-formfield-label.ts | 55 ++++++ .../components/supervisor-snapshot-content.ts | 162 +++++++++--------- 2 files changed, 137 insertions(+), 80 deletions(-) create mode 100644 hassio/src/components/supervisor-formfield-label.ts diff --git a/hassio/src/components/supervisor-formfield-label.ts b/hassio/src/components/supervisor-formfield-label.ts new file mode 100644 index 000000000000..e173c0c44c3c --- /dev/null +++ b/hassio/src/components/supervisor-formfield-label.ts @@ -0,0 +1,55 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../../src/components/ha-svg-icon"; + +@customElement("supervisor-formfield-label") +class SupervisorFormfieldLabel extends LitElement { + @property({ type: String }) public label!: string; + + @property({ type: String }) public imageUrl?: string; + + @property({ type: String }) public iconPath?: string; + + @property({ type: String }) public version?: string; + + protected render(): TemplateResult { + return html` + ${this.imageUrl + ? html`` + : this.iconPath + ? html`` + : ""} + ${this.label} + ${this.version + ? html`(${this.version})` + : ""} + `; + } + + static get styles(): CSSResultGroup { + return css` + :host { + cursor: pointer; + display: flex; + align-items: center; + } + .label { + margin-right: 4px; + } + .version { + color: var(--secondary-text-color); + } + .icon { + max-height: 22px; + max-width: 22px; + margin-right: 8px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "supervisor-formfield-label": SupervisorFormfieldLabel; + } +} diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index dfd3d8082a14..4ca12a240a14 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -1,3 +1,4 @@ +import "./supervisor-formfield-label"; import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js"; import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; @@ -149,20 +150,24 @@ export class SupervisorSnapshotContent extends LitElement { ${this.snapshot && this.snapshotType === "partial" ? html` ${this.snapshot.homeassistant - ? html`
{ - this.homeAssistant = !this.homeAssistant; - }} - > - - - - Home Assistant - - (${this.snapshot.homeassistant}) - -
` + ? html` + + `} + > + { + this.homeAssistant = !this.homeAssistant; + }} + > + + + ` : ""} ` : ""} @@ -170,31 +175,38 @@ export class SupervisorSnapshotContent extends LitElement { ? html` ${this.folders?.length ? html` -
- this._toggleSection("folders", this.folders!)} + + `} > + this._toggleSection("folders", this.folders!)} > - - ${this.supervisor.localize("snapshot.folders")} -
+
${this.folders.map( (item) => html` -
this._updateFolder(item)} + + `} > - - - - ${item.name} -
+ + + ` )}
@@ -202,28 +214,28 @@ export class SupervisorSnapshotContent extends LitElement { : ""} ${this.addons?.length ? html` -
this._toggleSection("addons", this.addons!)} + + `} > + this._toggleSection("addons", this.addons!)} > - - ${this.supervisor.localize("snapshot.addons")} -
+
${this.addons.map( (item) => html` -
this._updateAddon(item)} - > - -
- ${atLeastVersion( + addon.slug === item.slug )?.icon - ? html`` - : html` - `} - ${item.name} - (${item.version}) -
-
+ ? `/api/hassio/addons/${item.slug}/icon` + : undefined} + .iconPath=${mdiPuzzle} + .version=${item.version} + > + `} + > + + + ` )}
@@ -288,40 +300,28 @@ export class SupervisorSnapshotContent extends LitElement { display: block; margin: 4px 12px 8px 0; } - .details, - .item-version { - color: var(--secondary-text-color); - } - .icon { - max-height: 22px; - max-width: 22px; - margin-right: 8px; + ha-formfield { + display: block; } - .checkbox-row { - display: flex; + supervisor-formfield-label { + display: inline-flex; align-items: center; - font-size: 0.875rem; - cursor: pointer; } - .item-name { - margin-right: 4px; + paper-input[type="password"] { + display: block; + margin: 4px 0 4px 16px; + } + .details { + color: var(--secondary-text-color); } .section-content { display: flex; flex-direction: column; margin-left: 16px; } - .addon { - display: inline-flex; - align-items: center; - } .security { margin-top: 16px; } - paper-input[type="password"] { - display: block; - margin: 4px 0 4px 16px; - } `; } @@ -362,7 +362,8 @@ export class SupervisorSnapshotContent extends LitElement { })); } - private _updateFolder(item: CheckboxItem): void { + private _updateFolder(ev: any): void { + const item = ev.currentTarget.item; this.folders = this.folders?.map((folder) => { if (folder.slug === item.slug) { folder.checked = !folder.checked; @@ -371,7 +372,8 @@ export class SupervisorSnapshotContent extends LitElement { }); } - private _updateAddon(item: AddonCheckboxItem): void { + private _updateAddon(ev: any): void { + const item = ev.currentTarget.item; this.addons = this.addons?.map((addon) => { if (addon.slug === item.slug) { addon.checked = !addon.checked; From a252fbe43dd1356a53d166c1088d1fc29a7a04e7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 25 May 2021 11:36:12 +0200 Subject: [PATCH 05/13] Update supervisor-snapshot-content.ts --- .../components/supervisor-snapshot-content.ts | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index 4ca12a240a14..ef5af9304138 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -185,8 +185,8 @@ export class SupervisorSnapshotContent extends LitElement { - this._toggleSection("folders", this.folders!)} + .section=${"folders"} + @change=${this._toggleSection} > @@ -203,7 +203,7 @@ export class SupervisorSnapshotContent extends LitElement { @@ -224,8 +224,8 @@ export class SupervisorSnapshotContent extends LitElement { - this._toggleSection("addons", this.addons!)} + .section=${"addons"} + @change=${this._toggleSection} > @@ -253,7 +253,7 @@ export class SupervisorSnapshotContent extends LitElement { @@ -265,18 +265,15 @@ export class SupervisorSnapshotContent extends LitElement { ` : ""} ${!this.snapshot - ? html`
- - - ${this.supervisor.localize("snapshot.password_protection")} - - -
` + ` : ""} ${this.snapshotHasPassword ? html` @@ -350,33 +347,32 @@ export class SupervisorSnapshotContent extends LitElement { return checked.length !== 0 && checked.length !== items.length; } - private _toggleSection(section: "addons" | "folders", items): void { - const shouldCheck = - this._sectionIndeterminate(items) || !this._sectionCheked(items); + private _toggleSection(ev): void { + const section = ev.currentTarget.section; - items = items.map((item) => ({ ...item, checked: shouldCheck })); - - this[section] = items.map((item) => ({ - ...item, - checked: shouldCheck, - })); + this[section] = (section === "addons" ? this.addons : this.folders)!.map( + (item) => ({ + ...item, + checked: ev.currentTarget.checked, + }) + ); } - private _updateFolder(ev: any): void { + private _updateFolder(ev): void { const item = ev.currentTarget.item; this.folders = this.folders?.map((folder) => { if (folder.slug === item.slug) { - folder.checked = !folder.checked; + folder.checked = ev.currentTarget.checked; } return folder; }); } - private _updateAddon(ev: any): void { + private _updateAddon(ev): void { const item = ev.currentTarget.item; this.addons = this.addons?.map((addon) => { if (addon.slug === item.slug) { - addon.checked = !addon.checked; + addon.checked = ev.currentTarget.checked; } return addon; }); From 1ec7a36c87c292da35f0c43290c9debc1cc16c71 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 09:56:44 +0000 Subject: [PATCH 06/13] limit loops --- .../components/supervisor-snapshot-content.ts | 133 ++++++++---------- 1 file changed, 60 insertions(+), 73 deletions(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index ef5af9304138..05a58023fbc7 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -1,4 +1,3 @@ -import "./supervisor-formfield-label"; import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js"; import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; @@ -13,6 +12,7 @@ import { HassioSnapshotDetail } from "../../../src/data/hassio/snapshot"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { PolymerChangedEvent } from "../../../src/polymer-types"; import { HomeAssistant } from "../../../src/types"; +import "./supervisor-formfield-label"; interface CheckboxItem { slug: string; @@ -100,6 +100,9 @@ export class SupervisorSnapshotContent extends LitElement { if (!this.supervisor) { return html``; } + const foldersSection = this._getSection("folders"); + const addonsSection = this._getSection("addons"); + return html` ${this.snapshot ? html`
@@ -173,7 +176,7 @@ export class SupervisorSnapshotContent extends LitElement { : ""} ${this.snapshotType === "partial" ? html` - ${this.folders?.length + ${foldersSection.templates.length ? html` `} > -
- ${this.folders.map( - (item) => html` - - `} - > - - - - ` - )} -
+
${foldersSection.templates}
` : ""} - ${this.addons?.length + ${addonsSection.templates.length ? html` `} > -
- ${this.addons.map( - (item) => html` - addon.slug === item.slug - )?.icon - ? `/api/hassio/addons/${item.slug}/icon` - : undefined} - .iconPath=${mdiPuzzle} - .version=${item.version} - > - `} - > - - - - ` - )} -
+
${addonsSection.templates}
` : ""} ` @@ -322,6 +276,50 @@ export class SupervisorSnapshotContent extends LitElement { `; } + private _getSection(section: string) { + const templates: TemplateResult[] = []; + let checkedItems = 0; + this[section].forEach((item) => { + templates.push(html` addon.slug === item.slug + )?.icon + ? `/api/hassio/addons/${item.slug}/icon` + : undefined} + .version=${item.version} + > + `} + > + + + `); + + if (item.checked) { + checkedItems++; + } + }); + + const checked = checkedItems === this[section].length; + + return { + templates, + checked, + indeterminate: !checked && checkedItems !== 0, + }; + } + private _handleRadioValueChanged(ev: CustomEvent) { const input = ev.currentTarget as HaRadio; this[input.name] = input.value; @@ -336,17 +334,6 @@ export class SupervisorSnapshotContent extends LitElement { this.snapshotHasPassword = !this.snapshotHasPassword; } - private _sectionCheked(items: CheckboxItem[] | AddonCheckboxItem[]): boolean { - return !items.some((item) => !item.checked); - } - - private _sectionIndeterminate( - items: CheckboxItem[] | AddonCheckboxItem[] - ): boolean { - const checked = items.filter((item) => item.checked); - return checked.length !== 0 && checked.length !== items.length; - } - private _toggleSection(ev): void { const section = ev.currentTarget.section; @@ -362,7 +349,7 @@ export class SupervisorSnapshotContent extends LitElement { const item = ev.currentTarget.item; this.folders = this.folders?.map((folder) => { if (folder.slug === item.slug) { - folder.checked = ev.currentTarget.checked; + folder.checked = !folder.checked; } return folder; }); @@ -372,7 +359,7 @@ export class SupervisorSnapshotContent extends LitElement { const item = ev.currentTarget.item; this.addons = this.addons?.map((addon) => { if (addon.slug === item.slug) { - addon.checked = ev.currentTarget.checked; + addon.checked = !addon.checked; } return addon; }); From b06dae3aec30cb3abd06ac9898a47af40251dc5a Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 09:59:12 +0000 Subject: [PATCH 07/13] intersept closing event --- hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts index 28eb526c53fe..a033b8497379 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts @@ -88,6 +88,7 @@ class HassioSnapshotDialog fixed slot="primaryAction" @action=${this._handleMenuAction} + @closing=${(ev: Event) => ev.stopPropagation()} > From bb0eecd88cde2ef1ab67baa772d4560008b597b7 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 10:41:07 +0000 Subject: [PATCH 08/13] Fix section entry updates --- .../components/supervisor-snapshot-content.ts | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index 05a58023fbc7..a5c1ad3d32b1 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -299,9 +299,8 @@ export class SupervisorSnapshotContent extends LitElement { `); @@ -345,24 +344,14 @@ export class SupervisorSnapshotContent extends LitElement { ); } - private _updateFolder(ev): void { + private _updateSectionEntry(ev): void { const item = ev.currentTarget.item; - this.folders = this.folders?.map((folder) => { - if (folder.slug === item.slug) { - folder.checked = !folder.checked; - } - return folder; - }); - } - - private _updateAddon(ev): void { - const item = ev.currentTarget.item; - this.addons = this.addons?.map((addon) => { - if (addon.slug === item.slug) { - addon.checked = !addon.checked; - } - return addon; - }); + const section = ev.currentTarget.section; + this[section] = this[section].map((entry) => ({ + ...entry, + checked: + entry.slug === item.slug ? ev.currentTarget.checked : entry.checked, + })); } } From 99245dc2e285b76867930c1187f0f094fa4c92d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 25 May 2021 13:51:24 +0200 Subject: [PATCH 09/13] Update hassio/src/components/supervisor-snapshot-content.ts Co-authored-by: Bram Kragten --- hassio/src/components/supervisor-snapshot-content.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index a5c1ad3d32b1..e60dd7b57f0d 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -347,11 +347,10 @@ export class SupervisorSnapshotContent extends LitElement { private _updateSectionEntry(ev): void { const item = ev.currentTarget.item; const section = ev.currentTarget.section; - this[section] = this[section].map((entry) => ({ + this[section] = this[section].map((entry) => entry.slug === item.slug ? { ...entry, - checked: - entry.slug === item.slug ? ev.currentTarget.checked : entry.checked, - })); + checked: ev.currentTarget.checked, + } : entry); } } From 31d9f417729f72fb05713e1673ce66b0cebfab73 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 11:58:44 +0000 Subject: [PATCH 10/13] Prettier --- hassio/src/components/supervisor-snapshot-content.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index e60dd7b57f0d..f7a4ac77348f 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -347,10 +347,14 @@ export class SupervisorSnapshotContent extends LitElement { private _updateSectionEntry(ev): void { const item = ev.currentTarget.item; const section = ev.currentTarget.section; - this[section] = this[section].map((entry) => entry.slug === item.slug ? { - ...entry, - checked: ev.currentTarget.checked, - } : entry); + this[section] = this[section].map((entry) => + entry.slug === item.slug + ? { + ...entry, + checked: ev.currentTarget.checked, + } + : entry + ); } } From 6e4e91ed7aaa6f87b5e2f0a58d6dae4d1ea9af08 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 12:04:27 +0000 Subject: [PATCH 11/13] fix style --- hassio/src/components/supervisor-snapshot-content.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index f7a4ac77348f..6ecaf6e19a70 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -252,7 +252,7 @@ export class SupervisorSnapshotContent extends LitElement { margin: 4px 12px 8px 0; } ha-formfield { - display: block; + display: contents; } supervisor-formfield-label { display: inline-flex; @@ -273,6 +273,9 @@ export class SupervisorSnapshotContent extends LitElement { .security { margin-top: 16px; } + .snapshot-types { + display: flex; + } `; } From d06febd4a7ab23c4e3131948c94b65179c51beb2 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 12:11:47 +0000 Subject: [PATCH 12/13] simplify logic --- .../src/components/supervisor-snapshot-content.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index 6ecaf6e19a70..1320c641b400 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -286,13 +286,12 @@ export class SupervisorSnapshotContent extends LitElement { templates.push(html` addon.slug === item.slug - )?.icon + .iconPath=${section === "addons" ? mdiPuzzle : mdiFolder} + .imageUrl=${section === "addons" && + atLeastVersion(this.hass.config.version, 0, 105) && + this.supervisor!.addon.addons.find( + (addon) => addon.slug === item.slug + )?.icon ? `/api/hassio/addons/${item.slug}/icon` : undefined} .version=${item.version} From 77de3f5295b1bc4ccc88e82a48cee331168304a1 Mon Sep 17 00:00:00 2001 From: Ludeeus Date: Tue, 25 May 2021 15:51:33 +0000 Subject: [PATCH 13/13] Add snapshotDetails --- .../components/supervisor-snapshot-content.ts | 113 +++++++++++++----- .../snapshot/dialog-hassio-create-snapshot.ts | 29 +---- .../snapshot/dialog-hassio-snapshot.ts | 45 ++----- src/data/hassio/snapshot.ts | 5 +- 4 files changed, 98 insertions(+), 94 deletions(-) diff --git a/hassio/src/components/supervisor-snapshot-content.ts b/hassio/src/components/supervisor-snapshot-content.ts index 1320c641b400..8c8797632982 100644 --- a/hassio/src/components/supervisor-snapshot-content.ts +++ b/hassio/src/components/supervisor-snapshot-content.ts @@ -3,12 +3,17 @@ import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { atLeastVersion } from "../../../src/common/config/version"; +import { formatDate } from "../../../src/common/datetime/format_date"; import { formatDateTime } from "../../../src/common/datetime/format_date_time"; import "../../../src/components/ha-checkbox"; import "../../../src/components/ha-formfield"; import "../../../src/components/ha-radio"; import type { HaRadio } from "../../../src/components/ha-radio"; -import { HassioSnapshotDetail } from "../../../src/data/hassio/snapshot"; +import { + HassioFullSnapshotCreateParams, + HassioPartialSnapshotCreateParams, + HassioSnapshotDetail, +} from "../../../src/data/hassio/snapshot"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { PolymerChangedEvent } from "../../../src/polymer-types"; import { HomeAssistant } from "../../../src/types"; @@ -100,8 +105,10 @@ export class SupervisorSnapshotContent extends LitElement { if (!this.supervisor) { return html``; } - const foldersSection = this._getSection("folders"); - const addonsSection = this._getSection("addons"); + const foldersSection = + this.snapshotType === "partial" ? this._getSection("folders") : undefined; + const addonsSection = + this.snapshotType === "partial" ? this._getSection("addons") : undefined; return html` ${this.snapshot @@ -120,35 +127,35 @@ export class SupervisorSnapshotContent extends LitElement { > `} ${!this.snapshot || this.snapshot.type === "full" - ? html`
-
+ ? html`
${!this.snapshot ? this.supervisor.localize("snapshot.type") : this.supervisor.localize("snapshot.select_type")}
- - + - - - - + + + - - -
` + + + +
` : ""} ${this.snapshot && this.snapshotType === "partial" ? html` @@ -176,7 +183,7 @@ export class SupervisorSnapshotContent extends LitElement { : ""} ${this.snapshotType === "partial" ? html` - ${foldersSection.templates.length + ${foldersSection?.templates.length ? html` ${foldersSection.templates}
` : ""} - ${addonsSection.templates.length + ${addonsSection?.templates.length ? html` addon.checked) + .map((addon) => addon.slug); + const folders = this.folders + ?.filter((folder) => folder.checked) + .map((folder) => folder.slug); + + if (addons?.length) { + data.addons = addons; + } + if (folders?.length) { + data.folders = folders; + } + if (this.homeAssistant) { + data.homeassistant = this.homeAssistant; + } + + return data; + } + private _getSection(section: string) { const templates: TemplateResult[] = []; + const addons = + section === "addons" + ? new Map( + this.supervisor!.addon.addons.map((item) => [item.slug, item]) + ) + : undefined; let checkedItems = 0; this[section].forEach((item) => { templates.push(html` addon.slug === item.slug - )?.icon + addons?.get(item.slug)?.icon ? `/api/hassio/addons/${item.slug}/icon` : undefined} .version=${item.version} diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts index a81fd5befabf..2bc817fe2ba5 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts @@ -1,7 +1,6 @@ import "@material/mwc-button"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { formatDate } from "../../../../src/common/datetime/format_date"; import { fireEvent } from "../../../../src/common/dom/fire_event"; import "../../../../src/components/buttons/ha-progress-button"; import { createCloseHeading } from "../../../../src/components/ha-dialog"; @@ -9,8 +8,6 @@ import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { createHassioFullSnapshot, createHassioPartialSnapshot, - HassioFullSnapshotCreateParams, - HassioPartialSnapshotCreateParams, } from "../../../../src/data/hassio/snapshot"; import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; @@ -29,7 +26,7 @@ class HassioCreateSnapshotDialog extends LitElement { @state() private _creatingSnapshot = false; - @query("supervisor-snapshot-content", true) + @query("supervisor-snapshot-content") private _snapshotContent!: SupervisorSnapshotContent; public showDialog(params: HassioCreateSnapshotDialogParams) { @@ -93,6 +90,7 @@ class HassioCreateSnapshotDialog extends LitElement { }); return; } + const snapshotDetails = this._snapshotContent.snapshotDetails(); this._creatingSnapshot = true; this._error = ""; @@ -106,31 +104,12 @@ class HassioCreateSnapshotDialog extends LitElement { this._creatingSnapshot = false; return; } - const name = - this._snapshotContent.snapshotName || - formatDate(new Date(), this.hass.locale); try { if (this._snapshotContent.snapshotType === "full") { - const data: HassioFullSnapshotCreateParams = { name }; - if (this._snapshotContent.snapshotHasPassword) { - data.password = this._snapshotContent.snapshotPassword; - } - await createHassioFullSnapshot(this.hass, data); + await createHassioFullSnapshot(this.hass, snapshotDetails); } else { - const data: HassioPartialSnapshotCreateParams = { - name, - folders: this._snapshotContent - .folders!.filter((folder) => folder.checked) - .map((folder) => folder.slug), - addons: this._snapshotContent - .addons!.filter((addon) => addon.checked) - .map((addon) => addon.slug), - }; - if (this._snapshotContent.snapshotHasPassword) { - data.password = this._snapshotContent.snapshotPassword; - } - await createHassioPartialSnapshot(this.hass, data); + await createHassioPartialSnapshot(this.hass, snapshotDetails); } this._dialogParams!.onCreate(); diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts index a033b8497379..3ffbfcd53d93 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts @@ -39,7 +39,7 @@ class HassioSnapshotDialog @state() private _restoringSnapshot = false; - @query("supervisor-snapshot-content", true) + @query("supervisor-snapshot-content") private _snapshotContent!: SupervisorSnapshotContent; public async showDialog(params: HassioSnapshotDialogParams) { @@ -128,16 +128,17 @@ class HassioSnapshotDialog } private async _restoreClicked() { + const snapshotDetails = this._snapshotContent.snapshotDetails(); this._restoringSnapshot = true; if (this._snapshotContent.snapshotType === "full") { - await this._fullRestoreClicked(); + await this._fullRestoreClicked(snapshotDetails); } else { - await this._partialRestoreClicked(); + await this._partialRestoreClicked(snapshotDetails); } this._restoringSnapshot = false; } - private async _partialRestoreClicked() { + private async _partialRestoreClicked(snapshotDetails) { if ( this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor.info.state !== "running" @@ -158,36 +159,13 @@ class HassioSnapshotDialog return; } - const addons = this._snapshotContent - .addons!.filter((addon) => addon.checked) - .map((addon) => addon.slug); - - const folders = this._snapshotContent - .folders!.filter((folder) => folder.checked) - .map((folder) => folder.slug); - - const data: { - homeassistant: boolean; - addons: any; - folders: any; - password?: string; - } = { - homeassistant: this._snapshotContent.homeAssistant, - addons, - folders, - }; - - if (this._snapshot!.protected) { - data.password = this._snapshotContent.snapshotPassword; - } - if (!this._dialogParams?.onboarding) { this.hass .callApi( "POST", `hassio/snapshots/${this._snapshot!.slug}/restore/partial`, - data + snapshotDetails ) .then( () => { @@ -201,13 +179,13 @@ class HassioSnapshotDialog fireEvent(this, "restoring"); fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, { method: "POST", - body: JSON.stringify(data), + body: JSON.stringify(snapshotDetails), }); this.closeDialog(); } } - private async _fullRestoreClicked() { + private async _fullRestoreClicked(snapshotDetails) { if ( this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor.info.state !== "running" @@ -229,15 +207,12 @@ class HassioSnapshotDialog return; } - const data = this._snapshot!.protected - ? { password: this._snapshotContent.snapshotPassword } - : undefined; if (!this._dialogParams?.onboarding) { this.hass .callApi( "POST", `hassio/snapshots/${this._snapshot!.slug}/restore/full`, - data + snapshotDetails ) .then( () => { @@ -251,7 +226,7 @@ class HassioSnapshotDialog fireEvent(this, "restoring"); fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, { method: "POST", - body: JSON.stringify(data), + body: JSON.stringify(snapshotDetails), }); this.closeDialog(); } diff --git a/src/data/hassio/snapshot.ts b/src/data/hassio/snapshot.ts index 0281f0d9a419..ba34776228b2 100644 --- a/src/data/hassio/snapshot.ts +++ b/src/data/hassio/snapshot.ts @@ -42,11 +42,10 @@ export interface HassioFullSnapshotCreateParams { name: string; password?: string; } -export interface HassioPartialSnapshotCreateParams { - name: string; +export interface HassioPartialSnapshotCreateParams + extends HassioFullSnapshotCreateParams { folders?: string[]; addons?: string[]; - password?: string; homeassistant?: boolean; }