Compare commits

..

2 Commits

Author SHA1 Message Date
Ludeeus
70063031c5 spesific 2021-05-04 20:09:00 +00:00
Ludeeus
41934320c0 Expect the connection to be lost 2021-05-04 19:16:51 +00:00
922 changed files with 13856 additions and 19423 deletions

View File

@@ -28,7 +28,9 @@
"__BUILD__": false, "__BUILD__": false,
"__VERSION__": false, "__VERSION__": false,
"__STATIC_PATH__": false, "__STATIC_PATH__": false,
"Polymer": true "Polymer": true,
"webkitSpeechRecognition": false,
"ResizeObserver": false
}, },
"env": { "env": {
"browser": true, "browser": true,
@@ -104,6 +106,5 @@
"lit/attribute-value-entities": 0 "lit/attribute-value-entities": 0
}, },
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"], "plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"processor": "disable/disable", "processor": "disable/disable"
"ignorePatterns": ["src/resources/lit-virtualizer/*"]
} }

View File

@@ -6,7 +6,8 @@ on:
- published - published
env: env:
PYTHON_VERSION: 3.8 WHEELS_TAG: 3.7-alpine3.11
PYTHON_VERSION: 3.7
NODE_VERSION: 12.1 NODE_VERSION: 12.1
jobs: jobs:
@@ -63,9 +64,6 @@ jobs:
strategy: strategy:
matrix: matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.8-alpine3.12"
- "3.9-alpine3.13"
steps: steps:
- name: Download requirements.txt - name: Download requirements.txt
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
@@ -75,7 +73,7 @@ jobs:
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@master uses: home-assistant/wheels@master
with: with:
tag: ${{ matrix.tag }} tag: ${{ env.WHEELS_TAG }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }} wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}

21
.gitignore vendored
View File

@@ -1,17 +1,10 @@
.DS_Store
.reify-cache
# build
build build
build-translations/* build-translations/*
hass_frontend/*
dist
# yarn
.yarn
yarn-error.log
node_modules/* node_modules/*
npm-debug.log npm-debug.log
.DS_Store
hass_frontend/*
.reify-cache
# Python stuff # Python stuff
*.py[cod] *.py[cod]
@@ -21,8 +14,11 @@ npm-debug.log
# venv stuff # venv stuff
pyvenv.cfg pyvenv.cfg
pip-selfcheck.json pip-selfcheck.json
venv/* venv
.venv .venv
lib
bin
dist
# vscode # vscode
.vscode/* .vscode/*
@@ -35,8 +31,9 @@ src/cast/dev_const.ts
# Secrets # Secrets
.lokalise_token .lokalise_token
yarn-error.log
# asdf #asdf
.tool-versions .tool-versions
# Home Assistant config # Home Assistant config

View File

@@ -1,4 +0,0 @@
module.exports = {
require: "test-mocha/testconf.js",
timeout: 10000,
};

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const env = require("./env.js"); const env = require("./env.js");
const paths = require("./paths.js"); const paths = require("./paths.js");
@@ -52,16 +51,15 @@ module.exports.terserOptions = (latestBuild) => ({
module.exports.babelOptions = ({ latestBuild }) => ({ module.exports.babelOptions = ({ latestBuild }) => ({
babelrc: false, babelrc: false,
compact: false,
presets: [ presets: [
!latestBuild && [ !latestBuild && [
"@babel/preset-env", require("@babel/preset-env").default,
{ {
useBuiltIns: "entry", useBuiltIns: "entry",
corejs: "3.6", corejs: "3.6",
}, },
], ],
"@babel/preset-typescript", require("@babel/preset-typescript").default,
].filter(Boolean), ].filter(Boolean),
plugins: [ plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2}) // Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
@@ -74,12 +72,23 @@ module.exports.babelOptions = ({ latestBuild }) => ({
"@babel/plugin-syntax-dynamic-import", "@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], [
["@babel/plugin-proposal-private-methods", { loose: true }], require("@babel/plugin-proposal-decorators").default,
["@babel/plugin-proposal-class-properties", { loose: true }], { decoratorsBeforeExport: true },
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
].filter(Boolean), ].filter(Boolean),
}); });
// Are already ES5, cause warnings when babelified.
module.exports.babelExclude = () => [
require.resolve("@mdi/js/mdi.js"),
require.resolve("hls.js"),
];
const outputPath = (outputRoot, latestBuild) => const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5"); path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const paths = require("./paths.js"); const paths = require("./paths.js");

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
module.exports = { module.exports = {

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const commonjs = require("@rollup/plugin-commonjs"); const commonjs = require("@rollup/plugin-commonjs");
@@ -33,77 +32,88 @@ const createRollupConfig = ({
publicPath, publicPath,
dontHash, dontHash,
isWDS, isWDS,
}) => ({ }) => {
/** return {
* @type { import("rollup").InputOptions } /**
*/ * @type { import("rollup").InputOptions }
inputOptions: { */
input: entry, inputOptions: {
// Some entry points contain no JavaScript. This setting silences a warning about that. input: entry,
// https://rollupjs.org/guide/en/#preserveentrysignatures // Some entry points contain no JavaScript. This setting silences a warning about that.
preserveEntrySignatures: false, // https://rollupjs.org/guide/en/#preserveentrysignatures
plugins: [ preserveEntrySignatures: false,
ignore({ plugins: [
files: bundle.emptyPackages({ latestBuild }), ignore({
}), files: bundle.emptyPackages({ latestBuild }),
resolve({
extensions,
preferBuiltins: false,
browser: true,
rootDir: paths.polymer_dir,
}),
commonjs(),
json(),
babel({
...bundle.babelOptions({ latestBuild }),
extensions,
babelHelpers: isWDS ? "inline" : "bundled",
}),
string({
// Import certain extensions as strings
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
}),
replace(bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })),
!isWDS &&
manifest({
publicPath,
}), }),
!isWDS && worker(), resolve({
!isWDS && dontHashPlugin({ dontHash }), extensions,
!isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)), preferBuiltins: false,
!isWDS && browser: true,
isStatsBuild && rootDir: paths.polymer_dir,
visualizer({
// https://github.com/btd/rollup-plugin-visualizer#options
open: true,
sourcemap: true,
}), }),
].filter(Boolean), commonjs({
}, namedExports: {
/** "js-yaml": ["safeDump", "safeLoad"],
* @type { import("rollup").OutputOptions } },
*/ }),
outputOptions: { json(),
// https://rollupjs.org/guide/en/#outputdir babel({
dir: outputPath, ...bundle.babelOptions({ latestBuild }),
// https://rollupjs.org/guide/en/#outputformat extensions,
format: latestBuild ? "es" : "systemjs", exclude: bundle.babelExclude(),
// https://rollupjs.org/guide/en/#outputexternallivebindings babelHelpers: isWDS ? "inline" : "bundled",
externalLiveBindings: false, }),
// https://rollupjs.org/guide/en/#outputentryfilenames string({
// https://rollupjs.org/guide/en/#outputchunkfilenames // Import certain extensions as strings
// https://rollupjs.org/guide/en/#outputassetfilenames include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
entryFileNames: }),
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js", replace(
chunkFileNames: isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js", bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
assetFileNames: isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js", ),
// https://rollupjs.org/guide/en/#outputsourcemap !isWDS &&
sourcemap: isProdBuild ? true : "inline", manifest({
}, publicPath,
}); }),
!isWDS && worker(),
!isWDS && dontHashPlugin({ dontHash }),
!isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)),
!isWDS &&
isStatsBuild &&
visualizer({
// https://github.com/btd/rollup-plugin-visualizer#options
open: true,
sourcemap: true,
}),
].filter(Boolean),
},
/**
* @type { import("rollup").OutputOptions }
*/
outputOptions: {
// https://rollupjs.org/guide/en/#outputdir
dir: outputPath,
// https://rollupjs.org/guide/en/#outputformat
format: latestBuild ? "es" : "systemjs",
// https://rollupjs.org/guide/en/#outputexternallivebindings
externalLiveBindings: false,
// https://rollupjs.org/guide/en/#outputentryfilenames
// https://rollupjs.org/guide/en/#outputchunkfilenames
// https://rollupjs.org/guide/en/#outputassetfilenames
entryFileNames:
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
chunkFileNames:
isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
assetFileNames:
isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
// https://rollupjs.org/guide/en/#outputsourcemap
sourcemap: isProdBuild ? true : "inline",
},
};
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => {
createRollupConfig( return createRollupConfig(
bundle.config.app({ bundle.config.app({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
@@ -111,24 +121,31 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) =>
isWDS, isWDS,
}) })
); );
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
createRollupConfig( return createRollupConfig(
bundle.config.demo({ bundle.config.demo({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
}) })
); );
};
const createCastConfig = ({ isProdBuild, latestBuild }) => const createCastConfig = ({ isProdBuild, latestBuild }) => {
createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild })); return createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => const createHassioConfig = ({ isProdBuild, latestBuild }) => {
createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild })); return createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
createRollupConfig(bundle.config.gallery({ isProdBuild, latestBuild })); return createRollupConfig(
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = { module.exports = {
createAppConfig, createAppConfig,

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");

View File

@@ -1,10 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const webpack = require("webpack"); const webpack = require("webpack");
const path = require("path"); const path = require("path");
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin"); const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const paths = require("./paths.js"); const paths = require("./paths.js");
const bundle = require("./bundle.js"); const bundle = require("./bundle");
const log = require("fancy-log"); const log = require("fancy-log");
class LogStartCompilePlugin { class LogStartCompilePlugin {
@@ -47,6 +46,7 @@ const createWebpackConfig = ({
rules: [ rules: [
{ {
test: /\.m?js$|\.ts$/, test: /\.m?js$|\.ts$/,
exclude: bundle.babelExclude(),
use: { use: {
loader: "babel-loader", loader: "babel-loader",
options: bundle.babelOptions({ latestBuild }), options: bundle.babelOptions({ latestBuild }),
@@ -94,7 +94,6 @@ const createWebpackConfig = ({
? path.resolve(context, resource) ? path.resolve(context, resource)
: require.resolve(resource); : require.resolve(resource);
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console
console.error( console.error(
"Error in Home Assistant ignore plugin", "Error in Home Assistant ignore plugin",
resource, resource,
@@ -115,9 +114,8 @@ const createWebpackConfig = ({
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one // We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
new webpack.NormalModuleReplacementPlugin( new webpack.NormalModuleReplacementPlugin(
new RegExp( new RegExp(
path.resolve( require.resolve(
paths.polymer_dir, "lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
"src/resources/lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
) )
), ),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js") path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
@@ -126,11 +124,6 @@ const createWebpackConfig = ({
].filter(Boolean), ].filter(Boolean),
resolve: { resolve: {
extensions: [".ts", ".js", ".json"], extensions: [".ts", ".js", ".json"],
alias: {
"lit/decorators$": "lit/decorators.js",
"lit/directive$": "lit/directive.js",
"lit/polyfill-support$": "lit/polyfill-support.js",
},
}, },
output: { output: {
filename: ({ chunk }) => { filename: ({ chunk }) => {
@@ -151,24 +144,33 @@ const createWebpackConfig = ({
}; };
}; };
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
createWebpackConfig( return createWebpackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild }) bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
); );
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
createWebpackConfig( return createWebpackConfig(
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild }) bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
); );
};
const createCastConfig = ({ isProdBuild, latestBuild }) => const createCastConfig = ({ isProdBuild, latestBuild }) => {
createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild })); return createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => const createHassioConfig = ({ isProdBuild, latestBuild }) => {
createWebpackConfig(bundle.config.hassio({ isProdBuild, latestBuild })); return createWebpackConfig(
bundle.config.hassio({ isProdBuild, latestBuild })
);
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild })); return createWebpackConfig(
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = { module.exports = {
createAppConfig, createAppConfig,

View File

@@ -1,9 +1,16 @@
import "@material/mwc-button/mwc-button";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket"; import { Auth, Connection } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../../src/cast/cast_manager"; import { CastManager } from "../../../../src/cast/cast_manager";
import { import {
castSendShowLovelaceView, castSendShowLovelaceView,
@@ -25,6 +32,7 @@ import {
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config"; import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout"; import "./hc-layout";
import "@material/mwc-button/mwc-button";
@customElement("hc-cast") @customElement("hc-cast")
class HcCast extends LitElement { class HcCast extends LitElement {
@@ -34,9 +42,9 @@ class HcCast extends LitElement {
@property() public castManager!: CastManager; @property() public castManager!: CastManager;
@state() private askWrite = false; @internalProperty() private askWrite = false;
@state() private lovelaceConfig?: LovelaceConfig | null; @internalProperty() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult { protected render(): TemplateResult {
if (this.lovelaceConfig === undefined) { if (this.lovelaceConfig === undefined) {
@@ -196,7 +204,7 @@ class HcCast extends LitElement {
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
.center-item { .center-item {
display: flex; display: flex;

View File

@@ -11,8 +11,15 @@ import {
getAuth, getAuth,
getAuthOptions, getAuthOptions,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
TemplateResult,
internalProperty,
} from "lit-element";
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager"; import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../../src/cast/receiver_messages"; import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
import { import {
@@ -53,19 +60,19 @@ const INTRO = html`
@customElement("hc-connect") @customElement("hc-connect")
export class HcConnect extends LitElement { export class HcConnect extends LitElement {
@state() private loading = false; @internalProperty() private loading = false;
// If we had stored credentials but we cannot connect, // If we had stored credentials but we cannot connect,
// show a screen asking retry or logout. // show a screen asking retry or logout.
@state() private cannotConnect = false; @internalProperty() private cannotConnect = false;
@state() private error?: string | TemplateResult; @internalProperty() private error?: string | TemplateResult;
@state() private auth?: Auth; @internalProperty() private auth?: Auth;
@state() private connection?: Connection; @internalProperty() private connection?: Connection;
@state() private castManager?: CastManager | null; @internalProperty() private castManager?: CastManager | null;
private openDemo = false; private openDemo = false;
@@ -290,7 +297,7 @@ export class HcConnect extends LitElement {
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
.card-content a { .card-content a {
color: var(--primary-color); color: var(--primary-color);

View File

@@ -4,8 +4,15 @@ import {
getUser, getUser,
HassUser, HassUser,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@customElement("hc-layout") @customElement("hc-layout")
@@ -62,7 +69,7 @@ class HcLayout extends LitElement {
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
display: flex; display: flex;

View File

@@ -1,5 +1,10 @@
import { html, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; customElement,
html,
internalProperty,
property,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../../demo/src/stubs/history"; import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { import {
@@ -16,7 +21,7 @@ import "./hc-lovelace";
class HcDemo extends HassElement { class HcDemo extends HassElement {
@property({ attribute: false }) public lovelacePath!: string; @property({ attribute: false }) public lovelacePath!: string;
@state() private _lovelaceConfig?: LovelaceConfig; @internalProperty() private _lovelaceConfig?: LovelaceConfig;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._lovelaceConfig) { if (!this._lovelaceConfig) {
@@ -33,10 +38,10 @@ class HcDemo extends HassElement {
protected firstUpdated(changedProps) { protected firstUpdated(changedProps) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this._initializeHass(); this._initialize();
} }
private async _initializeHass() { private async _initialize() {
const initial: Partial<MockHomeAssistant> = { const initial: Partial<MockHomeAssistant> = {
// Override updateHass so that the correct hass lifecycle methods are called // Override updateHass so that the correct hass lifecycle methods are called
updateHass: (hassUpdate: Partial<HomeAssistant>) => updateHass: (hassUpdate: Partial<HomeAssistant>) =>

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
@customElement("hc-launch-screen") @customElement("hc-launch-screen")
@@ -22,7 +29,7 @@ class HcLaunchScreen extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
display: block; display: block;

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { LovelaceConfig } from "../../../../src/data/lovelace"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { Lovelace } from "../../../../src/panels/lovelace/types"; import { Lovelace } from "../../../../src/panels/lovelace/types";
import "../../../../src/panels/lovelace/views/hui-view"; import "../../../../src/panels/lovelace/views/hui-view";
@@ -84,7 +91,7 @@ class HcLovelace extends LitElement {
return undefined; return undefined;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
min-height: 100vh; min-height: 100vh;

View File

@@ -3,8 +3,12 @@ import {
getAuth, getAuth,
UnsubscribeFunc, UnsubscribeFunc,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { html, TemplateResult } from "lit"; import {
import { customElement, state } from "lit/decorators"; customElement,
html,
internalProperty,
TemplateResult,
} from "lit-element";
import { CAST_NS } from "../../../../src/cast/const"; import { CAST_NS } from "../../../../src/cast/const";
import { import {
ConnectMessage, ConnectMessage,
@@ -32,13 +36,13 @@ let resourcesLoaded = false;
@customElement("hc-main") @customElement("hc-main")
export class HcMain extends HassElement { export class HcMain extends HassElement {
@state() private _showDemo = false; @internalProperty() private _showDemo = false;
@state() private _lovelaceConfig?: LovelaceConfig; @internalProperty() private _lovelaceConfig?: LovelaceConfig;
@state() private _lovelacePath: string | number | null = null; @internalProperty() private _lovelacePath: string | number | null = null;
@state() private _error?: string; @internalProperty() private _error?: string;
private _unsubLovelace?: UnsubscribeFunc; private _unsubLovelace?: UnsubscribeFunc;

View File

@@ -440,43 +440,57 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
type: "horizontal-stack", type: "horizontal-stack",
}, },
{ {
type: "grid",
columns: 2,
cards: [ cards: [
{ {
graph: "line", cards: [
type: "sensor", {
entity: "sensor.temperature_bedroom", graph: "line",
type: "sensor",
entity: "sensor.temperature_bedroom",
},
{
graph: "line",
type: "sensor",
name: "S's room",
entity: "sensor.temperature_stefan",
},
],
type: "horizontal-stack",
}, },
{ {
graph: "line", cards: [
type: "sensor", {
name: "S's room", graph: "line",
entity: "sensor.temperature_stefan", type: "sensor",
entity: "sensor.temperature_passage",
},
{
graph: "line",
type: "sensor",
name: "Bathroom",
entity: "sensor.temperature_downstairs_bathroom",
},
],
type: "horizontal-stack",
}, },
{ {
graph: "line", cards: [
type: "sensor", {
entity: "sensor.temperature_passage", graph: "line",
}, type: "sensor",
{ entity: "sensor.temperature_storage",
graph: "line", },
type: "sensor", {
name: "Bathroom", graph: "line",
entity: "sensor.temperature_downstairs_bathroom", type: "sensor",
}, name: "Refrigerator",
{ entity: "sensor.refrigerator",
graph: "line", },
type: "sensor", ],
entity: "sensor.temperature_storage", type: "horizontal-stack",
},
{
graph: "line",
type: "sensor",
name: "Refrigerator",
entity: "sensor.refrigerator",
}, },
], ],
type: "vertical-stack",
}, },
{ {
entities: [ entities: [

View File

@@ -1,5 +1,5 @@
/* eslint-disable */ /* eslint-disable */
import { LitElement } from "lit"; import { LitElement } from "lit-element";
import "./card-tools"; import "./card-tools";
class CardModder extends LitElement { class CardModder extends LitElement {

View File

@@ -1,5 +1,5 @@
/* eslint-disable */ /* eslint-disable */
import { html, LitElement } from "lit"; import { html, LitElement } from "lit-element";
if (!window.cardTools) { if (!window.cardTools) {
const version = 0.2; const version = 0.2;

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../src/cast/cast_manager"; import { CastManager } from "../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../src/cast/receiver_messages"; import { castSendShowDemo } from "../../../src/cast/receiver_messages";
import "../../../src/components/ha-icon"; import "../../../src/components/ha-icon";
@@ -13,7 +20,7 @@ import { HomeAssistant } from "../../../src/types";
class CastDemoRow extends LitElement implements LovelaceRow { class CastDemoRow extends LitElement implements LovelaceRow {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@state() private _castManager?: CastManager | null; @internalProperty() private _castManager?: CastManager | null;
public setConfig(_config: CastConfig): void { public setConfig(_config: CastConfig): void {
// No config possible. // No config possible.
@@ -66,7 +73,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
this.style.display = this._castManager ? "" : "none"; this.style.display = this._castManager ? "" : "none";
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
display: flex; display: flex;

View File

@@ -1,7 +1,14 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { property, state } from "lit/decorators"; css,
import { until } from "lit/directives/until"; CSSResult,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { until } from "lit-html/directives/until";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace"; import { LovelaceCardConfig } from "../../../src/data/lovelace";
@@ -19,7 +26,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: MockHomeAssistant; @property({ attribute: false }) public hass!: MockHomeAssistant;
@state() private _switching?: boolean; @internalProperty() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card; private _hidden = localStorage.hide_demo_card;
@@ -106,7 +113,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
css` css`
a { a {

View File

@@ -22,9 +22,9 @@ import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations"; import { mockTranslations } from "./stubs/translations";
class HaDemo extends HomeAssistantAppEl { class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() { protected async _initialize() {
const initial: Partial<MockHomeAssistant> = { const initial: Partial<MockHomeAssistant> = {
panelUrl: (this as any)._panelUrl, panelUrl: (this as any).panelUrl,
// Override updateHass so that the correct hass lifecycle methods are called // Override updateHass so that the correct hass lifecycle methods are called
updateHass: (hassUpdate: Partial<HomeAssistant>) => updateHass: (hassUpdate: Partial<HomeAssistant>) =>
this._updateHass(hassUpdate), this._updateHass(hassUpdate),
@@ -70,7 +70,7 @@ class HaDemo extends HomeAssistantAppEl {
} }
e.preventDefault(); e.preventDefault();
navigate(href); navigate(this, href);
}, },
{ capture: true } { capture: true }
); );

View File

@@ -1,7 +1,7 @@
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import { load } from "js-yaml"; import { safeLoad } from "js-yaml";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element"; import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
class DemoCard extends PolymerElement { class DemoCard extends PolymerElement {
@@ -80,7 +80,7 @@ class DemoCard extends PolymerElement {
card.removeChild(card.lastChild); card.removeChild(card.lastChild);
} }
const el = this._createCardElement(load(config.config)[0]); const el = this._createCardElement(safeLoad(config.config)[0]);
card.appendChild(el); card.appendChild(el);
this._getSize(el); this._getSize(el);
} }

View File

@@ -1,6 +1,12 @@
import { dump } from "js-yaml"; import { safeDump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; customElement,
html,
css,
LitElement,
TemplateResult,
property,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { describeAction } from "../../../src/data/script_i18n"; import { describeAction } from "../../../src/data/script_i18n";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -56,7 +62,7 @@ export class DemoAutomationDescribeAction extends LitElement {
(conf) => html` (conf) => html`
<div class="action"> <div class="action">
<span>${describeAction(this.hass, conf as any)}</span> <span>${describeAction(this.hass, conf as any)}</span>
<pre>${dump(conf)}</pre> <pre>${safeDump(conf)}</pre>
</div> </div>
` `
)} )}

View File

@@ -1,6 +1,11 @@
import { dump } from "js-yaml"; import { safeDump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit"; import {
import { customElement } from "lit/decorators"; customElement,
html,
css,
LitElement,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { describeCondition } from "../../../src/data/automation_i18n"; import { describeCondition } from "../../../src/data/automation_i18n";
@@ -26,7 +31,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
(conf) => html` (conf) => html`
<div class="condition"> <div class="condition">
<span>${describeCondition(conf as any)}</span> <span>${describeCondition(conf as any)}</span>
<pre>${dump(conf)}</pre> <pre>${safeDump(conf)}</pre>
</div> </div>
` `
)} )}

View File

@@ -1,6 +1,11 @@
import { dump } from "js-yaml"; import { safeDump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit"; import {
import { customElement } from "lit/decorators"; customElement,
html,
css,
LitElement,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { describeTrigger } from "../../../src/data/automation_i18n"; import { describeTrigger } from "../../../src/data/automation_i18n";
@@ -29,7 +34,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
(conf) => html` (conf) => html`
<div class="trigger"> <div class="trigger">
<span>${describeTrigger(conf as any)}</span> <span>${describeTrigger(conf as any)}</span>
<pre>${dump(conf)}</pre> <pre>${safeDump(conf)}</pre>
</div> </div>
` `
)} )}

View File

@@ -1,5 +1,11 @@
import { html, css, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; customElement,
html,
css,
LitElement,
TemplateResult,
property,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph"; import "../../../src/components/trace/hat-script-graph";
import "../../../src/components/trace/hat-trace-timeline"; import "../../../src/components/trace/hat-trace-timeline";

View File

@@ -1,4 +1,12 @@
import { html, css, LitElement, TemplateResult } from "lit"; import {
customElement,
html,
css,
LitElement,
TemplateResult,
internalProperty,
property,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph"; import "../../../src/components/trace/hat-script-graph";
import "../../../src/components/trace/hat-trace-timeline"; import "../../../src/components/trace/hat-trace-timeline";
@@ -7,7 +15,6 @@ import { HomeAssistant } from "../../../src/types";
import { DemoTrace } from "../data/traces/types"; import { DemoTrace } from "../data/traces/types";
import { basicTrace } from "../data/traces/basic_trace"; import { basicTrace } from "../data/traces/basic_trace";
import { motionLightTrace } from "../data/traces/motion-light-trace"; import { motionLightTrace } from "../data/traces/motion-light-trace";
import { customElement, property, state } from "lit/decorators";
const traces: DemoTrace[] = [basicTrace, motionLightTrace]; const traces: DemoTrace[] = [basicTrace, motionLightTrace];
@@ -15,7 +22,7 @@ const traces: DemoTrace[] = [basicTrace, motionLightTrace];
export class DemoAutomationTrace extends LitElement { export class DemoAutomationTrace extends LitElement {
@property({ attribute: false }) hass?: HomeAssistant; @property({ attribute: false }) hass?: HomeAssistant;
@state() private _selected = {}; @internalProperty() private _selected = {};
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) { if (!this.hass) {

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../demo/src/stubs/history"; import { mockHistory } from "../../../demo/src/stubs/history";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";

View File

@@ -1,5 +1,4 @@
import { html, LitElement, TemplateResult } from "lit"; import { customElement, html, LitElement, TemplateResult } from "lit-element";
import { customElement } from "lit/decorators";
import "../components/demo-cards"; import "../components/demo-cards";
const CONFIGS = [ const CONFIGS = [

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { mockTemplate } from "../../../demo/src/stubs/template"; import { mockTemplate } from "../../../demo/src/stubs/template";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
import { createMediaPlayerEntities } from "../data/media_players"; import { createMediaPlayerEntities } from "../data/media_players";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
import { createMediaPlayerEntities } from "../data/media_players"; import { createMediaPlayerEntities } from "../data/media_players";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";
import { createPlantEntities } from "../data/plants"; import { createPlantEntities } from "../data/plants";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,5 +1,11 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, query } from "lit/decorators"; customElement,
html,
LitElement,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-cards"; import "../components/demo-cards";

View File

@@ -1,4 +1,12 @@
import { html, css, LitElement, TemplateResult } from "lit"; import {
customElement,
html,
css,
internalProperty,
LitElement,
TemplateResult,
property,
} from "lit-element";
import "../../../src/components/ha-formfield"; import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
@@ -15,8 +23,7 @@ import type {
} from "../../../src/panels/config/integrations/ha-config-integrations"; } from "../../../src/panels/config/integrations/ha-config-integrations";
import { DeviceRegistryEntry } from "../../../src/data/device_registry"; import { DeviceRegistryEntry } from "../../../src/data/device_registry";
import { EntityRegistryEntry } from "../../../src/data/entity_registry"; import { EntityRegistryEntry } from "../../../src/data/entity_registry";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import { customElement, property, state } from "lit/decorators";
const createConfigEntry = ( const createConfigEntry = (
title: string, title: string,
@@ -28,11 +35,10 @@ const createConfigEntry = (
title, title,
source: "zeroconf", source: "zeroconf",
state: "loaded", state: "loaded",
connection_class: "local_push",
supports_options: false, supports_options: false,
supports_unload: true, supports_unload: true,
disabled_by: null, disabled_by: null,
pref_disable_new_entities: false,
pref_disable_polling: false,
reason: null, reason: null,
...override, ...override,
}); });
@@ -65,9 +71,6 @@ const configPanelEntry = createConfigEntry("Config Panel", {
const optionsFlowEntry = createConfigEntry("Options Flow", { const optionsFlowEntry = createConfigEntry("Options Flow", {
supports_options: true, supports_options: true,
}); });
const disabledPollingEntry = createConfigEntry("Disabled Polling", {
pref_disable_polling: true,
});
const setupErrorEntry = createConfigEntry("Setup Error", { const setupErrorEntry = createConfigEntry("Setup Error", {
state: "setup_error", state: "setup_error",
}); });
@@ -140,7 +143,6 @@ const configEntries: Array<{
{ items: [loadedEntry] }, { items: [loadedEntry] },
{ items: [configPanelEntry] }, { items: [configPanelEntry] },
{ items: [optionsFlowEntry] }, { items: [optionsFlowEntry] },
{ items: [disabledPollingEntry] },
{ items: [nameAsDomainEntry] }, { items: [nameAsDomainEntry] },
{ items: [longNameEntry] }, { items: [longNameEntry] },
{ items: [longNonBreakingNameEntry] }, { items: [longNonBreakingNameEntry] },
@@ -218,9 +220,9 @@ const createDeviceRegistryEntries = (
export class DemoIntegrationCard extends LitElement { export class DemoIntegrationCard extends LitElement {
@property({ attribute: false }) hass?: HomeAssistant; @property({ attribute: false }) hass?: HomeAssistant;
@state() isCustomIntegration = false; @internalProperty() isCustomIntegration = false;
@state() isCloud = false; @internalProperty() isCloud = false;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) { if (!this.hass) {

View File

@@ -1,5 +1,12 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import {
import { customElement, property, query } from "lit/decorators"; customElement,
html,
LitElement,
property,
PropertyValues,
query,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { import {
LightColorModes, LightColorModes,

View File

@@ -1,6 +1,5 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { html, LitElement, TemplateResult } from "lit"; import { customElement, html, LitElement, TemplateResult } from "lit-element";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { ActionHandlerEvent } from "../../../src/data/lovelace"; import { ActionHandlerEvent } from "../../../src/data/lovelace";
import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive"; import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive";

View File

@@ -1,6 +1,12 @@
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js"; import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { property } from "lit/decorators"; css,
CSSResultArray,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
@@ -120,10 +126,10 @@ class HassioAddonRepositoryEl extends LitElement {
} }
private _addonTapped(ev) { private _addonTapped(ev) {
navigate(`/hassio/addon/${ev.currentTarget.addon.slug}`); navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}`);
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultArray {
return [ return [
hassioStyle, hassioStyle,
css` css`

View File

@@ -4,13 +4,13 @@ import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { import {
css, css,
CSSResultGroup, CSSResult,
html, internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, } from "lit-element";
} from "lit"; import { html, TemplateResult } from "lit-html";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
@@ -58,7 +58,7 @@ class HassioAddonStore extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@state() private _filter?: string; @internalProperty() private _filter?: string;
public async refreshData() { public async refreshData() {
await reloadHassioAddons(this.hass); await reloadHassioAddons(this.hass);
@@ -138,7 +138,7 @@ class HassioAddonStore extends LitElement {
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
const repositoryUrl = extractSearchParam("repository_url"); const repositoryUrl = extractSearchParam("repository_url");
navigate("/hassio/store", { replace: true }); navigate(this, "/hassio/store", true);
if (repositoryUrl) { if (repositoryUrl) {
this._manageRepositories(repositoryUrl); this._manageRepositories(repositoryUrl);
} }
@@ -218,7 +218,7 @@ class HassioAddonStore extends LitElement {
this._filter = e.detail.value; this._filter = e.detail.value;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
hassio-addon-repository { hassio-addon-repository {
margin-top: 24px; margin-top: 24px;

View File

@@ -4,13 +4,15 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
css, css,
CSSResultGroup, CSSResult,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit-element";
import { customElement, property, state } from "lit/decorators";
import "web-animations-js/web-animations-next-lite.min"; import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@@ -37,15 +39,15 @@ class HassioAddonAudio extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@state() private _error?: string; @internalProperty() private _error?: string;
@state() private _inputDevices?: HassioHardwareAudioDevice[]; @internalProperty() private _inputDevices?: HassioHardwareAudioDevice[];
@state() private _outputDevices?: HassioHardwareAudioDevice[]; @internalProperty() private _outputDevices?: HassioHardwareAudioDevice[];
@state() private _selectedInput!: null | string; @internalProperty() private _selectedInput!: null | string;
@state() private _selectedOutput!: null | string; @internalProperty() private _selectedOutput!: null | string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
@@ -107,7 +109,7 @@ class HassioAddonAudio extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -63,7 +70,7 @@ class HassioAddonConfigDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -3,16 +3,18 @@ import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea"; import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import { DEFAULT_SCHEMA, Type } from "js-yaml";
import { import {
css, css,
CSSResultGroup, CSSResult,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
query,
TemplateResult, TemplateResult,
} from "lit"; } from "lit-element";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
@@ -28,7 +30,6 @@ import {
HassioAddonDetails, HassioAddonDetails,
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
setHassioAddonOption, setHassioAddonOption,
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -40,13 +41,6 @@ import { hassioStyle } from "../../resources/hassio-style";
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"]; const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
new Type("!secret", {
kind: "scalar",
construct: (data) => `!secret ${data}`,
}),
]);
@customElement("hassio-addon-config") @customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement { class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@@ -59,15 +53,15 @@ class HassioAddonConfig extends LitElement {
@property({ type: Boolean }) private _valid = true; @property({ type: Boolean }) private _valid = true;
@state() private _canShowSchema = false; @internalProperty() private _canShowSchema = false;
@state() private _showOptional = false; @internalProperty() private _showOptional = false;
@state() private _error?: string; @internalProperty() private _error?: string;
@state() private _options?: Record<string, unknown>; @internalProperty() private _options?: Record<string, unknown>;
@state() private _yamlMode = false; @internalProperty() private _yamlMode = false;
@query("ha-yaml-editor") private _editor?: HaYamlEditor; @query("ha-yaml-editor") private _editor?: HaYamlEditor;
@@ -134,7 +128,6 @@ class HassioAddonConfig extends LitElement {
></ha-form>` ></ha-form>`
: html` <ha-yaml-editor : html` <ha-yaml-editor
@value-changed=${this._configChanged} @value-changed=${this._configChanged}
.schema=${ADDON_YAML_SCHEMA}
></ha-yaml-editor>`} ></ha-yaml-editor>`}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${!this._yamlMode || ${!this._yamlMode ||
@@ -269,45 +262,36 @@ class HassioAddonConfig extends LitElement {
private async _saveTapped(ev: CustomEvent): Promise<void> { private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any; const button = ev.currentTarget as any;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
button.progress = true; button.progress = true;
this._error = undefined; this._error = undefined;
try { try {
const validation = await validateHassioAddonOption(
this.hass,
this.addon.slug,
this._editor?.value
);
if (!validation.valid) {
throw Error(validation.message);
}
await setHassioAddonOption(this.hass, this.addon.slug, { await setHassioAddonOption(this.hass, this.addon.slug, {
options: this._yamlMode ? this._editor?.value : this._options, options: this._yamlMode ? this._editor?.value : this._options,
}); });
this._configHasChanged = false; this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") { if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
} }
} catch (err) { } catch (err) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.configuration.options.failed_to_save",
"error", "error",
extractApiErrorMessage(err) extractApiErrorMessage(err)
); );
eventdata.success = false;
} }
button.progress = false; button.progress = false;
fireEvent(this, "hass-api-called", eventdata);
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,13 +1,15 @@
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResultGroup, CSSResult,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit-element";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@@ -41,9 +43,9 @@ class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@state() private _error?: string; @internalProperty() private _error?: string;
@state() private _config?: NetworkItem[]; @internalProperty() private _config?: NetworkItem[];
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
@@ -214,7 +216,7 @@ class HassioAddonNetwork extends LitElement {
button.progress = false; button.progress = false;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,5 +1,14 @@
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import { import {
@@ -12,7 +21,6 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
@customElement("hassio-addon-documentation-tab") @customElement("hassio-addon-documentation-tab")
class HassioAddonDocumentationDashboard extends LitElement { class HassioAddonDocumentationDashboard extends LitElement {
@@ -22,9 +30,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
@property({ attribute: false }) public addon?: HassioAddonDetails; @property({ attribute: false }) public addon?: HassioAddonDetails;
@state() private _error?: string; @internalProperty() private _error?: string;
@state() private _content?: string; @internalProperty() private _content?: string;
public async connectedCallback(): Promise<void> { public async connectedCallback(): Promise<void> {
super.connectedCallback(); super.connectedCallback();
@@ -49,7 +57,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -4,8 +4,16 @@ import {
mdiInformationVariant, mdiInformationVariant,
mdiMathLog, mdiMathLog,
} from "@mdi/js"; } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
@@ -44,7 +52,7 @@ class HassioAddonDashboard extends LitElement {
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@state() _error?: string; @internalProperty() _error?: string;
private _computeTail = memoizeOne((route: Route) => { private _computeTail = memoizeOne((route: Route) => {
const dividerPos = route.path.indexOf("/", 1); const dividerPos = route.path.indexOf("/", 1);
@@ -125,7 +133,7 @@ class HassioAddonDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,
@@ -175,7 +183,7 @@ class HassioAddonDashboard extends LitElement {
if (!validAddon) { if (!validAddon) {
this._error = this.supervisor.localize("my.error_addon_not_found"); this._error = this.supervisor.localize("my.error_addon_not_found");
} else { } else {
navigate(`/hassio/addon/${requestedAddon}`, { replace: true }); navigate(this, `/hassio/addon/${requestedAddon}`, true);
} }
} }
} }
@@ -183,10 +191,6 @@ class HassioAddonDashboard extends LitElement {
} }
private async _apiCalled(ev): Promise<void> { private async _apiCalled(ev): Promise<void> {
if (!ev.detail.success) {
return;
}
const pathSplit: string[] = ev.detail.path?.split("/"); const pathSplit: string[] = ev.detail.path?.split("/");
if (!pathSplit || pathSplit.length === 0) { if (!pathSplit || pathSplit.length === 0) {

View File

@@ -1,4 +1,4 @@
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit-element";
import { HassioAddonDetails } from "../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import {

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -35,7 +42,7 @@ class HassioAddonInfoDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -14,9 +14,17 @@ import {
mdiPound, mdiPound,
mdiShield, mdiShield,
} from "@mdi/js"; } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
import { classMap } from "lit/directives/class-map"; CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../../src/common/config/version"; import { atLeastVersion } from "../../../../src/common/config/version";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
@@ -82,9 +90,9 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@state() private _metrics?: HassioStats; @internalProperty() private _metrics?: HassioStats;
@state() private _error?: string; @internalProperty() private _error?: string;
private _addonStoreInfo = memoizeOne( private _addonStoreInfo = memoizeOne(
(slug: string, storeAddons: StoreAddon[]) => (slug: string, storeAddons: StoreAddon[]) =>
@@ -163,16 +171,16 @@ class HassioAddonInfo extends LitElement {
: ""} : ""}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._updateClicked}>
${this.supervisor.localize("common.update")}
</mwc-button>
${this.addon.changelog ${this.addon.changelog
? html` ? html`
<mwc-button @click=${this._openChangelog}> <mwc-button @click=${this._openChangelog}>
${this.supervisor.localize("addon.dashboard.changelog")} ${this.supervisor.localize("addon.dashboard.changelog")}
</mwc-button> </mwc-button>
` `
: html`<span></span>`} : ""}
<mwc-button @click=${this._updateClicked}>
${this.supervisor.localize("common.update")}
</mwc-button>
</div> </div>
</ha-card> </ha-card>
` `
@@ -761,7 +769,7 @@ class HassioAddonInfo extends LitElement {
} }
private _openIngress(): void { private _openIngress(): void {
navigate(`/hassio/ingress/${this.addon.slug}`); navigate(this, `/hassio/ingress/${this.addon.slug}`);
} }
private get _computeShowIngressUI(): boolean { private get _computeShowIngressUI(): boolean {
@@ -1051,7 +1059,7 @@ class HassioAddonInfo extends LitElement {
} }
private _openConfiguration(): void { private _openConfiguration(): void {
navigate(`/hassio/addon/${this.addon.slug}/config`); navigate(this, `/hassio/addon/${this.addon.slug}/config`);
} }
private async _uninstallClicked(ev: CustomEvent): Promise<void> { private async _uninstallClicked(ev: CustomEvent): Promise<void> {
@@ -1090,7 +1098,7 @@ class HassioAddonInfo extends LitElement {
button.progress = false; button.progress = false;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -31,7 +38,7 @@ class HassioAddonLogDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,6 +1,14 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { import {
fetchHassioAddonLogs, fetchHassioAddonLogs,
@@ -21,9 +29,9 @@ class HassioAddonLogs extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@state() private _error?: string; @internalProperty() private _error?: string;
@state() private _content?: string; @internalProperty() private _content?: string;
public async connectedCallback(): Promise<void> { public async connectedCallback(): Promise<void> {
super.connectedCallback(); super.connectedCallback();
@@ -51,7 +59,7 @@ class HassioAddonLogs extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
interface State { interface State {
bold: boolean; bold: boolean;
@@ -18,7 +25,7 @@ class HassioAnsiToHtml extends LitElement {
return html`${this._parseTextToColoredPre(this.content)}`; return html`${this._parseTextToColoredPre(this.content)}`;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
pre { pre {
overflow-x: auto; overflow-x: auto;

View File

@@ -1,6 +1,13 @@
import { mdiHelpCircle } from "@mdi/js"; import { mdiHelpCircle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-relative-time"; import "../../../src/components/ha-relative-time";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@@ -70,7 +77,7 @@ class HassioCardContent extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
ha-svg-icon { ha-svg-icon {
margin-right: 24px; margin-right: 24px;

View File

@@ -2,8 +2,13 @@ import "@material/mwc-icon-button/mwc-icon-button";
import { mdiFolderUpload } from "@mdi/js"; import { mdiFolderUpload } from "@mdi/js";
import "@polymer/iron-input/iron-input"; import "@polymer/iron-input/iron-input";
import "@polymer/paper-input/paper-input-container"; import "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit"; import {
import { customElement, state } from "lit/decorators"; customElement,
html,
internalProperty,
LitElement,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload"; import "../../../src/components/ha-file-upload";
@@ -28,9 +33,9 @@ const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
export class HassioUploadSnapshot extends LitElement { export class HassioUploadSnapshot extends LitElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@state() public value: string | null = null; @internalProperty() public value: string | null = null;
@state() private _uploading = false; @internalProperty() private _uploading = false;
public render(): TemplateResult { public render(): TemplateResult {
return html` return html`

View File

@@ -1,54 +0,0 @@
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`<img loading="lazy" .src=${this.imageUrl} class="icon" />`
: this.iconPath
? html`<ha-svg-icon .path=${this.iconPath} class="icon"></ha-svg-icon>`
: ""}
<span class="label">${this.label}</span>
${this.version
? html`<span class="version">(${this.version})</span>`
: ""}
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
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;
}
}

View File

@@ -1,6 +1,13 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
import { classMap } from "lit/directives/class-map"; CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../src/components/ha-bar"; import "../../../src/components/ha-bar";
import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-settings-row";
import { roundWithOneDecimal } from "../../../src/util/calculate"; import { roundWithOneDecimal } from "../../../src/util/calculate";
@@ -30,7 +37,7 @@ class SupervisorMetric extends LitElement {
</ha-settings-row>`; </ha-settings-row>`;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
ha-settings-row { ha-settings-row {
padding: 0; padding: 0;
@@ -64,7 +71,6 @@ class SupervisorMetric extends LitElement {
.value { .value {
width: 48px; width: 48px;
padding-right: 4px; padding-right: 4px;
flex-shrink: 0;
} }
`; `;
} }

View File

@@ -1,450 +0,0 @@
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 { formatDate } from "../../../src/common/datetime/format_date";
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
import { LocalizeFunc } from "../../../src/common/translations/localize";
import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio";
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";
import "./supervisor-formfield-label";
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("media")) {
list.push({ slug: "media", name: "Media", 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() public localize?: LocalizeFunc;
@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({ type: Boolean }) public onboarding = false;
@property() public snapshotName = "";
@property() public snapshotPassword = "";
@property() public confirmSnapshotPassword = "";
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;
}
}
private _localize = (string: string) =>
this.supervisor?.localize(`snapshot.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult {
if (!this.onboarding && !this.supervisor) {
return html``;
}
const foldersSection =
this.snapshotType === "partial" ? this._getSection("folders") : undefined;
const addonsSection =
this.snapshotType === "partial" ? this._getSection("addons") : undefined;
return html`
${this.snapshot
? html`<div class="details">
${this.snapshot.type === "full"
? this._localize("full_snapshot")
: this._localize("partial_snapshot")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
${this.hass
? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
: this.snapshot.date}
</div>`
: html`<paper-input
name="snapshotName"
.label=${this.supervisor?.localize("snapshot.name") || "Name"}
.value=${this.snapshotName}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`}
${!this.snapshot || this.snapshot.type === "full"
? html`<div class="sub-header">
${!this.snapshot
? this._localize("type")
: this._localize("select_type")}
</div>
<div class="snapshot-types">
<ha-formfield .label=${this._localize("full_snapshot")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="full"
name="snapshotType"
.checked=${this.snapshotType === "full"}
>
</ha-radio>
</ha-formfield>
<ha-formfield .label=${this._localize("partial_snapshot")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="partial"
name="snapshotType"
.checked=${this.snapshotType === "partial"}
>
</ha-radio>
</ha-formfield>
</div>`
: ""}
${this.snapshotType === "partial"
? html`<div class="partial-picker">
${this.snapshot && this.snapshot.homeassistant
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.snapshot.homeassistant}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@click=${() => {
this.homeAssistant = !this.homeAssistant;
}}
>
</ha-checkbox>
</ha-formfield>
`
: ""}
${foldersSection?.templates.length
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this._localize("folders")}
.iconPath=${mdiFolder}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
@change=${this._toggleSection}
.checked=${foldersSection.checked}
.indeterminate=${foldersSection.indeterminate}
.section=${"folders"}
>
</ha-checkbox>
</ha-formfield>
<div class="section-content">${foldersSection.templates}</div>
`
: ""}
${addonsSection?.templates.length
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this._localize("addons")}
.iconPath=${mdiPuzzle}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
@change=${this._toggleSection}
.checked=${addonsSection.checked}
.indeterminate=${addonsSection.indeterminate}
.section=${"addons"}
>
</ha-checkbox>
</ha-formfield>
<div class="section-content">${addonsSection.templates}</div>
`
: ""}
</div> `
: ""}
${this.snapshotType === "partial" &&
(!this.snapshot || this.snapshotHasPassword)
? html`<hr />`
: ""}
${!this.snapshot
? html`<ha-formfield
class="password"
.label=${this._localize("password_protection")}
>
<ha-checkbox
.checked=${this.snapshotHasPassword}
@change=${this._toggleHasPassword}
>
</ha-checkbox>
</ha-formfield>`
: ""}
${this.snapshotHasPassword
? html`
<paper-input
.label=${this._localize("password")}
type="password"
name="snapshotPassword"
.value=${this.snapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>
${!this.snapshot
? html` <paper-input
.label=${this.supervisor?.localize("confirm_password")}
type="password"
name="confirmSnapshotPassword"
.value=${this.confirmSnapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`
: ""}
`
: ""}
`;
}
static get styles(): CSSResultGroup {
return css`
.partial-picker ha-formfield {
display: block;
}
.partial-picker ha-checkbox {
--mdc-checkbox-touch-target-size: 32px;
}
.partial-picker {
display: block;
margin: 0px -6px;
}
supervisor-formfield-label {
display: inline-flex;
align-items: center;
}
hr {
border-color: var(--divider-color);
border-bottom: none;
margin: 16px 0;
}
.details {
color: var(--secondary-text-color);
}
.section-content {
display: flex;
flex-direction: column;
margin-left: 30px;
}
ha-formfield.password {
display: block;
margin: 0 -14px -16px;
}
.snapshot-types {
display: flex;
margin-left: -13px;
}
.sub-header {
margin-top: 8px;
}
`;
}
public snapshotDetails():
| HassioPartialSnapshotCreateParams
| HassioFullSnapshotCreateParams {
const data: any = {};
if (!this.snapshot) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale);
}
if (this.snapshotHasPassword) {
data.password = this.snapshotPassword;
if (!this.snapshot) {
data.confirm_password = this.confirmSnapshotPassword;
}
}
if (this.snapshotType === "full") {
return data;
}
const addons = this.addons
?.filter((addon) => 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`<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${item.name}
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
.imageUrl=${section === "addons" &&
!this.onboarding &&
atLeastVersion(this.hass.config.version, 0, 105) &&
addons?.get(item.slug)?.icon
? `/api/hassio/addons/${item.slug}/icon`
: undefined}
.version=${item.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.item=${item}
.checked=${item.checked}
.section=${section}
@change=${this._updateSectionEntry}
>
</ha-checkbox>
</ha-formfield>`);
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;
}
private _handleTextValueChanged(ev: PolymerChangedEvent<string>) {
const input = ev.currentTarget as PaperInputElement;
this[input.name!] = ev.detail.value;
}
private _toggleHasPassword(): void {
this.snapshotHasPassword = !this.snapshotHasPassword;
}
private _toggleSection(ev): void {
const section = ev.currentTarget.section;
this[section] = (section === "addons" ? this.addons : this.folders)!.map(
(item) => ({
...item,
checked: ev.currentTarget.checked,
})
);
}
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
);
}
}
declare global {
interface HTMLElementTagNameMap {
"supervisor-snapshot-content": SupervisorSnapshotContent;
}
}

View File

@@ -1,6 +1,13 @@
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js"; import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { compare } from "../../../src/common/string/compare"; import { compare } from "../../../src/common/string/compare";
@@ -83,7 +90,7 @@ class HassioAddons extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,
@@ -96,11 +103,11 @@ class HassioAddons extends LitElement {
} }
private _addonTapped(ev: any): void { private _addonTapped(ev: any): void {
navigate(`/hassio/addon/${ev.currentTarget.addon.slug}/info`); navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}/info`);
} }
private _openStore(): void { private _openStore(): void {
navigate("/hassio/store"); navigate(this, "/hassio/store");
} }
} }

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
@@ -46,7 +53,7 @@ class HassioDashboard extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
css` css`

View File

@@ -1,7 +1,14 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { mdiHomeAssistant } from "@mdi/js"; import { mdiHomeAssistant } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
@@ -220,32 +227,13 @@ export class HassioUpdate extends LitElement {
} }
private async _updateCore(): Promise<void> { private async _updateCore(): Promise<void> {
try { await updateCore(this.hass);
await updateCore(this.hass);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_update_name",
"name",
"Home Assistant Core"
),
text: extractApiErrorMessage(err),
});
return;
}
}
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "core", collection: "core",
}); });
fireEvent(this, "supervisor-applying-update", {
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
});
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -1,194 +0,0 @@
import { mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/common/search/search-input";
import { compare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { dump } from "../../../../src/resources/js-yaml-dump";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
const _filterDevices = memoizeOne(
(showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) =>
hardware.devices
.filter(
(device) =>
(showAdvanced ||
["tty", "gpio", "input"].includes(device.subsystem)) &&
(device.by_id?.toLowerCase().includes(filter) ||
device.name.toLowerCase().includes(filter) ||
device.dev_path.toLocaleLowerCase().includes(filter) ||
JSON.stringify(device.attributes)
.toLocaleLowerCase()
.includes(filter))
)
.sort((a, b) => compare(a.name, b.name))
);
@customElement("dialog-hassio-hardware")
class HassioHardwareDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioHardwareDialogParams;
@state() private _filter?: string;
public showDialog(params: HassioHardwareDialogParams) {
this._dialogParams = params;
}
public closeDialog() {
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
const devices = _filterDevices(
this.hass.userData?.showAdvanced || false,
this._dialogParams.hardware,
(this._filter || "").toLowerCase()
);
return html`
<ha-dialog
open
scrimClickAction
hideActions
@closed=${this.closeDialog}
.heading=${true}
>
<div class="header" slot="heading">
<h2>
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
</h2>
<mwc-icon-button dialogAction="close">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<search-input
autofocus
no-label-float
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
.label=${this._dialogParams.supervisor.localize(
"dialog.hardware.search"
)}
>
</search-input>
</div>
${devices.map(
(device) =>
html`<ha-expansion-panel
.header=${device.name}
.secondary=${device.by_id || undefined}
outlined
>
<div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.subsystem"
)}:
</span>
<span>${device.subsystem}</span>
</div>
<div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.device_path"
)}:
</span>
<code>${device.dev_path}</code>
</div>
${device.by_id
? html` <div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.id"
)}:
</span>
<code>${device.by_id}</code>
</div>`
: ""}
<div class="attributes">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.attributes"
)}:
</span>
<pre>${dump(device.attributes, { indent: 2 })}</pre>
</div>
</ha-expansion-panel>`
)}
</ha-dialog>
`;
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
mwc-icon-button {
position: absolute;
right: 16px;
top: 10px;
text-decoration: none;
color: var(--primary-text-color);
}
h2 {
margin: 18px 42px 0 18px;
color: var(--primary-text-color);
}
ha-expansion-panel {
margin: 4px 0;
}
pre,
code {
background-color: var(--markdown-code-background-color, none);
border-radius: 3px;
}
pre {
padding: 16px;
overflow: auto;
line-height: 1.45;
font-family: var(--code-font-family, monospace);
}
code {
font-size: 85%;
padding: 0.2em 0.4em;
}
search-input {
margin: 0 16px;
display: block;
}
.device-property {
display: flex;
justify-content: space-between;
}
.attributes {
margin-top: 12px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-hardware": HassioHardwareDialog;
}
}

View File

@@ -1,19 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioHardwareDialogParams {
supervisor: Supervisor;
hardware: HassioHardwareInfo;
}
export const showHassioHardwareDialog = (
element: HTMLElement,
dialogParams: HassioHardwareDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-hardware",
dialogImport: () => import("./dialog-hassio-hardware"),
dialogParams,
});
};

View File

@@ -1,5 +1,13 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import { haStyleDialog } from "../../../../src/resources/styles"; import { haStyleDialog } from "../../../../src/resources/styles";
@@ -15,7 +23,7 @@ class HassioMarkdownDialog extends LitElement {
@property() public content!: string; @property() public content!: string;
@state() private _opened = false; @internalProperty() private _opened = false;
public showDialog(params: HassioMarkdownDialogParams) { public showDialog(params: HassioMarkdownDialogParams) {
this.title = params.title; this.title = params.title;
@@ -42,7 +50,7 @@ class HassioMarkdownDialog extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyleDialog, haStyleDialog,
hassioStyle, hassioStyle,

View File

@@ -6,9 +6,17 @@ import "@material/mwc-tab";
import "@material/mwc-tab-bar"; import "@material/mwc-tab-bar";
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
import { cache } from "lit/directives/cache"; CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { cache } from "lit-html/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
@@ -46,23 +54,23 @@ export class DialogHassioNetwork
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@state() private _accessPoints?: AccessPoints; @internalProperty() private _accessPoints?: AccessPoints;
@state() private _curTabIndex = 0; @internalProperty() private _curTabIndex = 0;
@state() private _dirty = false; @internalProperty() private _dirty = false;
@state() private _interface?: NetworkInterface; @internalProperty() private _interface?: NetworkInterface;
@state() private _interfaces!: NetworkInterface[]; @internalProperty() private _interfaces!: NetworkInterface[];
@state() private _params?: HassioNetworkDialogParams; @internalProperty() private _params?: HassioNetworkDialogParams;
@state() private _processing = false; @internalProperty() private _processing = false;
@state() private _scanning = false; @internalProperty() private _scanning = false;
@state() private _wifiConfiguration?: WifiConfiguration; @internalProperty() private _wifiConfiguration?: WifiConfiguration;
public async showDialog(params: HassioNetworkDialogParams): Promise<void> { public async showDialog(params: HassioNetworkDialogParams): Promise<void> {
this._params = params; this._params = params;
@@ -535,7 +543,7 @@ export class DialogHassioNetwork
this._wifiConfiguration![id] = value; this._wifiConfiguration![id] = value;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyleDialog, haStyleDialog,
css` css`

View File

@@ -3,8 +3,16 @@ import "@material/mwc-icon-button/mwc-icon-button";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDelete } from "@mdi/js"; import { mdiDelete } from "@mdi/js";
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
@@ -31,21 +39,21 @@ class HassioRegistriesDialog extends LitElement {
username: string; username: string;
}[]; }[];
@state() private _registry?: string; @internalProperty() private _registry?: string;
@state() private _username?: string; @internalProperty() private _username?: string;
@state() private _password?: string; @internalProperty() private _password?: string;
@state() private _opened = false; @internalProperty() private _opened = false;
@state() private _addingRegistry = false; @internalProperty() private _addingRegistry = false;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-dialog <ha-dialog
.open=${this._opened} .open=${this._opened}
@closed=${this.closeDialog} @closing=${this.closeDialog}
scrimClickAction scrimClickAction
escapeKeyAction escapeKeyAction
.heading=${createCloseHeading( .heading=${createCloseHeading(
@@ -212,7 +220,7 @@ class HassioRegistriesDialog extends LitElement {
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
haStyleDialog, haStyleDialog,
@@ -244,6 +252,9 @@ class HassioRegistriesDialog extends LitElement {
mwc-list-item span[slot="secondary"] { mwc-list-item span[slot="secondary"] {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-paper-dropdown-menu {
display: block;
}
`, `,
]; ];
} }

View File

@@ -5,8 +5,17 @@ import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, query, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
@@ -28,15 +37,15 @@ class HassioRepositoriesDialog extends LitElement {
@query("#repository_input", true) private _optionInput?: PaperInputElement; @query("#repository_input", true) private _optionInput?: PaperInputElement;
@state() private _repositories?: HassioAddonRepository[]; @internalProperty() private _repositories?: HassioAddonRepository[];
@state() private _dialogParams?: HassioRepositoryDialogParams; @internalProperty() private _dialogParams?: HassioRepositoryDialogParams;
@state() private _opened = false; @internalProperty() private _opened = false;
@state() private _processing = false; @internalProperty() private _processing = false;
@state() private _error?: string; @internalProperty() private _error?: string;
public async showDialog( public async showDialog(
dialogParams: HassioRepositoryDialogParams dialogParams: HassioRepositoryDialogParams
@@ -67,7 +76,7 @@ class HassioRepositoriesDialog extends LitElement {
return html` return html`
<ha-dialog <ha-dialog
.open=${this._opened} .open=${this._opened}
@closed=${this.closeDialog} @closing=${this.closeDialog}
scrimClickAction scrimClickAction
escapeKeyAction escapeKeyAction
.heading=${createCloseHeading( .heading=${createCloseHeading(
@@ -128,7 +137,7 @@ class HassioRepositoriesDialog extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
haStyleDialog, haStyleDialog,
@@ -150,6 +159,9 @@ class HassioRepositoriesDialog extends LitElement {
mwc-button { mwc-button {
margin-left: 8px; margin-left: 8px;
} }
ha-paper-dropdown-menu {
display: block;
}
ha-circular-progress { ha-circular-progress {
display: block; display: block;
margin: 32px; margin: 32px;

View File

@@ -1,151 +0,0 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
} from "../../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
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";
@customElement("dialog-hassio-create-snapshot")
class HassioCreateSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams;
@state() private _error?: string;
@state() private _creatingSnapshot = false;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
public showDialog(params: HassioCreateSnapshotDialogParams) {
this._dialogParams = params;
this._creatingSnapshot = false;
}
public closeDialog() {
this._dialogParams = undefined;
this._creatingSnapshot = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this._dialogParams.supervisor.localize("snapshot.create_snapshot")
)}
>
${this._creatingSnapshot
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
>
</supervisor-snapshot-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")}
</mwc-button>
<mwc-button
.disabled=${this._creatingSnapshot}
slot="primaryAction"
@click=${this._createSnapshot}
>
${this._dialogParams.supervisor.localize("snapshot.create")}
</mwc-button>
</ha-dialog>
`;
}
private async _createSnapshot(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize(
"snapshot.could_not_create"
),
text: this._dialogParams!.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this._dialogParams!.supervisor.info.state
),
});
return;
}
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._creatingSnapshot = true;
this._error = "";
if (snapshotDetails.password && !snapshotDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password"
);
this._creatingSnapshot = false;
return;
}
if (
snapshotDetails.password &&
snapshotDetails.password !== snapshotDetails.confirm_password
) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.passwords_not_matching"
);
this._creatingSnapshot = false;
return;
}
delete snapshotDetails.confirm_password;
try {
if (this._snapshotContent.snapshotType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails);
} else {
await createHassioPartialSnapshot(this.hass, snapshotDetails);
}
this._dialogParams!.onCreate();
this.closeDialog();
} catch (err) {
this._error = extractApiErrorMessage(err);
}
this._creatingSnapshot = false;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-circular-progress {
display: block;
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog;
}
}

View File

@@ -1,6 +1,14 @@
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-header-bar";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
@@ -15,7 +23,7 @@ export class DialogHassioSnapshotUpload
implements HassDialog<HassioSnapshotUploadDialogParams> { implements HassDialog<HassioSnapshotUploadDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioSnapshotUploadDialogParams; @internalProperty() private _params?: HassioSnapshotUploadDialogParams;
public async showDialog( public async showDialog(
params: HassioSnapshotUploadDialogParams params: HassioSnapshotUploadDialogParams
@@ -70,7 +78,7 @@ export class DialogHassioSnapshotUpload
this.closeDialog(); this.closeDialog();
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyleDialog, haStyleDialog,
css` css`

View File

@@ -1,12 +1,19 @@
import { ActionDetail } from "@material/mwc-list"; import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item"; import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
import { mdiClose, mdiDotsVertical } from "@mdi/js"; import "@polymer/paper-checkbox/paper-checkbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
import { customElement, property, query, state } from "lit/decorators"; import "@polymer/paper-input/paper-input";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth"; import { getSignedPath } from "../../../../src/data/auth";
@@ -15,47 +22,95 @@ import {
fetchHassioSnapshotInfo, fetchHassioSnapshotInfo,
HassioSnapshotDetail, HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box"; } 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 { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot"; 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") @customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog class HassioSnapshotDialog extends LitElement {
extends LitElement
implements HassDialog<HassioSnapshotDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string; @property({ attribute: false }) public supervisor?: Supervisor;
@state() private _snapshot?: HassioSnapshotDetail; @internalProperty() private _error?: string;
@state() private _dialogParams?: HassioSnapshotDialogParams; @internalProperty() private _onboarding = false;
@state() private _restoringSnapshot = false; @internalProperty() private _snapshot?: HassioSnapshotDetail;
@query("supervisor-snapshot-content") @internalProperty() private _folders!: FolderItem[];
private _snapshotContent!: SupervisorSnapshotContent;
@internalProperty() private _addons!: AddonItem[];
@internalProperty() private _dialogParams?: HassioSnapshotDialogParams;
@internalProperty() private _snapshotPassword!: string;
@internalProperty() private _restoreHass = true;
public async showDialog(params: HassioSnapshotDialogParams) { public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._dialogParams = params; this._folders = _computeFolders(
this._restoringSnapshot = false; 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));
public closeDialog() { this._dialogParams = params;
this._snapshot = undefined; this._onboarding = params.onboarding ?? false;
this._dialogParams = undefined; this.supervisor = params.supervisor;
this._restoringSnapshot = false; if (!this._snapshot.homeassistant) {
this._error = undefined; this._restoreHass = false;
fireEvent(this, "dialog-closed", { dialog: this.localName }); }
} }
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -63,110 +118,202 @@ class HassioSnapshotDialog
return html``; return html``;
} }
return html` return html`
<ha-dialog <ha-dialog open @closing=${this._closeDialog} .heading=${true}>
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${true}
>
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title">${this._snapshot.name}</span> <span slot="title"> ${this._computeName} </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel"> <mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
</div> </div>
${this._restoringSnapshot <div class="details">
? html` <ha-circular-progress active></ha-circular-progress>` ${this._snapshot.type === "full"
: html`<supervisor-snapshot-content ? "Full snapshot"
.hass=${this.hass} : "Partial snapshot"}
.supervisor=${this._dialogParams.supervisor} (${this._computeSize})<br />
.snapshot=${this._snapshot} ${this._formatDatetime(this._snapshot.date)}
.onboarding=${this._dialogParams.onboarding || false} </div>
.localize=${this._dialogParams.localize} ${this._snapshot.homeassistant
> ? html`<div>Home Assistant:</div>
</supervisor-snapshot-content>`} <paper-checkbox
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""} .checked=${this._restoreHass}
@change="${(ev: Event) => {
<mwc-button this._restoreHass = (ev.target as PaperCheckboxElement).checked!;
.disabled=${this._restoringSnapshot} }}"
slot="secondaryAction" >
@click=${this._restoreClicked} Home Assistant ${this._snapshot.homeassistant}
> </paper-checkbox>`
Restore
</mwc-button>
${!this._dialogParams.onboarding
? html`<ha-button-menu
fixed
slot="primaryAction"
@action=${this._handleMenuAction}
@closed=${(ev: Event) => ev.stopPropagation()}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>Download Snapshot</mwc-list-item>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
</ha-button-menu>`
: ""} : ""}
${this._folders.length
? html`
<div>Folders:</div>
<paper-dialog-scrollable class="no-margin-top">
${this._folders.map(
(item) => html`
<paper-checkbox
.checked=${item.checked}
@change="${(ev: Event) =>
this._updateFolders(
item,
(ev.target as PaperCheckboxElement).checked
)}"
>
${item.name}
</paper-checkbox>
`
)}
</paper-dialog-scrollable>
`
: ""}
${this._addons.length
? html`
<div>Add-on:</div>
<paper-dialog-scrollable class="no-margin-top">
${this._addons.map(
(item) => html`
<paper-checkbox
.checked=${item.checked}
@change="${(ev: Event) =>
this._updateAddons(
item,
(ev.target as PaperCheckboxElement).checked
)}"
>
${item.name}
</paper-checkbox>
`
)}
</paper-dialog-scrollable>
`
: ""}
${this._snapshot.protected
? html`
<paper-input
autofocus=""
label="Password"
type="password"
@value-changed=${this._passwordInput}
.value=${this._snapshotPassword}
></paper-input>
`
: ""}
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
<div class="button-row" slot="primaryAction">
<mwc-button @click=${this._partialRestoreClicked}>
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon>
Restore Selected
</mwc-button>
${!this._onboarding
? html`
<mwc-button @click=${this._deleteClicked}>
<ha-svg-icon .path=${mdiDelete} class="icon warning">
</ha-svg-icon>
<span class="warning">Delete Snapshot</span>
</mwc-button>
`
: ""}
</div>
<div class="button-row" slot="secondaryAction">
${this._snapshot.type === "full"
? html`
<mwc-button @click=${this._fullRestoreClicked}>
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon>
Restore Everything
</mwc-button>
`
: ""}
${!this._onboarding
? html`<mwc-button @click=${this._downloadClicked}>
<ha-svg-icon .path=${mdiDownload} class="icon"></ha-svg-icon>
Download Snapshot
</mwc-button>`
: ""}
</div>
</ha-dialog> </ha-dialog>
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
haStyleDialog, haStyleDialog,
css` css`
ha-svg-icon { paper-checkbox {
color: var(--primary-text-color);
}
ha-circular-progress {
display: block; display: block;
text-align: center; 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;
} }
ha-header-bar { ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color); --mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface); --mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0; flex-shrink: 0;
display: block; }
/* 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);
}
} }
`, `,
]; ];
} }
private _handleMenuAction(ev: CustomEvent<ActionDetail>) { private _updateFolders(item: FolderItem, value: boolean | null | undefined) {
switch (ev.detail.index) { this._folders = this._folders.map((folder) => {
case 0: if (folder.slug === item.slug) {
this._downloadClicked(); folder.checked = value;
break; }
case 1: return folder;
this._deleteClicked(); });
break;
}
} }
private async _restoreClicked() { private _updateAddons(item: AddonItem, value: boolean | null | undefined) {
const snapshotDetails = this._snapshotContent.snapshotDetails(); this._addons = this._addons.map((addon) => {
this._restoringSnapshot = true; if (addon.slug === item.slug) {
if (this._snapshotContent.snapshotType === "full") { addon.checked = value;
await this._fullRestoreClicked(snapshotDetails); }
} else { return addon;
await this._partialRestoreClicked(snapshotDetails); });
}
this._restoringSnapshot = false;
} }
private async _partialRestoreClicked(snapshotDetails) { private _passwordInput(ev: PolymerChangedEvent<string>) {
this._snapshotPassword = ev.detail.value;
}
private async _partialRestoreClicked() {
if ( if (
this._dialogParams?.supervisor !== undefined && this.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore snapshot", title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
}); });
return; return;
} }
@@ -180,17 +327,41 @@ class HassioSnapshotDialog
return; return;
} }
if (!this._dialogParams?.onboarding) { const addons = this._addons
.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this._folders
.filter((folder) => folder.checked)
.map((folder) => folder.slug);
const data: {
homeassistant: boolean;
addons: any;
folders: any;
password?: string;
} = {
homeassistant: this._restoreHass,
addons,
folders,
};
if (this._snapshot!.protected) {
data.password = this._snapshotPassword;
}
if (!this._onboarding) {
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`, `hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
snapshotDetails data
) )
.then( .then(
() => { () => {
this.closeDialog(); alert("Snapshot restored!");
this._closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -200,20 +371,20 @@ class HassioSnapshotDialog
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, { fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
method: "POST", method: "POST",
body: JSON.stringify(snapshotDetails), body: JSON.stringify(data),
}); });
this.closeDialog(); this._closeDialog();
} }
} }
private async _fullRestoreClicked(snapshotDetails) { private async _fullRestoreClicked() {
if ( if (
this._dialogParams?.supervisor !== undefined && this.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore snapshot", title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
}); });
return; return;
} }
@@ -228,16 +399,20 @@ class HassioSnapshotDialog
return; return;
} }
if (!this._dialogParams?.onboarding) { const data = this._snapshot!.protected
? { password: this._snapshotPassword }
: undefined;
if (!this._onboarding) {
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`, `hassio/snapshots/${this._snapshot!.slug}/restore/full`,
snapshotDetails data
) )
.then( .then(
() => { () => {
this.closeDialog(); alert("Snapshot restored!");
this._closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -247,9 +422,9 @@ class HassioSnapshotDialog
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, { fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
method: "POST", method: "POST",
body: JSON.stringify(snapshotDetails), body: JSON.stringify(data),
}); });
this.closeDialog(); this._closeDialog();
} }
} }
@@ -272,7 +447,7 @@ class HassioSnapshotDialog
if (this._dialogParams!.onDelete) { if (this._dialogParams!.onDelete) {
this._dialogParams!.onDelete(); this._dialogParams!.onDelete();
} }
this.closeDialog(); this._closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -288,9 +463,7 @@ class HassioSnapshotDialog
`/api/hassio/snapshots/${this._snapshot!.slug}/download` `/api/hassio/snapshots/${this._snapshot!.slug}/download`
); );
} catch (err) { } catch (err) {
await showAlertDialog(this, { alert(`Error: ${extractApiErrorMessage(err)}`);
text: extractApiErrorMessage(err),
});
return; return;
} }
@@ -307,11 +480,13 @@ class HassioSnapshotDialog
} }
} }
fileDownload( const name = this._computeName.replace(/[^a-z0-9]+/gi, "_");
this, const a = document.createElement("a");
signedPath.path, a.href = signedPath.path;
`home_assistant_snapshot_${slugify(this._computeName)}.tar` a.download = `Hass_io_${name}.tar`;
); this.shadowRoot!.appendChild(a);
a.click();
this.shadowRoot!.removeChild(a);
} }
private get _computeName() { private get _computeName() {
@@ -319,6 +494,29 @@ class HassioSnapshotDialog
? this._snapshot.name || this._snapshot.slug ? this._snapshot.name || this._snapshot.slug
: "Unnamed snapshot"; : "Unnamed snapshot";
} }
private get _computeSize() {
return Math.ceil(this._snapshot!.size * 10) / 10 + " MB";
}
private _formatDatetime(datetime) {
return new Date(datetime).toLocaleDateString(navigator.language, {
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
});
}
private _closeDialog() {
this._dialogParams = undefined;
this._snapshot = undefined;
this._snapshotPassword = "";
this._folders = [];
this._addons = [];
}
} }
declare global { declare global {

View File

@@ -1,18 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioCreateSnapshotDialogParams {
supervisor: Supervisor;
onCreate: () => void;
}
export const showHassioCreateSnapshotDialog = (
element: HTMLElement,
dialogParams: HassioCreateSnapshotDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-create-snapshot",
dialogImport: () => import("./dialog-hassio-create-snapshot"),
dialogParams,
});
};

View File

@@ -1,5 +1,4 @@
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioSnapshotDialogParams { export interface HassioSnapshotDialogParams {
@@ -7,7 +6,6 @@ export interface HassioSnapshotDialogParams {
onDelete?: () => void; onDelete?: () => void;
onboarding?: boolean; onboarding?: boolean;
supervisor?: Supervisor; supervisor?: Supervisor;
localize?: LocalizeFunc;
} }
export const showHassioSnapshotDialog = ( export const showHassioSnapshotDialog = (

View File

@@ -1,4 +1,4 @@
import type { LitElement } from "lit"; import type { LitElement } from "lit-element";
import { import {
HassioAddonDetails, HassioAddonDetails,
restartHassioAddon, restartHassioAddon,

View File

@@ -1,13 +1,23 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch"; import "../../../../src/components/ha-switch";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot"; import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
@@ -17,15 +27,15 @@ import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
class DialogSupervisorUpdate extends LitElement { class DialogSupervisorUpdate extends LitElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@state() private _opened = false; @internalProperty() private _opened = false;
@state() private _createSnapshot = true; @internalProperty() private _createSnapshot = true;
@state() private _action: "snapshot" | "update" | null = null; @internalProperty() private _action: "snapshot" | "update" | null = null;
@state() private _error?: string; @internalProperty() private _error?: string;
@state() @internalProperty()
private _dialogParams?: SupervisorDialogSupervisorUpdateParams; private _dialogParams?: SupervisorDialogSupervisorUpdateParams;
public async showDialog( public async showDialog(
@@ -150,11 +160,20 @@ class DialogSupervisorUpdate extends LitElement {
} }
this._action = "update"; this._action = "update";
await this._dialogParams!.updateHandler!(); try {
await this._dialogParams!.updateHandler!();
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
}
this._action = null;
return;
}
this.closeDialog(); this.closeDialog();
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
haStyleDialog, haStyleDialog,

View File

@@ -1,18 +1,17 @@
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { HassioPartialSnapshotCreateParams } from "../../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface SupervisorDialogSupervisorUpdateParams { export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor; supervisor: Supervisor;
name: string; name: string;
version: string; version: string;
snapshotParams: HassioPartialSnapshotCreateParams; snapshotParams: any;
updateHandler: () => Promise<void>; updateHandler: () => Promise<void>;
} }
export const showDialogSupervisorUpdate = ( export const showDialogSupervisorUpdate = (
element: HTMLElement, element: HTMLElement,
dialogParams: Partial<SupervisorDialogSupervisorUpdateParams> dialogParams: SupervisorDialogSupervisorUpdateParams
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-supervisor-update", dialogTag: "dialog-supervisor-update",

View File

@@ -15,11 +15,5 @@ body {
padding: 0; padding: 0;
height: 100vh; height: 100vh;
} }
@media (prefers-color-scheme: dark) {
body {
background-color: #111111;
color: #e1e1e1;
}
}
`; `;
document.head.appendChild(styleEl); document.head.appendChild(styleEl);

View File

@@ -1,11 +1,7 @@
import { html, PropertyValues } from "lit"; import { customElement, html, property, PropertyValues } from "lit-element";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../src/common/config/version"; import { atLeastVersion } from "../../src/common/config/version";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../src/common/dom/fire_event"; import { fireEvent } from "../../src/common/dom/fire_event";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { mainWindow } from "../../src/common/dom/get_main_window";
import { navigate } from "../../src/common/navigate";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
@@ -50,23 +46,14 @@ export class HassioMain extends SupervisorBaseElement {
// listen on this element for navigation events, so we need to forward them. // listen on this element for navigation events, so we need to forward them.
// Joakim - April 26, 2021 // Joakim - April 26, 2021
// Due to changes in behavior in Google Chrome, we changed navigate to listen on the top element // Due to changes in behavior in Google Chrome, we changed navigate to fire on the top element
mainWindow.addEventListener("location-changed", (ev) => top.addEventListener("location-changed", (ev) =>
// @ts-ignore // @ts-ignore
fireEvent(this, ev.type, ev.detail, { fireEvent(this, ev.type, ev.detail, {
bubbles: false, bubbles: false,
}) })
); );
// Paulus - May 17, 2021
// Convert the <a> tags to native nav in Home Assistant < 2021.6
document.body.addEventListener("click", (ev) => {
const href = isNavigationClick(ev);
if (href) {
navigate(href);
}
});
// Forward haptic events to parent window. // Forward haptic events to parent window.
window.addEventListener("haptic", (ev) => { window.addEventListener("haptic", (ev) => {
// @ts-ignore // @ts-ignore
@@ -103,7 +90,7 @@ export class HassioMain extends SupervisorBaseElement {
private _applyTheme() { private _applyTheme() {
let themeName: string; let themeName: string;
let themeSettings: Partial<HomeAssistant["selectedTheme"]> | undefined; let options: Partial<HomeAssistant["selectedTheme"]> | undefined;
if (atLeastVersion(this.hass.config.version, 0, 114)) { if (atLeastVersion(this.hass.config.version, 0, 114)) {
themeName = themeName =
@@ -112,9 +99,9 @@ export class HassioMain extends SupervisorBaseElement {
? this.hass.themes.default_dark_theme! ? this.hass.themes.default_dark_theme!
: this.hass.themes.default_theme); : this.hass.themes.default_theme);
themeSettings = this.hass.selectedTheme; options = this.hass.selectedTheme;
if (themeSettings?.dark === undefined) { if (themeName === "default" && options?.dark === undefined) {
themeSettings = { options = {
...this.hass.selectedTheme, ...this.hass.selectedTheme,
dark: this.hass.themes.darkMode, dark: this.hass.themes.darkMode,
}; };
@@ -129,7 +116,7 @@ export class HassioMain extends SupervisorBaseElement {
this.parentElement, this.parentElement,
this.hass.themes, this.hass.themes,
themeName, themeName,
themeSettings options
); );
} }
} }

View File

@@ -1,4 +1,11 @@
import { html, LitElement, TemplateResult } from "lit"; import {
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { sanitizeUrl } from "@braintree/sanitize-url"; import { sanitizeUrl } from "@braintree/sanitize-url";
import { import {
createSearchParam, createSearchParam,
@@ -13,7 +20,6 @@ import {
import { navigate } from "../../src/common/navigate"; import { navigate } from "../../src/common/navigate";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
const REDIRECTS: Redirects = { const REDIRECTS: Redirects = {
supervisor: { supervisor: {
@@ -37,12 +43,6 @@ const REDIRECTS: Redirects = {
addon: "string", addon: "string",
}, },
}, },
supervisor_ingress: {
redirect: "/hassio/ingress",
params: {
addon: "string",
},
},
supervisor_add_addon_repository: { supervisor_add_addon_repository: {
redirect: "/hassio/store", redirect: "/hassio/store",
params: { params: {
@@ -59,7 +59,7 @@ class HassioMyRedirect extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@state() public _error?: TemplateResult | string; @internalProperty() public _error?: TemplateResult | string;
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
@@ -89,7 +89,7 @@ class HassioMyRedirect extends LitElement {
return; return;
} }
navigate(url, { replace: true }); navigate(this, url, true);
} }
protected render(): TemplateResult { protected render(): TemplateResult {

View File

@@ -1,4 +1,4 @@
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit-element";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { import {
HassRouterPage, HassRouterPage,

View File

@@ -1,26 +1,19 @@
import { import {
css, css,
CSSResultGroup, CSSResult,
customElement,
html, html,
LitElement, LitElement,
PropertyValues, property,
TemplateResult, TemplateResult,
} from "lit"; } from "lit-element";
import { customElement, property, state } from "lit/decorators";
import { import {
Supervisor, Supervisor,
supervisorApplyUpdateDetails,
supervisorCollection, supervisorCollection,
} from "../../src/data/supervisor/supervisor"; } from "../../src/data/supervisor/supervisor";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router"; import "./hassio-panel-router";
declare global {
interface HASSDomEvents {
"supervisor-applying-update": supervisorApplyUpdateDetails;
}
}
@customElement("hassio-panel") @customElement("hassio-panel")
class HassioPanel extends LitElement { class HassioPanel extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -31,16 +24,6 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@state() private _applyingUpdate?: supervisorApplyUpdateDetails;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._applyingUpdate = undefined;
this.addEventListener("supervisor-applying-update", (ev) => {
this._applyingUpdate = ev.detail;
});
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) { if (!this.hass) {
return html`<hass-loading-screen></hass-loading-screen>`; return html`<hass-loading-screen></hass-loading-screen>`;
@@ -53,16 +36,6 @@ class HassioPanel extends LitElement {
) { ) {
return html`<hass-loading-screen></hass-loading-screen>`; return html`<hass-loading-screen></hass-loading-screen>`;
} }
if (this._applyingUpdate !== undefined) {
return html`<hass-loading-screen no-toolbar>
${this.supervisor.localize("dialog.update.updating", {
name: this._applyingUpdate.name,
version: this._applyingUpdate.version,
})}
</hass-loading-screen>`;
}
return html` return html`
<hassio-panel-router <hassio-panel-router
.hass=${this.hass} .hass=${this.hass}
@@ -73,7 +46,7 @@ class HassioPanel extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
--app-header-background-color: var(--sidebar-background-color); --app-header-background-color: var(--sidebar-background-color);

View File

@@ -1,4 +1,4 @@
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit-element";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { import {
@@ -61,10 +61,11 @@ class HassioRouter extends HassRouterPage {
el.hass = this.hass; el.hass = this.hass;
el.narrow = this.narrow; el.narrow = this.narrow;
el.route = route; el.route = route;
el.supervisor = this.supervisor;
if (el.localName === "hassio-ingress-view") { if (el.localName === "hassio-ingress-view") {
el.ingressPanel = this.panel.config && this.panel.config.ingress; el.ingressPanel = this.panel.config && this.panel.config.ingress;
} else {
el.supervisor = this.supervisor;
} }
} }

View File

@@ -1,27 +1,25 @@
import { mdiMenu } from "@mdi/js"; import { mdiMenu } from "@mdi/js";
import { import {
css, css,
CSSResultGroup, CSSResult,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit-element";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import { nextRender } from "../../../src/common/util/render-status";
import { import {
fetchHassioAddonInfo, fetchHassioAddonInfo,
HassioAddonDetails, HassioAddonDetails,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
createHassioSession, createHassioSession,
validateHassioSession, validateHassioSession,
} from "../../../src/data/hassio/ingress"; } from "../../../src/data/hassio/ingress";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
@@ -31,13 +29,11 @@ import { HomeAssistant, Route } from "../../../src/types";
class HassioIngressView extends LitElement { class HassioIngressView extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property() public route!: Route; @property() public route!: Route;
@property() public ingressPanel = false; @property() public ingressPanel = false;
@state() private _addon?: HassioAddonDetails; @internalProperty() private _addon?: HassioAddonDetails;
@property({ type: Boolean }) @property({ type: Boolean })
public narrow = false; public narrow = false;
@@ -84,43 +80,6 @@ class HassioIngressView extends LitElement {
: iframe}`; : iframe}`;
} }
protected async firstUpdated(): Promise<void> {
if (this.route.path === "") {
const requestedAddon = extractSearchParam("addon");
let addonInfo: HassioAddonDetails;
if (requestedAddon) {
try {
addonInfo = await fetchHassioAddonInfo(this.hass, requestedAddon);
} catch (err) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
title: requestedAddon,
});
await nextRender();
navigate("/hassio/store", { replace: true });
return;
}
if (!addonInfo.version) {
await showAlertDialog(this, {
text: this.supervisor.localize("my.error_addon_not_installed"),
title: addonInfo.name,
});
await nextRender();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
} else if (!addonInfo.ingress) {
await showAlertDialog(this, {
text: this.supervisor.localize("my.error_addon_no_ingress"),
title: addonInfo.name,
});
await nextRender();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
} else {
navigate(`/hassio/ingress/${addonInfo.slug}`, { replace: true });
}
}
}
}
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
super.updated(changedProps); super.updated(changedProps);
@@ -150,7 +109,6 @@ class HassioIngressView extends LitElement {
text: "Unable to fetch add-on info to start Ingress", text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor", title: "Supervisor",
}); });
await nextRender();
history.back(); history.back();
return; return;
} }
@@ -160,7 +118,6 @@ class HassioIngressView extends LitElement {
text: "Add-on does not support Ingress", text: "Add-on does not support Ingress",
title: addon.name, title: addon.name,
}); });
await nextRender();
history.back(); history.back();
return; return;
} }
@@ -170,8 +127,7 @@ class HassioIngressView extends LitElement {
text: "Add-on is not running. Please start it first", text: "Add-on is not running. Please start it first",
title: addon.name, title: addon.name,
}); });
await nextRender(); navigate(this, `/hassio/addon/${addon.slug}/info`, true);
navigate(`/hassio/addon/${addon.slug}/info`, { replace: true });
return; return;
} }
@@ -184,7 +140,6 @@ class HassioIngressView extends LitElement {
text: "Unable to create an Ingress session", text: "Unable to create an Ingress session",
title: addon.name, title: addon.name,
}); });
await nextRender();
history.back(); history.back();
return; return;
} }
@@ -207,7 +162,7 @@ class HassioIngressView extends LitElement {
fireEvent(this, "hass-toggle-menu"); fireEvent(this, "hass-toggle-menu");
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
iframe { iframe {
display: block; display: block;

View File

@@ -1,4 +1,4 @@
import { css } from "lit"; import { css } from "lit-element";
export const hassioStyle = css` export const hassioStyle = css`
.content { .content {

View File

@@ -1,179 +1,118 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list"; import "@material/mwc-icon-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js"; import {
mdiDotsVertical,
mdiPackageVariant,
mdiPackageVariantClosed,
} 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 type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-radio-button/paper-radio-button";
import "@polymer/paper-radio-group/paper-radio-group";
import type { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
import { import {
css, css,
CSSResultGroup, CSSResultArray,
customElement,
html, html,
internalProperty,
LitElement, LitElement,
property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit"; } from "lit-element";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import relativeTime from "../../../src/common/datetime/relative_time"; import "../../../src/components/buttons/ha-progress-button";
import { HASSDomEvent } from "../../../src/common/dom/fire_event";
import {
DataTableColumnContainer,
RowClickedEvent,
SelectionChangedEvent,
} from "../../../src/components/data-table/ha-data-table";
import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab"; import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
fetchHassioSnapshots, fetchHassioSnapshots,
friendlyFolderName, HassioFullSnapshotCreateParams,
HassioPartialSnapshotCreateParams,
HassioSnapshot, HassioSnapshot,
reloadHassioSnapshots, reloadHassioSnapshots,
removeSnapshot,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
showAlertDialog, import "../../../src/layouts/hass-tabs-subpage";
showConfirmationDialog, import { PolymerChangedEvent } from "../../../src/polymer-types";
} from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot"; import "../components/hassio-card-content";
import "../components/hassio-upload-snapshot";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload"; import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
interface CheckboxItem {
slug: string;
checked: boolean;
name?: string;
}
@customElement("hassio-snapshots") @customElement("hassio-snapshots")
export class HassioSnapshots extends LitElement { class HassioSnapshots extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Object }) public route!: Route;
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property({ type: Boolean }) public isWide!: boolean; @property({ attribute: false }) public route!: Route;
@state() private _selectedSnapshots: string[] = []; @property({ attribute: false }) public supervisor!: Supervisor;
@state() private _snapshots?: HassioSnapshot[] = []; @internalProperty() private _snapshotName = "";
@query("hass-tabs-subpage-data-table", true) @internalProperty() private _snapshotPassword = "";
private _dataTable!: HaTabsSubpageDataTable;
private _firstUpdatedCalled = false; @internalProperty() private _snapshotHasPassword = false;
public connectedCallback(): void { @internalProperty() private _snapshotType: HassioSnapshot["type"] = "full";
super.connectedCallback();
if (this.hass && this._firstUpdatedCalled) { @internalProperty() private _snapshots?: HassioSnapshot[] = [];
this.refreshData();
} @internalProperty() private _addonList: CheckboxItem[] = [];
}
@internalProperty() private _folderList: CheckboxItem[] = [
{
slug: "homeassistant",
checked: true,
},
{ slug: "ssl", checked: true },
{ slug: "share", checked: true },
{ slug: "media", checked: true },
{ slug: "addons/local", checked: true },
];
@internalProperty() private _error = "";
public async refreshData() { public async refreshData() {
await reloadHassioSnapshots(this.hass); await reloadHassioSnapshots(this.hass);
await this.fetchSnapshots(); await this._updateSnapshots();
} }
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => {
if (snapshot.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot");
}
const content: string[] = [];
if (snapshot.content.homeassistant) {
content.push("Home Assistant");
}
if (snapshot.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) {
content.push(friendlyFolderName[folder] || folder);
}
}
if (snapshot.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) {
content.push(
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
)?.name || addon
);
}
}
return content.join(", ");
};
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this.hass && this.isConnected) {
this.refreshData();
}
this._firstUpdatedCalled = true;
}
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor?.localize("snapshot.name") || "",
sortable: true,
filterable: true,
grows: true,
template: (entry: string, snapshot: any) =>
html`${entry || snapshot.slug}
<div class="secondary">${snapshot.secondary}</div>`,
},
date: {
title: this.supervisor?.localize("snapshot.created") || "",
width: "15%",
direction: "desc",
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: string) =>
relativeTime(new Date(entry), this.hass.localize),
},
secondary: {
title: "",
hidden: true,
filterable: true,
},
})
);
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) =>
snapshots.map((snapshot) => ({
...snapshot,
secondary: this._computeSnapshotContent(snapshot),
}))
);
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisor) {
return html``;
}
return html` return html`
<hass-tabs-subpage-data-table <hass-tabs-subpage
.tabs=${supervisorTabs}
.hass=${this.hass} .hass=${this.hass}
.localizeFunc=${this.supervisor.localize} .localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")}
.narrow=${this.narrow} .narrow=${this.narrow}
.route=${this.route} .route=${this.route}
.columns=${this._columns(this.narrow)} .tabs=${supervisorTabs}
.data=${this._snapshotData(this._snapshots || [])}
id="slug"
@row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged}
clickable
selectable
hasFab
main-page main-page
supervisor supervisor
> >
<span slot="header">
${this.supervisor.localize("panel.snapshots")}
</span>
<ha-button-menu <ha-button-menu
corner="BOTTOM_START" corner="BOTTOM_START"
slot="toolbar-icon" slot="toolbar-icon"
@@ -183,66 +122,172 @@ export class HassioSnapshots extends LitElement {
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item> <mwc-list-item>
${this.supervisor?.localize("common.reload")} ${this.supervisor.localize("common.reload")}
</mwc-list-item> </mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116) ${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item> ? html`<mwc-list-item>
${this.supervisor?.localize("snapshot.upload_snapshot")} ${this.supervisor.localize("snapshot.upload_snapshot")}
</mwc-list-item>` </mwc-list-item>`
: ""} : ""}
</ha-button-menu> </ha-button-menu>
${this._selectedSnapshots.length <div class="content">
? html`<div <h1>${this.supervisor.localize("snapshot.create_snapshot")}</h1>
class=${classMap({ <p class="description">
"header-toolbar": this.narrow, ${this.supervisor.localize("snapshot.description")}
"table-header": !this.narrow, </p>
})} <div class="card-group">
slot="header" <ha-card>
> <div class="card-content">
<p class="selected-txt"> <paper-input
${this.supervisor.localize("snapshot.selected", { autofocus
number: this._selectedSnapshots.length, .label=${this.supervisor.localize("snapshot.name")}
})} name="snapshotName"
</p> .value=${this._snapshotName}
<div class="header-btns"> @value-changed=${this._handleTextValueChanged}
${!this.narrow ></paper-input>
? html` ${this.supervisor.localize("snapshot.type")}:
<mwc-button <paper-radio-group
@click=${this._deleteSelected} name="snapshotType"
class="warning" type="${this.supervisor.localize("snapshot.type")}"
> .selected=${this._snapshotType}
${this.supervisor.localize("snapshot.delete_selected")} @selected-changed=${this._handleRadioValueChanged}
</mwc-button> >
` <paper-radio-button name="full">
${this.supervisor.localize("snapshot.full_snapshot")}
</paper-radio-button>
<paper-radio-button name="partial">
${this.supervisor.localize("snapshot.partial_snapshot")}
</paper-radio-button>
</paper-radio-group>
${this._snapshotType === "full"
? undefined
: html` : html`
<mwc-icon-button ${this.supervisor.localize("snapshot.folders")}:
id="delete-btn" ${this._folderList.map(
class="warning" (folder, idx) => html`
@click=${this._deleteSelected} <paper-checkbox
> .idx=${idx}
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon> .checked=${folder.checked}
</mwc-icon-button> @checked-changed=${this._folderChecked}
<paper-tooltip animation-delay="0" for="delete-btn"> >
${this.supervisor.localize("snapshot.delete_selected")} ${this.supervisor.localize(
</paper-tooltip> `snapshot.folder.${folder.slug}`
)}
</paper-checkbox>
`
)}
${this.supervisor.localize("snapshot.addons")}:
${this._addonList.map(
(addon, idx) => html`
<paper-checkbox
.idx=${idx}
.checked=${addon.checked}
@checked-changed=${this._addonChecked}
>
${addon.name}
</paper-checkbox>
`
)}
`} `}
${this.supervisor.localize("snapshot.security")}:
<paper-checkbox
name="snapshotHasPassword"
.checked=${this._snapshotHasPassword}
@checked-changed=${this._handleCheckboxValueChanged}
>
${this.supervisor.localize("snapshot.password_protection")}
</paper-checkbox>
${this._snapshotHasPassword
? html`
<paper-input
.label=${this.supervisor.localize("snapshot.password")}
type="password"
name="snapshotPassword"
.value=${this._snapshotPassword}
@value-changed=${this._handleTextValueChanged}
></paper-input>
`
: undefined}
${this._error !== ""
? html` <p class="error">${this._error}</p> `
: undefined}
</div> </div>
</div> ` <div class="card-actions">
: ""} <ha-progress-button
@click=${this._createSnapshot}
.title=${this.supervisor.info.state !== "running"
? this.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor.info.state
)
: ""}
.disabled=${this.supervisor.info.state !== "running"}
>
${this.supervisor.localize("snapshot.create")}
</ha-progress-button>
</div>
</ha-card>
</div>
<ha-fab <h1>${this.supervisor.localize("snapshot.available_snapshots")}</h1>
slot="fab" <div class="card-group">
@click=${this._createSnapshot} ${this._snapshots === undefined
.label=${this.supervisor.localize("snapshot.create_snapshot")} ? undefined
extended : this._snapshots.length === 0
> ? html`
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> <ha-card>
</ha-fab> <div class="card-content">
</hass-tabs-subpage-data-table> ${this.supervisor.localize("snapshot.no_snapshots")}
</div>
</ha-card>
`
: this._snapshots.map(
(snapshot) => html`
<ha-card
class="pointer"
.snapshot=${snapshot}
@click=${this._snapshotClicked}
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${snapshot.name || snapshot.slug}
.description=${this._computeDetails(snapshot)}
.datetime=${snapshot.date}
.icon=${snapshot.type === "full"
? mdiPackageVariantClosed
: mdiPackageVariant}
icon-class="snapshot"
></hassio-card-content>
</div>
</ha-card>
`
)}
</div>
</div>
</hass-tabs-subpage>
`; `;
} }
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.refreshData();
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("supervisor")) {
this._addonList = this.supervisor.supervisor.addons
.map((addon) => ({
slug: addon.slug,
name: addon.name,
checked: true,
}))
.sort((a, b) => (a.name < b.name ? -1 : 1));
}
}
private _handleAction(ev: CustomEvent<ActionDetail>) { private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) { switch (ev.detail.index) {
case 0: case 0:
@@ -254,10 +299,121 @@ export class HassioSnapshots extends LitElement {
} }
} }
private _handleSelectionChanged( private _handleTextValueChanged(ev: PolymerChangedEvent<string>) {
ev: HASSDomEvent<SelectionChangedEvent> const input = ev.currentTarget as PaperInputElement;
): void { this[`_${input.name}`] = ev.detail.value;
this._selectedSnapshots = ev.detail.value; }
private _handleCheckboxValueChanged(ev) {
const input = ev.currentTarget as PaperCheckboxElement;
this[`_${input.name}`] = input.checked;
}
private _handleRadioValueChanged(ev: PolymerChangedEvent<string>) {
const input = ev.currentTarget as PaperRadioGroupElement;
this[`_${input.getAttribute("name")}`] = ev.detail.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 _updateSnapshots() {
try {
this._snapshots = await fetchHassioSnapshots(this.hass);
this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1));
} catch (err) {
this._error = extractApiErrorMessage(err);
}
}
private async _createSnapshot(ev: CustomEvent): Promise<void> {
if (this.supervisor.info.state !== "running") {
await showAlertDialog(this, {
title: this.supervisor.localize("snapshot.could_not_create"),
text: this.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor.info.state
),
});
}
const button = ev.currentTarget as any;
button.progress = true;
this._error = "";
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
this._error = this.supervisor.localize("snapshot.enter_password");
button.progress = false;
return;
}
await this.updateComplete;
const name =
this._snapshotName ||
new Date().toLocaleDateString(navigator.language, {
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
});
try {
if (this._snapshotType === "full") {
const data: HassioFullSnapshotCreateParams = { name };
if (this._snapshotHasPassword) {
data.password = this._snapshotPassword;
}
await createHassioFullSnapshot(this.hass, data);
} else {
const addons = this._addonList
.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this._folderList
.filter((folder) => folder.checked)
.map((folder) => folder.slug);
const data: HassioPartialSnapshotCreateParams = {
name,
folders,
addons,
};
if (this._snapshotHasPassword) {
data.password = this._snapshotPassword;
}
await createHassioPartialSnapshot(this.hass, data);
}
this._updateSnapshots();
} catch (err) {
this._error = extractApiErrorMessage(err);
}
button.progress = false;
}
private _computeDetails(snapshot: HassioSnapshot) {
const type =
snapshot.type === "full"
? this.supervisor.localize("snapshot.full_snapshot")
: this.supervisor.localize("snapshot.partial_snapshot");
return snapshot.protected ? `${type}, password protected` : type;
}
private _snapshotClicked(ev) {
showHassioSnapshotDialog(this, {
slug: ev.currentTarget!.snapshot.slug,
supervisor: this.supervisor,
onDelete: () => this._updateSnapshots(),
});
} }
private _showUploadSnapshotDialog() { private _showUploadSnapshotDialog() {
@@ -266,110 +422,31 @@ export class HassioSnapshots extends LitElement {
showHassioSnapshotDialog(this, { showHassioSnapshotDialog(this, {
slug, slug,
supervisor: this.supervisor, supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(), onDelete: () => this._updateSnapshots(),
}), }),
reloadSnapshot: () => this.refreshData(), reloadSnapshot: () => this.refreshData(),
}); });
} }
private async fetchSnapshots() { static get styles(): CSSResultArray {
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
}
private async _deleteSelected() {
const confirm = await showConfirmationDialog(this, {
title: this.supervisor.localize("snapshot.delete_snapshot_title"),
text: this.supervisor.localize("snapshot.delete_snapshot_text", {
number: this._selectedSnapshots.length,
}),
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
});
if (!confirm) {
return;
}
try {
await Promise.all(
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("snapshot.failed_to_delete"),
text: extractApiErrorMessage(err),
});
return;
}
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
this._dataTable.clearSelection();
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id;
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(),
});
}
private _createSnapshot() {
if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, {
title: this.supervisor!.localize("snapshot.could_not_create"),
text: this.supervisor!.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor!.info.state
),
});
return;
}
showHassioCreateSnapshotDialog(this, {
supervisor: this.supervisor!,
onCreate: () => this.fetchSnapshots(),
});
}
static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,
css` css`
.table-header { paper-radio-group {
display: flex; display: block;
justify-content: space-between;
align-items: center;
height: 58px;
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
} }
.header-toolbar { paper-radio-button {
display: flex; padding: 0 0 2px 2px;
justify-content: space-between;
align-items: center;
color: var(--secondary-text-color);
position: relative;
top: -4px;
} }
.selected-txt { paper-radio-button,
font-weight: bold; paper-checkbox,
padding-left: 16px; paper-input[type="password"] {
color: var(--primary-text-color); display: block;
margin: 4px 0 4px 48px;
} }
.table-header .selected-txt { .pointer {
margin-top: 20px; cursor: pointer;
}
.header-toolbar .selected-txt {
font-size: 16px;
}
.header-toolbar .header-btns {
margin-right: -12px;
}
.header-btns > mwc-button,
.header-btns > mwc-icon-button {
margin: 8px;
} }
`, `,
]; ];

View File

@@ -1,6 +1,10 @@
import { Collection, UnsubscribeFunc } from "home-assistant-js-websocket"; import { Collection, UnsubscribeFunc } from "home-assistant-js-websocket";
import { LitElement, PropertyValues } from "lit"; import {
import { property, state } from "lit/decorators"; internalProperty,
LitElement,
property,
PropertyValues,
} from "lit-element";
import { atLeastVersion } from "../../src/common/config/version"; import { atLeastVersion } from "../../src/common/config/version";
import { computeLocalize } from "../../src/common/translations/localize"; import { computeLocalize } from "../../src/common/translations/localize";
import { fetchHassioAddonsInfo } from "../../src/data/hassio/addon"; import { fetchHassioAddonsInfo } from "../../src/data/hassio/addon";
@@ -42,11 +46,14 @@ export class SupervisorBaseElement extends urlSyncMixin(
localize: () => "", localize: () => "",
}; };
@state() private _unsubs: Record<string, UnsubscribeFunc> = {}; @internalProperty() private _unsubs: Record<string, UnsubscribeFunc> = {};
@state() private _collections: Record<string, Collection<unknown>> = {}; @internalProperty() private _collections: Record<
string,
Collection<unknown>
> = {};
@state() private _language = "en"; @internalProperty() private _language = "en";
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();

View File

@@ -1,7 +1,15 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-button-menu";
@@ -11,7 +19,6 @@ import {
extractApiErrorMessage, extractApiErrorMessage,
fetchHassioStats, fetchHassioStats,
HassioStats, HassioStats,
ignoreSupervisorError,
} from "../../../src/data/hassio/common"; } from "../../../src/data/hassio/common";
import { restartCore, updateCore } from "../../../src/data/supervisor/core"; import { restartCore, updateCore } from "../../../src/data/supervisor/core";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
@@ -32,7 +39,7 @@ class HassioCoreInfo extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@state() private _metrics?: HassioStats; @internalProperty() private _metrics?: HassioStats;
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const metrics = [ const metrics = [
@@ -151,7 +158,7 @@ class HassioCoreInfo extends LitElement {
title: this.supervisor.localize( title: this.supervisor.localize(
"common.failed_to_restart_name", "common.failed_to_restart_name",
"name", "name",
"Home Assistant Core" "Home AssistantCore"
), ),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
@@ -176,32 +183,13 @@ class HassioCoreInfo extends LitElement {
} }
private async _updateCore(): Promise<void> { private async _updateCore(): Promise<void> {
try { await updateCore(this.hass);
await updateCore(this.hass);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_update_name",
"name",
"Home Assistant Core"
),
text: extractApiErrorMessage(err),
});
return;
}
}
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "core", collection: "core",
}); });
fireEvent(this, "supervisor-applying-update", {
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
});
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

View File

@@ -2,8 +2,16 @@ import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { safeDump } from "js-yaml";
import { customElement, property } from "lit/decorators"; import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
@@ -40,8 +48,8 @@ import {
roundWithOneDecimal, roundWithOneDecimal,
} from "../../../src/util/calculate"; } from "../../../src/util/calculate";
import "../components/supervisor-metric"; import "../components/supervisor-metric";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network"; import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-host-info") @customElement("hassio-host-info")
@@ -228,19 +236,20 @@ class HassioHostInfo extends LitElement {
} }
private async _showHardware(): Promise<void> { private async _showHardware(): Promise<void> {
let hardware;
try { try {
hardware = await fetchHassioHardwareInfo(this.hass); const content = await fetchHassioHardwareInfo(this.hass);
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("system.host.hardware"),
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
});
} catch (err) { } catch (err) {
await showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.host.failed_to_get_hardware_list" "system.host.failed_to_get_hardware_list"
), ),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
return;
} }
showHassioHardwareDialog(this, { supervisor: this.supervisor, hardware });
} }
private async _hostReboot(ev: CustomEvent): Promise<void> { private async _hostReboot(ev: CustomEvent): Promise<void> {
@@ -406,7 +415,7 @@ class HassioHostInfo extends LitElement {
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,

Some files were not shown because too many files have changed in this diff Show More