Compare commits

..

4 Commits

Author SHA1 Message Date
Joakim Sørensen
09f4922ad3 change 2021-06-15 15:44:59 +00:00
Joakim Sørensen
36831d26e4 fix typing 2021-06-15 09:51:27 +00:00
Joakim Sørensen
5829660894 Better event handling 2021-06-15 09:48:19 +00:00
Joakim Sørensen
4e3fbc1169 Add applying update "screen" 2021-06-14 18:18:54 +00:00
570 changed files with 32655 additions and 52967 deletions

View File

@@ -35,51 +35,55 @@
"es6": true
},
"rules": {
"class-methods-use-this": "off",
"new-cap": "off",
"prefer-template": "off",
"object-shorthand": "off",
"func-names": "off",
"no-underscore-dangle": "off",
"strict": "off",
"no-plusplus": "off",
"no-bitwise": "error",
"comma-dangle": "off",
"vars-on-top": "off",
"no-continue": "off",
"no-param-reassign": "off",
"no-multi-assign": "off",
"no-console": "error",
"radix": "off",
"no-alert": "off",
"no-nested-ternary": "off",
"prefer-destructuring": "off",
"class-methods-use-this": 0,
"new-cap": 0,
"prefer-template": 0,
"object-shorthand": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"no-underscore-dangle": 0,
"strict": 0,
"prefer-spread": 0,
"no-plusplus": 0,
"no-bitwise": 2,
"comma-dangle": 0,
"vars-on-top": 0,
"no-continue": 0,
"no-param-reassign": 0,
"no-multi-assign": 0,
"no-console": 2,
"radix": 0,
"no-alert": 0,
"no-return-await": 0,
"no-nested-ternary": 0,
"prefer-destructuring": 0,
"no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": "off",
"import/prefer-default-export": "off",
"import/no-default-export": "off",
"import/no-unresolved": "off",
"import/no-cycle": "off",
"prefer-promise-reject-errors": 0,
"import/order": 0,
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/no-cycle": 0,
"import/extensions": [
"error",
2,
"ignorePackages",
{ "ts": "never", "js": "never" }
],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": "off",
"default-case": "off",
"wc/no-self-class": "off",
"no-shadow": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"object-curly-newline": 0,
"default-case": 0,
"wc/no-self-class": 0,
"no-shadow": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/naming-convention": [
"off",
0,
{
"selector": "default",
"format": ["camelCase", "snake_case"],
@@ -97,20 +101,9 @@
"format": ["PascalCase"]
}
],
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-vars": [
"error",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
],
"unused-imports/no-unused-imports": "error",
"lit/attribute-value-entities": "off"
"lit/attribute-value-entities": 0
},
"plugins": ["disable", "unused-imports"],
"processor": "disable/disable"
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"processor": "disable/disable",
"ignorePatterns": ["src/resources/lit-virtualizer/*"]
}

View File

@@ -10,21 +10,26 @@ on:
- dev
- master
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:
@@ -37,35 +42,51 @@ jobs:
run: yarn run lint:types
- name: Run prettier
run: yarn run lint:prettier
- name: Check for duplicate dependencies
run: yarn dedupe --check
test:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:
CI: true
- name: Run Mocha
run: yarn run mocha
run: npm run mocha
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:
@@ -80,11 +101,20 @@ jobs:
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:

View File

@@ -4,22 +4,26 @@ on:
push:
branches:
- dev
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:

View File

@@ -7,8 +7,7 @@ on:
env:
PYTHON_VERSION: 3.8
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
NODE_VERSION: 12.1
jobs:
release:
@@ -30,15 +29,7 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download Translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Build and release package
run: |
python3 -m pip install twine

View File

@@ -1,6 +1,8 @@
name: Translations
on:
schedule:
- cron: "30 0 * * *"
push:
branches:
- dev
@@ -8,7 +10,7 @@ on:
- src/translations/en.json
env:
NODE_VERSION: 14
NODE_VERSION: 12
jobs:
upload:
@@ -18,8 +20,46 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Upload Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base
download:
name: Download
needs: upload
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
npm install
./script/translations_download
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
with:
name: GitHub Action
email: github-action@users.noreply.github.com
- name: Update translation
run: |
git add translations
git commit -am "Translation update"
git push

10
.gitignore vendored
View File

@@ -8,15 +8,9 @@ hass_frontend/*
dist
# yarn
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
node_modules/*
.yarn
yarn-error.log
node_modules/*
npm-debug.log
# Python stuff

2
.nvmrc
View File

@@ -1 +1 @@
14
12.1

File diff suppressed because one or more lines are too long

View File

@@ -1,29 +0,0 @@
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
@@ -1,14 +1,15 @@
-let _ET, ET;
+let _ET;
+let ET;
export default async function EventTarget() {
- return ET || init();
+ return ET || init();
}
async function init() {
- _ET = window.EventTarget;
- try {
- new _ET();
- }
- catch (_a) {
- _ET = (await import('event-target-shim')).EventTarget;
- }
- return (ET = _ET);
+ _ET = window.EventTarget;
+ try {
+ new _ET();
+ } catch (_a) {
+ _ET = (await import("event-target-shim")).default.EventTarget;
+ }
+ return (ET = _ET);
}

View File

@@ -1,34 +0,0 @@
diff --git a/lib/legacy/class.js b/lib/legacy/class.js
index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644
--- a/lib/legacy/class.js
+++ b/lib/legacy/class.js
@@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) {
// only proceed if the generated class' prototype has not been registered.
const generatedProto = PolymerGenerated.prototype;
if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) {
- generatedProto.__hasRegisterFinished = true;
+ // make sure legacy lifecycle is called on the *element*'s prototype
+ // and not the generated class prototype; if the element has been
+ // extended, these are *not* the same.
+ const proto = Object.getPrototypeOf(this);
+ // Only set flag when generated prototype itself is registered,
+ // as this element may be extended from, and needs to run `registered`
+ // on all behaviors on the subclass as well.
+ if (proto === generatedProto) {
+ generatedProto.__hasRegisterFinished = true;
+ }
// ensure superclass is registered first.
super._registered();
// copy properties onto the generated class lazily if we're optimizing,
- if (legacyOptimizations) {
+ if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) {
+ generatedProto.__hasCopiedProperties = true;
copyPropertiesToProto(generatedProto);
}
- // make sure legacy lifecycle is called on the *element*'s prototype
- // and not the generated class prototype; if the element has been
- // extended, these are *not* the same.
- const proto = Object.getPrototypeOf(this);
let list = lifecycle.beforeRegister;
if (list) {
for (let i=0; i < list.length; i++) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +0,0 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-2.4.2.cjs

View File

@@ -1,170 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
module.exports = function inlineConstants(babel, options, cwd) {
const t = babel.types;
if (!Array.isArray(options.modules)) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `modules` array to be passed"
);
}
if (options.resolveExtensions && !Array.isArray(options.resolveExtensions)) {
throw new TypeError(
"babel-plugin-inline-constants: expected `resolveExtensions` to be an array"
);
}
const ignoreModuleNotFound = options.ignoreModuleNotFound;
const resolveExtensions = options.resolveExtensions;
const hasRelativeModules = options.modules.some(
(module) => module.startsWith(".") || module.startsWith("/")
);
const modules = Object.fromEntries(
options.modules.map((module) => {
const absolute = module.startsWith(".")
? require.resolve(module, { paths: [cwd] })
: module;
// eslint-disable-next-line import/no-dynamic-require
return [absolute, require(absolute)];
})
);
const toLiteral = (value) => {
if (typeof value === "string") {
return t.stringLiteral(value);
}
if (typeof value === "number") {
return t.numericLiteral(value);
}
if (typeof value === "boolean") {
return t.booleanLiteral(value);
}
if (value === null) {
return t.nullLiteral();
}
throw new Error(
"babel-plugin-inline-constants: cannot handle non-literal `" + value + "`"
);
};
const resolveAbsolute = (value, state, resolveExtensionIndex) => {
if (!state.filename) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `filename` to be set for files"
);
}
if (resolveExtensions && resolveExtensionIndex !== undefined) {
value += resolveExtensions[resolveExtensionIndex];
}
try {
return require.resolve(value, { paths: [path.dirname(state.filename)] });
} catch (error) {
if (
error.code === "MODULE_NOT_FOUND" &&
resolveExtensions &&
(resolveExtensionIndex === undefined ||
resolveExtensionIndex < resolveExtensions.length - 1)
) {
const resolveExtensionIdx = (resolveExtensionIndex || -1) + 1;
return resolveAbsolute(value, state, resolveExtensionIdx);
}
if (error.code === "MODULE_NOT_FOUND" && ignoreModuleNotFound) {
return undefined;
}
throw error;
}
};
const importDeclaration = (p, state) => {
if (p.node.type !== "ImportDeclaration") {
return;
}
const absolute =
hasRelativeModules && p.node.source.value.startsWith(".")
? resolveAbsolute(p.node.source.value, state)
: p.node.source.value;
if (!absolute || !(absolute in modules)) {
return;
}
const module = modules[absolute];
for (const specifier of p.node.specifiers) {
if (
specifier.type === "ImportDefaultSpecifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!("default" in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access default export from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module.default);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else if (
specifier.type === "ImportSpecifier" &&
specifier.imported &&
specifier.imported.type === "Identifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!(specifier.imported.name in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access `" +
specifier.imported.name +
"` from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module[specifier.imported.name]);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else {
throw new Error("Cannot handle specifier `" + specifier.type + "`");
}
}
p.remove();
};
return {
visitor: {
ImportDeclaration: importDeclaration,
},
};
};

View File

@@ -5,6 +5,8 @@ const paths = require("./paths.js");
// Files from NPM Packages that should not be imported
module.exports.ignorePackages = ({ latestBuild }) => [
// Bloats bundle and it's not used.
path.resolve(require.resolve("moment"), "../locale"),
// Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"),
];
@@ -18,8 +20,7 @@ module.exports.emptyPackages = ({ latestBuild }) =>
require.resolve("@polymer/paper-styles/default-theme.js"),
// Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
// Compatibility not needed for latest builds
latestBuild &&
// wrapped in require.resolve so it blows up if file no longer exists
@@ -57,23 +58,12 @@ module.exports.babelOptions = ({ latestBuild }) => ({
"@babel/preset-env",
{
useBuiltIns: "entry",
corejs: "3.15",
bugfixes: true,
corejs: "3.6",
},
],
"@babel/preset-typescript",
].filter(Boolean),
plugins: [
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/inline-constants-plugin.js"
),
{
modules: ["@mdi/js"],
ignoreModuleNotFound: true,
},
],
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [
"@babel/plugin-proposal-object-rest-spread",
@@ -86,14 +76,8 @@ module.exports.babelOptions = ({ latestBuild }) => ({
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
].filter(Boolean),
exclude: [
// \\ for Windows, / for Mac OS and Linux
/node_modules[\\/]core-js/,
/node_modules[\\/]webpack[\\/]buildin/,
],
});
const outputPath = (outputRoot, latestBuild) =>

View File

@@ -47,8 +47,8 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests
...(env.isTest() ? [] : ["compress-app"]),
...// Don't compress running tests
(env.isTest() ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",

View File

@@ -302,23 +302,15 @@ gulp.task("gen-index-hassio-prod", async () => {
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
function loadES5() {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
var el = document.createElement('script');
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
loadES5();
} else {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
loadES5();
}
}
`,
{ encoding: "utf-8" }

View File

@@ -2,6 +2,7 @@
const gulp = require("gulp");
const path = require("path");
const cpx = require("cpx");
const fs = require("fs-extra");
const paths = require("../paths");
@@ -12,10 +13,8 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath =
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts);
const genStaticPath = (staticDir) => (...parts) =>
path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) {
const staticPath = genStaticPath(staticDir);
@@ -63,12 +62,9 @@ function copyLoaderJS(staticDir) {
function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir);
// Local fonts
fs.copySync(
npmPath("roboto-fontface/fonts/roboto/"),
staticPath("fonts/roboto/"),
{
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
}
cpx.copySync(
npmPath("roboto-fontface/fonts/roboto/*.woff2"),
staticPath("fonts/roboto")
);
}

View File

@@ -1,5 +1,4 @@
// Tasks to run webpack.
const fs = require("fs");
const gulp = require("gulp");
const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
@@ -19,13 +18,6 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }),
];
const isWsl =
fs.existsSync("/proc/version") &&
fs
.readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase()
.includes("microsoft");
/**
* @param {{
* compiler: import("webpack").Compiler,
@@ -86,15 +78,8 @@ const prodBuild = (conf) =>
gulp.task("webpack-watch-app", () => {
// This command will run forever because we don't close compiler
webpack(
process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true })
).watch(
{
ignored: /build-translations/,
poll: isWsl,
},
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
{ ignored: /build-translations/ },
doneHandler()
);
gulp.watch(
@@ -152,7 +137,7 @@ gulp.task("webpack-watch-hassio", () => {
isProdBuild: false,
latestBuild: true,
})
).watch({ ignored: /build-translations/, poll: isWsl }, doneHandler());
).watch({ ignored: /build-translations/ }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),

View File

@@ -49,16 +49,12 @@ const createWebpackConfig = ({
test: /\.m?js$|\.ts$/,
use: {
loader: "babel-loader",
options: {
...bundle.babelOptions({ latestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
options: bundle.babelOptions({ latestBuild }),
},
},
{
test: /\.css$/,
type: "asset/source",
use: "raw-loader",
},
],
},
@@ -70,8 +66,6 @@ const createWebpackConfig = ({
terserOptions: bundle.terserOptions(latestBuild),
}),
],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
new WebpackManifestPlugin({
@@ -118,6 +112,16 @@ const createWebpackConfig = ({
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// 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 RegExp(
path.resolve(
paths.polymer_dir,
"src/resources/lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
)
),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
),
!isProdBuild && new LogStartCompilePlugin(),
].filter(Boolean),
resolve: {
@@ -130,13 +134,15 @@ const createWebpackConfig = ({
},
output: {
filename: ({ chunk }) => {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
if (!isProdBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
},
chunkFilename:
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js"
: "[name].chunk.js",
path: outputPath,
publicPath,
// To silence warning in worker plugin

View File

@@ -139,7 +139,7 @@
Your authentication credentials or Home Assistant url are never sent
to the Cloud. You can validate this behavior in
<a
href="https://github.com/home-assistant/frontend/tree/dev/cast"
href="https://github.com/home-assistant/home-assistant-polymer/tree/dev/cast"
target="_blank"
>the source code</a
>.

View File

@@ -5,8 +5,8 @@ import {
import { castContext } from "../cast_context";
export const castDemoLovelace: () => LovelaceConfig = () => {
const touchSupported =
castContext.getDeviceCapabilities().touch_input_supported;
const touchSupported = castContext.getDeviceCapabilities()
.touch_input_supported;
return {
views: [
{

View File

@@ -29,11 +29,6 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
},
],
},
{
title: "Energy distribution today",
type: "energy-distribution",
link_dashboard: true,
},
{
type: "thermostat",
entity: "climate.upstairs",
@@ -118,7 +113,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -200,7 +196,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -280,7 +277,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -317,7 +315,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {

View File

@@ -1,6 +1,5 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace } from "../../../src/panels/lovelace/types";
import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
@@ -13,8 +12,9 @@ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
// eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfigIndex = 0;
// eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfig: Promise<DemoConfig> =
demoConfigs[selectedDemoConfigIndex]();
export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[
selectedDemoConfigIndex
]();
export const setDemoConfig = async (
hass: MockHomeAssistant,
@@ -28,7 +28,6 @@ export const setDemoConfig = async (
selectedDemoConfig = confProm;
hass.addEntities(config.entities(hass.localize), true);
hass.addEntities(energyEntities());
lovelace.saveConfig(config.lovelace(hass.localize));
hass.mockTheme(config.theme());
};

View File

@@ -980,7 +980,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-off",
custom_ui_state_card: "state-card-custom-ui",
templates: {
icon: "if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
icon:
"if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
},
@@ -1004,7 +1005,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-multiple-minus",
custom_ui_state_card: "state-card-custom-ui",
templates: {
icon: "if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
icon:
"if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
},

View File

@@ -19,7 +19,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: MockHomeAssistant;
@state() private _switching = false;
@state() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card;
@@ -27,7 +27,12 @@ export class HADemoCard extends LitElement implements LovelaceCard {
return this._hidden ? 0 : 2;
}
public setConfig(_config: LovelaceCardConfig) {}
public setConfig(
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
config: LovelaceCardConfig
// eslint-disable-next-line @typescript-eslint/no-empty-function
) {}
protected render(): TemplateResult {
if (this._hidden) {

View File

@@ -1,3 +1,5 @@
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style";
import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch";

View File

@@ -20,10 +20,6 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
import { mockForecastSolar } from "./stubs/forecast_solar";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
@@ -51,13 +47,8 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass);
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockForecastSolar(hass);
mockConfig(hass);
mockPersistentNotification(hass);
hass.addEntities(energyEntities());
// Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then(
([conf, localize]) => {

View File

@@ -1,41 +0,0 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
hass.mockWS("config/entity_registry/list", () => [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
},
]);
};

View File

@@ -1,83 +0,0 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEnergy = (hass: MockHomeAssistant) => {
hass.mockWS("energy/get_prefs", () => ({
energy_sources: [
{
type: "grid",
flow_from: [
{
stat_energy_from: "sensor.energy_consumption_tarif_1",
stat_cost: "sensor.energy_consumption_tarif_1_cost",
entity_energy_from: "sensor.energy_consumption_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_from: "sensor.energy_consumption_tarif_2",
stat_cost: "sensor.energy_consumption_tarif_2_cost",
entity_energy_from: "sensor.energy_consumption_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
flow_to: [
{
stat_energy_to: "sensor.energy_production_tarif_1",
stat_compensation: "sensor.energy_production_tarif_1_compensation",
entity_energy_to: "sensor.energy_production_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_to: "sensor.energy_production_tarif_2",
stat_compensation: "sensor.energy_production_tarif_2_compensation",
entity_energy_to: "sensor.energy_production_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
cost_adjustment_day: 0,
},
{
type: "solar",
stat_energy_from: "sensor.solar_production",
config_entry_solar_forecast: ["solar_forecast"],
},
{
type: "battery",
stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input",
},
{
type: "gas",
stat_energy_from: "sensor.energy_gas",
stat_cost: "sensor.energy_gas_cost",
entity_energy_from: "sensor.energy_gas",
entity_energy_price: null,
number_energy_price: null,
},
],
device_consumption: [
{
stat_consumption: "sensor.energy_car",
},
{
stat_consumption: "sensor.energy_ac",
},
{
stat_consumption: "sensor.energy_washing_machine",
},
{
stat_consumption: "sensor.energy_dryer",
},
{
stat_consumption: "sensor.energy_heat_pump",
},
{
stat_consumption: "sensor.energy_boiler",
},
],
}));
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
};

View File

@@ -1,178 +0,0 @@
import { convertEntities } from "../../../src/fake_data/entity";
export const energyEntities = () =>
convertEntities({
"sensor.grid_fossil_fuel_percentage": {
entity_id: "sensor.grid_fossil_fuel_percentage",
state: "88.6",
attributes: {
unit_of_measurement: "%",
},
},
"sensor.solar_production": {
entity_id: "sensor.solar_production",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Solar",
unit_of_measurement: "kWh",
},
},
"sensor.battery_input": {
entity_id: "sensor.battery_input",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Input",
unit_of_measurement: "kWh",
},
},
"sensor.battery_output": {
entity_id: "sensor.battery_output",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Output",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1": {
entity_id: "sensor.energy_consumption_tarif_1 ",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_2": {
entity_id: "sensor.energy_consumption_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_1": {
entity_id: "sensor.energy_production_tarif_1",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_2": {
entity_id: "sensor.energy_production_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1_cost": {
entity_id: "sensor.energy_consumption_tarif_1_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_consumption_tarif_2_cost": {
entity_id: "sensor.energy_consumption_tarif_2_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_1_compensation": {
entity_id: "sensor.energy_production_tarif_1_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_2_compensation": {
entity_id: "sensor.energy_production_tarif_2_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas_cost": {
entity_id: "sensor.energy_gas_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas": {
entity_id: "sensor.energy_gas",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Gas",
unit_of_measurement: "m³",
},
},
"sensor.energy_car": {
entity_id: "sensor.energy_car",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Electric car",
unit_of_measurement: "kWh",
},
},
"sensor.energy_ac": {
entity_id: "sensor.energy_ac",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Air conditioning",
unit_of_measurement: "kWh",
},
},
"sensor.energy_washing_machine": {
entity_id: "sensor.energy_washing_machine",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Washing machine",
unit_of_measurement: "kWh",
},
},
"sensor.energy_dryer": {
entity_id: "sensor.energy_dryer",
state: "5.5",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Dryer",
unit_of_measurement: "kWh",
},
},
"sensor.energy_heat_pump": {
entity_id: "sensor.energy_heat_pump",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Heat pump",
unit_of_measurement: "kWh",
},
},
"sensor.energy_boiler": {
entity_id: "sensor.energy_boiler",
state: "7",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Boiler",
unit_of_measurement: "kWh",
},
},
});

View File

@@ -1,55 +0,0 @@
import { format, startOfToday, startOfTomorrow } from "date-fns";
import { ForecastSolarForecast } from "../../../src/data/forecast_solar";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockForecastSolar = (hass: MockHomeAssistant) => {
const todayString = format(startOfToday(), "yyyy-MM-dd");
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
hass.mockWS(
"forecast_solar/forecasts",
(): Record<string, ForecastSolarForecast> => ({
solar_forecast: {
wh_hours: {
[`${todayString}T06:00:00`]: 0,
[`${todayString}T06:23:00`]: 6,
[`${todayString}T06:45:00`]: 39,
[`${todayString}T07:00:00`]: 28,
[`${todayString}T08:00:00`]: 208,
[`${todayString}T09:00:00`]: 352,
[`${todayString}T10:00:00`]: 544,
[`${todayString}T11:00:00`]: 748,
[`${todayString}T12:00:00`]: 1259,
[`${todayString}T13:00:00`]: 1361,
[`${todayString}T14:00:00`]: 1373,
[`${todayString}T15:00:00`]: 1370,
[`${todayString}T16:00:00`]: 1186,
[`${todayString}T17:00:00`]: 937,
[`${todayString}T18:00:00`]: 652,
[`${todayString}T19:00:00`]: 370,
[`${todayString}T20:00:00`]: 155,
[`${todayString}T21:48:00`]: 24,
[`${todayString}T22:36:00`]: 0,
[`${tomorrowString}T06:01:00`]: 0,
[`${tomorrowString}T06:23:00`]: 9,
[`${tomorrowString}T06:45:00`]: 47,
[`${tomorrowString}T07:00:00`]: 48,
[`${tomorrowString}T08:00:00`]: 473,
[`${tomorrowString}T09:00:00`]: 827,
[`${tomorrowString}T10:00:00`]: 1153,
[`${tomorrowString}T11:00:00`]: 1413,
[`${tomorrowString}T12:00:00`]: 1590,
[`${tomorrowString}T13:00:00`]: 1652,
[`${tomorrowString}T14:00:00`]: 1612,
[`${tomorrowString}T15:00:00`]: 1438,
[`${tomorrowString}T16:00:00`]: 1149,
[`${tomorrowString}T17:00:00`]: 830,
[`${tomorrowString}T18:00:00`]: 542,
[`${tomorrowString}T19:00:00`]: 311,
[`${tomorrowString}T20:00:00`]: 140,
[`${tomorrowString}T21:47:00`]: 22,
[`${tomorrowString}T22:34:00`]: 0,
},
},
})
);
};

View File

@@ -1,6 +1,4 @@
import { addHours, differenceInHours, endOfDay } from "date-fns";
import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams {
@@ -66,219 +64,17 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"];
const generateMeanStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let lastVal = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const delta = Math.random() * maxDiff;
const mean = lastVal + delta;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean,
min: mean - Math.random() * maxDiff,
max: mean + Math.random() * maxDiff,
last_reset: "1970-01-01T00:00:00+00:00",
state: mean,
sum: null,
});
lastVal = mean;
currentDate = addHours(currentDate, 1);
}
return statistics;
};
const generateSumStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum,
});
currentDate = addHours(currentDate, 1);
}
return statistics;
};
const generateCurvedStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number,
metered: boolean
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const hours = differenceInHours(end, start) - 1;
let i = 0;
let half = false;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += i * add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum: metered ? sum : null,
});
currentDate = addHours(currentDate, 1);
if (!half && i > hours / 2) {
half = true;
}
i += half ? -1 : 1;
}
return statistics;
};
const statisticsFunctions: Record<
string,
(id: string, start: Date, end: Date) => StatisticValue[]
> = {
"sensor.energy_consumption_tarif_1": (id: string, start: Date, end: Date) => {
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
const morningLow = generateSumStatistics(id, start, morningEnd, 0, 0.7);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const morningFinalVal = morningLow.length
? morningLow[morningLow.length - 1].sum!
: 0;
const empty = generateSumStatistics(
id,
morningEnd,
eveningStart,
morningFinalVal,
0
);
const eveningLow = generateSumStatistics(
id,
eveningStart,
end,
morningFinalVal,
0.7
);
return [...morningLow, ...empty, ...eveningLow];
},
"sensor.energy_consumption_tarif_2": (id: string, start: Date, end: Date) => {
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const highTarif = generateSumStatistics(
id,
morningEnd,
eveningStart,
0,
0.3
);
const highTarifFinalVal = highTarif.length
? highTarif[highTarif.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, morningEnd, 0, 0);
const evening = generateSumStatistics(
id,
eveningStart,
end,
highTarifFinalVal,
0
);
return [...morning, ...highTarif, ...evening];
},
"sensor.energy_production_tarif_1": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_1_compensation": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_2": (id, start, end) => {
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
0,
0.15,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 1);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.solar_production": (id, start, end) => {
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
0,
0.3,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 2);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
generateMeanStatistics(id, start, end, 35, 1.3),
};
export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI(
new RegExp("history/period/.+"),
(hass, _method, path, _parameters) => {
(
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
@@ -299,7 +95,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
const numberState = Number(state.state);
if (isNaN(numberState)) {
// eslint-disable-next-line no-console
// eslint-disable-next-line
console.log(
"Ignoring state with unparsable state but with a unit",
entityId,
@@ -344,40 +140,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results;
}
);
mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS(
"history/statistics_during_period",
({ statistic_ids, start_time, end_time }, hass) => {
const start = new Date(start_time);
const end = end_time ? new Date(end_time) : new Date();
const statistics: Record<string, StatisticValue[]> = {};
statistic_ids.forEach((id: string) => {
if (id in statisticsFunctions) {
statistics[id] = statisticsFunctions[id](id, start, end);
} else {
const entityState = hass.states[id];
const state = entityState ? Number(entityState.state) : 1;
statistics[id] =
entityState && "last_reset" in entityState.attributes
? generateSumStatistics(
id,
start,
end,
state,
state * (state > 80 ? 0.01 : 0.05)
)
: generateMeanStatistics(
id,
start,
end,
state,
state * (state > 80 ? 0.05 : 0.1)
);
}
});
return statistics;
}
);
};

View File

@@ -10,9 +10,10 @@ export const mockLovelace = (
localizePromise: Promise<LocalizeFunc>
) => {
hass.mockWS("lovelace/config", () =>
Promise.all([selectedDemoConfig, localizePromise]).then(
([config, localize]) => config.lovelace(localize)
)
Promise.all([
selectedDemoConfig,
localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
);
hass.mockWS("lovelace/config/save", () => Promise.resolve());

View File

@@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
body: { message: "Template dev tool does not work in the demo." },
})
);
hass.mockWS("render_template", (msg, _hass, onChange) => {
hass.mockWS("render_template", (msg, onChange) => {
onChange!({
result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false },

View File

@@ -2,12 +2,12 @@ import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph";
import "../../../src/components/trace/hat-trace-timeline";
import { customElement, property, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../src/types";
import { DemoTrace } from "../data/traces/types";
import { basicTrace } from "../data/traces/basic_trace";
import { motionLightTrace } from "../data/traces/motion-light-trace";
import { customElement, property, state } from "lit/decorators";
const traces: DemoTrace[] = [basicTrace, motionLightTrace];

View File

@@ -2,8 +2,6 @@ import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
import { IntegrationManifest } from "../../../src/data/integration";
import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -17,6 +15,8 @@ import type {
} from "../../../src/panels/config/integrations/ha-config-integrations";
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
const createConfigEntry = (
title: string,

View File

@@ -2,6 +2,7 @@ import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import { DEFAULT_SCHEMA, Type } from "js-yaml";
import {
css,
@@ -133,7 +134,7 @@ class HassioAddonConfig extends LitElement {
></ha-form>`
: html` <ha-yaml-editor
@value-changed=${this._configChanged}
.yamlSchema=${ADDON_YAML_SCHEMA}
.schema=${ADDON_YAML_SCHEMA}
></ha-yaml-editor>`}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${!this._yamlMode ||
@@ -268,9 +269,6 @@ class HassioAddonConfig extends LitElement {
private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
const options: Record<string, unknown> = this._yamlMode
? this._editor?.value
: this._options;
const eventdata = {
success: true,
response: undefined,
@@ -284,13 +282,13 @@ class HassioAddonConfig extends LitElement {
const validation = await validateHassioAddonOption(
this.hass,
this.addon.slug,
options
this._editor?.value
);
if (!validation.valid) {
throw Error(validation.message);
}
await setHassioAddonOption(this.hass, this.addon.slug, {
options,
options: this._yamlMode ? this._editor?.value : this._options,
});
this._configHasChanged = false;
@@ -328,6 +326,10 @@ class HassioAddonConfig extends LitElement {
color: var(--error-color);
margin-top: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: var(--code-font-family, monospace);
}
.syntaxerror {
color: var(--error-color);
}

View File

@@ -2,7 +2,6 @@ import "../../../../src/components/ha-card";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import { customElement, property, state } from "lit/decorators";
import {
fetchHassioAddonDocumentation,
HassioAddonDetails,
@@ -13,6 +12,7 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
@customElement("hassio-addon-documentation-tab")
class HassioAddonDocumentationDashboard extends LitElement {

View File

@@ -892,19 +892,10 @@ class HassioAddonInfo extends LitElement {
private async _openChangelog(): Promise<void> {
try {
let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug);
if (
content.includes(`# ${this.addon.version}`) &&
content.includes(`# ${this.addon.version_latest}`)
) {
const newcontent = content.split(`# ${this.addon.version}`)[0];
if (newcontent.includes(`# ${this.addon.version_latest}`)) {
// Only change the content if the new version still exist
// if the changelog does not have the newests version on top
// this will not be true, and we don't modify the content
content = newcontent;
}
}
const content = await fetchHassioAddonChangelog(
this.hass,
this.addon.slug
);
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"),
content,
@@ -987,7 +978,7 @@ class HassioAddonInfo extends LitElement {
supervisor: this.supervisor,
name: this.addon.name,
version: this.addon.version_latest,
backupParams: {
snapshotParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug],
homeassistant: false,
@@ -1149,7 +1140,6 @@ class HassioAddonInfo extends LitElement {
margin-bottom: 16px;
}
img.logo {
max-width: 100%;
max-height: 60px;
margin: 16px 0;
display: block;
@@ -1159,10 +1149,10 @@ class HassioAddonInfo extends LitElement {
display: flex;
}
ha-svg-icon.running {
color: var(--success-color);
color: var(--paper-green-400);
}
ha-svg-icon.stopped {
color: var(--error-color);
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;

View File

@@ -41,16 +41,16 @@ class HassioAnsiToHtml extends LitElement {
text-decoration: underline line-through;
}
.fg-red {
color: var(--error-color);
color: rgb(222, 56, 43);
}
.fg-green {
color: var(--success-color);
color: rgb(57, 181, 74);
}
.fg-yellow {
color: var(--warning-color);
color: rgb(255, 199, 6);
}
.fg-blue {
color: var(--info-color);
color: rgb(0, 111, 184);
}
.fg-magenta {
color: rgb(118, 38, 113);
@@ -65,16 +65,16 @@ class HassioAnsiToHtml extends LitElement {
background-color: rgb(0, 0, 0);
}
.bg-red {
background-color: var(--error-color);
background-color: rgb(222, 56, 43);
}
.bg-green {
background-color: var(--success-color);
background-color: rgb(57, 181, 74);
}
.bg-yellow {
background-color: var(--warning-color);
background-color: rgb(255, 199, 6);
}
.bg-blue {
background-color: var(--info-color);
background-color: rgb(0, 111, 184);
}
.bg-magenta {
background-color: rgb(118, 38, 113);

View File

@@ -80,14 +80,14 @@ class HassioCardContent extends LitElement {
color: var(--secondary-text-color);
}
ha-svg-icon.update {
color: var(--warning-color);
color: var(--paper-orange-400);
}
ha-svg-icon.running,
ha-svg-icon.installed {
color: var(--success-color);
color: var(--paper-green-400);
}
ha-svg-icon.hassupdate,
ha-svg-icon.backup {
ha-svg-icon.snapshot {
color: var(--paper-item-icon-color);
}
ha-svg-icon.not_available {
@@ -122,7 +122,7 @@ class HassioCardContent extends LitElement {
}
.dot {
position: absolute;
background-color: var(--warning-color);
background-color: var(--paper-orange-400);
width: 12px;
height: 12px;
top: 8px;

View File

@@ -1,5 +1,6 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiFolderUpload } from "@mdi/js";
import "@polymer/iron-input/iron-input";
import "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
@@ -8,20 +9,23 @@ import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload";
import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
import {
HassioSnapshot,
uploadSnapshot,
} from "../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types";
declare global {
interface HASSDomEvents {
"backup-uploaded": { backup: HassioBackup };
"snapshot-uploaded": { snapshot: HassioSnapshot };
}
}
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-backup")
export class HassioUploadBackup extends LitElement {
@customElement("hassio-upload-snapshot")
export class HassioUploadSnapshot extends LitElement {
public hass!: HomeAssistant;
@state() public value: string | null = null;
@@ -34,7 +38,7 @@ export class HassioUploadBackup extends LitElement {
.uploading=${this._uploading}
.icon=${mdiFolderUpload}
accept="application/x-tar"
label="Upload backup"
label="Upload snapshot"
@file-picked=${this._uploadFile}
auto-open-file-dialog
></ha-file-upload>
@@ -46,10 +50,10 @@ export class HassioUploadBackup extends LitElement {
if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, {
title: "Backup file is too big",
title: "Snapshot file is too big",
text: html`The maximum allowed filesize is 1GB.<br />
<a
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-backup-on-a-new-install"
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-snapshot-on-a-new-install"
target="_blank"
>Have a look here on how to restore it.</a
>`,
@@ -61,15 +65,15 @@ export class HassioUploadBackup extends LitElement {
if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, {
title: "Unsupported file format",
text: "Please choose a Home Assistant backup file (.tar)",
text: "Please choose a Home Assistant snapshot file (.tar)",
confirmText: "ok",
});
return;
}
this._uploading = true;
try {
const backup = await uploadBackup(this.hass, file);
fireEvent(this, "backup-uploaded", { backup: backup.data });
const snapshot = await uploadSnapshot(this.hass, file);
fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data });
} catch (err) {
showAlertDialog(this, {
title: "Upload failed",
@@ -84,6 +88,6 @@ export class HassioUploadBackup extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"hassio-upload-backup": HassioUploadBackup;
"hassio-upload-snapshot": HassioUploadSnapshot;
}
}

View File

@@ -11,10 +11,10 @@ import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio";
import {
HassioFullBackupCreateParams,
HassioPartialBackupCreateParams,
HassioBackupDetail,
} from "../../../src/data/hassio/backup";
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";
@@ -64,17 +64,17 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
}))
.sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-backup-content")
export class SupervisorBackupContent extends LitElement {
@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 backup?: HassioBackupDetail;
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
@property() public backupType: HassioBackupDetail["type"] = "full";
@property() public snapshotType: HassioSnapshotDetail["type"] = "full";
@property({ attribute: false }) public folders?: CheckboxItem[];
@@ -82,35 +82,37 @@ export class SupervisorBackupContent extends LitElement {
@property({ type: Boolean }) public homeAssistant = false;
@property({ type: Boolean }) public backupHasPassword = false;
@property({ type: Boolean }) public snapshotHasPassword = false;
@property({ type: Boolean }) public onboarding = false;
@property() public backupName = "";
@property() public snapshotName = "";
@property() public backupPassword = "";
@property() public snapshotPassword = "";
@property() public confirmBackupPassword = "";
@property() public confirmSnapshotPassword = "";
public willUpdate(changedProps) {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
this.folders = _computeFolders(
this.backup
? this.backup.folders
this.snapshot
? this.snapshot.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
this.snapshot
? this.snapshot.addons
: this.supervisor?.supervisor.addons
);
this.backupType = this.backup?.type || "full";
this.backupName = this.backup?.name || "";
this.backupHasPassword = this.backup?.protected || false;
this.snapshotType = this.snapshot?.type || "full";
this.snapshotName = this.snapshot?.name || "";
this.snapshotHasPassword = this.snapshot?.protected || false;
}
}
private _localize = (string: string) =>
this.supervisor?.localize(`backup.${string}`) ||
this.supervisor?.localize(`snapshot.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult {
@@ -118,64 +120,64 @@ export class SupervisorBackupContent extends LitElement {
return html``;
}
const foldersSection =
this.backupType === "partial" ? this._getSection("folders") : undefined;
this.snapshotType === "partial" ? this._getSection("folders") : undefined;
const addonsSection =
this.backupType === "partial" ? this._getSection("addons") : undefined;
this.snapshotType === "partial" ? this._getSection("addons") : undefined;
return html`
${this.backup
${this.snapshot
? html`<div class="details">
${this.backup.type === "full"
? this._localize("full_backup")
: this._localize("partial_backup")}
(${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br />
${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.backup.date), this.hass.locale)
: this.backup.date}
? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
: this.snapshot.date}
</div>`
: html`<paper-input
name="backupName"
.label=${this._localize("name")}
.value=${this.backupName}
name="snapshotName"
.label=${this.supervisor?.localize("snapshot.name") || "Name"}
.value=${this.snapshotName}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`}
${!this.backup || this.backup.type === "full"
${!this.snapshot || this.snapshot.type === "full"
? html`<div class="sub-header">
${!this.backup
${!this.snapshot
? this._localize("type")
: this._localize("select_type")}
</div>
<div class="backup-types">
<ha-formfield .label=${this._localize("full_backup")}>
<div class="snapshot-types">
<ha-formfield .label=${this._localize("full_snapshot")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="full"
name="backupType"
.checked=${this.backupType === "full"}
name="snapshotType"
.checked=${this.snapshotType === "full"}
>
</ha-radio>
</ha-formfield>
<ha-formfield .label=${this._localize("partial_backup")}>
<ha-formfield .label=${this._localize("partial_snapshot")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="partial"
name="backupType"
.checked=${this.backupType === "partial"}
name="snapshotType"
.checked=${this.snapshotType === "partial"}
>
</ha-radio>
</ha-formfield>
</div>`
: ""}
${this.backupType === "partial"
${this.snapshotType === "partial"
? html`<div class="partial-picker">
${this.backup && this.backup.homeassistant
${this.snapshot && this.snapshot.homeassistant
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup.homeassistant}
.version=${this.snapshot.homeassistant}
>
</supervisor-formfield-label>`}
>
@@ -231,38 +233,38 @@ export class SupervisorBackupContent extends LitElement {
: ""}
</div> `
: ""}
${this.backupType === "partial" &&
(!this.backup || this.backupHasPassword)
${this.snapshotType === "partial" &&
(!this.snapshot || this.snapshotHasPassword)
? html`<hr />`
: ""}
${!this.backup
${!this.snapshot
? html`<ha-formfield
class="password"
.label=${this._localize("password_protection")}
>
<ha-checkbox
.checked=${this.backupHasPassword}
.checked=${this.snapshotHasPassword}
@change=${this._toggleHasPassword}
>
</ha-checkbox>
</ha-formfield>`
: ""}
${this.backupHasPassword
${this.snapshotHasPassword
? html`
<paper-input
.label=${this._localize("password")}
type="password"
name="backupPassword"
.value=${this.backupPassword}
name="snapshotPassword"
.value=${this.snapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>
${!this.backup
${!this.snapshot
? html` <paper-input
.label=${this._localize("confirm_password")}
.label=${this.supervisor?.localize("confirm_password")}
type="password"
name="confirmBackupPassword"
.value=${this.confirmBackupPassword}
name="confirmSnapshotPassword"
.value=${this.confirmSnapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`
@@ -305,7 +307,7 @@ export class SupervisorBackupContent extends LitElement {
display: block;
margin: 0 -14px -16px;
}
.backup-types {
.snapshot-types {
display: flex;
margin-left: -13px;
}
@@ -315,23 +317,23 @@ export class SupervisorBackupContent extends LitElement {
`;
}
public backupDetails():
| HassioPartialBackupCreateParams
| HassioFullBackupCreateParams {
public snapshotDetails():
| HassioPartialSnapshotCreateParams
| HassioFullSnapshotCreateParams {
const data: any = {};
if (!this.backup) {
data.name = this.backupName || formatDate(new Date(), this.hass.locale);
if (!this.snapshot) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale);
}
if (this.backupHasPassword) {
data.password = this.backupPassword;
if (!this.backup) {
data.confirm_password = this.confirmBackupPassword;
if (this.snapshotHasPassword) {
data.password = this.snapshotPassword;
if (!this.snapshot) {
data.confirm_password = this.confirmSnapshotPassword;
}
}
if (this.backupType === "full") {
if (this.snapshotType === "full") {
return data;
}
@@ -413,7 +415,7 @@ export class SupervisorBackupContent extends LitElement {
}
private _toggleHasPassword(): void {
this.backupHasPassword = !this.backupHasPassword;
this.snapshotHasPassword = !this.snapshotHasPassword;
}
private _toggleSection(ev): void {
@@ -443,6 +445,6 @@ export class SupervisorBackupContent extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"supervisor-backup-content": SupervisorBackupContent;
"supervisor-snapshot-content": SupervisorSnapshotContent;
}
}

View File

@@ -86,7 +86,7 @@ export class HassioUpdate extends LitElement {
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
)}
${this.supervisor.host.features.includes("haos")
${this.supervisor.host.features.includes("hassos")
? this._renderUpdateCard(
"Operating System",
"os",
@@ -162,7 +162,7 @@ export class HassioUpdate extends LitElement {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
backupParams: {
snapshotParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
@@ -220,10 +220,29 @@ export class HassioUpdate extends LitElement {
}
private async _updateCore(): Promise<void> {
await updateCore(this.hass);
try {
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", {
collection: "core",
});
fireEvent(this, "supervisor-applying-update", {
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
});
}
static get styles(): CSSResultGroup {

View File

@@ -1,19 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-backup-upload";
export interface HassioBackupUploadDialogParams {
showBackup: (slug: string) => void;
reloadBackup?: () => Promise<void>;
onboarding?: boolean;
}
export const showBackupUploadDialog = (
element: HTMLElement,
dialogParams: HassioBackupUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup-upload",
dialogImport: () => import("./dialog-hassio-backup-upload"),
dialogParams,
});
};

View File

@@ -61,6 +61,10 @@ class HassioMarkdownDialog extends LitElement {
app-toolbar [main-title] {
margin-left: 16px;
}
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-paper-dialog {
max-height: 100%;

View File

@@ -41,8 +41,7 @@ const IP_VERSIONS = ["ipv4", "ipv6"];
@customElement("dialog-hassio-network")
export class DialogHassioNetwork
extends LitElement
implements HassDialog<HassioNetworkDialogParams>
{
implements HassDialog<HassioNetworkDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -108,7 +107,7 @@ export class DialogHassioNetwork
</mwc-icon-button>
</ha-header-bar>
${this._interfaces.length > 1
? html`<mwc-tab-bar
? html` <mwc-tab-bar
.activeIndex=${this._curTabIndex}
@MDCTabBar:activated=${this._handleTabActivated}
>${this._interfaces.map(
@@ -493,7 +492,7 @@ export class DialogHassioNetwork
}
private _handleRadioValueChangedAp(ev: CustomEvent): void {
const value = (ev.target as any).value as string as
const value = ((ev.target as any).value as string) as
| "open"
| "wep"
| "wpa-psk";

View File

@@ -161,9 +161,9 @@ class HassioRegistriesDialog extends LitElement {
public focus(): void {
this.updateComplete.then(() =>
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
);
}

View File

@@ -161,9 +161,9 @@ class HassioRepositoriesDialog extends LitElement {
public focus() {
this.updateComplete.then(() =>
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
);
}

View File

@@ -6,37 +6,37 @@ import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
createHassioFullBackup,
createHassioPartialBackup,
} from "../../../../src/data/hassio/backup";
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-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
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-backup")
class HassioCreateBackupDialog extends LitElement {
@customElement("dialog-hassio-create-snapshot")
class HassioCreateSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateBackupDialogParams;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams;
@state() private _error?: string;
@state() private _creatingBackup = false;
@state() private _creatingSnapshot = false;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
public showDialog(params: HassioCreateBackupDialogParams) {
public showDialog(params: HassioCreateSnapshotDialogParams) {
this._dialogParams = params;
this._creatingBackup = false;
this._creatingSnapshot = false;
}
public closeDialog() {
this._dialogParams = undefined;
this._creatingBackup = false;
this._creatingSnapshot = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -52,74 +52,74 @@ class HassioCreateBackupDialog extends LitElement {
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this._dialogParams.supervisor.localize("backup.create_backup")
this._dialogParams.supervisor.localize("snapshot.create_snapshot")
)}
>
${this._creatingBackup
${this._creatingSnapshot
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-backup-content
: html`<supervisor-snapshot-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
>
</supervisor-backup-content>`}
</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._creatingBackup}
.disabled=${this._creatingSnapshot}
slot="primaryAction"
@click=${this._createBackup}
@click=${this._createSnapshot}
>
${this._dialogParams.supervisor.localize("backup.create")}
${this._dialogParams.supervisor.localize("snapshot.create")}
</mwc-button>
</ha-dialog>
`;
}
private async _createBackup(): Promise<void> {
private async _createSnapshot(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize(
"backup.could_not_create"
"snapshot.could_not_create"
),
text: this._dialogParams!.supervisor.localize(
"backup.create_blocked_not_running",
"snapshot.create_blocked_not_running",
"state",
this._dialogParams!.supervisor.info.state
),
});
return;
}
const backupDetails = this._backupContent.backupDetails();
this._creatingBackup = true;
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._creatingSnapshot = true;
this._error = "";
if (backupDetails.password && !backupDetails.password.length) {
if (snapshotDetails.password && !snapshotDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize(
"backup.enter_password"
"snapshot.enter_password"
);
this._creatingBackup = false;
this._creatingSnapshot = false;
return;
}
if (
backupDetails.password &&
backupDetails.password !== backupDetails.confirm_password
snapshotDetails.password &&
snapshotDetails.password !== snapshotDetails.confirm_password
) {
this._error = this._dialogParams!.supervisor.localize(
"backup.passwords_not_matching"
"snapshot.passwords_not_matching"
);
this._creatingBackup = false;
this._creatingSnapshot = false;
return;
}
delete backupDetails.confirm_password;
delete snapshotDetails.confirm_password;
try {
if (this._backupContent.backupType === "full") {
await createHassioFullBackup(this.hass, backupDetails);
if (this._snapshotContent.snapshotType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails);
} else {
await createHassioPartialBackup(this.hass, backupDetails);
await createHassioPartialSnapshot(this.hass, snapshotDetails);
}
this._dialogParams!.onCreate();
@@ -127,7 +127,7 @@ class HassioCreateBackupDialog extends LitElement {
} catch (err) {
this._error = extractApiErrorMessage(err);
}
this._creatingBackup = false;
this._creatingSnapshot = false;
}
static get styles(): CSSResultGroup {
@@ -146,6 +146,6 @@ class HassioCreateBackupDialog extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-create-backup": HassioCreateBackupDialog;
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog;
}
}

View File

@@ -6,20 +6,19 @@ import "../../../../src/components/ha-header-bar";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-upload-backup";
import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload";
import "../../components/hassio-upload-snapshot";
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload";
@customElement("dialog-hassio-backup-upload")
export class DialogHassioBackupUpload
@customElement("dialog-hassio-snapshot-upload")
export class DialogHassioSnapshotUpload
extends LitElement
implements HassDialog<HassioBackupUploadDialogParams>
{
implements HassDialog<HassioSnapshotUploadDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioBackupUploadDialogParams;
@state() private _params?: HassioSnapshotUploadDialogParams;
public async showDialog(
params: HassioBackupUploadDialogParams
params: HassioSnapshotUploadDialogParams
): Promise<void> {
this._params = params;
await this.updateComplete;
@@ -27,8 +26,8 @@ export class DialogHassioBackupUpload
public closeDialog(): void {
if (this._params && !this._params.onboarding) {
if (this._params.reloadBackup) {
this._params.reloadBackup();
if (this._params.reloadSnapshot) {
this._params.reloadSnapshot();
}
}
this._params = undefined;
@@ -51,23 +50,23 @@ export class DialogHassioBackupUpload
>
<div slot="heading">
<ha-header-bar>
<span slot="title"> Upload backup </span>
<span slot="title"> Upload snapshot </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
<hassio-upload-backup
@backup-uploaded=${this._backupUploaded}
<hassio-upload-snapshot
@snapshot-uploaded=${this._snapshotUploaded}
.hass=${this.hass}
></hassio-upload-backup>
></hassio-upload-snapshot>
</ha-dialog>
`;
}
private _backupUploaded(ev) {
const backup = ev.detail.backup;
this._params?.showBackup(backup.slug);
private _snapshotUploaded(ev) {
const snapshot = ev.detail.snapshot;
this._params?.showSnapshot(snapshot.slug);
this.closeDialog();
}
@@ -94,6 +93,6 @@ export class DialogHassioBackupUpload
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-backup-upload": DialogHassioBackupUpload;
"dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload;
}
}

View File

@@ -12,9 +12,9 @@ import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
fetchHassioBackupInfo,
HassioBackupDetail,
} from "../../../../src/data/hassio/backup";
fetchHassioSnapshotInfo,
HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot";
import {
showAlertDialog,
showConfirmationDialog,
@@ -23,45 +23,43 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import { atLeastVersion } from "../../../../src/common/config/version";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
@customElement("dialog-hassio-backup")
class HassioBackupDialog
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog
extends LitElement
implements HassDialog<HassioBackupDialogParams>
{
implements HassDialog<HassioSnapshotDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string;
@state() private _backup?: HassioBackupDetail;
@state() private _snapshot?: HassioSnapshotDetail;
@state() private _dialogParams?: HassioBackupDialogParams;
@state() private _dialogParams?: HassioSnapshotDialogParams;
@state() private _restoringBackup = false;
@state() private _restoringSnapshot = false;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
public async showDialog(params: HassioBackupDialogParams) {
this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._dialogParams = params;
this._restoringBackup = false;
this._restoringSnapshot = false;
}
public closeDialog() {
this._backup = undefined;
this._snapshot = undefined;
this._dialogParams = undefined;
this._restoringBackup = false;
this._restoringSnapshot = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams || !this._backup) {
if (!this._dialogParams || !this._snapshot) {
return html``;
}
return html`
@@ -73,26 +71,26 @@ class HassioBackupDialog
>
<div slot="heading">
<ha-header-bar>
<span slot="title">${this._backup.name}</span>
<span slot="title">${this._snapshot.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
${this._restoringBackup
${this._restoringSnapshot
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-backup-content
: html`<supervisor-snapshot-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.backup=${this._backup}
.snapshot=${this._snapshot}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
>
</supervisor-backup-content>`}
</supervisor-snapshot-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button
.disabled=${this._restoringBackup}
.disabled=${this._restoringSnapshot}
slot="secondaryAction"
@click=${this._restoreClicked}
>
@@ -109,8 +107,8 @@ class HassioBackupDialog
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>Download Backup</mwc-list-item>
<mwc-list-item class="error">Delete Backup</mwc-list-item>
<mwc-list-item>Download Snapshot</mwc-list-item>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
</ha-button-menu>`
: ""}
</ha-dialog>
@@ -151,30 +149,30 @@ class HassioBackupDialog
}
private async _restoreClicked() {
const backupDetails = this._backupContent.backupDetails();
this._restoringBackup = true;
if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(backupDetails);
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._restoringSnapshot = true;
if (this._snapshotContent.snapshotType === "full") {
await this._fullRestoreClicked(snapshotDetails);
} else {
await this._partialRestoreClicked(backupDetails);
await this._partialRestoreClicked(snapshotDetails);
}
this._restoringBackup = false;
this._restoringSnapshot = false;
}
private async _partialRestoreClicked(backupDetails) {
private async _partialRestoreClicked(snapshotDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore backup",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
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.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this backup?",
title: "Are you sure you want partially to restore this snapshot?",
confirmText: "restore",
dismissText: "cancel",
}))
@@ -187,12 +185,8 @@ class HassioBackupDialog
.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
snapshotDetails
)
.then(
() => {
@@ -204,29 +198,29 @@ class HassioBackupDialog
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
body: JSON.stringify(snapshotDetails),
});
this.closeDialog();
}
}
private async _fullRestoreClicked(backupDetails) {
private async _fullRestoreClicked(snapshotDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore backup",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
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.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title:
"Are you sure you want to wipe your system and restore this backup?",
"Are you sure you want to wipe your system and restore this snapshot?",
confirmText: "restore",
dismissText: "cancel",
}))
@@ -238,12 +232,8 @@ class HassioBackupDialog
this.hass
.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
`hassio/snapshots/${this._snapshot!.slug}/restore/full`,
snapshotDetails
)
.then(
() => {
@@ -255,9 +245,9 @@ class HassioBackupDialog
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
method: "POST",
body: JSON.stringify(backupDetails),
body: JSON.stringify(snapshotDetails),
});
this.closeDialog();
}
@@ -266,7 +256,7 @@ class HassioBackupDialog
private async _deleteClicked() {
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this backup?",
title: "Are you sure you want to delete this snapshot?",
confirmText: "delete",
dismissText: "cancel",
}))
@@ -276,14 +266,7 @@ class HassioBackupDialog
this.hass
.callApi(
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/remove`
)
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
.then(
() => {
if (this._dialogParams!.onDelete) {
@@ -302,11 +285,7 @@ class HassioBackupDialog
try {
signedPath = await getSignedPath(
this.hass,
`/api/hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/download`
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
);
} catch (err) {
await showAlertDialog(this, {
@@ -318,7 +297,8 @@ class HassioBackupDialog
if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, {
title: "Potential slow download",
text: "Downloading backups over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
text:
"Downloading snapshots over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
confirmText: "continue",
dismissText: "cancel",
});
@@ -330,19 +310,19 @@ class HassioBackupDialog
fileDownload(
this,
signedPath.path,
`home_assistant_backup_${slugify(this._computeName)}.tar`
`home_assistant_snapshot_${slugify(this._computeName)}.tar`
);
}
private get _computeName() {
return this._backup
? this._backup.name || this._backup.slug
: "Unnamed backup";
return this._snapshot
? this._snapshot.name || this._snapshot.slug
: "Unnamed snapshot";
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-backup": HassioBackupDialog;
"dialog-hassio-snapshot": HassioSnapshotDialog;
}
}

View File

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

View File

@@ -2,7 +2,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioBackupDialogParams {
export interface HassioSnapshotDialogParams {
slug: string;
onDelete?: () => void;
onboarding?: boolean;
@@ -10,13 +10,13 @@ export interface HassioBackupDialogParams {
localize?: LocalizeFunc;
}
export const showHassioBackupDialog = (
export const showHassioSnapshotDialog = (
element: HTMLElement,
dialogParams: HassioBackupDialogParams
dialogParams: HassioSnapshotDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup",
dialogImport: () => import("./dialog-hassio-backup"),
dialogTag: "dialog-hassio-snapshot",
dialogImport: () => import("./dialog-hassio-snapshot"),
dialogParams,
});
};

View File

@@ -0,0 +1,19 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-snapshot-upload";
export interface HassioSnapshotUploadDialogParams {
showSnapshot: (slug: string) => void;
reloadSnapshot?: () => Promise<void>;
onboarding?: boolean;
}
export const showSnapshotUploadDialog = (
element: HTMLElement,
dialogParams: HassioSnapshotUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot-upload",
dialogImport: () => import("./dialog-hassio-snapshot-upload"),
dialogParams,
});
};

View File

@@ -7,11 +7,8 @@ import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import { createHassioPartialBackup } from "../../../../src/data/hassio/backup";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
@@ -22,9 +19,9 @@ class DialogSupervisorUpdate extends LitElement {
@state() private _opened = false;
@state() private _createBackup = true;
@state() private _createSnapshot = true;
@state() private _action: "backup" | "update" | null = null;
@state() private _action: "snapshot" | "update" | null = null;
@state() private _error?: string;
@@ -41,7 +38,7 @@ class DialogSupervisorUpdate extends LitElement {
public closeDialog(): void {
this._action = null;
this._createBackup = true;
this._createSnapshot = true;
this._error = undefined;
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
@@ -49,9 +46,9 @@ class DialogSupervisorUpdate extends LitElement {
public focus(): void {
this.updateComplete.then(() =>
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
);
}
@@ -84,20 +81,20 @@ class DialogSupervisorUpdate extends LitElement {
<ha-settings-row>
<span slot="heading">
${this._dialogParams.supervisor.localize(
"dialog.update.backup"
"dialog.update.snapshot"
)}
</span>
<span slot="description">
${this._dialogParams.supervisor.localize(
"dialog.update.create_backup",
"dialog.update.create_snapshot",
"name",
this._dialogParams.name
)}
</span>
<ha-switch
.checked=${this._createBackup}
.checked=${this._createSnapshot}
haptic
@click=${this._toggleBackup}
@click=${this._toggleSnapshot}
>
</ha-switch>
</ha-settings-row>
@@ -123,7 +120,7 @@ class DialogSupervisorUpdate extends LitElement {
this._dialogParams.version
)
: this._dialogParams.supervisor.localize(
"dialog.update.creating_backup",
"dialog.update.snapshotting",
"name",
this._dialogParams.name
)}
@@ -133,17 +130,17 @@ class DialogSupervisorUpdate extends LitElement {
`;
}
private _toggleBackup() {
this._createBackup = !this._createBackup;
private _toggleSnapshot() {
this._createSnapshot = !this._createSnapshot;
}
private async _update() {
if (this._createBackup) {
this._action = "backup";
if (this._createSnapshot) {
this._action = "snapshot";
try {
await createHassioPartialBackup(
await createHassioPartialSnapshot(
this.hass,
this._dialogParams!.backupParams
this._dialogParams!.snapshotParams
);
} catch (err) {
this._error = extractApiErrorMessage(err);
@@ -153,16 +150,7 @@ class DialogSupervisorUpdate extends LitElement {
}
this._action = "update";
try {
await this._dialogParams!.updateHandler!();
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
this._action = null;
}
return;
}
await this._dialogParams!.updateHandler!();
this.closeDialog();
}

View File

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

View File

@@ -121,7 +121,7 @@ export class HassioMain extends SupervisorBaseElement {
}
} else {
themeName =
(this.hass.selectedTheme as unknown as string) ||
((this.hass.selectedTheme as unknown) as string) ||
this.hass.themes.default_theme;
}

View File

@@ -1,19 +1,19 @@
import { sanitizeUrl } from "@braintree/sanitize-url";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../src/common/navigate";
import { sanitizeUrl } from "@braintree/sanitize-url";
import {
createSearchParam,
extractSearchParamsObject,
} from "../../src/common/url/search-params";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import "../../src/layouts/hass-error-screen";
import {
ParamType,
Redirect,
Redirects,
} from "../../src/panels/my/ha-panel-my";
import { navigate } from "../../src/common/navigate";
import { HomeAssistant, Route } from "../../src/types";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
const REDIRECTS: Redirects = {
supervisor: {
@@ -26,10 +26,7 @@ const REDIRECTS: Redirects = {
redirect: "/hassio/system",
},
supervisor_snapshots: {
redirect: "/hassio/backups",
},
supervisor_backups: {
redirect: "/hassio/backups",
redirect: "/hassio/snapshots",
},
supervisor_store: {
redirect: "/hassio/store",

View File

@@ -9,7 +9,7 @@ import "./addon-store/hassio-addon-store";
// Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard";
// Don't codesplit the others, because it breaks the UI when pushed to a Pi
import "./backups/hassio-backups";
import "./snapshots/hassio-snapshots";
import "./system/hassio-system";
@customElement("hassio-panel-router")
@@ -23,8 +23,6 @@ class HassioPanelRouter extends HassRouterPage {
@property({ type: Boolean }) public narrow!: boolean;
protected routerOptions: RouterOptions = {
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
routes: {
dashboard: {
tag: "hassio-dashboard",
@@ -32,8 +30,8 @@ class HassioPanelRouter extends HassRouterPage {
store: {
tag: "hassio-addon-store",
},
backups: {
tag: "hassio-backups",
snapshots: {
tag: "hassio-snapshots",
},
system: {
tag: "hassio-system",

View File

@@ -1,12 +1,26 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import {
Supervisor,
supervisorApplyUpdateDetails,
supervisorCollection,
} from "../../src/data/supervisor/supervisor";
import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router";
declare global {
interface HASSDomEvents {
"supervisor-applying-update": supervisorApplyUpdateDetails;
}
}
@customElement("hassio-panel")
class HassioPanel extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -17,6 +31,16 @@ class HassioPanel extends LitElement {
@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 {
if (!this.hass) {
return html`<hass-loading-screen></hass-loading-screen>`;
@@ -29,6 +53,16 @@ class HassioPanel extends LitElement {
) {
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`
<hassio-panel-router
.hass=${this.hass}

View File

@@ -23,8 +23,6 @@ class HassioRouter extends HassRouterPage {
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
initialLoad: () => this._redirectIngress(),
showLoading: true,
routes: {
@@ -32,7 +30,7 @@ class HassioRouter extends HassRouterPage {
tag: "hassio-panel",
cache: true,
},
backups: "dashboard",
snapshots: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {

View File

@@ -13,8 +13,8 @@ export const supervisorTabs: PageNavigation[] = [
iconPath: mdiStore,
},
{
translationKey: "panel.backups",
path: `/hassio/backups`,
translationKey: "panel.snapshots",
path: `/hassio/snapshots`,
iconPath: mdiBackupRestore,
},
{

View File

@@ -25,12 +25,12 @@ import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
fetchHassioBackups,
fetchHassioSnapshots,
friendlyFolderName,
HassioBackup,
reloadHassioBackups,
removeBackup,
} from "../../../src/data/hassio/backup";
HassioSnapshot,
reloadHassioSnapshots,
removeSnapshot,
} from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
@@ -40,14 +40,14 @@ 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 { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload";
import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-backups")
export class HassioBackups extends LitElement {
@customElement("hassio-snapshots")
export class HassioSnapshots extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -58,9 +58,9 @@ export class HassioBackups extends LitElement {
@property({ type: Boolean }) public isWide!: boolean;
@state() private _selectedBackups: string[] = [];
@state() private _selectedSnapshots: string[] = [];
@state() private _backups?: HassioBackup[] = [];
@state() private _snapshots?: HassioSnapshot[] = [];
@query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
@@ -75,26 +75,26 @@ export class HassioBackups extends LitElement {
}
public async refreshData() {
await reloadHassioBackups(this.hass);
await this.fetchBackups();
await reloadHassioSnapshots(this.hass);
await this.fetchSnapshots();
}
private _computeBackupContent = (backup: HassioBackup): string => {
if (backup.type === "full") {
return this.supervisor.localize("backup.full_backup");
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => {
if (snapshot.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot");
}
const content: string[] = [];
if (backup.content.homeassistant) {
if (snapshot.content.homeassistant) {
content.push("Home Assistant");
}
if (backup.content.folders.length !== 0) {
for (const folder of backup.content.folders) {
if (snapshot.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) {
content.push(friendlyFolderName[folder] || folder);
}
}
if (backup.content.addons.length !== 0) {
for (const addon of backup.content.addons) {
if (snapshot.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) {
content.push(
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
@@ -117,16 +117,16 @@ export class HassioBackups extends LitElement {
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor?.localize("backup.name") || "",
title: this.supervisor?.localize("snapshot.name") || "",
sortable: true,
filterable: true,
grows: true,
template: (entry: string, backup: any) =>
html`${entry || backup.slug}
<div class="secondary">${backup.secondary}</div>`,
template: (entry: string, snapshot: any) =>
html`${entry || snapshot.slug}
<div class="secondary">${snapshot.secondary}</div>`,
},
date: {
title: this.supervisor?.localize("backup.created") || "",
title: this.supervisor?.localize("snapshot.created") || "",
width: "15%",
direction: "desc",
hidden: narrow,
@@ -143,10 +143,10 @@ export class HassioBackups extends LitElement {
})
);
private _backupData = memoizeOne((backups: HassioBackup[]) =>
backups.map((backup) => ({
...backup,
secondary: this._computeBackupContent(backup),
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) =>
snapshots.map((snapshot) => ({
...snapshot,
secondary: this._computeSnapshotContent(snapshot),
}))
);
@@ -160,11 +160,11 @@ export class HassioBackups extends LitElement {
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("backup.no_backups")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")}
.narrow=${this.narrow}
.route=${this.route}
.columns=${this._columns(this.narrow)}
.data=${this._backupData(this._backups || [])}
.data=${this._snapshotData(this._snapshots || [])}
id="slug"
@row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged}
@@ -187,12 +187,12 @@ export class HassioBackups extends LitElement {
</mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item>
${this.supervisor?.localize("backup.upload_backup")}
${this.supervisor?.localize("snapshot.upload_snapshot")}
</mwc-list-item>`
: ""}
</ha-button-menu>
${this._selectedBackups.length
${this._selectedSnapshots.length
? html`<div
class=${classMap({
"header-toolbar": this.narrow,
@@ -201,8 +201,8 @@ export class HassioBackups extends LitElement {
slot="header"
>
<p class="selected-txt">
${this.supervisor.localize("backup.selected", {
number: this._selectedBackups.length,
${this.supervisor.localize("snapshot.selected", {
number: this._selectedSnapshots.length,
})}
</p>
<div class="header-btns">
@@ -212,7 +212,7 @@ export class HassioBackups extends LitElement {
@click=${this._deleteSelected}
class="warning"
>
${this.supervisor.localize("backup.delete_selected")}
${this.supervisor.localize("snapshot.delete_selected")}
</mwc-button>
`
: html`
@@ -224,7 +224,7 @@ export class HassioBackups extends LitElement {
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("backup.delete_selected")}
${this.supervisor.localize("snapshot.delete_selected")}
</paper-tooltip>
`}
</div>
@@ -233,8 +233,8 @@ export class HassioBackups extends LitElement {
<ha-fab
slot="fab"
@click=${this._createBackup}
.label=${this.supervisor.localize("backup.create_backup")}
@click=${this._createSnapshot}
.label=${this.supervisor.localize("snapshot.create_snapshot")}
extended
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
@@ -249,7 +249,7 @@ export class HassioBackups extends LitElement {
this.refreshData();
break;
case 1:
this._showUploadBackupDialog();
this._showUploadSnapshotDialog();
break;
}
}
@@ -257,33 +257,33 @@ export class HassioBackups extends LitElement {
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedBackups = ev.detail.value;
this._selectedSnapshots = ev.detail.value;
}
private _showUploadBackupDialog() {
showBackupUploadDialog(this, {
showBackup: (slug: string) =>
showHassioBackupDialog(this, {
private _showUploadSnapshotDialog() {
showSnapshotUploadDialog(this, {
showSnapshot: (slug: string) =>
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchBackups(),
onDelete: () => this.fetchSnapshots(),
}),
reloadBackup: () => this.refreshData(),
reloadSnapshot: () => this.refreshData(),
});
}
private async fetchBackups() {
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
private async fetchSnapshots() {
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
}
private async _deleteSelected() {
const confirm = await showConfirmationDialog(this, {
title: this.supervisor.localize("backup.delete_backup_title"),
text: this.supervisor.localize("backup.delete_backup_text", {
number: this._selectedBackups.length,
title: this.supervisor.localize("snapshot.delete_snapshot_title"),
text: this.supervisor.localize("snapshot.delete_snapshot_text", {
number: this._selectedSnapshots.length,
}),
confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
});
if (!confirm) {
@@ -292,44 +292,44 @@ export class HassioBackups extends LitElement {
try {
await Promise.all(
this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("backup.failed_to_delete"),
title: this.supervisor.localize("snapshot.failed_to_delete"),
text: extractApiErrorMessage(err),
});
return;
}
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
this._dataTable.clearSelection();
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id;
showHassioBackupDialog(this, {
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchBackups(),
onDelete: () => this.fetchSnapshots(),
});
}
private _createBackup() {
private _createSnapshot() {
if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, {
title: this.supervisor!.localize("backup.could_not_create"),
title: this.supervisor!.localize("snapshot.could_not_create"),
text: this.supervisor!.localize(
"backup.create_blocked_not_running",
"snapshot.create_blocked_not_running",
"state",
this.supervisor!.info.state
),
});
return;
}
showHassioCreateBackupDialog(this, {
showHassioCreateSnapshotDialog(this, {
supervisor: this.supervisor!,
onCreate: () => this.fetchBackups(),
onCreate: () => this.fetchSnapshots(),
});
}
@@ -378,6 +378,6 @@ export class HassioBackups extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"hassio-backups": HassioBackups;
"hassio-snapshots": HassioSnapshots;
}
}

View File

@@ -86,8 +86,10 @@ export class SupervisorBaseElement extends urlSyncMixin(
const unsubs = Object.keys(this._unsubs);
for (const collection of Object.keys(this._collections)) {
if (!unsubs.includes(collection)) {
this._unsubs[collection] = this._collections[collection].subscribe(
(data) => this._updateSupervisor({ [collection]: data })
this._unsubs[collection] = this._collections[
collection
].subscribe((data) =>
this._updateSupervisor({ [collection]: data })
);
}
}

View File

@@ -11,6 +11,7 @@ import {
extractApiErrorMessage,
fetchHassioStats,
HassioStats,
ignoreSupervisorError,
} from "../../../src/data/hassio/common";
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
@@ -150,7 +151,7 @@ class HassioCoreInfo extends LitElement {
title: this.supervisor.localize(
"common.failed_to_restart_name",
"name",
"Home AssistantCore"
"Home Assistant Core"
),
text: extractApiErrorMessage(err),
});
@@ -165,7 +166,7 @@ class HassioCoreInfo extends LitElement {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
backupParams: {
snapshotParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
@@ -175,10 +176,29 @@ class HassioCoreInfo extends LitElement {
}
private async _updateCore(): Promise<void> {
await updateCore(this.hass);
try {
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", {
collection: "core",
});
fireEvent(this, "supervisor-applying-update", {
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
});
}
static get styles(): CSSResultGroup {

View File

@@ -113,7 +113,7 @@ class HassioHostInfo extends LitElement {
`
: ""}
</ha-settings-row>
${!this.supervisor.host.features.includes("haos")
${!this.supervisor.host.features.includes("hassos")
? html`<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.host.docker_version")}
@@ -190,7 +190,7 @@ class HassioHostInfo extends LitElement {
<mwc-list-item>
${this.supervisor.localize("system.host.hardware")}
</mwc-list-item>
${this.supervisor.host.features.includes("haos")
${this.supervisor.host.features.includes("hassos")
? html`<mwc-list-item>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>`

View File

@@ -1,4 +1,4 @@
module.exports = {
"*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
"*.{js,ts}": "eslint --fix",
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
};

View File

@@ -1,3 +0,0 @@
[build.environment]
YARN_VERSION = "1.22.11"
NODE_OPTIONS = "--max_old_space_size=4096"

View File

@@ -17,7 +17,7 @@
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
"format": "yarn run format:eslint && yarn run format:prettier",
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
"test": "yarn run mocha"
"test": "yarn run lint && yarn run mocha"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
@@ -42,72 +42,72 @@
"@fullcalendar/daygrid": "5.1.0",
"@fullcalendar/interaction": "5.1.0",
"@fullcalendar/list": "5.1.0",
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
"@material/chips": "12.0.0-canary.22d29cbb4.0",
"@material/data-table": "12.0.0-canary.22d29cbb4.0",
"@material/mwc-button": "0.22.1",
"@material/mwc-checkbox": "0.22.1",
"@material/mwc-circular-progress": "0.22.1",
"@material/mwc-dialog": "0.22.1",
"@material/mwc-fab": "0.22.1",
"@material/mwc-formfield": "0.22.1",
"@material/mwc-icon-button": "0.22.1",
"@material/mwc-linear-progress": "0.22.1",
"@material/mwc-list": "0.22.1",
"@material/mwc-menu": "0.22.1",
"@material/mwc-radio": "0.22.1",
"@material/mwc-ripple": "0.22.1",
"@material/mwc-switch": "0.22.1",
"@material/mwc-tab": "0.22.1",
"@material/mwc-tab-bar": "0.22.1",
"@material/top-app-bar": "12.0.0-canary.22d29cbb4.0",
"@lit-labs/virtualizer": "^0.6.0",
"@material/chips": "=12.0.0-canary.1a8d06483.0",
"@material/mwc-button": "canary",
"@material/mwc-checkbox": "canary",
"@material/mwc-circular-progress": "canary",
"@material/mwc-dialog": "canary",
"@material/mwc-fab": "canary",
"@material/mwc-formfield": "canary",
"@material/mwc-icon-button": "canary",
"@material/mwc-list": "canary",
"@material/mwc-menu": "canary",
"@material/mwc-radio": "canary",
"@material/mwc-ripple": "canary",
"@material/mwc-switch": "canary",
"@material/mwc-tab": "canary",
"@material/mwc-tab-bar": "canary",
"@material/top-app-bar": "=12.0.0-canary.1a8d06483.0",
"@mdi/js": "5.9.55",
"@mdi/svg": "5.9.55",
"@polymer/app-layout": "^3.1.0",
"@polymer/app-layout": "^3.0.2",
"@polymer/app-storage": "^3.0.2",
"@polymer/iron-autogrow-textarea": "^3.0.1",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
"@polymer/iron-input": "^3.0.1",
"@polymer/iron-overlay-behavior": "^3.0.3",
"@polymer/iron-overlay-behavior": "^3.0.2",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/paper-checkbox": "^3.1.0",
"@polymer/paper-dialog": "^3.0.1",
"@polymer/paper-dialog-behavior": "^3.0.1",
"@polymer/paper-dialog-scrollable": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.2.0",
"@polymer/paper-input": "^3.2.1",
"@polymer/paper-dropdown-menu": "^3.0.1",
"@polymer/paper-input": "^3.0.1",
"@polymer/paper-item": "^3.0.1",
"@polymer/paper-listbox": "^3.0.1",
"@polymer/paper-menu-button": "^3.1.0",
"@polymer/paper-menu-button": "^3.0.1",
"@polymer/paper-progress": "^3.0.1",
"@polymer/paper-radio-button": "^3.0.1",
"@polymer/paper-radio-group": "^3.0.1",
"@polymer/paper-ripple": "^3.0.2",
"@polymer/paper-ripple": "^3.0.1",
"@polymer/paper-slider": "^3.0.1",
"@polymer/paper-styles": "^3.0.1",
"@polymer/paper-tabs": "^3.1.0",
"@polymer/paper-tabs": "^3.0.1",
"@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1",
"@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.5.2",
"@vaadin/vaadin-combo-box": "^20.0.1",
"@vaadin/vaadin-date-picker": "^20.0.1",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
"@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.10",
"chart.js": "^3.3.2",
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "^2.9.4",
"chartjs-chart-timeline": "^0.4.0",
"comlink": "^4.3.1",
"core-js": "^3.15.2",
"core-js": "^3.6.5",
"cropperjs": "^1.5.11",
"date-fns": "^2.22.1",
"deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1",
"fecha": "^4.2.0",
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.0.7",
"home-assistant-js-websocket": "^5.11.1",
"hls.js": "^1.0.5",
"home-assistant-js-websocket": "^5.10.0",
"idb-keyval": "^5.0.5",
"intl-messageformat": "^9.6.16",
"js-yaml": "^4.1.0",
@@ -122,7 +122,7 @@
"proxy-polyfill": "^0.3.1",
"punycode": "^2.1.1",
"qrcode": "^1.4.4",
"regenerator-runtime": "^0.13.8",
"regenerator-runtime": "^0.13.2",
"resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0",
"sortablejs": "^1.10.2",
@@ -144,17 +144,17 @@
"xss": "^1.0.9"
},
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/plugin-external-helpers": "^7.14.5",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-decorators": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.14.7",
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/core": "^7.14.3",
"@babel/plugin-external-helpers": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-decorators": "^7.13.15",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
"@babel/plugin-proposal-object-rest-spread": "^7.13.8",
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/preset-env": "^7.14.7",
"@babel/preset-typescript": "^7.14.5",
"@babel/preset-env": "^7.14.2",
"@babel/preset-typescript": "^7.13.0",
"@koa/cors": "^3.1.0",
"@open-wc/dev-server-hmr": "^0.0.2",
"@rollup/plugin-babel": "^5.2.1",
@@ -171,22 +171,22 @@
"@types/mocha": "^8.2.2",
"@types/sortablejs": "^1.10.6",
"@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.28.3",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"@web/dev-server": "^0.0.24",
"@web/dev-server-rollup": "^0.2.11",
"babel-loader": "^8.2.2",
"babel-loader": "^8.1.0",
"chai": "^4.3.4",
"cpx": "^1.5.0",
"del": "^4.0.0",
"eslint": "^7.30.0",
"eslint": "^7.25.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.1",
"eslint-import-resolver-webpack": "^0.13.0",
"eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-lit": "^1.5.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-lit": "^1.3.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-unused-imports": "^1.1.2",
"eslint-plugin-wc": "^1.3.0",
"fancy-log": "^1.3.3",
"fs-extra": "^7.0.1",
@@ -198,7 +198,7 @@
"gulp-zopfli-green": "^3.0.1",
"html-minifier": "^4.0.0",
"husky": "^1.3.1",
"lint-staged": "^11.0.1",
"lint-staged": "^10.5.4",
"lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0",
"magic-string": "^0.25.7",
@@ -207,7 +207,8 @@
"mocha": "^8.4.0",
"object-hash": "^2.0.3",
"open": "^7.0.4",
"prettier": "^2.3.2",
"prettier": "^2.0.4",
"raw-loader": "^2.0.0",
"require-dir": "^1.2.0",
"rollup": "^2.8.2",
"rollup-plugin-string": "^3.0.0",
@@ -217,22 +218,23 @@
"sinon": "^11.0.0",
"source-map-url": "^0.4.0",
"systemjs": "^6.3.2",
"terser-webpack-plugin": "^5.1.4",
"terser-webpack-plugin": "^5.1.2",
"ts-lit-plugin": "^1.2.1",
"ts-mocha": "^8.0.0",
"typescript": "^4.3.5",
"typescript": "^4.2.4",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"webpack": "^5.43.0",
"webpack-cli": "^4.7.2",
"webpack": "^5.24.1",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2",
"webpack-manifest-plugin": "^3.1.1",
"webpack-manifest-plugin": "^3.0.0",
"workbox-build": "^6.1.5"
},
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
"resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10",
"@polymer/polymer": "3.1.0",
"lit-html": "2.0.0-rc.3",
"lit-element": "3.0.0-rc.2"
},

View File

@@ -9,6 +9,12 @@ if [ -z "${DEVCONTAINER}" ]; then
exit 1
fi
if [ ! -z "${CODESPACES}" ]; then
WORKSPACE="/root/workspace/frontend"
else
WORKSPACE="/workspaces/frontend"
fi
if [ -z $(which hass) ]; then
echo "Installing Home Asstant core from dev."
python3 -m pip install --upgrade \
@@ -16,9 +22,9 @@ if [ -z $(which hass) ]; then
git+git://github.com/home-assistant/home-assistant.git@dev
fi
if [ ! -d "/workspaces/frontend/config" ]; then
if [ ! -d "${WORKSPACE}/config" ]; then
echo "Creating default configuration."
mkdir -p "/workspaces/frontend/config";
mkdir -p "${WORKSPACE}/config";
hass --script ensure_config -c config
echo "demo:
@@ -26,24 +32,24 @@ logger:
default: info
logs:
homeassistant.components.frontend: debug
" >> /workspaces/frontend/config/configuration.yaml
" >> "${WORKSPACE}/config/configuration.yaml"
if [ ! -z "${HASSIO}" ]; then
echo "
# frontend:
# development_repo: /workspaces/frontend
# development_repo: ${WORKSPACE}
hassio:
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
else
echo "
frontend:
development_repo: /workspaces/frontend
development_repo: ${WORKSPACE}
# hassio:
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
# development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
fi
fi
hass -c /workspaces/frontend/config
hass -c "${WORKSPACE}/config"

View File

@@ -2,9 +2,9 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20210813.0",
version="20210603.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
author_email="hello@home-assistant.io",
license="Apache-2.0",

View File

@@ -7,7 +7,6 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import "./ha-password-manager-polyfill";
import { property, state } from "lit/decorators";
import "../components/ha-form/ha-form";
import "../components/ha-markdown";
@@ -21,7 +20,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
type State = "loading" | "error" | "step";
class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@property({ attribute: false }) public authProvider?: AuthProvider;
@property() public authProvider?: AuthProvider;
@property() public clientId?: string;
@@ -38,15 +37,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@state() private _errorMessage?: string;
protected render() {
return html`
<form>${this._renderForm()}</form>
<ha-password-manager-polyfill
.step=${this._step}
.stepData=${this._stepData}
@form-submitted=${this._handleSubmit}
@value-changed=${this._stepDataChanged}
></ha-password-manager-polyfill>
`;
return html` <form>${this._renderForm()}</form> `;
}
protected firstUpdated(changedProps: PropertyValues) {
@@ -240,17 +231,11 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
await this.updateComplete;
// 100ms to give all the form elements time to initialize.
setTimeout(() => {
const form = this.renderRoot.querySelector("ha-form");
const form = this.shadowRoot!.querySelector("ha-form");
if (form) {
(form as any).focus();
}
}, 100);
setTimeout(() => {
this.renderRoot.querySelector(
"ha-password-manager-polyfill"
)!.boundingRect = this.getBoundingClientRect();
}, 500);
}
private _stepDataChanged(ev: CustomEvent) {
@@ -344,9 +329,3 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
}
}
customElements.define("ha-auth-flow", HaAuthFlow);
declare global {
interface HTMLElementTagNameMap {
"ha-auth-flow": HaAuthFlow;
}
}

View File

@@ -1,110 +0,0 @@
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { HaFormSchema } from "../components/ha-form/ha-form";
import { DataEntryFlowStep } from "../data/data_entry_flow";
declare global {
interface HTMLElementTagNameMap {
"ha-password-manager-polyfill": HaPasswordManagerPolyfill;
}
interface HASSDomEvents {
"form-submitted": undefined;
}
}
const ENABLED_HANDLERS = [
"homeassistant",
"legacy_api_password",
"command_line",
];
@customElement("ha-password-manager-polyfill")
export class HaPasswordManagerPolyfill extends LitElement {
@property({ attribute: false }) public step?: DataEntryFlowStep;
@property({ attribute: false }) public stepData: any;
@property({ attribute: false }) public boundingRect?: DOMRect;
protected createRenderRoot() {
// Add under document body so the element isn't placed inside any shadow roots
return document.body;
}
private get styles() {
return `
.password-manager-polyfill {
position: absolute;
top: ${this.boundingRect?.y || 148}px;
left: calc(50% - ${(this.boundingRect?.width || 360) / 2}px);
width: ${this.boundingRect?.width || 360}px;
opacity: 0;
z-index: -1;
}
.password-manager-polyfill input {
width: 100%;
height: 62px;
padding: 0;
border: 0;
}
.password-manager-polyfill input[type="submit"] {
width: 0;
height: 0;
}
`;
}
protected render(): TemplateResult {
if (
this.step &&
this.step.type === "form" &&
this.step.step_id === "init" &&
ENABLED_HANDLERS.includes(this.step.handler[0])
) {
return html`
<form
class="password-manager-polyfill"
aria-hidden="true"
@submit=${this._handleSubmit}
>
${this.step.data_schema.map((input) => this.render_input(input))}
<input type="submit" />
<style>
${this.styles}
</style>
</form>
`;
}
return html``;
}
private render_input(schema: HaFormSchema): TemplateResult | string {
const inputType = schema.name.includes("password") ? "password" : "text";
if (schema.type !== "string") {
return "";
}
return html`
<input
tabindex="-1"
.id=${schema.name}
.type=${inputType}
.value=${this.stepData[schema.name] || ""}
@input=${this._valueChanged}
/>
`;
}
private _handleSubmit(ev: Event) {
ev.preventDefault();
fireEvent(this, "form-submitted");
}
private _valueChanged(ev: Event) {
const target = ev.target! as HTMLInputElement;
this.stepData = { ...this.stepData, [target.id]: target.value };
fireEvent(this, "value-changed", {
value: this.stepData,
});
}
}

View File

@@ -1,63 +0,0 @@
export const COLORS = [
"#377eb8",
"#984ea3",
"#00d2d5",
"#ff7f00",
"#af8d00",
"#7f80cd",
"#b3e900",
"#c42e60",
"#a65628",
"#f781bf",
"#8dd3c7",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#fccde5",
"#bc80bd",
"#ffed6f",
"#c4eaff",
"#cf8c00",
"#1b9e77",
"#d95f02",
"#e7298a",
"#e6ab02",
"#a6761d",
"#0097ff",
"#00d067",
"#f43600",
"#4ba93b",
"#5779bb",
"#927acc",
"#97ee3f",
"#bf3947",
"#9f5b00",
"#f48758",
"#8caed6",
"#f2b94f",
"#eff26e",
"#e43872",
"#d9b100",
"#9d7a00",
"#698cff",
"#d9d9d9",
"#00d27e",
"#d06800",
"#009f82",
"#c49200",
"#cbe8ff",
"#fecddf",
"#c27eb6",
"#8cd2ce",
"#c4b8d9",
"#f883b0",
"#a49100",
"#f48800",
"#27d0df",
"#a04a9b",
];
export function getColorByIndex(index: number) {
return COLORS[index % COLORS.length];
}

View File

@@ -1,4 +1,4 @@
export const luminosity = (rgb: [number, number, number]): number => {
const luminosity = (rgb: [number, number, number]): number => {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
const lum: [number, number, number] = [0, 0, 0];
for (let i = 0; i < rgb.length; i++) {

View File

@@ -42,7 +42,6 @@ export const FIXED_DOMAIN_ICONS = {
remote: "hass:remote",
scene: "hass:palette",
script: "hass:script-text",
select: "hass:format-list-bulleted",
sensor: "hass:eye",
simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny",
@@ -59,11 +58,10 @@ export const FIXED_DEVICE_CLASS_ICONS = {
current: "hass:current-ac",
carbon_dioxide: "mdi:molecule-co2",
carbon_monoxide: "mdi:molecule-co",
energy: "hass:lightning-bolt",
energy: "hass:flash",
humidity: "hass:water-percent",
illuminance: "hass:brightness-5",
temperature: "hass:thermometer",
monetary: "mdi:cash",
pressure: "hass:gauge",
power: "hass:flash",
power_factor: "hass:angle-acute",
@@ -85,7 +83,6 @@ export const DOMAINS_WITH_CARD = [
"number",
"scene",
"script",
"select",
"timer",
"vacuum",
"water_heater",
@@ -124,7 +121,6 @@ export const DOMAINS_HIDE_MORE_INFO = [
"input_text",
"number",
"scene",
"select",
];
/** Domains that should have the history hidden in the more info dialog. */

View File

@@ -26,9 +26,6 @@ function checkToLocaleStringSupportsOptions() {
return false;
}
export const toLocaleDateStringSupportsOptions =
checkToLocaleDateStringSupportsOptions();
export const toLocaleTimeStringSupportsOptions =
checkToLocaleTimeStringSupportsOptions();
export const toLocaleStringSupportsOptions =
checkToLocaleStringSupportsOptions();
export const toLocaleDateStringSupportsOptions = checkToLocaleDateStringSupportsOptions();
export const toLocaleTimeStringSupportsOptions = checkToLocaleTimeStringSupportsOptions();
export const toLocaleStringSupportsOptions = checkToLocaleStringSupportsOptions();

View File

@@ -1,31 +0,0 @@
import { HaDurationData } from "../../components/ha-duration-input";
import { ForDict } from "../../data/automation";
export const createDurationData = (
duration: string | number | ForDict | undefined
): HaDurationData => {
if (duration === undefined) {
return {};
}
if (typeof duration !== "object") {
if (typeof duration === "string" || isNaN(duration)) {
const parts = duration?.toString().split(":") || [];
return {
hours: Number(parts[0]) || 0,
minutes: Number(parts[1]) || 0,
seconds: Number(parts[2]) || 0,
milliseconds: Number(parts[3]) || 0,
};
}
return { seconds: duration };
}
const { days, minutes, seconds, milliseconds } = duration;
let hours = duration.hours || 0;
hours = (hours || 0) + (days || 0) * 24;
return {
hours,
minutes,
seconds,
milliseconds,
};
};

View File

@@ -3,25 +3,6 @@ import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { toLocaleDateStringSupportsOptions } from "./check_options_support";
// Tuesday, August 10
export const formatDateWeekday = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateWeekdayMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "dddd, MMMM D");
const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
month: "long",
day: "numeric",
})
);
// August 10, 2021
export const formatDate = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY");
const formatDateMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
@@ -31,66 +12,21 @@ const formatDateMem = memoizeOne(
})
);
// 10/08/2021
export const formatDateNumeric = toLocaleDateStringSupportsOptions
export const formatDate = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateNumericMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "M/D/YYYY");
const formatDateNumericMem = memoizeOne(
formatDateMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "longDate");
const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "numeric",
weekday: "long",
month: "long",
day: "numeric",
})
);
// Aug 10
export const formatDateShort = toLocaleDateStringSupportsOptions
export const formatDateWeekday = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateShortMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMM D");
const formatDateShortMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
day: "numeric",
month: "short",
})
);
// August 2021
export const formatDateMonthYear = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMonthYearMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMMM YYYY");
const formatDateMonthYearMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
year: "numeric",
})
);
// August
export const formatDateMonth = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMonthMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMMM");
const formatDateMonthMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
})
);
// 2021
export const formatDateYear = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateYearMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "YYYY");
const formatDateYearMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
})
);
formatDateWeekdayMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "dddd, MMM D");

View File

@@ -4,12 +4,6 @@ import { FrontendLocaleData } from "../../data/translation";
import { toLocaleStringSupportsOptions } from "./check_options_support";
import { useAmPm } from "./use_am_pm";
// August 9, 2021, 8:23 AM
export const formatDateTime = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
const formatDateTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
@@ -22,12 +16,12 @@ const formatDateTimeMem = memoizeOne(
})
);
// August 9, 2021, 8:23:15 AM
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
export const formatDateTime = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeWithSecondsMem(locale).format(dateObj)
formatDateTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
const formatDateTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
@@ -41,20 +35,8 @@ const formatDateTimeWithSecondsMem = memoizeOne(
})
);
// 9/8/2021, 8:23 AM
export const formatDateTimeNumeric = toLocaleStringSupportsOptions
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeNumericMem(locale).format(dateObj)
formatDateTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "M/D/YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
const formatDateTimeNumericMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: useAmPm(locale),
})
);
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");

View File

@@ -4,12 +4,6 @@ import { FrontendLocaleData } from "../../data/translation";
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
import { useAmPm } from "./use_am_pm";
// 9:15 PM || 21:15
export const formatTime = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
const formatTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
@@ -19,12 +13,12 @@ const formatTimeMem = memoizeOne(
})
);
// 9:15:24 PM || 21:15:24
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
export const formatTime = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWithSecondsMem(locale).format(dateObj)
formatTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
const formatTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
@@ -35,12 +29,12 @@ const formatTimeWithSecondsMem = memoizeOne(
})
);
// Tuesday 7:00 PM || Tuesday 19:00
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWeekdayMem(locale).format(dateObj)
formatTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
const formatTimeWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
@@ -50,3 +44,9 @@ const formatTimeWeekdayMem = memoizeOne(
hour12: useAmPm(locale),
})
);
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWeekdayMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");

View File

@@ -1,7 +1,6 @@
import memoizeOne from "memoize-one";
import { FrontendLocaleData, TimeFormat } from "../../data/translation";
export const useAmPm = memoizeOne((locale: FrontendLocaleData): boolean => {
export const useAmPm = (locale: FrontendLocaleData): boolean => {
if (
locale.time_format === TimeFormat.language ||
locale.time_format === TimeFormat.system
@@ -13,4 +12,4 @@ export const useAmPm = memoizeOne((locale: FrontendLocaleData): boolean => {
}
return locale.time_format === TimeFormat.am_pm;
});
};

View File

@@ -82,71 +82,67 @@ class Storage {
const storage = new Storage();
export const LocalStorage =
(
storageKey?: string,
property?: boolean,
propertyOptions?: PropertyDeclaration
): any =>
(clsElement: ClassElement) => {
const key = String(clsElement.key);
storageKey = storageKey || String(clsElement.key);
const initVal = clsElement.initializer
? clsElement.initializer()
: undefined;
export const LocalStorage = (
storageKey?: string,
property?: boolean,
propertyOptions?: PropertyDeclaration
): any => (clsElement: ClassElement) => {
const key = String(clsElement.key);
storageKey = storageKey || String(clsElement.key);
const initVal = clsElement.initializer ? clsElement.initializer() : undefined;
storage.addFromStorage(storageKey);
storage.addFromStorage(storageKey);
const subscribe = (el: ReactiveElement): UnsubscribeFunc =>
storage.subscribeChanges(storageKey!, (oldValue) => {
el.requestUpdate(clsElement.key, oldValue);
});
const subscribe = (el: ReactiveElement): UnsubscribeFunc =>
storage.subscribeChanges(storageKey!, (oldValue) => {
el.requestUpdate(clsElement.key, oldValue);
});
const getValue = (): any =>
storage.hasKey(storageKey!) ? storage.getValue(storageKey!) : initVal;
const getValue = (): any =>
storage.hasKey(storageKey!) ? storage.getValue(storageKey!) : initVal;
const setValue = (el: ReactiveElement, value: any) => {
let oldValue: unknown | undefined;
if (property) {
oldValue = getValue();
}
storage.setValue(storageKey!, value);
if (property) {
el.requestUpdate(clsElement.key, oldValue);
}
};
return {
kind: "method",
placement: "prototype",
key: clsElement.key,
descriptor: {
set(this: ReactiveElement, value: unknown) {
setValue(this, value);
},
get() {
return getValue();
},
enumerable: true,
configurable: true,
},
finisher(cls: typeof ReactiveElement) {
if (property) {
const connectedCallback = cls.prototype.connectedCallback;
const disconnectedCallback = cls.prototype.disconnectedCallback;
cls.prototype.connectedCallback = function () {
connectedCallback.call(this);
this[`__unbsubLocalStorage${key}`] = subscribe(this);
};
cls.prototype.disconnectedCallback = function () {
disconnectedCallback.call(this);
this[`__unbsubLocalStorage${key}`]();
};
cls.createProperty(clsElement.key, {
noAccessor: true,
...propertyOptions,
});
}
},
};
const setValue = (el: ReactiveElement, value: any) => {
let oldValue: unknown | undefined;
if (property) {
oldValue = getValue();
}
storage.setValue(storageKey!, value);
if (property) {
el.requestUpdate(clsElement.key, oldValue);
}
};
return {
kind: "method",
placement: "prototype",
key: clsElement.key,
descriptor: {
set(this: ReactiveElement, value: unknown) {
setValue(this, value);
},
get() {
return getValue();
},
enumerable: true,
configurable: true,
},
finisher(cls: typeof ReactiveElement) {
if (property) {
const connectedCallback = cls.prototype.connectedCallback;
const disconnectedCallback = cls.prototype.disconnectedCallback;
cls.prototype.connectedCallback = function () {
connectedCallback.call(this);
this[`__unbsubLocalStorage${key}`] = subscribe(this);
};
cls.prototype.disconnectedCallback = function () {
disconnectedCallback.call(this);
this[`__unbsubLocalStorage${key}`]();
};
cls.createProperty(clsElement.key, {
noAccessor: true,
...propertyOptions,
});
}
},
};
};

View File

@@ -1,33 +1,33 @@
import type { LitElement } from "lit";
import type { ClassElement } from "../../types";
export const restoreScroll =
(selector: string): any =>
(element: ClassElement) => ({
kind: "method",
placement: "prototype",
key: element.key,
descriptor: {
set(this: LitElement, value: number) {
this[`__${String(element.key)}`] = value;
},
get(this: LitElement) {
return this[`__${String(element.key)}`];
},
enumerable: true,
configurable: true,
export const restoreScroll = (selector: string): any => (
element: ClassElement
) => ({
kind: "method",
placement: "prototype",
key: element.key,
descriptor: {
set(this: LitElement, value: number) {
this[`__${String(element.key)}`] = value;
},
finisher(cls: typeof LitElement) {
const connectedCallback = cls.prototype.connectedCallback;
cls.prototype.connectedCallback = function () {
connectedCallback.call(this);
if (this[element.key]) {
const target = this.renderRoot.querySelector(selector);
if (!target) {
return;
}
target.scrollTop = this[element.key];
get(this: LitElement) {
return this[`__${String(element.key)}`];
},
enumerable: true,
configurable: true,
},
finisher(cls: typeof LitElement) {
const connectedCallback = cls.prototype.connectedCallback;
cls.prototype.connectedCallback = function () {
connectedCallback.call(this);
if (this[element.key]) {
const target = this.renderRoot.querySelector(selector);
if (!target) {
return;
}
};
},
});
target.scrollTop = this[element.key];
}
};
},
});

View File

@@ -21,16 +21,6 @@ export const computeStateDisplay = (
}
if (stateObj.attributes.unit_of_measurement) {
if (stateObj.attributes.device_class === "monetary") {
try {
return formatNumber(compareState, locale, {
style: "currency",
currency: stateObj.attributes.unit_of_measurement,
});
} catch (_err) {
// fallback to default
}
}
return `${formatNumber(compareState, locale)} ${
stateObj.attributes.unit_of_measurement
}`;
@@ -39,61 +29,37 @@ export const computeStateDisplay = (
const domain = computeStateDomain(stateObj);
if (domain === "input_datetime") {
if (state) {
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
// Attributes aren't available, we have to use `state`.
try {
const components = state.split(" ");
if (components.length === 2) {
// Date and time.
return formatDateTime(new Date(components.join("T")), locale);
}
if (components.length === 1) {
if (state.includes("-")) {
// Date only.
return formatDate(new Date(`${state}T00:00`), locale);
}
if (state.includes(":")) {
// Time only.
const now = new Date();
return formatTime(
new Date(`${now.toISOString().split("T")[0]}T${state}`),
locale
);
}
}
return state;
} catch {
// Formatting methods may throw error if date parsing doesn't go well,
// just return the state string in that case.
return state;
}
} else {
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
let date: Date;
if (!stateObj.attributes.has_time) {
date = new Date(
stateObj.attributes.year,
stateObj.attributes.month - 1,
stateObj.attributes.day
);
return formatDate(date, locale);
}
if (!stateObj.attributes.has_date) {
date = new Date();
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
return formatTime(date, locale);
}
let date: Date;
if (!stateObj.attributes.has_time) {
date = new Date(
stateObj.attributes.year,
stateObj.attributes.month - 1,
stateObj.attributes.day,
stateObj.attributes.day
);
return formatDate(date, locale);
}
if (!stateObj.attributes.has_date) {
const now = new Date();
date = new Date(
// Due to bugs.chromium.org/p/chromium/issues/detail?id=797548
// don't use artificial 1970 year.
now.getFullYear(),
now.getMonth(),
now.getDay(),
stateObj.attributes.hour,
stateObj.attributes.minute
);
return formatDateTime(date, locale);
return formatTime(date, locale);
}
date = new Date(
stateObj.attributes.year,
stateObj.attributes.month - 1,
stateObj.attributes.day,
stateObj.attributes.hour,
stateObj.attributes.minute
);
return formatDateTime(date, locale);
}
if (domain === "humidifier") {

View File

@@ -43,17 +43,7 @@ export const domainIcon = (
: "hass:air-humidifier";
case "lock":
switch (compareState) {
case "unlocked":
return "hass:lock-open";
case "jammed":
return "hass:lock-alert";
case "locking":
case "unlocking":
return "hass:lock-clock";
default:
return "hass:lock";
}
return compareState === "unlocked" ? "hass:lock-open" : "hass:lock";
case "media_player":
return compareState === "playing" ? "hass:cast-connected" : "hass:cast";

View File

@@ -4,7 +4,7 @@ import { DEFAULT_DOMAIN_ICON } from "../const";
import { computeDomain } from "./compute_domain";
import { domainIcon } from "./domain_icon";
export const stateIcon = (state?: HassEntity) => {
export const stateIcon = (state: HassEntity) => {
if (!state) {
return DEFAULT_DOMAIN_ICON;
}

View File

@@ -1,2 +0,0 @@
export const clamp = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);

View File

@@ -1,2 +0,0 @@
export const round = (value: number, precision = 2): number =>
Math.round(value * 10 ** precision) / 10 ** precision;

View File

@@ -67,11 +67,9 @@ class SearchInput extends LitElement {
changedProps.has("noUnderline") &&
(this.noUnderline || changedProps.get("noUnderline") !== undefined)
) {
(
this._input.inputElement!.parentElement!.shadowRoot!.querySelector(
"div.unfocused-line"
) as HTMLElement
).style.display = this.noUnderline ? "none" : "block";
(this._input.inputElement!.parentElement!.shadowRoot!.querySelector(
"div.unfocused-line"
) as HTMLElement).style.display = this.noUnderline ? "none" : "block";
}
}

View File

@@ -1,22 +1,4 @@
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
import { round } from "../number/round";
export const numberFormatToLocale = (
localeOptions: FrontendLocaleData
): string | string[] | undefined => {
switch (localeOptions.number_format) {
case NumberFormat.comma_decimal:
return ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
case NumberFormat.decimal_comma:
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
case NumberFormat.space_comma:
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
case NumberFormat.system:
return undefined;
default:
return localeOptions.language;
}
};
/**
* Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.
@@ -27,12 +9,27 @@ export const numberFormatToLocale = (
*/
export const formatNumber = (
num: string | number,
localeOptions?: FrontendLocaleData,
locale?: FrontendLocaleData,
options?: Intl.NumberFormatOptions
): string => {
const locale = localeOptions
? numberFormatToLocale(localeOptions)
: undefined;
let format: string | string[] | undefined;
switch (locale?.number_format) {
case NumberFormat.comma_decimal:
format = ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
break;
case NumberFormat.decimal_comma:
format = ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
break;
case NumberFormat.space_comma:
format = ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
break;
case NumberFormat.system:
format = undefined;
break;
default:
format = locale?.language;
}
// Polyfill for Number.isNaN, which is more reliable than the global isNaN()
Number.isNaN =
@@ -42,13 +39,13 @@ export const formatNumber = (
};
if (
localeOptions?.number_format !== NumberFormat.none &&
!Number.isNaN(Number(num)) &&
Intl
Intl &&
locale?.number_format !== NumberFormat.none
) {
try {
return new Intl.NumberFormat(
locale,
format,
getDefaultFormatOptions(num, options)
).format(Number(num));
} catch (error) {
@@ -61,12 +58,7 @@ export const formatNumber = (
).format(Number(num));
}
}
if (typeof num === "string") {
return num;
}
return `${round(num, options?.maximumFractionDigits).toString()}${
options?.style === "currency" ? ` ${options.currency}` : ""
}`;
return num.toString();
};
/**
@@ -78,10 +70,7 @@ const getDefaultFormatOptions = (
num: string | number,
options?: Intl.NumberFormatOptions
): Intl.NumberFormatOptions => {
const defaultOptions: Intl.NumberFormatOptions = {
maximumFractionDigits: 2,
...options,
};
const defaultOptions: Intl.NumberFormatOptions = options || {};
if (typeof num !== "string") {
return defaultOptions;

View File

@@ -6,7 +6,6 @@
// 3. Disallow dates based on week number.
// 4. Disallow dates only consisting of a year.
// https://regex101.com/r/kc5C14/3
const regexp =
/^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])[T| ](((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)(\8[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)$/;
const regexp = /^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])[T| ](((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)(\8[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)$/;
export const isTimestamp = (input: string): boolean => regexp.test(input);

View File

@@ -29,28 +29,31 @@ export const iconColorCSS = css`
}
ha-icon[data-domain="climate"][data-state="cooling"] {
color: var(--cool-color, var(--state-climate-cool-color));
color: var(--cool-color, #2b9af9);
}
ha-icon[data-domain="climate"][data-state="heating"] {
color: var(--heat-color, var(--state-climate-heat-color));
color: var(--heat-color, #ff8100);
}
ha-icon[data-domain="climate"][data-state="drying"] {
color: var(--dry-color, var(--state-climate-dry-color));
color: var(--dry-color, #efbd07);
}
ha-icon[data-domain="alarm_control_panel"] {
color: var(--alarm-color-armed, var(--label-badge-red));
}
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
color: var(--alarm-color-disarmed, var(--label-badge-green));
}
ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
color: var(--alarm-color-pending, var(--label-badge-yellow));
animation: pulse 1s infinite;
}
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
color: var(--alarm-color-triggered, var(--label-badge-red));
animation: pulse 1s infinite;
@@ -70,11 +73,11 @@ export const iconColorCSS = css`
ha-icon[data-domain="plant"][data-state="problem"],
ha-icon[data-domain="zwave"][data-state="dead"] {
color: var(--state-icon-error-color);
color: var(--error-state-color, #db4437);
}
/* Color the icon if unavailable */
ha-icon[data-state="unavailable"] {
color: var(--state-unavailable-color);
color: var(--state-icon-unavailable-color);
}
`;

View File

@@ -5,20 +5,32 @@
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `false for leading`. To disable execution on the trailing edge, ditto.
export const throttle = <T extends any[]>(
func: (...args: T) => void,
export const throttle = <T extends (...args) => unknown>(
func: T,
wait: number,
leading = true,
trailing = true
) => {
): T => {
let timeout: number | undefined;
let previous = 0;
return (...args: T): void => {
const later = () => {
previous = leading === false ? 0 : Date.now();
timeout = undefined;
func(...args);
};
let context: any;
let args: any;
const later = () => {
previous = leading === false ? 0 : Date.now();
timeout = undefined;
func.apply(context, args);
if (!timeout) {
context = null;
args = null;
}
};
// @ts-ignore
return function (...argmnts) {
// @ts-ignore
// @typescript-eslint/no-this-alias
context = this;
args = argmnts;
const now = Date.now();
if (!previous && leading === false) {
previous = now;
@@ -30,7 +42,7 @@ export const throttle = <T extends any[]>(
timeout = undefined;
}
previous = now;
func(...args);
func.apply(context, args);
} else if (!timeout && trailing !== false) {
timeout = window.setTimeout(later, remaining);
}

View File

@@ -7,10 +7,14 @@ interface ResultCache<T> {
export const timeCachePromiseFunc = async <T>(
cacheKey: string,
cacheTime: number,
func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
func: (
hass: HomeAssistant,
entityId: string,
...args: unknown[]
) => Promise<T>,
hass: HomeAssistant,
entityId: string,
...args: any[]
...args: unknown[]
): Promise<T> => {
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];

View File

@@ -64,18 +64,18 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
this.hass
.callService(this.domain, this.service, this.serviceData)
.then(
() => {
function () {
el.progress = false;
el.$.progress.actionSuccess();
eventData.success = true;
},
() => {
function () {
el.progress = false;
el.$.progress.actionError();
eventData.success = false;
}
)
.then(() => {
.then(function () {
el.fire("hass-service-called", eventData);
});
}

View File

@@ -5,7 +5,7 @@ import { customElement, property, query } from "lit/decorators";
import "../ha-circular-progress";
@customElement("ha-progress-button")
export class HaProgressButton extends LitElement {
class HaProgressButton extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public progress = false;

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