Compare commits

..

1 Commits

Author SHA1 Message Date
Bram Kragten
7f2fcc73b5 Delay display of init page 2023-09-20 17:05:27 +02:00
964 changed files with 26513 additions and 40811 deletions

View File

@@ -9,7 +9,7 @@ body:
If you have a feature or enhancement request for the frontend, please [start an 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 not not report issues for custom cards.**
[fr]: https://github.com/home-assistant/frontend/discussions [fr]: https://github.com/home-assistant/frontend/discussions
[releases]: https://github.com/home-assistant/home-assistant/releases [releases]: https://github.com/home-assistant/home-assistant/releases
@@ -24,7 +24,6 @@ body:
required: true required: true
- label: I have tried a different browser to see if it is related to my browser. - label: I have tried a different browser to see if it is related to my browser.
required: true required: true
- label: I have tried reproducing the issue in [safe mode](https://www.home-assistant.io/blog/2023/11/01/release-202311/#restarting-into-safe-mode) to rule out problems with unsupported custom resources.
- type: markdown - type: markdown
attributes: attributes:
value: | value: |

View File

@@ -2,7 +2,9 @@
You are amazing! Thanks for contributing to our project! You are amazing! Thanks for contributing to our project!
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed). Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
--> -->
## Breaking change ## Breaking change
<!-- <!--
If your PR contains a breaking change for existing users, it is important If your PR contains a breaking change for existing users, it is important
to tell them what breaks, how to make it work again and why we did this. to tell them what breaks, how to make it work again and why we did this.
@@ -11,8 +13,8 @@
Note: Remove this section if this PR is NOT a breaking change. Note: Remove this section if this PR is NOT a breaking change.
--> -->
## Proposed change ## Proposed change
<!-- <!--
Describe the big picture of your changes here to communicate to the Describe the big picture of your changes here to communicate to the
maintainers why we should accept this pull request. If it fixes a bug maintainers why we should accept this pull request. If it fixes a bug
@@ -20,8 +22,8 @@
in the additional information section. in the additional information section.
--> -->
## Type of change ## Type of change
<!-- <!--
What type of change does your PR introduce to the Home Assistant frontend? What type of change does your PR introduce to the Home Assistant frontend?
NOTE: Please, check only 1! box! NOTE: Please, check only 1! box!
@@ -36,6 +38,7 @@
- [ ] Code quality improvements to existing code or addition of tests - [ ] Code quality improvements to existing code or addition of tests
## Example configuration ## Example configuration
<!-- <!--
Supplying a configuration snippet, makes it easier for a maintainer to test Supplying a configuration snippet, makes it easier for a maintainer to test
your PR. your PR.
@@ -46,6 +49,7 @@
``` ```
## Additional information ## Additional information
<!-- <!--
Details are important, and help maintainers processing your PR. Details are important, and help maintainers processing your PR.
Please be sure to fill out additional details, if applicable. Please be sure to fill out additional details, if applicable.
@@ -56,6 +60,7 @@
- Link to documentation pull request: - Link to documentation pull request:
## Checklist ## Checklist
<!-- <!--
Put an `x` in the boxes that apply. You can also fill these out after Put an `x` in the boxes that apply. You can also fill these out after
creating the PR. If you're unsure about any of them, don't hesitate to ask. creating the PR. If you're unsure about any of them, don't hesitate to ask.

50
.github/labeler.yml vendored
View File

@@ -1,45 +1,31 @@
Build: Build:
- changed-files: - build-scripts/**
- any-glob-to-any-file: - .browserslistrc
- build-scripts/** - gulpfile.js
- .browserslistrc
- gulpfile.js
Cast: Cast:
- changed-files: - cast/src/**
- any-glob-to-any-file: - src/cast/**
- cast/src/**
- src/cast/**
Demo: Demo:
- changed-files: - demo/src/**
- any-glob-to-any-file: - src/fake_data/**
- demo/src/**
- src/fake_data/**
Design: Design:
- changed-files: - gallery/src/**
- any-glob-to-any-file: - src/fake_data/**
- gallery/src/**
- src/fake_data/**
Dependencies: Dependencies:
- changed-files: - package.json
- any-glob-to-any-file: - renovate.json
- package.json - yarn.lock
- renovate.json - .yarn/**
- yarn.lock - .yarnrc.yml
- .yarn/** - .nvmrc
- .yarnrc.yml
- .nvmrc
GitHub Actions: GitHub Actions:
- changed-files: - .github/workflows/**
- any-glob-to-any-file: - .github/*.yml
- .github/workflows/**
- .github/*.yml
Supervisor: Supervisor:
- changed-files: - hassio/src/**
- any-glob-to-any-file:
- hassio/src/**

View File

@@ -21,12 +21,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
with: with:
ref: dev ref: dev
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -57,12 +57,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
with: with:
ref: master ref: master
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn

View File

@@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -55,16 +55,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
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 build-translations build-locale-data
- name: Run Tests - name: Run Tests
run: yarn run test run: yarn run test
build: build:
@@ -73,9 +73,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -85,21 +85,15 @@ jobs:
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
uses: actions/upload-artifact@v3.1.3
with:
name: frontend-bundle-stats
path: build/stats/*.json
if-no-files-found: error
supervisor: supervisor:
name: Build supervisor name: Build supervisor
needs: [lint, test] needs: [lint, test]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -109,9 +103,3 @@ jobs:
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
uses: actions/upload-artifact@v3.1.3
with:
name: supervisor-bundle-stats
path: build/stats/*.json
if-no-files-found: error

View File

@@ -23,7 +23,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v3 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v2

View File

@@ -22,12 +22,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
with: with:
ref: dev ref: dev
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -58,12 +58,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
with: with:
ref: master ref: master
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn

View File

@@ -16,10 +16,10 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn

View File

@@ -21,10 +21,10 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview') if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn

View File

@@ -10,6 +10,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Apply labels - name: Apply labels
uses: actions/labeler@v5.0.0 uses: actions/labeler@v4.3.0
with: with:
sync-labels: true sync-labels: true

View File

@@ -9,10 +9,9 @@ jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v5.0.1 - uses: dessant/lock-threads@v4.0.1
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
process-only: "issues, prs"
issue-lock-inactive-days: "30" issue-lock-inactive-days: "30"
issue-exclude-created-before: "2020-10-01T00:00:00Z" issue-exclude-created-before: "2020-10-01T00:00:00Z"
issue-lock-reason: "" issue-lock-reason: ""

View File

@@ -20,15 +20,15 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -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@v3.1.3 uses: actions/upload-artifact@v3
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@v3.1.3 uses: actions/upload-artifact@v3
with: with:
name: translations name: translations
path: translations.tar.gz path: translations.tar.gz

View File

@@ -1,25 +0,0 @@
name: RelativeCI
on:
workflow_run:
workflows: [CI]
types:
- completed
jobs:
upload:
name: Upload stats
if: ${{ github.event.workflow_run.conclusion == 'success' }}
strategy:
matrix:
bundle: [frontend, supervisor]
build: [modern, legacy]
runs-on: ubuntu-latest
steps:
- name: Send bundle stats and build information to RelativeCI
uses: relative-ci/agent-action@v2.1.10
with:
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
token: ${{ github.token }}
artifactName: ${{ format('{0}-bundle-stats', matrix.bundle) }}
webpackStatsFile: ${{ format('{0}-{1}.json', matrix.bundle, matrix.build) }}

View File

@@ -23,18 +23,18 @@ jobs:
contents: write # Required to upload release assets contents: write # Required to upload release assets
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Verify version - name: Verify version
uses: home-assistant/actions/helpers/verify-version@master 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@v4
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.1 uses: actions/setup-node@v3.8.1
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -74,7 +74,7 @@ jobs:
echo "home-assistant-frontend==$version" > ./requirements.txt echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2023.10.5 uses: home-assistant/wheels@2023.04.0
with: with:
abi: cp311 abi: cp311
tag: musllinux_1_2 tag: musllinux_1_2

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 90 days stale policy - name: 90 days stale policy
uses: actions/stale@v9.0.0 uses: actions/stale@v8.0.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90 days-before-stale: 90

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.0.0
- name: Upload Translations - name: Upload Translations
run: | run: |

3
.gitignore vendored
View File

@@ -47,6 +47,3 @@ src/cast/dev_const.ts
# Home Assistant config # Home Assistant config
/config/ /config/
# Jetbrains
/.idea/

2
.nvmrc
View File

@@ -1 +1 @@
lts/iron 18

View File

@@ -1,4 +1,3 @@
CLA.md CLA.md
CODE_OF_CONDUCT.md CODE_OF_CONDUCT.md
LICENSE.md LICENSE.md
PULL_REQUEST_TEMPLATE.md

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

874
.yarn/releases/yarn-3.6.3.cjs vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,11 @@
compressionLevel: mixed
defaultSemverRangePrefix: "" defaultSemverRangePrefix: ""
enableGlobalCache: false
nodeLinker: node-modules nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.0.2.cjs plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.6.3.cjs

View File

@@ -1,56 +0,0 @@
import defineProvider from "@babel/helper-define-polyfill-provider";
// List of polyfill keys with supported browser targets for the functionality
const PolyfillSupport = {
fetch: {
android: 42,
chrome: 42,
edge: 14,
firefox: 39,
ios: 10.3,
opera: 29,
opera_mobile: 29,
safari: 10.1,
samsung: 4.0,
},
proxy: {
android: 49,
chrome: 49,
edge: 12,
firefox: 18,
ios: 10.0,
opera: 36,
opera_mobile: 36,
safari: 10.0,
samsung: 5.0,
},
};
// Map of global variables and/or instance and static properties to the
// corresponding polyfill key and actual module to import
const polyfillMap = {
global: {
Proxy: { key: "proxy", module: "proxy-polyfill" },
fetch: { key: "fetch", module: "unfetch/polyfill" },
},
instance: {},
static: {},
};
// Create plugin using the same factory as for CoreJS
export default defineProvider(
({ createMetaResolver, debug, shouldInjectPolyfill }) => {
const resolvePolyfill = createMetaResolver(polyfillMap);
return {
name: "HA Custom",
polyfills: PolyfillSupport,
usageGlobal(meta, utils) {
const polyfill = resolvePolyfill(meta);
if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
debug(polyfill.desc.key);
utils.injectGlobalImport(polyfill.desc.module);
}
},
};
}
);

View File

@@ -1,7 +1,6 @@
const path = require("path"); const path = require("path");
const env = require("./env.cjs"); const env = require("./env.cjs");
const paths = require("./paths.cjs"); const paths = require("./paths.cjs");
const { dependencies } = require("../package.json");
// GitHub base URL to use for production source maps // GitHub base URL to use for production source maps
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version // Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
@@ -13,7 +12,11 @@ module.exports.sourceMapURL = () => {
}; };
// Files from NPM Packages that should not be imported // Files from NPM Packages that should not be imported
module.exports.ignorePackages = () => []; // eslint-disable-next-line unused-imports/no-unused-vars
module.exports.ignorePackages = ({ latestBuild }) => [
// Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"),
];
// Files from NPM packages that we should replace with empty file // Files from NPM packages that we should replace with empty file
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) => module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
@@ -32,6 +35,8 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
require.resolve( require.resolve(
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts") path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
), ),
// This polyfill is loaded in workers to support ES5, filter it out.
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
// Icons in supervisor conflict with icons in HA so we don't load. // Icons in supervisor conflict with icons in HA so we don't load.
isHassioBuild && isHassioBuild &&
require.resolve( require.resolve(
@@ -86,12 +91,14 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
setSpreadProperties: true, setSpreadProperties: true,
}, },
browserslistEnv: latestBuild ? "modern" : "legacy", browserslistEnv: latestBuild ? "modern" : "legacy",
// Must be unambiguous because some dependencies are CommonJS only
sourceType: "unambiguous",
presets: [ presets: [
[ [
"@babel/preset-env", "@babel/preset-env",
{ {
useBuiltIns: latestBuild ? false : "usage", useBuiltIns: latestBuild ? false : "entry",
corejs: latestBuild ? false : dependencies["core-js"], corejs: latestBuild ? false : { version: "3.32", proposals: true },
bugfixes: true, bugfixes: true,
shippedProposals: true, shippedProposals: true,
}, },
@@ -109,39 +116,27 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
ignoreModuleNotFound: true, ignoreModuleNotFound: true,
}, },
], ],
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/custom-polyfill-plugin.js"
),
{ method: "usage-global" },
],
// Minify template literals for production // Minify template literals for production
isProdBuild && [ isProdBuild && [
"template-html-minifier", "template-html-minifier",
{ {
modules: { modules: {
...Object.fromEntries( lit: [
["lit", "lit-element", "lit-html"].map((m) => [ "html",
m, { name: "svg", encapsulation: "svg" },
[ { name: "css", encapsulation: "style" },
"html", ],
{ name: "svg", encapsulation: "svg" }, "@polymer/polymer/lib/utils/html-tag": ["html"],
{ name: "css", encapsulation: "style" },
],
])
),
"@polymer/polymer/lib/utils/html-tag.js": ["html"],
}, },
strictCSS: true, strictCSS: true,
htmlMinifier: module.exports.htmlMinifierOptions, htmlMinifier: module.exports.htmlMinifierOptions,
failOnError: false, // we can turn this off in case of false positives failOnError: true, // we can turn this off in case of false positives
}, },
], ],
// Import helpers and regenerator from runtime package // Import helpers and regenerator from runtime package
[ [
"@babel/plugin-transform-runtime", "@babel/plugin-transform-runtime",
{ version: dependencies["@babel/runtime"] }, { version: require("../package.json").dependencies["@babel/runtime"] },
], ],
// Support some proposals still in TC39 process // Support some proposals still in TC39 process
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
@@ -152,21 +147,9 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
/node_modules[\\/]webpack[\\/]buildin/, /node_modules[\\/]webpack[\\/]buildin/,
], ],
sourceMaps: !isTestBuild, sourceMaps: !isTestBuild,
overrides: [
{
// Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
// Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
sourceType: "unambiguous",
include: /\/node_modules\//,
exclude: [
"element-internals-polyfill",
"@?lit(?:-labs|-element|-html)?",
].map((p) => new RegExp(`/node_modules/${p}/`)),
},
],
}); });
const nameSuffix = (latestBuild) => (latestBuild ? "-modern" : "-legacy"); const nameSuffix = (latestBuild) => (latestBuild ? "-latest" : "-es5");
const outputPath = (outputRoot, latestBuild) => const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5"); path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
@@ -200,7 +183,7 @@ const publicPath = (latestBuild, root = "") =>
module.exports.config = { module.exports.config = {
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) { app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
return { return {
name: "frontend" + nameSuffix(latestBuild), name: "app" + nameSuffix(latestBuild),
entry: { entry: {
service_worker: "./src/entrypoints/service_worker.ts", service_worker: "./src/entrypoints/service_worker.ts",
app: "./src/entrypoints/app.ts", app: "./src/entrypoints/app.ts",

View File

@@ -30,8 +30,8 @@ gulp.task(
env.useWDS() env.useWDS()
? "wds-watch-app" ? "wds-watch-app"
: env.useRollup() : env.useRollup()
? "rollup-watch-app" ? "rollup-watch-app"
: "webpack-watch-app" : "webpack-watch-app"
) )
); );
@@ -45,8 +45,8 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-app", "copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app", env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod"),
// Don't compress running tests // Don't compress running tests
...(env.isTestBuild() ? [] : ["compress-app"]) ...(env.isTestBuild() ? [] : ["compress-app"]),
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod")
) )
); );

View File

@@ -161,10 +161,6 @@ gulp.task("fetch-lokalise", async function () {
}) })
); );
}) })
.catch((err) => {
console.error(err);
throw err;
})
) )
); );
}); });

View File

@@ -4,7 +4,6 @@ import fs from "fs-extra";
import gulp from "gulp"; import gulp from "gulp";
import path from "path"; import path from "path";
import paths from "../paths.cjs"; import paths from "../paths.cjs";
import env from "../env.cjs";
const npmPath = (...parts) => const npmPath = (...parts) =>
path.resolve(paths.polymer_dir, "node_modules", ...parts); path.resolve(paths.polymer_dir, "node_modules", ...parts);
@@ -63,9 +62,6 @@ function copyPolyfills(staticDir) {
} }
function copyLoaderJS(staticDir) { function copyLoaderJS(staticDir) {
if (!env.useRollup()) {
return;
}
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js")); copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js"));
copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js")); copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js"));

View File

@@ -1,54 +1,51 @@
import { deleteSync } from "del"; import { deleteSync } from "del";
import { mkdir, readFile, writeFile } from "fs/promises"; import { mkdir, readFile, writeFile } from "fs/promises";
import gulp from "gulp"; import gulp from "gulp";
import { join, resolve } from "node:path"; import path from "path";
import paths from "../paths.cjs"; import paths from "../paths.cjs";
const formatjsDir = join(paths.polymer_dir, "node_modules", "@formatjs"); const outDir = path.join(paths.build_dir, "locale-data");
const outDir = join(paths.build_dir, "locale-data");
const INTL_POLYFILLS = { const INTL_PACKAGES = {
"intl-relativetimeformat": "RelativeTimeFormat",
"intl-datetimeformat": "DateTimeFormat", "intl-datetimeformat": "DateTimeFormat",
"intl-numberformat": "NumberFormat",
"intl-displaynames": "DisplayNames", "intl-displaynames": "DisplayNames",
"intl-listformat": "ListFormat", "intl-listformat": "ListFormat",
"intl-numberformat": "NumberFormat",
"intl-relativetimeformat": "RelativeTimeFormat",
}; };
const convertToJSON = async ( const convertToJSON = async (pkg, lang) => {
pkg,
lang,
subDir = "locale-data",
addFunc = "__addLocaleData",
skipMissing = true
) => {
let localeData; let localeData;
try { try {
localeData = await readFile( localeData = await readFile(
join(formatjsDir, pkg, subDir, `${lang}.js`), path.resolve(
paths.polymer_dir,
`node_modules/@formatjs/${pkg}/locale-data/${lang}.js`
),
"utf-8" "utf-8"
); );
} catch (e) { } catch (e) {
// Ignore if language is missing (i.e. not supported by @formatjs) // Ignore if language is missing (i.e. not supported by @formatjs)
if (e.code === "ENOENT" && skipMissing) { if (e.code === "ENOENT") {
console.warn(`Skipped missing data for language ${lang} from ${pkg}`);
return; return;
} else {
throw e;
} }
throw e;
} }
// Convert to JSON // Convert to JSON
const obj = INTL_POLYFILLS[pkg]; const className = INTL_PACKAGES[pkg];
const dataRegex = new RegExp( localeData = localeData
`Intl\\.${obj}\\.${addFunc}\\((?<data>.*)\\)`, .replace(
"s" new RegExp(
); `\\/\\*\\s*@generated\\s*\\*\\/\\s*\\/\\/\\s*prettier-ignore\\s*if\\s*\\(Intl\\.${className}\\s*&&\\s*typeof\\s*Intl\\.${className}\\.__addLocaleData\\s*===\\s*'function'\\)\\s*{\\s*Intl\\.${className}\\.__addLocaleData\\(`,
localeData = localeData.match(dataRegex)?.groups?.data; "im"
if (!localeData) { ),
throw Error(`Failed to extract data for language ${lang} from ${pkg}`); ""
} )
.replace(/\)\s*}/im, "");
// Parse to validate JSON, then stringify to minify // Parse to validate JSON, then stringify to minify
localeData = JSON.stringify(JSON.parse(localeData)); localeData = JSON.stringify(JSON.parse(localeData));
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData); await writeFile(path.join(outDir, `${pkg}/${lang}.json`), localeData);
}; };
gulp.task("clean-locale-data", async () => deleteSync([outDir])); gulp.task("clean-locale-data", async () => deleteSync([outDir]));
@@ -56,27 +53,17 @@ gulp.task("clean-locale-data", async () => deleteSync([outDir]));
gulp.task("create-locale-data", async () => { gulp.task("create-locale-data", async () => {
const translationMeta = JSON.parse( const translationMeta = JSON.parse(
await readFile( await readFile(
resolve(paths.translations_src, "translationMetadata.json"), path.resolve(paths.translations_src, "translationMetadata.json"),
"utf-8" "utf-8"
) )
); );
const conversions = []; const conversions = [];
for (const pkg of Object.keys(INTL_POLYFILLS)) { for (const pkg of Object.keys(INTL_PACKAGES)) {
// eslint-disable-next-line no-await-in-loop await mkdir(path.join(outDir, pkg), { recursive: true });
await mkdir(join(outDir, pkg), { recursive: true });
for (const lang of Object.keys(translationMeta)) { for (const lang of Object.keys(translationMeta)) {
conversions.push(convertToJSON(pkg, lang)); conversions.push(convertToJSON(pkg, lang));
} }
} }
conversions.push(
convertToJSON(
"intl-datetimeformat",
"add-all-tz",
".",
"__addTZData",
false
)
);
await Promise.all(conversions); await Promise.all(conversions);
}); });

View File

@@ -1,7 +1,12 @@
import { createHash } from "crypto"; import { createHash } from "crypto";
import { deleteSync } from "del"; import { deleteSync } from "del";
import { mkdirSync, readdirSync, readFileSync, renameSync } from "fs"; import {
import { writeFile } from "node:fs/promises"; mkdirSync,
readdirSync,
readFileSync,
renameSync,
writeFile,
} from "fs";
import gulp from "gulp"; import gulp from "gulp";
import flatmap from "gulp-flatmap"; import flatmap from "gulp-flatmap";
import transform from "gulp-json-transform"; import transform from "gulp-json-transform";
@@ -131,23 +136,27 @@ gulp.task("ensure-translations-build-dir", async () => {
mkdirSync(workDir, { recursive: true }); mkdirSync(workDir, { recursive: true });
}); });
gulp.task("create-test-metadata", () => gulp.task("create-test-metadata", (cb) => {
env.isProdBuild() writeFile(
? Promise.resolve() workDir + "/testMetadata.json",
: writeFile( JSON.stringify({
workDir + "/testMetadata.json", test: {
JSON.stringify({ test: { nativeName: "Test" } }) nativeName: "Test",
) },
); }),
cb
);
});
gulp.task("create-test-translation", () => gulp.task(
env.isProdBuild() "create-test-translation",
? Promise.resolve() gulp.series("create-test-metadata", () =>
: gulp gulp
.src(path.join(paths.translations_src, "en.json")) .src(path.join(paths.translations_src, "en.json"))
.pipe(transform((data, _file) => recursiveEmpty(data))) .pipe(transform((data, _file) => recursiveEmpty(data)))
.pipe(rename("test.json")) .pipe(rename("test.json"))
.pipe(gulp.dest(workDir)) .pipe(gulp.dest(workDir))
)
); );
/** /**
@@ -179,11 +188,16 @@ gulp.task("build-master-translation", () => {
gulp.task("build-merged-translations", () => gulp.task("build-merged-translations", () =>
gulp gulp
.src([ .src(
inFrontendDir + "/*.json", [
"!" + inFrontendDir + "/en.json", inFrontendDir + "/*.json",
...(env.isProdBuild() ? [] : [workDir + "/test.json"]), "!" + inFrontendDir + "/en.json",
]) workDir + "/test.json",
],
{
allowEmpty: true,
}
)
.pipe(transform((data, file) => lokaliseTransform(data, data, file))) .pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe( .pipe(
flatmap((stream, file) => { flatmap((stream, file) => {
@@ -363,11 +377,14 @@ gulp.task("build-translation-flatten-supervisor", () =>
gulp.task("build-translation-write-metadata", () => gulp.task("build-translation-write-metadata", () =>
gulp gulp
.src([ .src(
path.join(paths.translations_src, "translationMetadata.json"), [
...(env.isProdBuild() ? [] : [workDir + "/testMetadata.json"]), path.join(paths.translations_src, "translationMetadata.json"),
workDir + "/translationFingerprints.json", workDir + "/testMetadata.json",
]) workDir + "/translationFingerprints.json",
],
{ allowEmpty: true }
)
.pipe(merge({})) .pipe(merge({}))
.pipe( .pipe(
transform((data) => { transform((data) => {
@@ -398,7 +415,7 @@ gulp.task("build-translation-write-metadata", () =>
gulp.task( gulp.task(
"create-translations", "create-translations",
gulp.series( gulp.series(
gulp.parallel("create-test-metadata", "create-test-translation"), ...(env.isProdBuild() ? [] : ["create-test-translation"]),
"build-master-translation", "build-master-translation",
"build-merged-translations", "build-merged-translations",
gulp.parallel(...splitTasks), gulp.parallel(...splitTasks),

View File

@@ -1,8 +1,6 @@
const { existsSync } = require("fs"); const { existsSync } = require("fs");
const path = require("path"); const path = require("path");
const webpack = require("webpack"); const webpack = require("webpack");
const { StatsWriterPlugin } = require("webpack-stats-plugin");
const filterStats = require("@bundle-stats/plugin-webpack-filter").default;
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin"); const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const log = require("fancy-log"); const log = require("fancy-log");
@@ -51,8 +49,8 @@ const createWebpackConfig = ({
devtool: isTestBuild devtool: isTestBuild
? false ? false
: isProdBuild : isProdBuild
? "nosources-source-map" ? "nosources-source-map"
: "eval-cheap-module-source-map", : "eval-cheap-module-source-map",
entry, entry,
node: false, node: false,
module: { module: {
@@ -154,15 +152,6 @@ const createWebpackConfig = ({
) )
), ),
!isProdBuild && new LogStartCompilePlugin(), !isProdBuild && new LogStartCompilePlugin(),
isProdBuild &&
new StatsWriterPlugin({
filename: path.relative(
outputPath,
path.join(paths.build_dir, "stats", `${name}.json`)
),
stats: { assets: true, chunks: true, modules: true },
transform: (stats) => JSON.stringify(filterStats(stats)),
}),
].filter(Boolean), ].filter(Boolean),
resolve: { resolve: {
extensions: [".ts", ".js", ".json"], extensions: [".ts", ".js", ".json"],
@@ -176,14 +165,11 @@ const createWebpackConfig = ({
"lit/directives/guard$": "lit/directives/guard.js", "lit/directives/guard$": "lit/directives/guard.js",
"lit/directives/cache$": "lit/directives/cache.js", "lit/directives/cache$": "lit/directives/cache.js",
"lit/directives/repeat$": "lit/directives/repeat.js", "lit/directives/repeat$": "lit/directives/repeat.js",
"lit/directives/live$": "lit/directives/live.js",
"lit/polyfill-support$": "lit/polyfill-support.js", "lit/polyfill-support$": "lit/polyfill-support.js",
"@lit-labs/virtualizer/layouts/grid": "@lit-labs/virtualizer/layouts/grid":
"@lit-labs/virtualizer/layouts/grid.js", "@lit-labs/virtualizer/layouts/grid.js",
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver": "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver":
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js", "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js",
"@lit-labs/observers/resize-controller":
"@lit-labs/observers/resize-controller.js",
}, },
}, },
output: { output: {
@@ -191,12 +177,11 @@ const createWebpackConfig = ({
filename: ({ chunk }) => filename: ({ chunk }) =>
!isProdBuild || isStatsBuild || dontHash.has(chunk.name) !isProdBuild || isStatsBuild || dontHash.has(chunk.name)
? "[name].js" ? "[name].js"
: "[name].[contenthash].js", : "[name]-[contenthash].js",
chunkFilename: chunkFilename:
isProdBuild && !isStatsBuild ? "[name].[contenthash].js" : "[name].js", isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
assetModuleFilename: assetModuleFilename:
isProdBuild && !isStatsBuild ? "[id].[contenthash][ext]" : "[id][ext]", isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
crossOriginLoading: "use-credentials",
hashFunction: "xxhash64", hashFunction: "xxhash64",
hashDigest: "base64url", hashDigest: "base64url",
hashDigestLength: 11, // full length of 64 bit base64url hashDigestLength: 11, // full length of 64 bit base64url

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,4 +1,4 @@
import "../../../src/resources/safari-14-attachshadow-patch"; import "../../../src/resources/safari-14-attachshadow-patch";
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "./layout/hc-connect"; import "./layout/hc-connect";
import("../../../src/resources/ha-style");

View File

@@ -3,7 +3,7 @@ import { mdiCast, mdiCastConnected } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket"; import { Auth, Connection } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { CastManager } from "../../../../src/cast/cast_manager"; import { CastManager } from "../../../../src/cast/cast_manager";
import { import {
@@ -22,9 +22,8 @@ import "../../../../src/components/ha-svg-icon";
import { import {
getLegacyLovelaceCollection, getLegacyLovelaceCollection,
getLovelaceCollection, getLovelaceCollection,
LovelaceConfig,
} from "../../../../src/data/lovelace"; } from "../../../../src/data/lovelace";
import { isStrategyDashboard } from "../../../../src/data/lovelace/config/types";
import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config"; import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout"; import "./hc-layout";
@@ -39,10 +38,10 @@ class HcCast extends LitElement {
@state() private askWrite = false; @state() private askWrite = false;
@state() private lovelaceViews?: LovelaceViewConfig[] | null; @state() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult { protected render(): TemplateResult {
if (this.lovelaceViews === undefined) { if (this.lovelaceConfig === undefined) {
return html`<hass-loading-screen no-toolbar></hass-loading-screen>`; return html`<hass-loading-screen no-toolbar></hass-loading-screen>`;
} }
@@ -73,44 +72,43 @@ class HcCast extends LitElement {
${error ${error
? html` <div class="card-content">${error}</div> ` ? html` <div class="card-content">${error}</div> `
: !this.castManager.status : !this.castManager.status
? html` ? html`
<p class="center-item"> <p class="center-item">
<mwc-button raised @click=${this._handleLaunch}> <mwc-button raised @click=${this._handleLaunch}>
<ha-svg-icon .path=${mdiCast}></ha-svg-icon> <ha-svg-icon .path=${mdiCast}></ha-svg-icon>
Start Casting Start Casting
</mwc-button> </mwc-button>
</p> </p>
` `
: html` : html`
<div class="section-header">PICK A VIEW</div> <div class="section-header">PICK A VIEW</div>
<paper-listbox <paper-listbox
attr-for-selected="data-path" attr-for-selected="data-path"
.selected=${this.castManager.status.lovelacePath || ""} .selected=${this.castManager.status.lovelacePath || ""}
> >
${( ${(this.lovelaceConfig
this.lovelaceViews ?? [ ? this.lovelaceConfig.views
generateDefaultViewConfig({}, {}, {}, {}, () => ""), : [generateDefaultViewConfig({}, {}, {}, {}, () => "")]
] ).map(
).map( (view, idx) => html`
(view, idx) => html` <paper-icon-item
<paper-icon-item @click=${this._handlePickView}
@click=${this._handlePickView} data-path=${view.path || idx}
data-path=${view.path || idx} >
> ${view.icon
${view.icon ? html`
? html` <ha-icon
<ha-icon .icon=${view.icon}
.icon=${view.icon} slot="item-icon"
slot="item-icon" ></ha-icon>
></ha-icon> `
` : ""}
: ""} ${view.title || view.path}
${view.title || view.path} </paper-icon-item>
</paper-icon-item> `
` )}
)} </paper-listbox>
</paper-listbox> `}
`}
<div class="card-actions"> <div class="card-actions">
${this.castManager.status ${this.castManager.status
? html` ? html`
@@ -138,15 +136,11 @@ class HcCast extends LitElement {
llColl.refresh().then( llColl.refresh().then(
() => { () => {
llColl.subscribe((config) => { llColl.subscribe((config) => {
if (isStrategyDashboard(config)) { this.lovelaceConfig = config;
this.lovelaceViews = null;
} else {
this.lovelaceViews = config.views;
}
}); });
}, },
async () => { async () => {
this.lovelaceViews = null; this.lovelaceConfig = null;
} }
); );
@@ -165,7 +159,9 @@ class HcCast extends LitElement {
toggleAttribute( toggleAttribute(
this, this,
"hide-icons", "hide-icons",
this.lovelaceViews ? !this.lovelaceViews.some((view) => view.icon) : true this.lovelaceConfig
? !this.lovelaceConfig.views.some((view) => view.icon)
: true
); );
} }

View File

@@ -1,5 +1,6 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { mdiCastConnected, mdiCast } from "@mdi/js"; import { mdiCastConnected, mdiCast } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import { import {
Auth, Auth,
Connection, Connection,
@@ -23,7 +24,6 @@ import "../../../../src/components/ha-svg-icon";
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/hass-loading-screen";
import { registerServiceWorker } from "../../../../src/util/register-service-worker"; import { registerServiceWorker } from "../../../../src/util/register-service-worker";
import "./hc-layout"; import "./hc-layout";
import "../../../../src/components/ha-textfield";
const seeFAQ = (qid) => html` const seeFAQ = (qid) => html`
See <a href="./faq.html${qid ? `#${qid}` : ""}">the FAQ</a> for more See <a href="./faq.html${qid ? `#${qid}` : ""}">the FAQ</a> for more
@@ -33,13 +33,13 @@ const translateErr = (err) =>
err === ERR_CANNOT_CONNECT err === ERR_CANNOT_CONNECT
? "Unable to connect" ? "Unable to connect"
: err === ERR_HASS_HOST_REQUIRED : err === ERR_HASS_HOST_REQUIRED
? "Please enter a Home Assistant URL." ? "Please enter a Home Assistant URL."
: err === ERR_INVALID_HTTPS_TO_HTTP : err === ERR_INVALID_HTTPS_TO_HTTP
? html` ? html`
Cannot connect to Home Assistant instances over "http://". Cannot connect to Home Assistant instances over "http://".
${seeFAQ("https")} ${seeFAQ("https")}
` `
: `Unknown error (${err}).`; : `Unknown error (${err}).`;
const INTRO = html` const INTRO = html`
<p> <p>
@@ -116,11 +116,13 @@ export class HcConnect extends LitElement {
To get started, enter your Home Assistant URL and click authorize. To get started, enter your Home Assistant URL and click authorize.
If you want a preview instead, click the show demo button. If you want a preview instead, click the show demo button.
</p> </p>
<ha-textfield <p>
label="Home Assistant URL" <paper-input
placeholder="https://abcdefghijklmnop.ui.nabu.casa" label="Home Assistant URL"
@keydown=${this._handleInputKeyDown} placeholder="https://abcdefghijklmnop.ui.nabu.casa"
></ha-textfield> @keydown=${this._handleInputKeyDown}
></paper-input>
</p>
${this.error ? html` <p class="error">${this.error}</p> ` : ""} ${this.error ? html` <p class="error">${this.error}</p> ` : ""}
</div> </div>
<div class="card-actions"> <div class="card-actions">
@@ -194,7 +196,7 @@ export class HcConnect extends LitElement {
} }
private async _handleConnect() { private async _handleConnect() {
const inputEl = this.shadowRoot!.querySelector("ha-textfield")!; const inputEl = this.shadowRoot!.querySelector("paper-input")!;
const value = inputEl.value || ""; const value = inputEl.value || "";
this.error = undefined; this.error = undefined;
@@ -313,10 +315,6 @@ export class HcConnect extends LitElement {
.spacer { .spacer {
flex: 1; flex: 1;
} }
ha-textfield {
width: 100%;
}
`; `;
} }
} }

View File

@@ -1,5 +1,7 @@
import { LovelaceCardConfig } from "../../../../src/data/lovelace/config/card"; import {
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; LovelaceCardConfig,
LovelaceConfig,
} from "../../../../src/data/lovelace";
import { castContext } from "../cast_context"; import { castContext } from "../cast_context";
export const castDemoLovelace: () => LovelaceConfig = () => { export const castDemoLovelace: () => LovelaceConfig = () => {

View File

@@ -1,7 +1,7 @@
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { mockHistory } from "../../../../demo/src/stubs/history"; import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { import {
MockHomeAssistant, MockHomeAssistant,
provideHass, provideHass,

View File

@@ -1,7 +1,7 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { Lovelace } from "../../../../src/panels/lovelace/types"; import { Lovelace } from "../../../../src/panels/lovelace/types";
import "../../../../src/panels/lovelace/views/hui-view"; import "../../../../src/panels/lovelace/views/hui-view";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
@@ -14,8 +14,7 @@ import "./hc-launch-screen";
class HcLovelace extends LitElement { class HcLovelace extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) @property({ attribute: false }) public lovelaceConfig!: LovelaceConfig;
public lovelaceConfig!: LovelaceConfig;
@property() public viewPath?: string | number; @property() public viewPath?: string | number;

View File

@@ -21,27 +21,17 @@ import {
import { atLeastVersion } from "../../../../src/common/config/version"; import { atLeastVersion } from "../../../../src/common/config/version";
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click"; import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
import { import {
fetchResources,
getLegacyLovelaceCollection, getLegacyLovelaceCollection,
getLovelaceCollection, getLovelaceCollection,
} from "../../../../src/data/lovelace";
import {
isStrategyDashboard,
LegacyLovelaceConfig, LegacyLovelaceConfig,
LovelaceConfig, LovelaceConfig,
LovelaceDashboardStrategyConfig, } from "../../../../src/data/lovelace";
} from "../../../../src/data/lovelace/config/types";
import { fetchResources } from "../../../../src/data/lovelace/resource";
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources"; import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
import { HassElement } from "../../../../src/state/hass-element"; import { HassElement } from "../../../../src/state/hass-element";
import { castContext } from "../cast_context"; import { castContext } from "../cast_context";
import "./hc-launch-screen"; import "./hc-launch-screen";
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
strategy: {
type: "original-states",
},
};
let resourcesLoaded = false; let resourcesLoaded = false;
@customElement("hc-main") @customElement("hc-main")
export class HcMain extends HassElement { export class HcMain extends HassElement {
@@ -101,16 +91,14 @@ export class HcMain extends HassElement {
.lovelaceConfig=${this._lovelaceConfig} .lovelaceConfig=${this._lovelaceConfig}
.viewPath=${this._lovelacePath} .viewPath=${this._lovelacePath}
.urlPath=${this._urlPath} .urlPath=${this._urlPath}
@config-refresh=${this._generateDefaultLovelaceConfig} @config-refresh=${this._generateLovelaceConfig}
></hc-lovelace> ></hc-lovelace>
`; `;
} }
protected firstUpdated(changedProps) { protected firstUpdated(changedProps) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
import("./hc-lovelace"); import("../second-load");
import("../../../../src/resources/ha-style");
window.addEventListener("location-changed", () => { window.addEventListener("location-changed", () => {
const panelPath = `/${this._urlPath || "lovelace"}/`; const panelPath = `/${this._urlPath || "lovelace"}/`;
if (location.pathname.startsWith(panelPath)) { if (location.pathname.startsWith(panelPath)) {
@@ -270,6 +258,7 @@ export class HcMain extends HassElement {
{ {
strategy: { strategy: {
type: "energy", type: "energy",
options: { show_date_selection: true },
}, },
}, },
], ],
@@ -292,20 +281,9 @@ export class HcMain extends HassElement {
// configuration. // configuration.
try { try {
await llColl.refresh(); await llColl.refresh();
this._unsubLovelace = llColl.subscribe(async (rawConfig) => { this._unsubLovelace = llColl.subscribe((lovelaceConfig) =>
if (isStrategyDashboard(rawConfig)) { this._handleNewLovelaceConfig(lovelaceConfig)
const { generateLovelaceDashboardStrategy } = await import( );
"../../../../src/panels/lovelace/strategies/get-strategy"
);
const config = await generateLovelaceDashboardStrategy(
rawConfig.strategy,
this.hass!
);
this._handleNewLovelaceConfig(config);
} else {
this._handleNewLovelaceConfig(rawConfig);
}
});
} catch (err: any) { } catch (err: any) {
if ( if (
atLeastVersion(this.hass.connection.haVersion, 0, 107) && atLeastVersion(this.hass.connection.haVersion, 0, 107) &&
@@ -319,7 +297,7 @@ export class HcMain extends HassElement {
} }
// Generate a Lovelace config. // Generate a Lovelace config.
this._unsubLovelace = () => undefined; this._unsubLovelace = () => undefined;
await this._generateDefaultLovelaceConfig(); await this._generateLovelaceConfig();
} }
} }
if (!resourcesLoaded) { if (!resourcesLoaded) {
@@ -328,21 +306,24 @@ export class HcMain extends HassElement {
? await fetchResources(this.hass!.connection) ? await fetchResources(this.hass!.connection)
: (this._lovelaceConfig as LegacyLovelaceConfig).resources; : (this._lovelaceConfig as LegacyLovelaceConfig).resources;
if (resources) { if (resources) {
loadLovelaceResources(resources, this.hass!); loadLovelaceResources(resources, this.hass!.auth.data.hassUrl);
} }
} }
this._sendStatus(); this._sendStatus();
} }
private async _generateDefaultLovelaceConfig() { private async _generateLovelaceConfig() {
const { generateLovelaceDashboardStrategy } = await import( const { generateLovelaceDashboardStrategy } = await import(
"../../../../src/panels/lovelace/strategies/get-strategy" "../../../../src/panels/lovelace/strategies/get-strategy"
); );
this._handleNewLovelaceConfig( this._handleNewLovelaceConfig(
await generateLovelaceDashboardStrategy( await generateLovelaceDashboardStrategy(
DEFAULT_CONFIG.strategy, {
this.hass! hass: this.hass!,
narrow: false,
},
"original-states"
) )
); );
} }

View File

@@ -0,0 +1,3 @@
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "./layout/hc-lovelace";

View File

@@ -8,67 +8,25 @@
"src": "/static/icons/favicon-192x192.png", "src": "/static/icons/favicon-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "maskable any"
}, },
{ {
"src": "/static/icons/favicon-384x384.png", "src": "/static/icons/favicon-384x384.png",
"sizes": "384x384", "sizes": "384x384",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "maskable any"
}, },
{ {
"src": "/static/icons/favicon-512x512.png", "src": "/static/icons/favicon-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "maskable any"
}, },
{ {
"src": "/static/icons/favicon-1024x1024.png", "src": "/static/icons/favicon-1024x1024.png",
"sizes": "1024x1024", "sizes": "1024x1024",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "maskable any"
},
{
"src": "/static/icons/maskable_icon-48x48.png",
"sizes": "48x48",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icons/maskable_icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
} }
], ],
"lang": "en-US", "lang": "en-US",

View File

@@ -3,15 +3,6 @@ import { DemoConfig } from "../types";
export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
convertEntities({ convertEntities({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",
attributes: {
supported_features: 15,
friendly_name: "Shopping List",
icon: "mdi:cart",
},
},
"zone.home": { "zone.home": {
entity_id: "zone.home", entity_id: "zone.home",
state: "zoning", state: "zoning",

View File

@@ -3,15 +3,6 @@ import { DemoConfig } from "../types";
export const demoEntitiesJimpower: DemoConfig["entities"] = () => export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
convertEntities({ convertEntities({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",
attributes: {
supported_features: 15,
friendly_name: "Shopping List",
icon: "mdi:cart",
},
},
"zone.powertec": { "zone.powertec": {
entity_id: "zone.powertec", entity_id: "zone.powertec",
state: "zoning", state: "zoning",

View File

@@ -4,11 +4,16 @@ export const demoThemeJimpower = () => ({
"primary-color": "#5294E2", "primary-color": "#5294E2",
"label-badge-red": "var(--accent-color)", "label-badge-red": "var(--accent-color)",
"paper-tabs-selection-bar-color": "green", "paper-tabs-selection-bar-color": "green",
"paper-slider-knob-color": "var(--accent-color)",
"light-primary-color": "var(--accent-color)", "light-primary-color": "var(--accent-color)",
"primary-background-color": "#383C45", "primary-background-color": "#383C45",
"primary-text-color": "#FFFFFF", "primary-text-color": "#FFFFFF",
"paper-item-selected_-_background-color": "#434954", "paper-item-selected_-_background-color": "#434954",
"paper-slider-active-color": "var(--accent-color)",
"secondary-background-color": "#383C45", "secondary-background-color": "#383C45",
"paper-slider-container-color":
"linear-gradient(var(--primary-background-color), var(--secondary-background-color)) no-repeat",
"paper-slider-disabled-active-color": "var(--disabled-text-color)",
"disabled-text-color": "#7F848E", "disabled-text-color": "#7F848E",
"paper-item-icon_-_color": "green", "paper-item-icon_-_color": "green",
"paper-grey-200": "#414A59", "paper-grey-200": "#414A59",
@@ -27,10 +32,14 @@ export const demoThemeJimpower = () => ({
"switch-unchecked-button-color": "var(--disabled-text-color)", "switch-unchecked-button-color": "var(--disabled-text-color)",
"label-badge-border-color": "green", "label-badge-border-color": "green",
"paper-listbox-color": "var(--primary-color)", "paper-listbox-color": "var(--primary-color)",
"paper-slider-disabled-secondary-color": "var(--disabled-text-color)",
"card-background-color": "#434954", "card-background-color": "#434954",
"label-badge-text-color": "var(--primary-text-color)", "label-badge-text-color": "var(--primary-text-color)",
"paper-slider-knob-start-color": "var(--accent-color)",
"switch-unchecked-track-color": "var(--disabled-text-color)", "switch-unchecked-track-color": "var(--disabled-text-color)",
"dark-primary-color": "var(--accent-color)", "dark-primary-color": "var(--accent-color)",
"paper-slider-secondary-color": "var(--secondary-background-color)",
"paper-slider-pin-color": "var(--accent-color)",
"paper-item-icon-active-color": "#F9C536", "paper-item-icon-active-color": "#F9C536",
"accent-color": "#E45E65", "accent-color": "#E45E65",
"table-row-alternative-background-color": "#3E424B", "table-row-alternative-background-color": "#3E424B",

View File

@@ -3,15 +3,6 @@ import { DemoConfig } from "../types";
export const demoEntitiesKernehed: DemoConfig["entities"] = () => export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
convertEntities({ convertEntities({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",
attributes: {
supported_features: 15,
friendly_name: "Shopping List",
icon: "mdi:cart",
},
},
"zone.anna": { "zone.anna": {
entity_id: "zone.anna", entity_id: "zone.anna",
state: "zoning", state: "zoning",

View File

@@ -5,12 +5,17 @@ export const demoThemeKernehed = () => ({
"primary-color": "#2980b9", "primary-color": "#2980b9",
"label-badge-red": "var(--accent-color)", "label-badge-red": "var(--accent-color)",
"paper-tabs-selection-bar-color": "green", "paper-tabs-selection-bar-color": "green",
"paper-slider-knob-color": "var(--accent-color)",
"primary-text-color": "#FFFFFF", "primary-text-color": "#FFFFFF",
"light-primary-color": "var(--accent-color)", "light-primary-color": "var(--accent-color)",
"primary-background-color": "#222222", "primary-background-color": "#222222",
"sidebar-icon-color": "#777777", "sidebar-icon-color": "#777777",
"paper-item-selected_-_background-color": "#292929", "paper-item-selected_-_background-color": "#292929",
"paper-slider-active-color": "var(--accent-color)",
"secondary-background-color": "#222222", "secondary-background-color": "#222222",
"paper-slider-container-color":
"linear-gradient(var(--primary-background-color), var(--secondary-background-color)) no-repeat",
"paper-slider-disabled-active-color": "var(--disabled-text-color)",
"disabled-text-color": "#777777", "disabled-text-color": "#777777",
"paper-item-icon_-_color": "green", "paper-item-icon_-_color": "green",
"paper-grey-200": "#222222", "paper-grey-200": "#222222",
@@ -28,10 +33,14 @@ export const demoThemeKernehed = () => ({
"switch-unchecked-button-color": "var(--disabled-text-color)", "switch-unchecked-button-color": "var(--disabled-text-color)",
"label-badge-border-color": "green", "label-badge-border-color": "green",
"paper-listbox-color": "#777777", "paper-listbox-color": "#777777",
"paper-slider-disabled-secondary-color": "var(--disabled-text-color)",
"card-background-color": "#292929", "card-background-color": "#292929",
"label-badge-text-color": "var(--primary-text-color)", "label-badge-text-color": "var(--primary-text-color)",
"paper-slider-knob-start-color": "var(--accent-color)",
"switch-unchecked-track-color": "var(--disabled-text-color)", "switch-unchecked-track-color": "var(--disabled-text-color)",
"dark-primary-color": "var(--accent-color)", "dark-primary-color": "var(--accent-color)",
"paper-slider-secondary-color": "var(--secondary-background-color)",
"paper-slider-pin-color": "var(--accent-color)",
"paper-item-icon-active-color": "#b58e31", "paper-item-icon-active-color": "#b58e31",
"accent-color": "#2980b9", "accent-color": "#2980b9",
"table-row-alternative-background-color": "#292929", "table-row-alternative-background-color": "#292929",

View File

@@ -3,15 +3,6 @@ import { DemoConfig } from "../types";
export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () => export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
convertEntities({ convertEntities({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",
attributes: {
supported_features: 15,
friendly_name: "Shopping List",
icon: "mdi:cart",
},
},
"sensor.pollen_grabo": { "sensor.pollen_grabo": {
entity_id: "sensor.pollen_grabo", entity_id: "sensor.pollen_grabo",
state: "", state: "",

View File

@@ -220,8 +220,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
state_filter: ["on"], state_filter: ["on"],
}, },
{ {
type: "todo-list", type: "shopping-list",
entity: "todo.shopping_list",
}, },
{ {
entities: [ entities: [

View File

@@ -1,5 +1,6 @@
export const demoThemeTeachingbirds = () => ({ export const demoThemeTeachingbirds = () => ({
"paper-card-header-color": "var(--paper-item-icon-color)", "paper-card-header-color": "var(--paper-item-icon-color)",
"paper-slider-pin-color": "var(--primary-color)",
"paper-listbox-background-color": "#202020", "paper-listbox-background-color": "#202020",
"paper-grey-50": "var(--primary-text-color)", "paper-grey-50": "var(--primary-text-color)",
"paper-item-icon-color": "#d3d3d3", "paper-item-icon-color": "#d3d3d3",
@@ -7,6 +8,8 @@ export const demoThemeTeachingbirds = () => ({
"primary-color": "#389638", "primary-color": "#389638",
"light-primary-color": "#6f956f", "light-primary-color": "#6f956f",
"label-badge-red": "var(--primary-color)", "label-badge-red": "var(--primary-color)",
"paper-slider-secondary-color": "var(--light-primary-color)",
"paper-slider-knob-color": "var(--primary-color)",
"paper-listbox-color": "#FFFFFF", "paper-listbox-color": "#FFFFFF",
"paper-toggle-button-checked-bar-color": "var(--light-primary-color)", "paper-toggle-button-checked-bar-color": "var(--light-primary-color)",
"switch-unchecked-track-color": "var(--primary-text-color)", "switch-unchecked-track-color": "var(--primary-text-color)",
@@ -14,7 +17,9 @@ export const demoThemeTeachingbirds = () => ({
"label-badge-text-color": "var(--text-primary-color)", "label-badge-text-color": "var(--text-primary-color)",
"primary-background-color": "#303030", "primary-background-color": "#303030",
"sidebar-icon-color": "var(--paper-item-icon-color)", "sidebar-icon-color": "var(--paper-item-icon-color)",
"paper-slider-active-color": "#d8bf50",
"secondary-background-color": "#2b2b2b", "secondary-background-color": "#2b2b2b",
"paper-slider-knob-start-color": "var(--primary-color)",
"paper-item-icon-active-color": "#d8bf50", "paper-item-icon-active-color": "#d8bf50",
"switch-checked-color": "var(--primary-color)", "switch-checked-color": "var(--primary-color)",
"secondary-text-color": "#389638", "secondary-text-color": "#389638",

View File

@@ -1,5 +1,5 @@
import { LocalizeFunc } from "../../../src/common/translations/localize"; import { LocalizeFunc } from "../../../src/common/translations/localize";
import { LovelaceConfig } from "../../../src/data/lovelace/config/types"; import { LovelaceConfig } from "../../../src/data/lovelace";
import { Entity } from "../../../src/fake_data/entity"; import { Entity } from "../../../src/fake_data/entity";
export interface DemoConfig { export interface DemoConfig {

View File

@@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import { until } from "lit/directives/until"; import { until } from "lit/directives/until";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card"; import { LovelaceCardConfig } from "../../../src/data/lovelace";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace, LovelaceCard } from "../../../src/panels/lovelace/types"; import { Lovelace, LovelaceCard } from "../../../src/panels/lovelace/types";
import { import {
@@ -39,9 +39,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
<div class="picker"> <div class="picker">
<div class="label"> <div class="label">
${this._switching ${this._switching
? html`<ha-circular-progress ? html`<ha-circular-progress active></ha-circular-progress>`
indeterminate
></ha-circular-progress>`
: until( : until(
selectedDemoConfig.then( selectedDemoConfig.then(
(conf) => html` (conf) => html`
@@ -50,7 +48,8 @@ export class HADemoCard extends LitElement implements LovelaceCard {
<a target="_blank" href=${conf.authorUrl}> <a target="_blank" href=${conf.authorUrl}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.page-demo.cards.demo.demo_by", "ui.panel.page-demo.cards.demo.demo_by",
{ name: conf.authorName } "name",
conf.authorName
)} )}
</a> </a>
</small> </small>

View File

@@ -1,4 +1,4 @@
import "../../src/resources/ha-style";
import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch"; import "../../src/resources/safari-14-attachshadow-patch";
import "./ha-demo"; import "./ha-demo";
import("../../src/resources/ha-style");

View File

@@ -22,7 +22,7 @@ import { mockLovelace } from "./stubs/lovelace";
import { mockMediaPlayer } from "./stubs/media_player"; import { mockMediaPlayer } from "./stubs/media_player";
import { mockPersistentNotification } from "./stubs/persistent_notification"; import { mockPersistentNotification } from "./stubs/persistent_notification";
import { mockRecorder } from "./stubs/recorder"; import { mockRecorder } from "./stubs/recorder";
import { mockTodo } from "./stubs/todo"; import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log"; import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template"; import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations"; import { mockTranslations } from "./stubs/translations";
@@ -49,7 +49,7 @@ export class HaDemo extends HomeAssistantAppEl {
mockTranslations(hass); mockTranslations(hass);
mockHistory(hass); mockHistory(hass);
mockRecorder(hass); mockRecorder(hass);
mockTodo(hass); mockShoppingList(hass);
mockSystemLog(hass); mockSystemLog(hass);
mockTemplate(hass); mockTemplate(hass);
mockEvents(hass); mockEvents(hass);

View File

@@ -62,24 +62,10 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
#ha-launch-screen svg {
width: 170px;
flex-shrink: 0;
}
#ha-launch-screen .ha-launch-screen-spacer {
flex: 1;
}
</style> </style>
</head> </head>
<body> <body>
<div id="ha-launch-screen"> <div id="ha-launch-screen"></div>
<div class="ha-launch-screen-spacer"></div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
</svg>
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
</div>
<ha-demo></ha-demo> <ha-demo></ha-demo>
<%= renderTemplate("../../../src/html/_js_base.html.template") %> <%= renderTemplate("../../../src/html/_js_base.html.template") %>
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %> <%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>

View File

@@ -43,8 +43,8 @@ const generateMeanStatistics = (
period === "day" period === "day"
? addDays(currentDate, 1) ? addDays(currentDate, 1)
: period === "month" : period === "month"
? addMonths(currentDate, 1) ? addMonths(currentDate, 1)
: addHours(currentDate, 1); : addHours(currentDate, 1);
} }
return statistics; return statistics;
}; };
@@ -80,8 +80,8 @@ const generateSumStatistics = (
period === "day" period === "day"
? addDays(currentDate, 1) ? addDays(currentDate, 1)
: period === "month" : period === "month"
? addMonths(currentDate, 1) ? addMonths(currentDate, 1)
: addHours(currentDate, 1); : addHours(currentDate, 1);
} }
return statistics; return statistics;
}; };

View File

@@ -0,0 +1,44 @@
import { ShoppingListItem } from "../../../src/data/shopping-list";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
let items: ShoppingListItem[] = [
{
id: 12,
name: "Milk",
complete: false,
},
{
id: 13,
name: "Eggs",
complete: false,
},
{
id: 14,
name: "Oranges",
complete: true,
},
];
export const mockShoppingList = (hass: MockHomeAssistant) => {
hass.mockWS("shopping_list/items", () => items);
hass.mockWS("shopping_list/items/add", (msg) => {
const item: ShoppingListItem = {
id: new Date().getTime(),
complete: false,
name: msg.name,
};
items.push(item);
hass.mockEvent("shopping_list_updated");
return item;
});
hass.mockWS("shopping_list/items/update", ({ type, item_id, ...updates }) => {
items = items.map((item) =>
item.id === item_id ? { ...item, ...updates } : item
);
hass.mockEvent("shopping_list_updated");
});
hass.mockWS("shopping_list/items/clear", () => {
items = items.filter((item) => !item.complete);
hass.mockEvent("shopping_list_updated");
});
};

View File

@@ -1,24 +0,0 @@
import { TodoItem, TodoItemStatus } from "../../../src/data/todo";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockTodo = (hass: MockHomeAssistant) => {
hass.mockWS("todo/item/list", () => ({
items: [
{
uid: "12",
summary: "Milk",
status: TodoItemStatus.NeedsAction,
},
{
uid: "13",
summary: "Eggs",
status: TodoItemStatus.NeedsAction,
},
{
uid: "14",
summary: "Oranges",
status: TodoItemStatus.Completed,
},
] as TodoItem[],
}));
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -23,7 +23,7 @@ class DemoMoreInfo extends LitElement {
<state-card-content <state-card-content
.stateObj=${state} .stateObj=${state}
.hass=${this.hass} .hass=${this.hass}
inDialog in-dialog
></state-card-content> ></state-card-content>
<more-info-content <more-info-content

View File

@@ -509,7 +509,7 @@ export default {
away_mode: "on", away_mode: "on",
aux_heat: "off", aux_heat: "off",
unit_of_measurement: "°C", unit_of_measurement: "°C",
friendly_name: "HVAC", friendly_name: "Hvac",
supported_features: 3833, supported_features: 3833,
}, },
last_changed: "2018-07-19T10:44:46.200650+00:00", last_changed: "2018-07-19T10:44:46.200650+00:00",

View File

@@ -1,5 +1,5 @@
import "../../src/resources/ha-style";
import "../../src/resources/roboto";
import "./ha-gallery"; import "./ha-gallery";
import("../../src/resources/ha-style");
document.body.appendChild(document.createElement("ha-gallery")); document.body.appendChild(document.createElement("ha-gallery"));

View File

@@ -1,17 +1,19 @@
import { css, html, LitElement, TemplateResult, nothing } from "lit"; import { mdiHomeAssistant } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators"; import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import "../../../../src/components/chips/ha-chip-set"; import "../../../../src/components/ha-chip";
import "../../../../src/components/chips/ha-assist-chip"; import "../../../../src/components/ha-chip-set";
import "../../../../src/components/chips/ha-input-chip";
import "../../../../src/components/chips/ha-filter-chip";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
const chips: { const chips: {
icon?: string; icon?: string;
content?: string; content?: string;
}[] = [ }[] = [
{},
{
icon: mdiHomeAssistant,
},
{ {
content: "Content", content: "Content",
}, },
@@ -27,73 +29,31 @@ export class DemoHaChips extends LitElement {
return html` return html`
<ha-card header="ha-chip demo"> <ha-card header="ha-chip demo">
<div class="card-content"> <div class="card-content">
<p>Action chip</p> ${chips.map(
(chip) => html`
<ha-chip .hasIcon=${chip.icon !== undefined}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: ""}
${chip.content}
</ha-chip>
`
)}
</div>
</ha-card>
<ha-card header="ha-chip-set demo">
<div class="card-content">
<ha-chip-set> <ha-chip-set>
${chips.map( ${chips.map(
(chip) => html` (chip) => html`
<ha-assist-chip .label=${chip.content}> <ha-chip .hasIcon=${chip.icon !== undefined}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-assist-chip>
`
)}
${chips.map(
(chip) => html`
<ha-assist-chip .label=${chip.content} selected>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-assist-chip>
`
)}
</ha-chip-set>
<p>Filter chip</p>
<ha-chip-set>
${chips.map(
(chip) => html`
<ha-filter-chip .label=${chip.content}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-filter-chip>
`
)}
${chips.map(
(chip) => html`
<ha-filter-chip .label=${chip.content} selected>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-filter-chip>
`
)}
</ha-chip-set>
<p>Input chip</p>
<ha-chip-set>
${chips.map(
(chip) => html`
<ha-input-chip .label=${chip.content}>
${chip.icon ${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}> ? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>` </ha-svg-icon>`
: ""} : ""}
${chip.content} ${chip.content}
</ha-input-chip> </ha-chip>
`
)}
${chips.map(
(chip) => html`
<ha-input-chip .label=${chip.content} selected>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-input-chip>
` `
)} )}
</ha-chip-set> </ha-chip-set>
@@ -108,10 +68,12 @@ export class DemoHaChips extends LitElement {
max-width: 600px; max-width: 600px;
margin: 24px auto; margin: 24px auto;
} }
ha-chip {
margin-bottom: 4px;
}
.card-content { .card-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start;
} }
`; `;
} }

View File

@@ -1,4 +0,0 @@
---
title: Circular Progress
subtitle: Can be used to indicate an ongoing task.
---

View File

@@ -1,64 +0,0 @@
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../../src/components/ha-bar";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-circular-progress";
import "@material/web/progress/circular-progress";
import { HomeAssistant } from "../../../../src/types";
@customElement("demo-components-ha-circular-progress")
export class DemoHaCircularProgress extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
protected render(): TemplateResult {
return html`<ha-card header="Basic circular progress">
<div class="card-content">
<ha-circular-progress indeterminate></ha-circular-progress></div
></ha-card>
<ha-card header="Different circular progress sizes">
<div class="card-content">
<ha-circular-progress
indeterminate
size="tiny"
></ha-circular-progress>
<ha-circular-progress
indeterminate
size="small"
></ha-circular-progress>
<ha-circular-progress
indeterminate
size="medium"
></ha-circular-progress>
<ha-circular-progress
indeterminate
size="large"
></ha-circular-progress></div
></ha-card>
<ha-card header="Circular progress with an aria-label">
<div class="card-content">
<ha-circular-progress
indeterminate
aria-label="Doing something..."
></ha-circular-progress>
<ha-circular-progress
indeterminate
.ariaLabel=${"Doing something..."}
></ha-circular-progress></div
></ha-card>`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-circular-progress": DemoHaCircularProgress;
}
}

View File

@@ -49,11 +49,11 @@ export class DemoHaCircularSlider extends LitElement {
<div class="field"> <div class="field">
<p>Current</p> <p>Current</p>
<ha-slider <ha-slider
labeled
min="10" min="10"
max="30" max="30"
.value=${this.current} .value=${this.current}
@change=${this._currentChanged} @change=${this._currentChanged}
pin
></ha-slider> ></ha-slider>
<p>${this.current} °C</p> <p>${this.current} °C</p>
</div> </div>

View File

@@ -11,7 +11,6 @@ const buttons: {
min?: number; min?: number;
max?: number; max?: number;
step?: number; step?: number;
unit?: string;
class?: string; class?: string;
}[] = [ }[] = [
{ {
@@ -30,11 +29,6 @@ const buttons: {
label: "Custom", label: "Custom",
class: "custom", class: "custom",
}, },
{
id: "unit",
label: "With unit",
unit: "m",
},
]; ];
@customElement("demo-components-ha-control-number-buttons") @customElement("demo-components-ha-control-number-buttons")
@@ -56,7 +50,6 @@ export class DemoHarControlNumberButtons extends LitElement {
<pre>Config: ${JSON.stringify(config)}</pre> <pre>Config: ${JSON.stringify(config)}</pre>
<ha-control-number-buttons <ha-control-number-buttons
.value=${this.value} .value=${this.value}
.unit=${config.unit}
.min=${config.min} .min=${config.min}
.max=${config.max} .max=${config.max}
.step=${config.step} .step=${config.step}

View File

@@ -9,7 +9,6 @@ const sliders: {
id: string; id: string;
label: string; label: string;
mode?: "start" | "end" | "cursor"; mode?: "start" | "end" | "cursor";
unit?: string;
class?: string; class?: string;
}[] = [ }[] = [
{ {
@@ -32,21 +31,18 @@ const sliders: {
label: "Slider (start mode) and custom style", label: "Slider (start mode) and custom style",
mode: "start", mode: "start",
class: "custom", class: "custom",
unit: "mm",
}, },
{ {
id: "slider-end-custom", id: "slider-end-custom",
label: "Slider (end mode) and custom style", label: "Slider (end mode) and custom style",
mode: "end", mode: "end",
class: "custom", class: "custom",
unit: "mm",
}, },
{ {
id: "slider-cursor-custom", id: "slider-cursor-custom",
label: "Slider (cursor mode) and custom style", label: "Slider (cursor mode) and custom style",
mode: "cursor", mode: "cursor",
class: "custom", class: "custom",
unit: "mm",
}, },
]; ];
@@ -97,7 +93,6 @@ export class DemoHaBarSlider extends LitElement {
@value-changed=${this.handleValueChanged} @value-changed=${this.handleValueChanged}
@slider-moved=${this.handleSliderMoved} @slider-moved=${this.handleSliderMoved}
aria-labelledby=${id} aria-labelledby=${id}
.unit=${config.unit}
> >
</ha-control-slider> </ha-control-slider>
</div> </div>
@@ -119,7 +114,6 @@ export class DemoHaBarSlider extends LitElement {
@value-changed=${this.handleValueChanged} @value-changed=${this.handleValueChanged}
@slider-moved=${this.handleSliderMoved} @slider-moved=${this.handleSliderMoved}
aria-label=${label} aria-label=${label}
.unit=${config.unit}
> >
</ha-control-slider> </ha-control-slider>
`; `;

View File

@@ -5,22 +5,9 @@ subtitle: Dialogs provide important prompts in a user flow.
# Material Design 3 # Material Design 3
Our dialogs are based on the latest version of Material Design. Please note that we have made some well-considered adjustments to these guideliness. Specs and guidelines can be found on its [website](https://m3.material.io/components/dialogs/overview). Our dialogs are based on the latest version of Material Design. Specs and guidelines can be found on its [website](https://m3.material.io/components/dialogs/overview).
# Guidelines # Highlighted guidelines
## Design
- Dialogs have a max width of 560px. Alert and confirmation dialogs got a fixed width of 320px. If you need more width, consider a dedicated page instead.
- The close X-icon is on the top left, on all screen sizes. Except for alert and confirmation dialogs, they only have buttons and no X-icon. This is different compared to the Material guideliness.
- Dialogs can't be closed with ESC or clicked outside of the dialog when there is a form that the user needs to fill out. Instead it will animate "no" by a little shake.
- Extra icon buttons are on the top right, for example help, settings and expand dialog. More than 2 icon buttons, they will be in an overflow menu.
- The submit button is grouped with a cancel button at the bottom right, on all screen sizes. Fullscreen mobile dialogs have them sticky at the bottom.
- Keep the labels short, for example `Save`, `Delete`, `Enable`.
- Dialog with actions must always have a discard button. On desktop a `Cancel` button and X-icon, on mobile only the X-icon.
- Destructive actions should be a red warning button.
- Alert or confirmation dialogs only have buttons and no X-icon.
- Try to avoid three buttons in one dialog. Especially when you leave the dialog task unfinished.
## Content ## Content
@@ -30,6 +17,14 @@ Our dialogs are based on the latest version of Material Design. Please note that
- If users become unsure, they read the description. Make sure this explains what will happen. - If users become unsure, they read the description. Make sure this explains what will happen.
- Strive for minimalism. - Strive for minimalism.
## Buttons and X-icon
- Keep the labels short, for example `Save`, `Delete`, `Enable`.
- Dialog with actions must always have a discard button. On desktop a `Cancel` button and X-icon, on mobile only the X-icon.
- Destructive actions should be a red warning button.
- Alert or confirmation dialogs only have buttons and no X-icon.
- Try to avoid three buttons in one dialog. Especially when you leave the dialog task unfinished.
## Example ## Example
### Confirmation dialog ### Confirmation dialog

View File

@@ -57,7 +57,6 @@ const DEVICES = [
sw_version: null, sw_version: null,
hw_version: null, hw_version: null,
via_device_id: null, via_device_id: null,
serial_number: null,
}, },
{ {
area_id: "backyard", area_id: "backyard",
@@ -75,7 +74,6 @@ const DEVICES = [
sw_version: null, sw_version: null,
hw_version: null, hw_version: null,
via_device_id: null, via_device_id: null,
serial_number: null,
}, },
{ {
area_id: null, area_id: null,
@@ -93,7 +91,6 @@ const DEVICES = [
sw_version: null, sw_version: null,
hw_version: null, hw_version: null,
via_device_id: null, via_device_id: null,
serial_number: null,
}, },
]; ];

View File

@@ -57,8 +57,8 @@ export class DemoHaHsColorPicker extends LitElement {
></ha-hs-color-picker> ></ha-hs-color-picker>
<p>Hue : ${this.value[0]}</p> <p>Hue : ${this.value[0]}</p>
<ha-slider <ha-slider
labeled
step="1" step="1"
pin
min="0" min="0"
max="360" max="360"
.value=${this.value[0]} .value=${this.value[0]}
@@ -67,8 +67,8 @@ export class DemoHaHsColorPicker extends LitElement {
</ha-slider> </ha-slider>
<p>Saturation : ${this.value[1]}</p> <p>Saturation : ${this.value[1]}</p>
<ha-slider <ha-slider
labeled
step="0.01" step="0.01"
pin
min="0" min="0"
max="1" max="1"
.value=${this.value[1]} .value=${this.value[1]}
@@ -77,8 +77,8 @@ export class DemoHaHsColorPicker extends LitElement {
</ha-slider> </ha-slider>
<p>Color Brighness : ${this.brightness}</p> <p>Color Brighness : ${this.brightness}</p>
<ha-slider <ha-slider
labeled
step="1" step="1"
pin
min="0" min="0"
max="255" max="255"
.value=${this.brightness} .value=${this.brightness}

View File

@@ -53,7 +53,6 @@ const DEVICES = [
sw_version: null, sw_version: null,
hw_version: null, hw_version: null,
via_device_id: null, via_device_id: null,
serial_number: null,
}, },
{ {
area_id: "backyard", area_id: "backyard",
@@ -71,7 +70,6 @@ const DEVICES = [
sw_version: null, sw_version: null,
hw_version: null, hw_version: null,
via_device_id: null, via_device_id: null,
serial_number: null,
}, },
{ {
area_id: null, area_id: null,
@@ -89,7 +87,6 @@ const DEVICES = [
sw_version: null, sw_version: null,
hw_version: null, hw_version: null,
via_device_id: null, via_device_id: null,
serial_number: null,
}, },
]; ];

View File

@@ -18,7 +18,7 @@ The Home Assistant interface is based on Material Design. It's a design system c
We want to make it as easy for designers to contribute as it is for developers. Theres a lot a designer can contribute to: We want to make it as easy for designers to contribute as it is for developers. Theres a lot a designer can contribute to:
- Meet us at <a href="https://www.home-assistant.io/join-chat" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas. - Meet us at <a href="https://discord.gg/BPBc8rZ9" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas.
- Start designing with our <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Figma DesignKit</a>. - Start designing with our <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Figma DesignKit</a>.
- Find the latest UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion! - Find the latest UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!

View File

@@ -0,0 +1,3 @@
---
title: Shopping List Card
---

View File

@@ -2,39 +2,25 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, query } from "lit/decorators"; import { customElement, query } from "lit/decorators";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { getEntity } from "../../../../src/fake_data/entity";
import { mockTodo } from "../../../../demo/src/stubs/todo";
const ENTITIES = [
getEntity("todo", "shopping_list", "2", {
friendly_name: "Shopping List",
supported_features: 15,
}),
getEntity("todo", "read_only", "2", {
friendly_name: "Read only",
}),
];
const CONFIGS = [ const CONFIGS = [
{ {
heading: "List example", heading: "List example",
config: ` config: `
- type: todo-list - type: shopping-list
entity: todo.shopping_list
`, `,
}, },
{ {
heading: "List with title example", heading: "List with title example",
config: ` config: `
- type: todo-list - type: shopping-list
title: Shopping List title: Shopping List
entity: todo.read_only
`, `,
}, },
]; ];
@customElement("demo-lovelace-todo-list-card") @customElement("demo-lovelace-shopping-list-card")
class DemoTodoListEntity extends LitElement { class DemoShoppingListEntity extends LitElement {
@query("#demos") private _demoRoot!: HTMLElement; @query("#demos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -46,14 +32,18 @@ class DemoTodoListEntity extends LitElement {
const hass = provideHass(this._demoRoot); const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en"); hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en"); hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockTodo(hass); hass.mockAPI("shopping_list", () => [
{ name: "list", id: 1, complete: false },
{ name: "all", id: 2, complete: false },
{ name: "the", id: 3, complete: false },
{ name: "things", id: 4, complete: true },
]);
} }
} }
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"demo-lovelace-todo-list-card": DemoTodoListEntity; "demo-lovelace-shopping-list-card": DemoShoppingListEntity;
} }
} }

View File

@@ -35,18 +35,6 @@ const ENTITIES = [
friendly_name: "Nest", friendly_name: "Nest",
supported_features: 43, supported_features: 43,
}), }),
getEntity("climate", "sensibo", "fan_only", {
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
hvac_modes: ["fan_only", "off"],
friendly_name: "Sensibo purifier",
fan_modes: ["low", "high"],
fan_mode: "low",
supported_features: 9,
}),
getEntity("climate", "unavailable", "unavailable", { getEntity("climate", "unavailable", "unavailable", {
supported_features: 43, supported_features: 43,
}), }),
@@ -69,23 +57,6 @@ const CONFIGS = [
entity: climate.nest entity: climate.nest
`, `,
}, },
{
heading: "Fan only example",
config: `
- type: thermostat
entity: climate.sensibo
features:
- type: climate-hvac-modes
hvac_modes:
- fan_only
- 'off'
- type: climate-fan-modes
style: icons
fan_modes:
- low
- high
`,
},
{ {
heading: "Unavailable", heading: "Unavailable",
config: ` config: `

View File

@@ -1,3 +0,0 @@
---
title: Todo List Card
---

View File

@@ -10,6 +10,7 @@ import { computeStateDisplay } from "../../../../src/common/entity/compute_state
import "../../../../src/components/data-table/ha-data-table"; import "../../../../src/components/data-table/ha-data-table";
import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table"; import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table";
import "../../../../src/components/entity/state-badge"; import "../../../../src/components/entity/state-badge";
import "../../../../src/components/ha-chip";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";

View File

@@ -213,7 +213,6 @@ const createDeviceRegistryEntries = (
name: "Tag Reader", name: "Tag Reader",
sw_version: null, sw_version: null,
hw_version: "1.0.0", hw_version: "1.0.0",
serial_number: "00_12_4B_00_22_98_88_7F",
id: "mock-device-id", id: "mock-device-id",
identifiers: [], identifiers: [],
via_device_id: null, via_device_id: null,

View File

@@ -2,7 +2,7 @@ import "@material/mwc-button";
import { css, html, LitElement, TemplateResult } from "lit"; import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators"; import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { ActionHandlerEvent } from "../../../../src/data/lovelace/action_handler"; import { ActionHandlerEvent } from "../../../../src/data/lovelace";
import { actionHandler } from "../../../../src/panels/lovelace/common/directives/action-handler-directive"; import { actionHandler } from "../../../../src/panels/lovelace/common/directives/action-handler-directive";
@customElement("demo-misc-util-long-press") @customElement("demo-misc-util-long-press")

View File

@@ -31,21 +31,6 @@ const ENTITIES = [
max_temp: 30, max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE, supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
}), }),
getEntity("climate", "fan", "fan_only", {
friendly_name: "Basic fan",
hvac_modes: ["fan_only", "off"],
hvac_mode: "fan_only",
fan_modes: ["low", "high"],
fan_mode: "low",
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
}),
getEntity("climate", "hvac", "auto", { getEntity("climate", "hvac", "auto", {
friendly_name: "Basic hvac", friendly_name: "Basic hvac",
hvac_modes: ["auto", "off"], hvac_modes: ["auto", "off"],

View File

@@ -1,3 +0,0 @@
---
title: Input Text
---

View File

@@ -1,46 +0,0 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("input_text", "text", "Inspiration", {
friendly_name: "Text",
mode: "text",
}),
];
@customElement("demo-more-info-input-text")
class DemoMoreInfoInputText extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-input-text": DemoMoreInfoInputText;
}
}

View File

@@ -1,3 +0,0 @@
---
title: Lock
---

View File

@@ -1,49 +0,0 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("lock", "lock", "locked", {
friendly_name: "Lock",
device_class: "lock",
}),
getEntity("lock", "unavailable", "unavailable", {
friendly_name: "Unavailable lock",
}),
];
@customElement("demo-more-info-lock")
class DemoMoreInfoLock extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-lock": DemoMoreInfoLock;
}
}

View File

@@ -1,3 +0,0 @@
---
title: Media Player
---

View File

@@ -1,41 +0,0 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { createMediaPlayerEntities } from "../../data/media_players";
const ENTITIES = createMediaPlayerEntities();
@customElement("demo-more-info-media-player")
class DemoMoreInfoMediaPlayer extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-media-player": DemoMoreInfoMediaPlayer;
}
}

View File

@@ -1,3 +0,0 @@
---
title: Number
---

View File

@@ -1,78 +0,0 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("number", "box1", 0, {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
}),
getEntity("number", "slider1", 0, {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
}),
getEntity("number", "auto1", 0, {
friendly_name: "Auto1",
min: 0,
max: 1000,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
}),
getEntity("number", "auto2", 0, {
friendly_name: "Auto2",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
}),
];
@customElement("demo-more-info-number")
class DemoMoreInfoNumber extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-number": DemoMoreInfoNumber;
}
}

View File

@@ -1,3 +0,0 @@
---
title: Scene
---

View File

@@ -1,49 +0,0 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("scene", "romantic_lights", "scening", {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
}),
getEntity("scene", "unavailable", "unavailable", {
friendly_name: "Romantic Scene",
}),
];
@customElement("demo-more-info-scene")
class DemoMoreInfoScene extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-scene": DemoMoreInfoScene;
}
}

View File

@@ -1,3 +0,0 @@
---
title: Timer
---

View File

@@ -1,46 +0,0 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
getEntity("timer", "timer", "idle", {
friendly_name: "Timer",
duration: "0:05:00",
}),
];
@customElement("demo-more-info-timer")
class DemoMoreInfoTimer extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-timer": DemoMoreInfoTimer;
}
}

View File

@@ -1,6 +1,12 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import {
UPDATE_SUPPORT_BACKUP,
UPDATE_SUPPORT_PROGRESS,
UPDATE_SUPPORT_INSTALL,
UPDATE_SUPPORT_RELEASE_NOTES,
} from "../../../../src/data/update";
import "../../../../src/dialogs/more-info/more-info-content"; import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity"; import { getEntity } from "../../../../src/fake_data/entity";
import { import {
@@ -9,14 +15,13 @@ import {
} from "../../../../src/fake_data/provide_hass"; } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos"; import "../../components/demo-more-infos";
import { LONG_TEXT } from "../../data/text"; import { LONG_TEXT } from "../../data/text";
import { UpdateEntityFeature } from "../../../../src/data/update";
const base_attributes = { const base_attributes = {
title: "Awesome", title: "Awesome",
installed_version: "1.2.2", installed_version: "1.2.2",
latest_version: "1.2.3", latest_version: "1.2.3",
release_url: "https://home-assistant.io", release_url: "https://home-assistant.io",
supported_features: UpdateEntityFeature.INSTALL, supported_features: UPDATE_SUPPORT_INSTALL,
skipped_version: null, skipped_version: null,
in_progress: false, in_progress: false,
release_summary: release_summary:
@@ -56,7 +61,7 @@ const ENTITIES = [
getEntity("update", "update7", "on", { getEntity("update", "update7", "on", {
...base_attributes, ...base_attributes,
supported_features: supported_features:
base_attributes.supported_features + UpdateEntityFeature.BACKUP, base_attributes.supported_features + UPDATE_SUPPORT_BACKUP,
friendly_name: "With backup support", friendly_name: "With backup support",
}), }),
getEntity("update", "update8", "on", { getEntity("update", "update8", "on", {
@@ -68,21 +73,21 @@ const ENTITIES = [
...base_attributes, ...base_attributes,
in_progress: 25, in_progress: 25,
supported_features: supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS, base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
friendly_name: "With 25 in_progress", friendly_name: "With 25 in_progress",
}), }),
getEntity("update", "update10", "on", { getEntity("update", "update10", "on", {
...base_attributes, ...base_attributes,
in_progress: 50, in_progress: 50,
supported_features: supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS, base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
friendly_name: "With 50 in_progress", friendly_name: "With 50 in_progress",
}), }),
getEntity("update", "update11", "on", { getEntity("update", "update11", "on", {
...base_attributes, ...base_attributes,
in_progress: 75, in_progress: 75,
supported_features: supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS, base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
friendly_name: "With 75 in_progress", friendly_name: "With 75 in_progress",
}), }),
getEntity("update", "update12", "unavailable", { getEntity("update", "update12", "unavailable", {
@@ -109,19 +114,19 @@ const ENTITIES = [
...base_attributes, ...base_attributes,
friendly_name: "Update with release notes", friendly_name: "Update with release notes",
supported_features: supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES, base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
}), }),
getEntity("update", "update17", "off", { getEntity("update", "update17", "off", {
...base_attributes, ...base_attributes,
friendly_name: "Update with release notes error", friendly_name: "Update with release notes error",
supported_features: supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES, base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
}), }),
getEntity("update", "update18", "off", { getEntity("update", "update18", "off", {
...base_attributes, ...base_attributes,
friendly_name: "Update with release notes loading", friendly_name: "Update with release notes loading",
supported_features: supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES, base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
}), }),
getEntity("update", "update19", "on", { getEntity("update", "update19", "on", {
...base_attributes, ...base_attributes,
@@ -137,10 +142,9 @@ const ENTITIES = [
getEntity("update", "update21", "on", { getEntity("update", "update21", "on", {
...base_attributes, ...base_attributes,
in_progress: true, in_progress: true,
friendly_name: friendly_name: "Update with in_progress true and UPDATE_SUPPORT_PROGRESS",
"Update with in_progress true and UpdateEntityFeature.PROGRESS",
supported_features: supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS, base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
}), }),
]; ];

View File

@@ -1,3 +0,0 @@
---
title: Vacuum
---

View File

@@ -1,50 +0,0 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
const ENTITIES = [
getEntity("vacuum", "first_floor_vacuum", "docked", {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
}),
];
@customElement("demo-more-info-vacuum")
class DemoMoreInfoVacuum extends LitElement {
@property() public hass!: MockHomeAssistant;
@query("demo-more-infos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-more-info-vacuum": DemoMoreInfoVacuum;
}
}

View File

@@ -49,9 +49,11 @@ export class HassioAddonRepositoryEl extends LitElement {
return html` return html`
<div class="content"> <div class="content">
<p class="description"> <p class="description">
${this.supervisor.localize("store.no_results_found", { ${this.supervisor.localize(
repository: repo.name, "store.no_results_found",
})} "repository",
repo.name
)}
</p> </p>
</div> </div>
`; `;
@@ -84,15 +86,15 @@ export class HassioAddonRepositoryEl extends LitElement {
) )
: this.supervisor.localize("addon.state.installed") : this.supervisor.localize("addon.state.installed")
: addon.available : addon.available
? this.supervisor.localize("addon.state.not_installed") ? this.supervisor.localize("addon.state.not_installed")
: this.supervisor.localize("addon.state.not_available")} : this.supervisor.localize("addon.state.not_available")}
.iconClass=${addon.installed .iconClass=${addon.installed
? addon.update_available ? addon.update_available
? "update" ? "update"
: "installed" : "installed"
: !addon.available : !addon.available
? "not_available" ? "not_available"
: ""} : ""}
.iconImage=${atLeastVersion( .iconImage=${atLeastVersion(
this.hass.config.version, this.hass.config.version,
0, 0,
@@ -106,8 +108,8 @@ export class HassioAddonRepositoryEl extends LitElement {
? "update" ? "update"
: "installed" : "installed"
: !addon.available : !addon.available
? "unavailable" ? "unavailable"
: ""} : ""}
></hassio-card-content> ></hassio-card-content>
</div> </div>
</ha-card> </ha-card>

View File

@@ -20,7 +20,7 @@ class HassioAddonConfigDashboard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.addon) { if (!this.addon) {
return html`<ha-circular-progress indeterminate></ha-circular-progress>`; return html`<ha-circular-progress active></ha-circular-progress>`;
} }
const hasConfiguration = const hasConfiguration =
(this.addon.options && Object.keys(this.addon.options).length) || (this.addon.options && Object.keys(this.addon.options).length) ||

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