Compare commits

..

2 Commits

Author SHA1 Message Date
Bram Kragten
9a6e48a82b Remove unneeded close dialog calls 2024-12-12 09:46:12 +01:00
Bram Kragten
a8b8fb4c30 Remove unneeded close dialog calls 2024-12-12 09:31:08 +01:00
888 changed files with 21260 additions and 29249 deletions

View File

@@ -1,4 +1,5 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.13 # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
FROM mcr.microsoft.com/devcontainers/python:3.12
ENV \ ENV \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \

View File

@@ -7,7 +7,7 @@ body:
value: | value: |
Make sure you are running the [latest version of Home Assistant][releases] before reporting an issue. Make sure you are running the [latest version of Home Assistant][releases] before reporting an issue.
If you have a feature or enhancement request for the frontend, please [start a discussion][fr] instead of creating an issue. If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue.
**Please do not report issues for custom cards.** **Please do not report issues for custom cards.**
@@ -74,7 +74,7 @@ body:
If known, otherwise leave blank. If known, otherwise leave blank.
- type: input - type: input
attributes: attributes:
label: In which browser are you experiencing the issue? label: In which browser are you experiencing the issue with?
placeholder: Google Chrome 88.0.4324.150 placeholder: Google Chrome 88.0.4324.150
description: > description: >
Provide the full name and don't forget to add the version! Provide the full name and don't forget to add the version!

View File

@@ -2,7 +2,7 @@ blank_issues_enabled: false
contact_links: contact_links:
- name: Request a feature for the UI / Dashboards - name: Request a feature for the UI / Dashboards
url: https://github.com/home-assistant/frontend/discussions/category_choices url: https://github.com/home-assistant/frontend/discussions/category_choices
about: Request a new feature for the Home Assistant frontend. about: Request an new feature for the Home Assistant frontend.
- name: Report a bug that is NOT related to the UI / Dashboards - name: Report a bug that is NOT related to the UI / Dashboards
url: https://github.com/home-assistant/core/issues url: https://github.com/home-assistant/core/issues
about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository. about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository.

View File

@@ -26,14 +26,20 @@ jobs:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.2
- name: Setup Node - name: Setup Node
id: setup-node
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.1.0
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
- uses: actions/cache@v4.2.0
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: "node_modules"
key: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}
- name: Install dependencies - name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install --immutable run: yarn install --immutable
- name: Check for duplicate dependencies
run: yarn dedupe --check
- name: Build resources - name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache - name: Setup lint cache
@@ -60,11 +66,19 @@ jobs:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.2
- name: Setup Node - name: Setup Node
id: setup-node
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.1.0
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
- uses: actions/cache@v4.2.0
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: "node_modules"
key: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}
- name: Install dependencies - name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install --immutable run: yarn install --immutable
- name: Build resources - name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data
@@ -78,18 +92,26 @@ jobs:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.2
- name: Setup Node - name: Setup Node
id: setup-node
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.1.0
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
- uses: actions/cache@v4.2.0
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: "node_modules"
key: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}
- name: Install dependencies - name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install --immutable run: yarn install --immutable
- name: Build Application - name: Build Application
run: ./node_modules/.bin/gulp build-app run: ./node_modules/.bin/gulp build-app
env: env:
IS_TEST: "true" IS_TEST: "true"
- name: Upload bundle stats - name: Upload bundle stats
uses: actions/upload-artifact@v4.6.0 uses: actions/upload-artifact@v4.4.3
with: with:
name: frontend-bundle-stats name: frontend-bundle-stats
path: build/stats/*.json path: build/stats/*.json
@@ -102,18 +124,26 @@ jobs:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.2
- name: Setup Node - name: Setup Node
id: setup-node
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.1.0
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
- uses: actions/cache@v4.2.0
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: "node_modules"
key: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}
- name: Install dependencies - name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install --immutable run: yarn install --immutable
- name: Build Application - name: Build Application
run: ./node_modules/.bin/gulp build-hassio run: ./node_modules/.bin/gulp build-hassio
env: env:
IS_TEST: "true" IS_TEST: "true"
- name: Upload bundle stats - name: Upload bundle stats
uses: actions/upload-artifact@v4.6.0 uses: actions/upload-artifact@v4.4.3
with: with:
name: supervisor-bundle-stats name: supervisor-bundle-stats
path: build/stats/*.json path: build/stats/*.json

View File

@@ -6,7 +6,7 @@ on:
- cron: "0 1 * * *" - cron: "0 1 * * *"
env: env:
PYTHON_VERSION: "3.13" PYTHON_VERSION: "3.12"
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
permissions: permissions:
@@ -57,14 +57,14 @@ jobs:
run: tar -czvf translations.tar.gz translations run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v4.6.0 uses: actions/upload-artifact@v4.4.3
with: with:
name: wheels name: wheels
path: dist/home_assistant_frontend*.whl path: dist/home_assistant_frontend*.whl
if-no-files-found: error if-no-files-found: error
- name: Upload translations - name: Upload translations
uses: actions/upload-artifact@v4.6.0 uses: actions/upload-artifact@v4.4.3
with: with:
name: translations name: translations
path: translations.tar.gz path: translations.tar.gz

View File

@@ -6,7 +6,7 @@ on:
- published - published
env: env:
PYTHON_VERSION: "3.13" PYTHON_VERSION: "3.12"
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions # Set default workflow permissions
@@ -25,14 +25,14 @@ jobs:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.2
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.1.0
with: with:
@@ -55,7 +55,7 @@ jobs:
script/release script/release
- name: Upload release assets - name: Upload release assets
uses: softprops/action-gh-release@v2.2.1 uses: softprops/action-gh-release@v2.1.0
with: with:
files: | files: |
dist/*.whl dist/*.whl
@@ -76,7 +76,7 @@ jobs:
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2024.11.0 uses: home-assistant/wheels@2024.11.0
with: with:
abi: cp313 abi: cp312
tag: musllinux_1_2 tag: musllinux_1_2
arch: amd64 arch: amd64
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}
@@ -107,7 +107,7 @@ jobs:
- name: Tar folder - name: Tar folder
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist . run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
- name: Upload release asset - name: Upload release asset
uses: softprops/action-gh-release@v2.2.1 uses: softprops/action-gh-release@v2.1.0
with: with:
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
@@ -136,6 +136,6 @@ jobs:
- name: Tar folder - name: Tar folder
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build . run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
- name: Upload release asset - name: Upload release asset
uses: softprops/action-gh-release@v2.2.1 uses: softprops/action-gh-release@v2.1.0
with: with:
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz

File diff suppressed because one or more lines are too long

View File

@@ -6,4 +6,4 @@ enableGlobalCache: false
nodeLinker: node-modules nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.6.0.cjs yarnPath: .yarn/releases/yarn-4.5.3.cjs

View File

@@ -5,7 +5,7 @@ import paths from "../paths.cjs";
const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills"); const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills");
// List of polyfill keys with supported browser targets for the functionality // List of polyfill keys with supported browser targets for the functionality
const polyfillSupport = { const PolyfillSupport = {
// Note states and shadowRoot properties should be supported. // Note states and shadowRoot properties should be supported.
"element-internals": { "element-internals": {
android: 90, android: 90,
@@ -18,6 +18,17 @@ const polyfillSupport = {
safari: 17.4, safari: 17.4,
samsung: 15.0, samsung: 15.0,
}, },
"element-append": {
android: 54,
chrome: 54,
edge: 17,
firefox: 49,
ios: 10.0,
opera: 41,
opera_mobile: 41,
safari: 10.0,
samsung: 6.0,
},
"element-getattributenames": { "element-getattributenames": {
android: 61, android: 61,
chrome: 61, chrome: 61,
@@ -40,18 +51,27 @@ const polyfillSupport = {
safari: 12.0, safari: 12.0,
samsung: 10.0, samsung: 10.0,
}, },
// FormatJS polyfill detects fix for https://bugs.chromium.org/p/v8/issues/detail?id=10682, fetch: {
// so adjusted to several months after that was marked fixed android: 42,
chrome: 42,
edge: 14,
firefox: 39,
ios: 10.3,
opera: 29,
opera_mobile: 29,
safari: 10.1,
samsung: 4.0,
},
"intl-getcanonicallocales": { "intl-getcanonicallocales": {
android: 90, android: 54,
chrome: 90, chrome: 54,
edge: 90, edge: 16,
firefox: 48, firefox: 48,
ios: 10.3, ios: 10.3,
opera: 76, opera: 41,
opera_mobile: 64, opera_mobile: 41,
safari: 10.1, safari: 10.1,
samsung: 15.0, samsung: 6.0,
}, },
"intl-locale": { "intl-locale": {
android: 74, android: 74,
@@ -67,6 +87,17 @@ const polyfillSupport = {
"intl-other": { "intl-other": {
// Not specified (i.e. always try polyfill) since compatibility depends on supported locales // Not specified (i.e. always try polyfill) since compatibility depends on supported locales
}, },
proxy: {
android: 49,
chrome: 49,
edge: 12,
firefox: 18,
ios: 10.0,
opera: 36,
opera_mobile: 36,
safari: 10.0,
samsung: 5.0,
},
"resize-observer": { "resize-observer": {
android: 64, android: 64,
chrome: 64, chrome: 64,
@@ -84,6 +115,8 @@ const polyfillSupport = {
// corresponding polyfill key and actual module to import // corresponding polyfill key and actual module to import
const polyfillMap = { const polyfillMap = {
global: { global: {
fetch: { key: "fetch", module: "unfetch/polyfill" },
Proxy: { key: "proxy", module: "proxy-polyfill" },
ResizeObserver: { ResizeObserver: {
key: "resize-observer", key: "resize-observer",
module: join(POLYFILL_DIR, "resize-observer.ts"), module: join(POLYFILL_DIR, "resize-observer.ts"),
@@ -95,7 +128,7 @@ const polyfillMap = {
module: "element-internals-polyfill", module: "element-internals-polyfill",
}, },
...Object.fromEntries( ...Object.fromEntries(
["getAttributeNames", "toggleAttribute"].map((prop) => { ["append", "getAttributeNames", "toggleAttribute"].map((prop) => {
const key = `element-${prop.toLowerCase()}`; const key = `element-${prop.toLowerCase()}`;
return [prop, { key, module: join(POLYFILL_DIR, `${key}.ts`) }]; return [prop, { key, module: join(POLYFILL_DIR, `${key}.ts`) }];
}) })
@@ -135,7 +168,7 @@ export default defineProvider(
const resolvePolyfill = createMetaResolver(polyfillMap); const resolvePolyfill = createMetaResolver(polyfillMap);
return { return {
name: "custom-polyfill", name: "custom-polyfill",
polyfills: polyfillSupport, polyfills: PolyfillSupport,
usageGlobal(meta, utils) { usageGlobal(meta, utils) {
const polyfill = resolvePolyfill(meta); const polyfill = resolvePolyfill(meta);
if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) { if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {

View File

@@ -9,7 +9,7 @@ export default [
"import/extensions": "off", "import/extensions": "off",
"import/no-dynamic-require": "off", "import/no-dynamic-require": "off",
"global-require": "off", "global-require": "off",
"@typescript-eslint/no-require-imports": "off", "@typescript-eslint/no-var-requires": "off",
"prefer-arrow-callback": "off", "prefer-arrow-callback": "off",
}, },
}, },

View File

@@ -3,7 +3,6 @@
import { constants } from "node:zlib"; import { constants } from "node:zlib";
import gulp from "gulp"; import gulp from "gulp";
import brotli from "gulp-brotli"; import brotli from "gulp-brotli";
import zopfli from "gulp-zopfli-green";
import paths from "../paths.cjs"; import paths from "../paths.cjs";
const filesGlob = "*.{js,json,css,svg,xml}"; const filesGlob = "*.{js,json,css,svg,xml}";
@@ -13,18 +12,17 @@ const brotliOptions = {
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY, [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
}, },
}; };
const zopfliOptions = { threshold: 150 };
const compressModern = (rootDir, modernDir, compress) => const compressModern = (rootDir, modernDir) =>
gulp gulp
.src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], { .src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
base: rootDir, base: rootDir,
allowEmpty: true, allowEmpty: true,
}) })
.pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions)) .pipe(brotli(brotliOptions))
.pipe(gulp.dest(rootDir)); .pipe(gulp.dest(rootDir));
const compressOther = (rootDir, modernDir, compress) => const compressOther = (rootDir, modernDir) =>
gulp gulp
.src( .src(
[ [
@@ -35,52 +33,21 @@ const compressOther = (rootDir, modernDir, compress) =>
], ],
{ base: rootDir, allowEmpty: true } { base: rootDir, allowEmpty: true }
) )
.pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions)) .pipe(brotli(brotliOptions))
.pipe(gulp.dest(rootDir)); .pipe(gulp.dest(rootDir));
const compressAppModernBrotli = () => const compressAppModern = () =>
compressModern(paths.app_output_root, paths.app_output_latest, "brotli"); compressModern(paths.app_output_root, paths.app_output_latest);
const compressAppModernZopfli = () => const compressHassioModern = () =>
compressModern(paths.app_output_root, paths.app_output_latest, "zopfli"); compressModern(paths.hassio_output_root, paths.hassio_output_latest);
const compressHassioModernBrotli = () => const compressAppOther = () =>
compressModern( compressOther(paths.app_output_root, paths.app_output_latest);
paths.hassio_output_root, const compressHassioOther = () =>
paths.hassio_output_latest, compressOther(paths.hassio_output_root, paths.hassio_output_latest);
"brotli"
);
const compressHassioModernZopfli = () =>
compressModern(
paths.hassio_output_root,
paths.hassio_output_latest,
"zopfli"
);
const compressAppOtherBrotli = () => gulp.task("compress-app", gulp.parallel(compressAppModern, compressAppOther));
compressOther(paths.app_output_root, paths.app_output_latest, "brotli");
const compressAppOtherZopfli = () =>
compressOther(paths.app_output_root, paths.app_output_latest, "zopfli");
const compressHassioOtherBrotli = () =>
compressOther(paths.hassio_output_root, paths.hassio_output_latest, "brotli");
const compressHassioOtherZopfli = () =>
compressOther(paths.hassio_output_root, paths.hassio_output_latest, "zopfli");
gulp.task(
"compress-app",
gulp.parallel(
compressAppModernBrotli,
compressAppOtherBrotli,
compressAppModernZopfli,
compressAppOtherZopfli
)
);
gulp.task( gulp.task(
"compress-hassio", "compress-hassio",
gulp.parallel( gulp.parallel(compressHassioModern, compressHassioOther)
compressHassioModernBrotli,
compressHassioOtherBrotli,
compressHassioModernZopfli,
compressHassioOtherZopfli
)
); );

View File

@@ -67,6 +67,12 @@ function copyPolyfills(staticDir) {
); );
} }
function copyLoaderJS(staticDir) {
const staticPath = genStaticPath(staticDir);
copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js"));
copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js"));
}
function copyFonts(staticDir) { function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
// Local fonts // Local fonts
@@ -134,6 +140,8 @@ gulp.task("copy-static-app", async () => {
const staticDir = paths.app_output_static; const staticDir = paths.app_output_static;
// Basic static files // Basic static files
fs.copySync(polyPath("public"), paths.app_output_root); fs.copySync(polyPath("public"), paths.app_output_root);
copyLoaderJS(staticDir);
copyPolyfills(staticDir); copyPolyfills(staticDir);
copyFonts(staticDir); copyFonts(staticDir);
copyTranslations(staticDir); copyTranslations(staticDir);
@@ -156,6 +164,8 @@ gulp.task("copy-static-demo", async () => {
); );
// Copy demo static files // Copy demo static files
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_output_root); fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_output_root);
copyLoaderJS(paths.demo_output_static);
copyPolyfills(paths.demo_output_static); copyPolyfills(paths.demo_output_static);
copyMapPanel(paths.demo_output_static); copyMapPanel(paths.demo_output_static);
copyFonts(paths.demo_output_static); copyFonts(paths.demo_output_static);
@@ -169,6 +179,8 @@ gulp.task("copy-static-cast", async () => {
fs.copySync(polyPath("public/static"), paths.cast_output_static); fs.copySync(polyPath("public/static"), paths.cast_output_static);
// Copy cast static files // Copy cast static files
fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_output_root); fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_output_root);
copyLoaderJS(paths.cast_output_static);
copyPolyfills(paths.cast_output_static); copyPolyfills(paths.cast_output_static);
copyMapPanel(paths.cast_output_static); copyMapPanel(paths.cast_output_static);
copyFonts(paths.cast_output_static); copyFonts(paths.cast_output_static);

View File

@@ -1,17 +0,0 @@
import "./app.js";
import "./cast.js";
import "./clean.js";
import "./compress.js";
import "./demo.js";
import "./download-translations.js";
import "./entry-html.js";
import "./fetch-nightly-translations.js";
import "./gallery.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./hassio.js";
import "./landing-page.js";
import "./locale-data.js";
import "./rspack.js";
import "./service-worker.js";
import "./translations.js";

View File

@@ -25,7 +25,7 @@ Home Assistant Cast is made up of two separate applications:
### Setting dev variables ### Setting dev variables
Open `src/cast/dev_const.ts` and change `CAST_DEV_APP_ID` to the ID of the app you just created. And set the `CAST_DEV_HASS_URL` to the url of your development machine. Open `src/cast/dev_const.ts` and change `CAST_DEV_APP_ID` to the ID of the app you just created. And set the `CAST_DEV_HASS_URL` to the url of you development machine.
### Changing configuration ### Changing configuration

View File

@@ -7,6 +7,7 @@
<%= renderTemplate("../../../src/html/_style_base.html.template") %> <%= renderTemplate("../../../src/html/_style_base.html.template") %>
<style> <style>
body { body {
background-color: white;
font-size: initial; font-size: initial;
} }
</style> </style>

View File

@@ -3,7 +3,7 @@ import "@material/mwc-list/mwc-list";
import type { ActionDetail } from "@material/mwc-list/mwc-list"; import type { ActionDetail } from "@material/mwc-list/mwc-list";
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js"; import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
import type { Auth, Connection } from "home-assistant-js-websocket"; import type { Auth, Connection } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { LitElement, css, html } from "lit"; import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import type { CastManager } from "../../../../src/cast/cast_manager"; import type { CastManager } from "../../../../src/cast/cast_manager";
@@ -203,12 +203,13 @@ class HcCast extends LitElement {
} }
this.connection.close(); this.connection.close();
location.reload(); location.reload();
} catch (_err: any) { } catch (err: any) {
alert("Unable to log out!"); alert("Unable to log out!");
} }
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
.center-item { .center-item {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
@@ -270,6 +271,7 @@ class HcCast extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -13,7 +13,7 @@ import {
ERR_INVALID_HTTPS_TO_HTTP, ERR_INVALID_HTTPS_TO_HTTP,
getAuth, getAuth,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
import type { CastManager } from "../../../../src/cast/cast_manager"; import type { CastManager } from "../../../../src/cast/cast_manager";
@@ -215,7 +215,7 @@ export class HcConnect extends LitElement {
let url: URL; let url: URL;
try { try {
url = new URL(value); url = new URL(value);
} catch (_err: any) { } catch (err: any) {
this.error = "Invalid URL"; this.error = "Invalid URL";
return; return;
} }
@@ -252,7 +252,7 @@ export class HcConnect extends LitElement {
this.loading = false; this.loading = false;
return; return;
} finally { } finally {
// Clear url if we have an auth callback in url. // Clear url if we have a auth callback in url.
if (location.search.includes("auth_callback=1")) { if (location.search.includes("auth_callback=1")) {
history.replaceState(null, "", location.pathname); history.replaceState(null, "", location.pathname);
} }
@@ -288,12 +288,13 @@ export class HcConnect extends LitElement {
try { try {
saveTokens(null); saveTokens(null);
location.reload(); location.reload();
} catch (_err: any) { } catch (err: any) {
alert("Unable to log out!"); alert("Unable to log out!");
} }
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
.card-content a { .card-content a {
color: var(--primary-color); color: var(--primary-color);
} }
@@ -322,6 +323,7 @@ export class HcConnect extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -1,6 +1,6 @@
import type { Auth, Connection, HassUser } from "home-assistant-js-websocket"; import type { Auth, Connection, HassUser } from "home-assistant-js-websocket";
import { getUser } from "home-assistant-js-websocket"; import { getUser } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@@ -63,7 +63,8 @@ class HcLayout extends LitElement {
} }
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
:host { :host {
display: flex; display: flex;
min-height: 100%; min-height: 100%;
@@ -152,6 +153,7 @@ class HcLayout extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -1,4 +1,4 @@
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
@@ -24,7 +24,8 @@ class HcLaunchScreen extends LitElement {
`; `;
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
:host { :host {
display: block; display: block;
height: 100vh; height: 100vh;
@@ -48,6 +49,7 @@ class HcLaunchScreen extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -1,4 +1,10 @@
import { css, html, LitElement, type TemplateResult } from "lit"; import {
css,
type CSSResultGroup,
html,
LitElement,
type TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import type { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; import type { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
@@ -8,7 +14,6 @@ import "../../../../src/panels/lovelace/views/hui-view";
import "../../../../src/panels/lovelace/views/hui-view-container"; import "../../../../src/panels/lovelace/views/hui-view-container";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import "./hc-launch-screen"; import "./hc-launch-screen";
import "../../../../src/panels/lovelace/views/hui-view-background";
(window as any).loadCardHelpers = () => (window as any).loadCardHelpers = () =>
import("../../../../src/panels/lovelace/custom-card-helpers"); import("../../../../src/panels/lovelace/custom-card-helpers");
@@ -52,9 +57,11 @@ class HcLovelace extends LitElement {
const background = viewConfig.background || this.lovelaceConfig.background; const background = viewConfig.background || this.lovelaceConfig.background;
return html` return html`
<hui-view-container .hass=${this.hass} .theme=${viewConfig.theme}> <hui-view-container
<hui-view-background .hass=${this.hass} .background=${background}> .hass=${this.hass}
</hui-view-background> .background=${background}
.theme=${viewConfig.theme}
>
<hui-view <hui-view
.hass=${this.hass} .hass=${this.hass}
.lovelace=${lovelace} .lovelace=${lovelace}
@@ -111,19 +118,21 @@ class HcLovelace extends LitElement {
return undefined; return undefined;
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
hui-view-container { hui-view-container {
display: flex; display: flex;
position: relative; position: relative;
min-height: 100vh; min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
} }
hui-view-container > * { hui-view {
flex: 1 1 100%; flex: 1 1 100%;
max-width: 100%; max-width: 100%;
} }
`; `;
} }
}
export interface CastViewChanged { export interface CastViewChanged {
title: string | undefined; title: string | undefined;

View File

@@ -3,7 +3,7 @@ import type { Lovelace } from "../../../src/panels/lovelace/types";
import { energyEntities } from "../stubs/entities"; import { energyEntities } from "../stubs/entities";
import type { DemoConfig } from "./types"; import type { DemoConfig } from "./types";
export const demoConfigs: (() => Promise<DemoConfig>)[] = [ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
() => import("./sections").then((mod) => mod.demoSections), () => import("./sections").then((mod) => mod.demoSections),
() => import("./arsaboo").then((mod) => mod.demoArsaboo), () => import("./arsaboo").then((mod) => mod.demoArsaboo),
() => import("./teachingbirds").then((mod) => mod.demoTeachingbirds), () => import("./teachingbirds").then((mod) => mod.demoTeachingbirds),

View File

@@ -1,4 +1,5 @@
import { mdiTelevision } from "@mdi/js"; import { mdiTelevision } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
import type { CastManager } from "../../../src/cast/cast_manager"; import type { CastManager } from "../../../src/cast/cast_manager";
@@ -66,7 +67,8 @@ class CastDemoRow extends LitElement implements LovelaceRow {
this.style.display = this._castManager ? "" : "none"; this.style.display = this._castManager ? "" : "none";
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
:host { :host {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -96,6 +98,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -32,7 +32,6 @@ export class HADemoCard extends LitElement implements LovelaceCard {
return this._hidden ? 0 : 2; return this._hidden ? 0 : 2;
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function
public setConfig(_config: LovelaceCardConfig) {} public setConfig(_config: LovelaceCardConfig) {}
protected render() { protected render() {

View File

@@ -131,7 +131,6 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
}); });
}, 1); }, 1);
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => {}; return () => {};
} }
); );

View File

@@ -43,7 +43,7 @@ customElements.whenDefined("hui-root").then(() => {
const index = (ev as CustomEvent).detail.index; const index = (ev as CustomEvent).detail.index;
try { try {
await setDemoConfig(this.hass, this.lovelace!, index); await setDemoConfig(this.hass, this.lovelace!, index);
} catch (_err: any) { } catch (err: any) {
setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex); setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
alert("Failed to switch config :-("); alert("Failed to switch config :-(");
} }

View File

@@ -15,7 +15,6 @@ export const mockPersistentNotification = (hass: MockHomeAssistant) => {
}, },
}, },
} as PersistentNotificationMessage); } as PersistentNotificationMessage);
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => {}; return () => {};
}); });
}; };

View File

@@ -15,7 +15,7 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
const generateMeanStatistics = ( const generateMeanStatistics = (
start: Date, start: Date,
end: Date, end: Date,
// eslint-disable-next-line default-param-last // eslint-disable-next-line @typescript-eslint/default-param-last
period: "5minute" | "hour" | "day" | "month" = "hour", period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number, initValue: number,
maxDiff: number maxDiff: number
@@ -52,7 +52,7 @@ const generateMeanStatistics = (
const generateSumStatistics = ( const generateSumStatistics = (
start: Date, start: Date,
end: Date, end: Date,
// eslint-disable-next-line default-param-last // eslint-disable-next-line @typescript-eslint/default-param-last
period: "5minute" | "hour" | "day" | "month" = "hour", period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number, initValue: number,
maxDiff: number maxDiff: number
@@ -89,7 +89,7 @@ const generateSumStatistics = (
const generateCurvedStatistics = ( const generateCurvedStatistics = (
start: Date, start: Date,
end: Date, end: Date,
// eslint-disable-next-line default-param-last // eslint-disable-next-line @typescript-eslint/default-param-last
_period: "5minute" | "hour" | "day" | "month" = "hour", _period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number, initValue: number,
maxDiff: number, maxDiff: number,

View File

@@ -11,7 +11,6 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
result: msg.template, result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false }, listeners: { all: false, domains: [], entities: [], time: false },
}); });
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => {}; return () => {};
}); });
}; };

View File

@@ -22,6 +22,5 @@ export const mockTodo = (hass: MockHomeAssistant) => {
}, },
] as TodoItem[], ] as TodoItem[],
})); }));
// eslint-disable-next-line @typescript-eslint/no-empty-function
hass.mockWS("todo/item/subscribe", (_msg, _hass) => () => {}); hass.mockWS("todo/item/subscribe", (_msg, _hass) => () => {});
}; };

View File

@@ -18,9 +18,8 @@ const compat = new FlatCompat({
export default [ export default [
...compat.extends( ...compat.extends(
"airbnb-base", "airbnb-base",
"airbnb-typescript/base",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/strict",
"plugin:@typescript-eslint/stylistic",
"plugin:wc/recommended", "plugin:wc/recommended",
"plugin:lit/all", "plugin:lit/all",
"plugin:lit-a11y/recommended", "plugin:lit-a11y/recommended",
@@ -51,6 +50,8 @@ export default [
ecmaFeatures: { ecmaFeatures: {
modules: true, modules: true,
}, },
project: "./tsconfig.json",
}, },
}, },
@@ -147,15 +148,15 @@ export default [
}, },
], ],
"@typescript-eslint/no-unused-vars": [ "@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-vars": [
"error", "error",
{ {
args: "all", vars: "all",
argsIgnorePattern: "^_",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_", varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
ignoreRestSiblings: true, ignoreRestSiblings: true,
}, },
], ],
@@ -173,16 +174,6 @@ export default [
"lit-a11y/role-has-required-aria-attrs": "error", "lit-a11y/role-has-required-aria-attrs": "error",
"@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-import-type-side-effects": "error", "@typescript-eslint/no-import-type-side-effects": "error",
camelcase: "off",
"@typescript-eslint/no-dynamic-delete": "off",
"@typescript-eslint/no-empty-object-type": [
"error",
{
allowInterfaces: "always",
allowObjectTypes: "always",
},
],
"no-use-before-define": "off",
}, },
}, },
]; ];

View File

@@ -42,7 +42,7 @@ In most cases, Create can be paired with Delete, and Add can be paired with Remo
## Add ## Add
An already-existing item. An already-exisiting item.
For example: For example:

View File

@@ -177,7 +177,8 @@ export class DemoAutomationDescribeAction extends LitElement {
this._action = ev.detail.isValid ? ev.detail.value : undefined; this._action = ev.detail.isValid ? ev.detail.value : undefined;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -196,6 +197,7 @@ export class DemoAutomationDescribeAction extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -98,7 +98,8 @@ export class DemoAutomationDescribeCondition extends LitElement {
this._condition = ev.detail.isValid ? ev.detail.value : undefined; this._condition = ev.detail.isValid ? ev.detail.value : undefined;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -117,6 +118,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -121,7 +121,8 @@ export class DemoAutomationDescribeTrigger extends LitElement {
this._trigger = ev.detail.isValid ? ev.detail.value : undefined; this._trigger = ev.detail.isValid ? ev.detail.value : undefined;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -140,6 +141,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -58,7 +58,8 @@ export class DemoAutomationTraceTimeline extends LitElement {
hass.updateTranslations("config", "en"); hass.updateTranslations("config", "en");
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px; margin: 24px;
@@ -73,6 +74,7 @@ export class DemoAutomationTraceTimeline extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -68,7 +68,8 @@ export class DemoAutomationTrace extends LitElement {
this._selected = { ...this._selected, [sampleIdx]: ev.detail.path }; this._selected = { ...this._selected, [sampleIdx]: ev.detail.path };
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px; margin: 24px;
@@ -89,6 +90,7 @@ export class DemoAutomationTrace extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -19,7 +19,7 @@ The alert offers four severity levels that set a distinctive icon and color.
</ha-alert> </ha-alert>
<ha-alert alert-type="warning"> <ha-alert alert-type="warning">
This is a warning alert — check it out! This is an warning alert — check it out!
</ha-alert> </ha-alert>
<ha-alert alert-type="info"> <ha-alert alert-type="info">
@@ -27,7 +27,7 @@ The alert offers four severity levels that set a distinctive icon and color.
</ha-alert> </ha-alert>
<ha-alert alert-type="success"> <ha-alert alert-type="success">
This is a success alert — check it out! This is an success alert — check it out!
</ha-alert> </ha-alert>
**Note:** This component is by <a href="https://mui.com/components/alert/" rel="noopener noreferrer" target="_blank">MUI</a> and is not documented in the <a href="https://material.io" rel="noopener noreferrer" target="_blank">Material Design guidelines</a>. **Note:** This component is by <a href="https://mui.com/components/alert/" rel="noopener noreferrer" target="_blank">MUI</a> and is not documented in the <a href="https://material.io" rel="noopener noreferrer" target="_blank">Material Design guidelines</a>.
@@ -95,7 +95,7 @@ Actions must have a tab index of 0 so that they can be reached by keyboard-only
</ha-alert> </ha-alert>
<ha-alert alert-type="warning"> <ha-alert alert-type="warning">
This is a warning alert — check it out! This is an warning alert — check it out!
</ha-alert> </ha-alert>
<ha-alert alert-type="info"> <ha-alert alert-type="info">
@@ -103,7 +103,7 @@ Actions must have a tab index of 0 so that they can be reached by keyboard-only
</ha-alert> </ha-alert>
<ha-alert alert-type="success"> <ha-alert alert-type="success">
This is a success alert — check it out! This is an success alert — check it out!
</ha-alert> </ha-alert>
```html ```html
@@ -122,37 +122,37 @@ Actions must have a tab index of 0 so that they can be reached by keyboard-only
The `title ` option should not be used without a description. The `title ` option should not be used without a description.
<ha-alert alert-type="success" title="Success"> <ha-alert alert-type="success" title="Success">
This is a success alert — check it out! This is an success alert — check it out!
</ha-alert> </ha-alert>
```html ```html
<ha-alert alert-type="success" title="Success"> <ha-alert alert-type="success" title="Success">
This is a success alert — check it out! This is an success alert — check it out!
</ha-alert> </ha-alert>
``` ```
**Dismissable** **Dismissable**
<ha-alert alert-type="success" dismissable> <ha-alert alert-type="success" dismissable>
This is a success alert — check it out! This is an success alert — check it out!
</ha-alert> </ha-alert>
```html ```html
<ha-alert alert-type="success" dismissable> <ha-alert alert-type="success" dismissable>
This is a success alert — check it out! This is an success alert — check it out!
</ha-alert> </ha-alert>
``` ```
**Slotted action** **Slotted action**
<ha-alert alert-type="success"> <ha-alert alert-type="success">
This is a success alert — check it out! This is an success alert — check it out!
<mwc-button slot="action" label="Undo"></mwc-button> <mwc-button slot="action" label="Undo"></mwc-button>
</ha-alert> </ha-alert>
```html ```html
<ha-alert alert-type="success"> <ha-alert alert-type="success">
This is a success alert — check it out! This is an success alert — check it out!
<mwc-button slot="action" label="Undo"></mwc-button> <mwc-button slot="action" label="Undo"></mwc-button>
</ha-alert> </ha-alert>
``` ```

View File

@@ -177,7 +177,8 @@ export class DemoHaAlert extends LitElement {
); );
} }
static styles = css` static get styles() {
return css`
:host { :host {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -216,6 +217,7 @@ export class DemoHaAlert extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -63,7 +63,8 @@ export class DemoHaBar extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -76,6 +77,7 @@ export class DemoHaBar extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -103,7 +103,8 @@ export class DemoHaChips extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -115,6 +116,7 @@ export class DemoHaChips extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -48,13 +48,15 @@ export class DemoHaCircularProgress extends LitElement {
></ha-card>`; ></ha-card>`;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -14,12 +14,12 @@ import "../../../../src/components/ha-card";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-control-button-group"; import "../../../../src/components/ha-control-button-group";
interface Button { type Button = {
label: string; label: string;
icon?: string; icon?: string;
class?: string; class?: string;
disabled?: boolean; disabled?: boolean;
} };
const buttons: Button[] = [ const buttons: Button[] = [
{ {
@@ -35,10 +35,10 @@ const buttons: Button[] = [
}, },
]; ];
interface ButtonGroup { type ButtonGroup = {
vertical?: boolean; vertical?: boolean;
class?: string; class?: string;
} };
const buttonGroups: ButtonGroup[] = [ const buttonGroups: ButtonGroup[] = [
{}, {},
@@ -137,7 +137,8 @@ export class DemoHaBarButton extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -183,6 +184,7 @@ export class DemoHaBarButton extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -133,7 +133,8 @@ export class DemoHaCircularSlider extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -165,6 +166,7 @@ export class DemoHaCircularSlider extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -73,7 +73,8 @@ export class DemoHarControlNumberButtons extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -98,6 +99,7 @@ export class DemoHarControlNumberButtons extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -8,19 +8,19 @@ import "../../../../src/components/ha-control-select-menu";
import "../../../../src/components/ha-list-item"; import "../../../../src/components/ha-list-item";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
interface SelectMenuOptions { type SelectMenuOptions = {
label: string; label: string;
value: string; value: string;
icon?: string; icon?: string;
} };
interface SelectMenu { type SelectMenu = {
label: string; label: string;
icon: string; icon: string;
class?: string; class?: string;
disabled?: boolean; disabled?: boolean;
options: SelectMenuOptions[]; options: SelectMenuOptions[];
} };
const selects: SelectMenu[] = [ const selects: SelectMenu[] = [
{ {
@@ -112,7 +112,8 @@ export class DemoHaControlSelectMenu extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -137,6 +138,7 @@ export class DemoHaControlSelectMenu extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -168,7 +168,8 @@ export class DemoHaControlSelect extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -203,6 +204,7 @@ export class DemoHaControlSelect extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -131,7 +131,8 @@ export class DemoHaBarSlider extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -167,6 +168,7 @@ export class DemoHaBarSlider extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -99,7 +99,8 @@ export class DemoHaControlSwitch extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -136,6 +137,7 @@ export class DemoHaControlSwitch extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -143,12 +143,14 @@ export class DemoHaExpansionPanel extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-expansion-panel { ha-expansion-panel {
margin: -16px; margin: -16px;
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -20,7 +20,7 @@ export class DemoHaFaded extends LitElement {
<ha-faded><span>${LONG_TEXT}</span></ha-faded> <ha-faded><span>${LONG_TEXT}</span></ha-faded>
<h3>No text</h3> <h3>No text</h3>
<ha-faded><span></span></ha-faded> <ha-faded><span></span></ha-faded>
<h3>Small text</h3> <h3>Smal text</h3>
<ha-faded><span>${SMALL_TEXT}</span></ha-faded> <ha-faded><span>${SMALL_TEXT}</span></ha-faded>
<h3>Long text in markdown</h3> <h3>Long text in markdown</h3>
<ha-faded> <ha-faded>
@@ -61,13 +61,15 @@ export class DemoHaFaded extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -76,7 +76,7 @@ export class DemoHaHsColorPicker extends LitElement {
@change=${this._saturationChanged} @change=${this._saturationChanged}
> >
</ha-slider> </ha-slider>
<p>Color Brightness : ${this.brightness}</p> <p>Color Brighness : ${this.brightness}</p>
<ha-slider <ha-slider
labeled labeled
step="1" step="1"
@@ -91,7 +91,8 @@ export class DemoHaHsColorPicker extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -111,6 +112,7 @@ export class DemoHaHsColorPicker extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -95,7 +95,8 @@ export class DemoHaLabelBadge extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
@@ -113,6 +114,7 @@ export class DemoHaLabelBadge extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -48,7 +48,8 @@ export class DemoHaTip extends LitElement {
); );
} }
static styles = css` static get styles() {
return css`
:host { :host {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -68,6 +69,7 @@ export class DemoHaTip extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -100,7 +100,8 @@ export class DemoDateTimeDateTimeNumeric extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-control-select { ha-control-select {
max-width: 800px; max-width: 800px;
margin: 12px auto; margin: 12px auto;
@@ -125,6 +126,7 @@ export class DemoDateTimeDateTimeNumeric extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -100,7 +100,8 @@ export class DemoDateTimeDateTimeSeconds extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-control-select { ha-control-select {
max-width: 800px; max-width: 800px;
margin: 12px auto; margin: 12px auto;
@@ -125,6 +126,7 @@ export class DemoDateTimeDateTimeSeconds extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -100,7 +100,8 @@ export class DemoDateTimeDateTimeShortYear extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-control-select { ha-control-select {
max-width: 800px; max-width: 800px;
margin: 12px auto; margin: 12px auto;
@@ -125,6 +126,7 @@ export class DemoDateTimeDateTimeShortYear extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -100,7 +100,8 @@ export class DemoDateTimeDateTimeShort extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-control-select { ha-control-select {
max-width: 800px; max-width: 800px;
margin: 12px auto; margin: 12px auto;
@@ -125,6 +126,7 @@ export class DemoDateTimeDateTimeShort extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -100,7 +100,8 @@ export class DemoDateTimeDateTime extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-control-select { ha-control-select {
max-width: 800px; max-width: 800px;
margin: 12px auto; margin: 12px auto;
@@ -125,6 +126,7 @@ export class DemoDateTimeDateTime extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -90,7 +90,8 @@ export class DemoDateTimeDate extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
.header { .header {
font-weight: bold; font-weight: bold;
} }
@@ -111,6 +112,7 @@ export class DemoDateTimeDate extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -100,7 +100,8 @@ export class DemoDateTimeTimeSeconds extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-control-select { ha-control-select {
max-width: 800px; max-width: 800px;
margin: 12px auto; margin: 12px auto;
@@ -125,6 +126,7 @@ export class DemoDateTimeTimeSeconds extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -100,7 +100,8 @@ export class DemoDateTimeTimeWeekday extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-control-select { ha-control-select {
max-width: 800px; max-width: 800px;
margin: 12px auto; margin: 12px auto;
@@ -125,6 +126,7 @@ export class DemoDateTimeTimeWeekday extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -100,7 +100,8 @@ export class DemoDateTimeTime extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-control-select { ha-control-select {
max-width: 800px; max-width: 800px;
margin: 12px auto; margin: 12px auto;
@@ -125,6 +126,7 @@ export class DemoDateTimeTime extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -321,13 +321,13 @@ function createEntity(
}; };
} }
interface EntityRowData { type EntityRowData = {
stateObj: HassEntity; stateObj: HassEntity;
entity_id: string; entity_id: string;
state: string; state: string;
device_class?: string; device_class?: string;
domain: string; domain: string;
} };
function createRowData(stateObj: HassEntity): EntityRowData { function createRowData(stateObj: HassEntity): EntityRowData {
return { return {
@@ -429,7 +429,8 @@ export class DemoEntityState extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
.color { .color {
display: block; display: block;
height: 20px; height: 20px;
@@ -439,6 +440,7 @@ export class DemoEntityState extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -76,13 +76,15 @@ export class DemoMiscMarkdown extends LitElement {
`; `;
} }
static styles = css` static get styles() {
return css`
ha-card { ha-card {
margin: 12px; margin: 12px;
padding: 12px; padding: 12px;
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -135,12 +135,12 @@ const configFlows: DataEntryFlowProgressExtended[] = [
}, },
]; ];
const configEntries: { const configEntries: Array<{
items: ConfigEntryExtended[]; items: ConfigEntryExtended[];
is_custom?: boolean; is_custom?: boolean;
disabled?: boolean; disabled?: boolean;
highlight?: string; highlight?: string;
}[] = [ }> = [
{ items: [loadedEntry] }, { items: [loadedEntry] },
{ items: [configPanelEntry] }, { items: [configPanelEntry] },
{ items: [optionsFlowEntry] }, { items: [optionsFlowEntry] },
@@ -349,7 +349,8 @@ export class DemoIntegrationCard extends LitElement {
this.isCloud = !this.isCloud; this.isCloud = !this.isCloud;
} }
static styles = css` static get styles() {
return css`
.container { .container {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
@@ -368,6 +369,7 @@ export class DemoIntegrationCard extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -1,4 +1,16 @@
import { globIterate } from "glob";
import { availableParallelism } from "node:os"; import { availableParallelism } from "node:os";
import "./build-scripts/gulp/index.mjs";
process.env.UV_THREADPOOL_SIZE = availableParallelism(); process.env.UV_THREADPOOL_SIZE = availableParallelism();
const gulpImports = [];
for await (const gulpModule of globIterate("build-scripts/gulp/*.?(c|m)js", {
dotRelative: true,
})) {
gulpImports.push(import(gulpModule));
}
// Since all tasks are currently registered with gulp.task(), this is enough
// If any are converted to named exports, need to loop and aggregate exports here
await Promise.all(gulpImports);

View File

@@ -1,7 +1,7 @@
import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@@ -211,7 +211,8 @@ export class HassioAddonStore extends LitElement {
this._filter = e.detail.value; this._filter = e.detail.value;
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
hassio-addon-repository { hassio-addon-repository {
margin-top: 24px; margin-top: 24px;
} }
@@ -239,6 +240,7 @@ export class HassioAddonStore extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -299,7 +299,7 @@ class HassioAddonConfig extends LitElement {
if (this.addon.schema && this._canShowSchema && !this._yamlMode) { if (this.addon.schema && this._canShowSchema && !this._yamlMode) {
this._valid = true; this._valid = true;
this._configHasChanged = true; this._configHasChanged = true;
this._options = ev.detail.value; this._options! = ev.detail.value;
} else { } else {
this._configHasChanged = true; this._configHasChanged = true;
this._valid = ev.detail.isValid; this._valid = ev.detail.isValid;

View File

@@ -151,7 +151,7 @@ class HassioAddonNetwork extends LitElement {
private async _configChanged(ev: CustomEvent): Promise<void> { private async _configChanged(ev: CustomEvent): Promise<void> {
this._configHasChanged = true; this._configHasChanged = true;
this._config = ev.detail.value; this._config! = ev.detail.value;
} }
private async _resetTapped(ev: CustomEvent): Promise<void> { private async _resetTapped(ev: CustomEvent): Promise<void> {

View File

@@ -1,5 +1,5 @@
import { mdiHelpCircle } from "@mdi/js"; import { mdiHelpCircle } from "@mdi/js";
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
@@ -64,7 +64,8 @@ class HassioCardContent extends LitElement {
`; `;
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
:host { :host {
direction: ltr; direction: ltr;
} }
@@ -143,6 +144,7 @@ class HassioCardContent extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -1,7 +1,7 @@
import { mdiFolderUpload } from "@mdi/js"; import { mdiFolderUpload } from "@mdi/js";
import type { TemplateResult } from "lit"; import type { TemplateResult } from "lit";
import { html, LitElement } from "lit"; import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload"; import "../../../src/components/ha-file-upload";
@@ -10,12 +10,10 @@ import { uploadBackup } from "../../../src/data/hassio/backup";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../src/types"; import type { HomeAssistant } from "../../../src/types";
import type { LocalizeFunc } from "../../../src/common/translations/localize";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
"backup-uploaded": { backup: HassioBackup }; "backup-uploaded": { backup: HassioBackup };
"backup-cleared": undefined;
} }
} }
@@ -23,8 +21,6 @@ declare global {
export class HassioUploadBackup extends LitElement { export class HassioUploadBackup extends LitElement {
public hass?: HomeAssistant; public hass?: HomeAssistant;
@property({ attribute: false }) public localize?: LocalizeFunc;
@state() public value: string | null = null; @state() public value: string | null = null;
@state() private _uploading = false; @state() private _uploading = false;
@@ -36,26 +32,13 @@ export class HassioUploadBackup extends LitElement {
.uploading=${this._uploading} .uploading=${this._uploading}
.icon=${mdiFolderUpload} .icon=${mdiFolderUpload}
accept="application/x-tar" accept="application/x-tar"
.label=${this.localize?.( label="Upload backup"
"ui.panel.page-onboarding.restore.upload_backup" supports="Supports .TAR files"
) || "Upload backup"}
.supports=${this.localize?.(
"ui.panel.page-onboarding.restore.upload_supports"
) || "Supports .TAR files"}
.secondary=${this.localize?.(
"ui.panel.page-onboarding.restore.upload_drop"
) || "Or drop your file here"}
@file-picked=${this._uploadFile} @file-picked=${this._uploadFile}
@files-cleared=${this._clear}
></ha-file-upload> ></ha-file-upload>
`; `;
} }
private _clear() {
this.value = null;
fireEvent(this, "backup-cleared");
}
private async _uploadFile(ev) { private async _uploadFile(ev) {
const file = ev.detail.files[0]; const file = ev.detail.files[0];

View File

@@ -1,5 +1,5 @@
import { mdiFolder, mdiPuzzle } from "@mdi/js"; import { mdiFolder, mdiPuzzle } from "@mdi/js";
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit"; import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
@@ -65,7 +65,7 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
@customElement("supervisor-backup-content") @customElement("supervisor-backup-content")
export class SupervisorBackupContent extends LitElement { export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localize?: LocalizeFunc; @property({ attribute: false }) public localize?: LocalizeFunc;
@@ -186,13 +186,12 @@ export class SupervisorBackupContent extends LitElement {
.iconPath=${mdiHomeAssistant} .iconPath=${mdiHomeAssistant}
.version=${this.backup .version=${this.backup
? this.backup.homeassistant ? this.backup.homeassistant
: this.hass?.config.version} : this.hass.config.version}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
> >
<ha-checkbox <ha-checkbox
.checked=${this.onboarding || this.homeAssistant} .checked=${this.homeAssistant}
.disabled=${this.onboarding}
@change=${this._toggleHomeAssistant} @change=${this._toggleHomeAssistant}
> >
</ha-checkbox> </ha-checkbox>
@@ -283,7 +282,8 @@ export class SupervisorBackupContent extends LitElement {
this.homeAssistant = !this.homeAssistant; this.homeAssistant = !this.homeAssistant;
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
.partial-picker ha-formfield { .partial-picker ha-formfield {
display: block; display: block;
} }
@@ -327,13 +327,14 @@ export class SupervisorBackupContent extends LitElement {
margin-top: 8px; margin-top: 8px;
} }
`; `;
}
public backupDetails(): public backupDetails():
| HassioPartialBackupCreateParams | HassioPartialBackupCreateParams
| HassioFullBackupCreateParams { | HassioFullBackupCreateParams {
const data: any = {}; const data: any = {};
if (!this.backup && this.hass) { if (!this.backup) {
data.name = data.name =
this.backupName || this.backupName ||
formatDate(new Date(), this.hass.locale, this.hass.config); formatDate(new Date(), this.hass.locale, this.hass.config);
@@ -363,9 +364,7 @@ export class SupervisorBackupContent extends LitElement {
if (folders?.length) { if (folders?.length) {
data.folders = folders; data.folders = folders;
} }
data.homeassistant = this.homeAssistant;
// onboarding needs at least homeassistant to restore
data.homeassistant = this.onboarding || this.homeAssistant;
return data; return data;
} }
@@ -387,7 +386,6 @@ export class SupervisorBackupContent extends LitElement {
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder} .iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
.imageUrl=${section === "addons" && .imageUrl=${section === "addons" &&
!this.onboarding && !this.onboarding &&
this.hass &&
atLeastVersion(this.hass.config.version, 0, 105) && atLeastVersion(this.hass.config.version, 0, 105) &&
addons?.get(item.slug)?.icon addons?.get(item.slug)?.icon
? `/api/hassio/addons/${item.slug}/icon` ? `/api/hassio/addons/${item.slug}/icon`

View File

@@ -1,4 +1,4 @@
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
@@ -30,7 +30,8 @@ class SupervisorFormfieldLabel extends LitElement {
`; `;
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
:host { :host {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -52,6 +53,7 @@ class SupervisorFormfieldLabel extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -1,4 +1,4 @@
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
@@ -31,7 +31,8 @@ class SupervisorMetric extends LitElement {
</ha-settings-row>`; </ha-settings-row>`;
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
ha-settings-row { ha-settings-row {
padding: 0; padding: 0;
height: 54px; height: 54px;
@@ -44,7 +45,10 @@ class SupervisorMetric extends LitElement {
justify-content: space-between; justify-content: space-between;
} }
ha-bar { ha-bar {
--ha-bar-primary-color: var(--hassio-bar-ok-color, var(--success-color)); --ha-bar-primary-color: var(
--hassio-bar-ok-color,
var(--success-color)
);
} }
.target-warning { .target-warning {
--ha-bar-primary-color: var( --ha-bar-primary-color: var(
@@ -67,6 +71,7 @@ class SupervisorMetric extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -28,7 +28,7 @@ export class DialogHassioBackupUpload
await this.updateComplete; await this.updateComplete;
} }
public closeDialog() { public closeDialog(): void {
if (this._dialogParams && !this._dialogParams.onboarding) { if (this._dialogParams && !this._dialogParams.onboarding) {
if (this._dialogParams.reloadBackup) { if (this._dialogParams.reloadBackup) {
this._dialogParams.reloadBackup(); this._dialogParams.reloadBackup();
@@ -36,7 +36,6 @@ export class DialogHassioBackupUpload
} }
this._dialogParams = undefined; this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
} }
protected render() { protected render() {

View File

@@ -8,11 +8,9 @@ import { atLeastVersion } from "../../../../src/common/config/version";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { stopPropagation } from "../../../../src/common/dom/stop_propagation"; import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
import { slugify } from "../../../../src/common/string/slugify"; import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/ha-md-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-dialog-header";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button";
import "../../../../src/components/ha-button-menu"; import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-icon-button"; import "../../../../src/components/ha-icon-button";
@@ -21,7 +19,6 @@ import type { HassioBackupDetail } from "../../../../src/data/hassio/backup";
import { import {
fetchHassioBackupInfo, fetchHassioBackupInfo,
removeBackup, removeBackup,
restoreBackup,
} from "../../../../src/data/hassio/backup"; } from "../../../../src/data/hassio/backup";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
@@ -36,7 +33,6 @@ import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content"; import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import type { HassioBackupDialogParams } from "./show-dialog-hassio-backup"; import type { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import type { BackupOrRestoreKey } from "../../util/translations"; import type { BackupOrRestoreKey } from "../../util/translations";
import type { HaMdDialog } from "../../../../src/components/ha-md-dialog";
@customElement("dialog-hassio-backup") @customElement("dialog-hassio-backup")
class HassioBackupDialog class HassioBackupDialog
@@ -56,20 +52,13 @@ class HassioBackupDialog
@query("supervisor-backup-content") @query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent; private _backupContent!: SupervisorBackupContent;
@query("ha-md-dialog") private _dialog?: HaMdDialog;
public async showDialog(dialogParams: HassioBackupDialogParams) { public async showDialog(dialogParams: HassioBackupDialogParams) {
this._dialogParams = dialogParams;
this._backup = await fetchHassioBackupInfo(this.hass, dialogParams.slug); this._backup = await fetchHassioBackupInfo(this.hass, dialogParams.slug);
if (!this._backup) { this._dialogParams = dialogParams;
this._error = this._localize("no_backup_found");
} else if (this._dialogParams.onboarding && !this._backup.homeassistant) {
this._error = this._localize("restore_no_home_assistant");
}
this._restoringBackup = false; this._restoringBackup = false;
} }
private _dialogClosed(): void { public closeDialog() {
this._backup = undefined; this._backup = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
this._restoringBackup = false; this._restoringBackup = false;
@@ -77,11 +66,6 @@ class HassioBackupDialog
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
public closeDialog() {
this._dialog?.close();
return true;
}
private _localize(key: BackupOrRestoreKey) { private _localize(key: BackupOrRestoreKey) {
return ( return (
this._dialogParams!.supervisor?.localize(`backup.${key}`) || this._dialogParams!.supervisor?.localize(`backup.${key}`) ||
@@ -94,26 +78,52 @@ class HassioBackupDialog
return nothing; return nothing;
} }
return html` return html`
<ha-md-dialog <ha-dialog
open open
.disableCancelAction=${!this._error} scrimClickAction
@closed=${this._dialogClosed} @closed=${this.closeDialog}
.heading=${this._backup.name}
> >
<ha-dialog-header slot="headline"> <div slot="heading">
<ha-header-bar>
<span slot="title">${this._backup.name}</span>
<ha-icon-button <ha-icon-button
slot="navigationIcon"
.label=${this._localize("close")} .label=${this._localize("close")}
.path=${mdiClose} .path=${mdiClose}
@click=${this.closeDialog} slot="actionItems"
.disabled=${this._restoringBackup} dialogAction="cancel"
></ha-icon-button> ></ha-icon-button>
<span slot="title" .title=${this._backup.name} </ha-header-bar>
>${this._backup.name}</span </div>
${this._restoringBackup
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
: html`
<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
dialogInitialFocus
> >
</supervisor-backup-content>
`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<mwc-button
.disabled=${this._restoringBackup}
slot="secondaryAction"
@click=${this._restoreClicked}
>
${this._localize("restore")}
</mwc-button>
${!this._dialogParams.onboarding && this._dialogParams.supervisor ${!this._dialogParams.onboarding && this._dialogParams.supervisor
? html`<ha-button-menu ? html`<ha-button-menu
slot="actionItems"
fixed fixed
slot="primaryAction"
@action=${this._handleMenuAction} @action=${this._handleMenuAction}
@closed=${stopPropagation} @closed=${stopPropagation}
> >
@@ -136,38 +146,32 @@ class HassioBackupDialog
> >
</ha-button-menu>` </ha-button-menu>`
: nothing} : nothing}
</ha-dialog-header> </ha-dialog>
<div slot="content">
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: this._restoringBackup
? html`<div class="loading">
<ha-circular-progress indeterminate></ha-circular-progress>
</div>`
: html`
<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
dialogInitialFocus
>
</supervisor-backup-content>
`}
</div>
<div slot="actions">
<ha-button
.disabled=${this._restoringBackup || !!this._error}
@click=${this._restoreClicked}
>
${this._localize("restore")}
</ha-button>
</div>
</ha-md-dialog>
`; `;
} }
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-circular-progress {
display: block;
text-align: center;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
ha-icon-button {
color: var(--secondary-text-color);
}
`,
];
}
private _handleMenuAction(ev: CustomEvent<ActionDetail>) { private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) { switch (ev.detail.index) {
case 0: case 0:
@@ -182,7 +186,16 @@ class HassioBackupDialog
private async _restoreClicked() { private async _restoreClicked() {
const backupDetails = this._backupContent.backupDetails(); const backupDetails = this._backupContent.backupDetails();
this._restoringBackup = true; this._restoringBackup = true;
this._dialogParams?.onRestoring?.();
if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(backupDetails);
} else {
await this._partialRestoreClicked(backupDetails);
}
this._restoringBackup = false;
}
private async _partialRestoreClicked(backupDetails) {
const supervisor = this._dialogParams?.supervisor; const supervisor = this._dialogParams?.supervisor;
if (supervisor !== undefined && supervisor.info.state !== "running") { if (supervisor !== undefined && supervisor.info.state !== "running") {
await showAlertDialog(this, { await showAlertDialog(this, {
@@ -191,45 +204,91 @@ class HassioBackupDialog
state: supervisor.info.state, state: supervisor.info.state,
}), }),
}); });
this._restoringBackup = false;
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: this._localize( title: this._localize("confirm_restore_partial_backup_title"),
this._backup!.type === "full" text: this._localize("confirm_restore_partial_backup_text"),
? "confirm_restore_full_backup_title"
: "confirm_restore_partial_backup_title"
),
text: this._localize(
this._backup!.type === "full"
? "confirm_restore_full_backup_text"
: "confirm_restore_partial_backup_text"
),
confirmText: this._localize("restore"), confirmText: this._localize("restore"),
dismissText: this._localize("cancel"), dismissText: this._localize("cancel"),
})) }))
) { ) {
this._restoringBackup = false;
return; return;
} }
if (!this._dialogParams?.onboarding) {
try { try {
await restoreBackup( await this.hass!.callApi(
this.hass, "POST",
this._backup!.type,
this._backup!.slug,
{ ...backupDetails, background: this._dialogParams?.onboarding },
!!this.hass && atLeastVersion(this.hass.config.version, 2021, 9)
);
this._dialogParams?.onRestoring?.(); `hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
);
this.closeDialog(); this.closeDialog();
} catch (error: any) { } catch (error: any) {
this._error = this._error = error.body.message;
error?.body?.message || this._localize("restore_start_failed"); }
} finally { } else {
this._restoringBackup = false; this._dialogParams?.onRestoring?.();
await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
this.closeDialog();
}
}
private async _fullRestoreClicked(backupDetails) {
const supervisor = this._dialogParams?.supervisor;
if (supervisor !== undefined && supervisor.info.state !== "running") {
await showAlertDialog(this, {
title: supervisor.localize("backup.could_not_restore"),
text: supervisor.localize("backup.restore_blocked_not_running", {
state: supervisor.info.state,
}),
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: this._localize("confirm_restore_full_backup_title"),
text: this._localize("confirm_restore_full_backup_text"),
confirmText: this._localize("restore"),
dismissText: this._localize("cancel"),
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass!.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
).then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
this._dialogParams?.onRestoring?.();
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
this.closeDialog();
} }
} }
@@ -302,36 +361,7 @@ class HassioBackupDialog
private get _computeName() { private get _computeName() {
return this._backup return this._backup
? this._backup.name || this._backup.slug ? this._backup.name || this._backup.slug
: this._localize("unnamed_backup"); : "Unnamed backup";
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-circular-progress {
display: block;
text-align: center;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
ha-icon-button {
color: var(--secondary-text-color);
}
.loading {
width: 100%;
display: flex;
height: 100%;
justify-content: center;
align-items: center;
}
`,
];
} }
} }

View File

@@ -82,11 +82,10 @@ export class DialogHassioNetwork
await this.updateComplete; await this.updateComplete;
} }
public closeDialog() { public closeDialog(): void {
this._params = undefined; this._params = undefined;
this._processing = false; this._processing = false;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
} }
protected render() { protected render() {

View File

@@ -92,7 +92,7 @@ class HassioMyRedirect extends LitElement {
let url: string; let url: string;
try { try {
url = this._createRedirectUrl(redirect); url = this._createRedirectUrl(redirect);
} catch (_err: any) { } catch (err: any) {
this._error = this.supervisor.localize("my.error"); this._error = this.supervisor.localize("my.error");
return; return;
} }

View File

@@ -1,4 +1,4 @@
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import type { Supervisor } from "../../src/data/supervisor/supervisor"; import type { Supervisor } from "../../src/data/supervisor/supervisor";
@@ -39,7 +39,8 @@ class HassioPanel extends LitElement {
`; `;
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
:host { :host {
--app-header-background-color: var(--sidebar-background-color); --app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color); --app-header-text-color: var(--sidebar-text-color);
@@ -47,6 +48,7 @@ class HassioPanel extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -1,5 +1,5 @@
import { mdiMenu } from "@mdi/js"; import { mdiMenu } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
@@ -158,7 +158,7 @@ class HassioIngressView extends LitElement {
try { try {
addon = await fetchHassioAddonInfo(this.hass, addonSlug); addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (_err: any) { } catch (err: any) {
await this.updateComplete; await this.updateComplete;
await showAlertDialog(this, { await showAlertDialog(this, {
text: text:
@@ -219,7 +219,7 @@ class HassioIngressView extends LitElement {
}); });
this._fetchData(addonSlug); this._fetchData(addonSlug);
return; return;
} catch (_err) { } catch (e) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: text:
this.supervisor.localize("ingress.error_starting_addon") || this.supervisor.localize("ingress.error_starting_addon") ||
@@ -264,7 +264,7 @@ class HassioIngressView extends LitElement {
try { try {
session = await createSessionPromise; session = await createSessionPromise;
} catch (_err: any) { } catch (err: any) {
if (this._sessionKeepAlive) { if (this._sessionKeepAlive) {
clearInterval(this._sessionKeepAlive); clearInterval(this._sessionKeepAlive);
} }
@@ -285,7 +285,7 @@ class HassioIngressView extends LitElement {
this._sessionKeepAlive = window.setInterval(async () => { this._sessionKeepAlive = window.setInterval(async () => {
try { try {
await validateHassioSession(this.hass, session); await validateHassioSession(this.hass, session);
} catch (_err: any) { } catch (err: any) {
session = await createHassioSession(this.hass); session = await createHassioSession(this.hass);
} }
}, 60000); }, 60000);
@@ -325,7 +325,8 @@ class HassioIngressView extends LitElement {
fireEvent(this, "hass-toggle-menu"); fireEvent(this, "hass-toggle-menu");
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
iframe { iframe {
display: block; display: block;
width: 100%; width: 100%;
@@ -369,6 +370,7 @@ class HassioIngressView extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -33,7 +33,7 @@ class HassioCoreInfo extends LitElement {
@state() private _metrics?: HassioStats; @state() private _metrics?: HassioStats;
protected render(): TemplateResult | undefined { protected render(): TemplateResult | void {
const metrics = [ const metrics = [
{ {
description: this.supervisor.localize("system.core.cpu_usage"), description: this.supervisor.localize("system.core.cpu_usage"),

View File

@@ -49,7 +49,7 @@ class HassioHostInfo extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
protected render(): TemplateResult | undefined { protected render(): TemplateResult | void {
const primaryIpAddress = this.supervisor.host.features.includes("network") const primaryIpAddress = this.supervisor.host.features.includes("network")
? this._primaryIpAddress(this.supervisor.network!) ? this._primaryIpAddress(this.supervisor.network!)
: ""; : "";

View File

@@ -44,7 +44,7 @@ class HassioSupervisorInfo extends LitElement {
@state() private _metrics?: HassioStats; @state() private _metrics?: HassioStats;
protected render(): TemplateResult | undefined { protected render(): TemplateResult | void {
const metrics = [ const metrics = [
{ {
description: this.supervisor.localize("system.supervisor.cpu_usage"), description: this.supervisor.localize("system.supervisor.cpu_usage"),

View File

@@ -65,7 +65,7 @@ class HassioSupervisorLog extends LitElement {
await this._loadData(); await this._loadData();
} }
protected render(): TemplateResult | undefined { protected render(): TemplateResult | void {
return html` return html`
<ha-card outlined> <ha-card outlined>
${this._error ${this._error

View File

@@ -23,7 +23,7 @@ class HassioSystem extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
protected render(): TemplateResult | undefined { protected render(): TemplateResult | void {
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
.hass=${this.hass} .hass=${this.hass}

View File

@@ -18,7 +18,10 @@ import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-faded"; import "../../../src/components/ha-faded";
import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-icon-button";
import "../../../src/components/ha-markdown"; import "../../../src/components/ha-markdown";
import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import "../../../src/components/ha-switch";
import type { HaSwitch } from "../../../src/components/ha-switch";
import type { HassioAddonDetails } from "../../../src/data/hassio/addon"; import type { HassioAddonDetails } from "../../../src/data/hassio/addon";
import { import {
fetchHassioAddonChangelog, fetchHassioAddonChangelog,
@@ -160,6 +163,19 @@ class UpdateAvailableCard extends LitElement {
)} )}
</p> </p>
</div> </div>
${["core", "addon"].includes(this._updateType)
? html`
<hr />
<ha-settings-row>
<span slot="heading">
${this.supervisor.localize(
"update_available.create_backup"
)}
</span>
<ha-switch id="create_backup" checked></ha-switch>
</ha-settings-row>
`
: nothing}
` `
: html`<ha-circular-progress : html`<ha-circular-progress
aria-label="Updating" aria-label="Updating"
@@ -227,6 +243,19 @@ class UpdateAvailableCard extends LitElement {
} }
} }
get _shouldCreateBackup(): boolean {
if (this._updateType && !["core", "addon"].includes(this._updateType)) {
return false;
}
const createBackupSwitch = this.shadowRoot?.getElementById(
"create-backup"
) as HaSwitch;
if (createBackupSwitch) {
return createBackupSwitch.checked;
}
return true;
}
get _version(): string { get _version(): string {
return this._updateType return this._updateType
? this._updateType === "addon" ? this._updateType === "addon"
@@ -341,14 +370,23 @@ class UpdateAvailableCard extends LitElement {
} }
private async _update() { private async _update() {
if (this._shouldCreateBackup && this.supervisor.info.state === "freeze") {
this._error = this.supervisor.localize("backup.backup_already_running");
return;
}
this._error = undefined; this._error = undefined;
this._updating = true; this._updating = true;
try { try {
if (this._updateType === "addon") { if (this._updateType === "addon") {
await updateHassioAddon(this.hass, this.addonSlug!); await updateHassioAddon(
this.hass,
this.addonSlug!,
this._shouldCreateBackup
);
} else if (this._updateType === "core") { } else if (this._updateType === "core") {
await updateCore(this.hass); await updateCore(this.hass, this._shouldCreateBackup);
} else if (this._updateType === "os") { } else if (this._updateType === "os") {
await updateOS(this.hass); await updateOS(this.hass);
} else if (this._updateType === "supervisor") { } else if (this._updateType === "supervisor") {
@@ -398,6 +436,11 @@ class UpdateAvailableCard extends LitElement {
padding-bottom: 8px; padding-bottom: 8px;
} }
ha-settings-row {
padding: 0;
margin-bottom: -16px;
}
hr { hr {
border-color: var(--divider-color); border-color: var(--divider-color);
border-bottom: none; border-bottom: none;

View File

@@ -1,4 +1,4 @@
import type { TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import type { Supervisor } from "../../../src/data/supervisor/supervisor"; import type { Supervisor } from "../../../src/data/supervisor/supervisor";
@@ -38,7 +38,8 @@ class UpdateAvailableDashboard extends LitElement {
history.back(); history.back();
} }
static styles = css` static get styles(): CSSResultGroup {
return css`
hass-subpage { hass-subpage {
--app-header-background-color: var(--primary-background-color); --app-header-background-color: var(--primary-background-color);
--app-header-text-color: var(--sidebar-text-color); --app-header-text-color: var(--sidebar-text-color);
@@ -51,6 +52,7 @@ class UpdateAvailableDashboard extends LitElement {
} }
`; `;
} }
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

View File

@@ -230,7 +230,7 @@ class LandingPageLogs extends LitElement {
this._startLogStream(); this._startLogStream();
return; return;
} }
} catch (_err) { } catch (err) {
// ignore and continue with observer logs // ignore and continue with observer logs
} }
this._loadObserverLogs(); this._loadObserverLogs();

View File

@@ -126,7 +126,7 @@ class HaLandingPage extends LandingPageBaseElement {
"selectedLanguage", "selectedLanguage",
JSON.stringify(language) JSON.stringify(language)
); );
} catch (_err: any) { } catch (err: any) {
// Ignore // Ignore
} }
} }

View File

@@ -27,24 +27,24 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@babel/runtime": "7.26.0", "@babel/runtime": "7.26.0",
"@braintree/sanitize-url": "7.1.1", "@braintree/sanitize-url": "7.1.0",
"@codemirror/autocomplete": "6.18.4", "@codemirror/autocomplete": "6.18.3",
"@codemirror/commands": "6.8.0", "@codemirror/commands": "6.7.1",
"@codemirror/language": "6.10.8", "@codemirror/language": "6.10.6",
"@codemirror/legacy-modes": "6.4.2", "@codemirror/legacy-modes": "6.4.2",
"@codemirror/search": "6.5.8", "@codemirror/search": "6.5.8",
"@codemirror/state": "6.5.1", "@codemirror/state": "6.4.1",
"@codemirror/view": "6.36.2", "@codemirror/view": "6.35.2",
"@egjs/hammerjs": "2.0.17", "@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.17.2", "@formatjs/intl-datetimeformat": "6.16.6",
"@formatjs/intl-displaynames": "6.8.9", "@formatjs/intl-displaynames": "6.8.6",
"@formatjs/intl-durationformat": "0.7.2", "@formatjs/intl-durationformat": "0.6.5",
"@formatjs/intl-getcanonicallocales": "2.5.4", "@formatjs/intl-getcanonicallocales": "2.5.3",
"@formatjs/intl-listformat": "7.7.9", "@formatjs/intl-listformat": "7.7.6",
"@formatjs/intl-locale": "4.2.9", "@formatjs/intl-locale": "4.2.6",
"@formatjs/intl-numberformat": "8.15.2", "@formatjs/intl-numberformat": "8.14.6",
"@formatjs/intl-pluralrules": "5.4.2", "@formatjs/intl-pluralrules": "5.3.6",
"@formatjs/intl-relativetimeformat": "11.4.9", "@formatjs/intl-relativetimeformat": "11.4.6",
"@fullcalendar/core": "6.1.15", "@fullcalendar/core": "6.1.15",
"@fullcalendar/daygrid": "6.1.15", "@fullcalendar/daygrid": "6.1.15",
"@fullcalendar/interaction": "6.1.15", "@fullcalendar/interaction": "6.1.15",
@@ -55,7 +55,7 @@
"@lit-labs/context": "0.4.1", "@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.7", "@lit-labs/motion": "1.0.7",
"@lit-labs/observers": "2.0.4", "@lit-labs/observers": "2.0.4",
"@lit-labs/virtualizer": "2.0.15", "@lit-labs/virtualizer": "2.0.14",
"@lrnwebcomponents/simple-tooltip": "8.0.2", "@lrnwebcomponents/simple-tooltip": "8.0.2",
"@material/chips": "=14.0.0-canary.53b3cad2f.0", "@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0", "@material/data-table": "=14.0.0-canary.53b3cad2f.0",
@@ -91,19 +91,20 @@
"@polymer/polymer": "3.5.2", "@polymer/polymer": "3.5.2",
"@replit/codemirror-indentation-markers": "6.5.3", "@replit/codemirror-indentation-markers": "6.5.3",
"@thomasloven/round-slider": "0.6.0", "@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.6.2", "@vaadin/combo-box": "24.5.5",
"@vaadin/vaadin-themable-mixin": "24.6.2", "@vaadin/vaadin-themable-mixin": "24.5.5",
"@vibrant/color": "4.0.0", "@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.3.0", "@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.9", "@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0", "@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1", "app-datepicker": "5.1.1",
"barcode-detector": "2.3.1", "barcode-detector": "2.3.1",
"chart.js": "4.4.7", "chart.js": "4.4.7",
"chartjs-plugin-zoom": "2.2.0",
"color-name": "2.0.0", "color-name": "2.0.0",
"comlink": "4.4.2", "comlink": "4.4.2",
"core-js": "3.40.0", "core-js": "3.39.0",
"cropperjs": "1.6.2", "cropperjs": "1.6.2",
"date-fns": "4.1.0", "date-fns": "4.1.0",
"date-fns-tz": "3.2.0", "date-fns-tz": "3.2.0",
@@ -113,20 +114,20 @@
"element-internals-polyfill": "1.3.12", "element-internals-polyfill": "1.3.12",
"fuse.js": "7.0.0", "fuse.js": "7.0.0",
"google-timezones-json": "1.2.0", "google-timezones-json": "1.2.0",
"gulp-zopfli-green": "6.0.2",
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch", "hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
"home-assistant-js-websocket": "9.4.0", "home-assistant-js-websocket": "9.4.0",
"idb-keyval": "6.2.1", "idb-keyval": "6.2.1",
"intl-messageformat": "10.7.11", "intl-messageformat": "10.7.8",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"leaflet": "1.9.4", "leaflet": "1.9.4",
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch", "leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
"lit": "2.8.0", "lit": "2.8.0",
"lit-html": "2.8.0", "lit-html": "2.8.0",
"luxon": "3.5.0", "luxon": "3.5.0",
"marked": "15.0.6", "marked": "15.0.3",
"memoize-one": "6.0.0", "memoize-one": "6.0.0",
"node-vibrant": "4.0.1", "node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
"punycode": "2.3.1", "punycode": "2.3.1",
"qr-scanner": "1.4.2", "qr-scanner": "1.4.2",
"qrcode": "1.5.4", "qrcode": "1.5.4",
@@ -138,7 +139,8 @@
"tinykeys": "3.0.0", "tinykeys": "3.0.0",
"tsparticles-engine": "2.12.0", "tsparticles-engine": "2.12.0",
"tsparticles-preset-links": "2.12.0", "tsparticles-preset-links": "2.12.0",
"ua-parser-js": "2.0.0", "ua-parser-js": "1.0.39",
"unfetch": "5.0.0",
"vis-data": "7.1.9", "vis-data": "7.1.9",
"vis-network": "9.1.9", "vis-network": "9.1.9",
"vue": "2.7.16", "vue": "2.7.16",
@@ -160,13 +162,13 @@
"@babel/preset-env": "7.26.0", "@babel/preset-env": "7.26.0",
"@babel/preset-typescript": "7.26.0", "@babel/preset-typescript": "7.26.0",
"@bundle-stats/plugin-webpack-filter": "4.17.0", "@bundle-stats/plugin-webpack-filter": "4.17.0",
"@lokalise/node-api": "13.0.0", "@lokalise/node-api": "12.8.0",
"@octokit/auth-oauth-device": "7.1.2", "@octokit/auth-oauth-device": "7.1.1",
"@octokit/plugin-retry": "7.1.3", "@octokit/plugin-retry": "7.1.2",
"@octokit/rest": "21.1.0", "@octokit/rest": "21.0.2",
"@rsdoctor/rspack-plugin": "0.4.13", "@rsdoctor/rspack-plugin": "0.4.11",
"@rspack/cli": "1.1.8", "@rspack/cli": "1.1.5",
"@rspack/core": "1.1.8", "@rspack/core": "1.1.5",
"@types/babel__plugin-transform-runtime": "7.9.5", "@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.20", "@types/chromecast-caf-receiver": "6.0.20",
"@types/chromecast-caf-sender": "1.0.11", "@types/chromecast-caf-sender": "1.0.11",
@@ -174,27 +176,29 @@
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
"@types/html-minifier-terser": "7.0.2", "@types/html-minifier-terser": "7.0.2",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.16", "@types/leaflet": "1.9.15",
"@types/leaflet-draw": "1.0.11", "@types/leaflet-draw": "1.0.11",
"@types/lodash.merge": "4.6.9", "@types/lodash.merge": "4.6.9",
"@types/luxon": "3.4.2", "@types/luxon": "3.4.2",
"@types/mocha": "10.0.10", "@types/mocha": "10.0.10",
"@types/qrcode": "1.5.5", "@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4",
"@types/sortablejs": "1.15.8", "@types/sortablejs": "1.15.8",
"@types/tar": "6.1.13", "@types/tar": "6.1.13",
"@types/ua-parser-js": "0.7.39", "@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29", "@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "8.20.0", "@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "8.20.0", "@typescript-eslint/parser": "7.18.0",
"@vitest/coverage-v8": "2.1.8", "@vitest/coverage-v8": "2.1.8",
"babel-loader": "9.2.1", "babel-loader": "9.2.1",
"babel-plugin-template-html-minifier": "4.1.0", "babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3", "browserslist-useragent-regexp": "4.1.3",
"del": "8.0.0", "del": "8.0.0",
"eslint": "9.18.0", "eslint": "9.16.0",
"eslint-config-airbnb-base": "15.0.0", "eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "18.0.0",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
"eslint-import-resolver-webpack": "0.13.10", "eslint-import-resolver-webpack": "0.13.9",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-lit": "1.15.0", "eslint-plugin-lit": "1.15.0",
"eslint-plugin-lit-a11y": "4.1.4", "eslint-plugin-lit-a11y": "4.1.4",
@@ -202,29 +206,33 @@
"eslint-plugin-wc": "2.2.0", "eslint-plugin-wc": "2.2.0",
"fancy-log": "2.0.0", "fancy-log": "2.0.0",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
"glob": "11.0.1", "glob": "11.0.0",
"gulp": "5.0.0", "gulp": "5.0.0",
"gulp-brotli": "3.0.0", "gulp-brotli": "3.0.0",
"gulp-json-transform": "0.5.0", "gulp-json-transform": "0.5.0",
"gulp-rename": "2.0.0", "gulp-rename": "2.0.0",
"html-minifier-terser": "7.2.0", "html-minifier-terser": "7.2.0",
"husky": "9.1.7", "husky": "9.1.7",
"jsdom": "26.0.0", "jsdom": "25.0.1",
"jszip": "3.10.1", "jszip": "3.10.1",
"lint-staged": "15.3.0", "lint-staged": "15.2.10",
"lit-analyzer": "2.0.3", "lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2", "lodash.merge": "4.6.2",
"lodash.template": "4.5.0", "lodash.template": "4.5.0",
"magic-string": "0.30.14",
"map-stream": "0.0.7", "map-stream": "0.0.7",
"object-hash": "3.0.0", "object-hash": "3.0.0",
"open": "10.1.0",
"pinst": "3.0.0", "pinst": "3.0.0",
"prettier": "3.4.2", "prettier": "3.4.2",
"rspack-manifest-plugin": "5.0.3", "rspack-manifest-plugin": "5.0.2",
"serve-handler": "6.1.6",
"sinon": "19.0.2", "sinon": "19.0.2",
"systemjs": "6.15.1",
"tar": "7.4.3", "tar": "7.4.3",
"terser-webpack-plugin": "5.3.11", "terser-webpack-plugin": "5.3.10",
"ts-lit-plugin": "2.0.2", "ts-lit-plugin": "2.0.2",
"typescript": "5.7.3", "typescript": "5.7.2",
"vitest": "2.1.8", "vitest": "2.1.8",
"webpack-stats-plugin": "1.1.3", "webpack-stats-plugin": "1.1.3",
"webpackbar": "7.0.0", "webpackbar": "7.0.0",
@@ -239,7 +247,7 @@
"clean-css": "5.3.3", "clean-css": "5.3.3",
"@lit/reactive-element": "1.6.3", "@lit/reactive-element": "1.6.3",
"@fullcalendar/daygrid": "6.1.15", "@fullcalendar/daygrid": "6.1.15",
"globals": "15.14.0" "globals": "15.13.0"
}, },
"packageManager": "yarn@4.6.0" "packageManager": "yarn@4.5.3"
} }

View File

@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20241224.0" version = "20241127.0"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "The Home Assistant frontend" description = "The Home Assistant frontend"
readme = "README.md" readme = "README.md"
authors = [ authors = [
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"} {name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
] ]
requires-python = ">=3.13.0" requires-python = ">=3.11.0"
[project.urls] [project.urls]
"Homepage" = "https://github.com/home-assistant/frontend" "Homepage" = "https://github.com/home-assistant/frontend"

View File

@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-require-imports */ /* eslint-disable @typescript-eslint/no-var-requires */
// Needs to remain CommonJS until eslint-import-resolver-webpack supports ES modules // Needs to remain CommonJS until eslint-import-resolver-webpack supports ES modules
const rspack = require("./build-scripts/rspack.cjs"); const rspack = require("./build-scripts/rspack.cjs");
const env = require("./build-scripts/env.cjs"); const env = require("./build-scripts/env.cjs");

View File

@@ -30,7 +30,7 @@ set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
# parse input parameters # parse input paramters
if [ -n "$DEVCONTAINER" ]; then if [ -n "$DEVCONTAINER" ]; then
frontendPort=8123 frontendPort=8123
else else

View File

@@ -232,7 +232,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
try { try {
url = new URL(this.redirectUri); url = new URL(this.redirectUri);
} catch (_err) { } catch (err) {
this._error = "Invalid redirect URI"; this._error = "Invalid redirect URI";
return; return;
} }
@@ -328,7 +328,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
try { try {
window.localStorage.setItem("selectedLanguage", JSON.stringify(language)); window.localStorage.setItem("selectedLanguage", JSON.stringify(language));
} catch (_err: any) { } catch (err: any) {
// Ignore // Ignore
} }
} }

View File

@@ -32,7 +32,7 @@ export class CastManager {
// If the cast connection is connected to our Hass. // If the cast connection is connected to our Hass.
public status?: ReceiverStatusMessage; public status?: ReceiverStatusMessage;
private _eventListeners: Record<string, CastEventListener[]> = {}; private _eventListeners: { [event: string]: CastEventListener[] } = {};
constructor(auth?: Auth) { constructor(auth?: Auth) {
this.auth = auth; this.auth = auth;

View File

@@ -1,20 +1,14 @@
type NonNullUndefined<T> = T extends undefined type NonUndefined<T> = T extends undefined ? never : T;
? never
: T extends null
? never
: T;
/** /**
* Ensure that the input is an array or wrap it in an array * Ensure that the input is an array or wrap it in an array
* @param value - The value to ensure is an array * @param value - The value to ensure is an array
*/ */
export function ensureArray(value: undefined): undefined; export function ensureArray(value: undefined): undefined;
export function ensureArray(value: null): null; export function ensureArray<T>(value: T | T[]): NonUndefined<T>[];
export function ensureArray<T>( export function ensureArray<T>(value: T | readonly T[]): NonUndefined<T>[];
value: T | T[] | readonly T[]
): NonNullUndefined<T>[];
export function ensureArray(value) { export function ensureArray(value) {
if (value === undefined || value === null || Array.isArray(value)) { if (value === undefined || Array.isArray(value)) {
return value; return value;
} }
return [value]; return [value];

View File

@@ -66,7 +66,7 @@ export function loadTokens() {
} else { } else {
tokenCache.tokens = null; tokenCache.tokens = null;
} }
} catch (_err: any) { } catch (err: any) {
tokenCache.tokens = null; tokenCache.tokens = null;
} }
} }

View File

@@ -90,9 +90,9 @@ export const lab2rgb = (
x = Xn * lab_xyz(x); x = Xn * lab_xyz(x);
z = Zn * lab_xyz(z); z = Zn * lab_xyz(z);
const r = Math.round(xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z)); // D65 -> sRGB const r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z); // D65 -> sRGB
const g = Math.round(xyz_rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z)); const g = xyz_rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z);
const b_ = Math.round(xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z)); const b_ = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
return [r, g, b_]; return [r, g, b_];
}; };

View File

@@ -8,9 +8,9 @@ export const temperature2rgb = (
): [number, number, number] => { ): [number, number, number] => {
const value = temperature / 100; const value = temperature / 100;
return [ return [
Math.round(temperatureRed(value)), temperatureRed(value),
Math.round(temperatureGreen(value)), temperatureGreen(value),
Math.round(temperatureBlue(value)), temperatureBlue(value),
]; ];
}; };
@@ -59,10 +59,10 @@ const matchMaxScale = (
}; };
export const mired2kelvin = (miredTemperature: number) => export const mired2kelvin = (miredTemperature: number) =>
miredTemperature === 0 ? 1000000 : Math.floor(1000000 / miredTemperature); Math.floor(1000000 / miredTemperature);
export const kelvin2mired = (kelvinTemperature: number) => export const kelvin2mired = (kelvintTemperature: number) =>
kelvinTemperature === 0 ? 1000000 : Math.floor(1000000 / kelvinTemperature); Math.floor(1000000 / kelvintTemperature);
export const rgbww2rgb = ( export const rgbww2rgb = (
rgbww: [number, number, number, number, number], rgbww: [number, number, number, number, number],

View File

@@ -14,8 +14,8 @@ export const hexBlend = (c1: string, c2: string, blend = 50): string => {
c1 = expandHex(c1); c1 = expandHex(c1);
c2 = expandHex(c2); c2 = expandHex(c2);
for (let i = 0; i <= 5; i += 2) { for (let i = 0; i <= 5; i += 2) {
const h1 = parseInt(c1.substring(i, i + 2), 16); const h1 = parseInt(c1.substr(i, 2), 16);
const h2 = parseInt(c2.substring(i, i + 2), 16); const h2 = parseInt(c2.substr(i, 2), 16);
let hex = Math.floor(h2 + (h1 - h2) * (blend / 100)).toString(16); let hex = Math.floor(h2 + (h1 - h2) * (blend / 100)).toString(16);
while (hex.length < 2) hex = "0" + hex; while (hex.length < 2) hex = "0" + hex;
color += hex; color += hex;

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