Compare commits
15 Commits
fix_new_sc
...
fix_downlo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
67a5152c36 | ||
![]() |
918fca4d0a | ||
![]() |
258a19028b | ||
![]() |
7b4536564e | ||
![]() |
d9cd428bf4 | ||
![]() |
be6ecefb9e | ||
![]() |
99bde50c01 | ||
![]() |
a2471f82a3 | ||
![]() |
c90e820c7f | ||
![]() |
0c0b657c79 | ||
![]() |
8941837697 | ||
![]() |
01adef6d9f | ||
![]() |
4a1adf42b8 | ||
![]() |
0c2e62ec91 | ||
![]() |
2218a7121b |
@@ -4,12 +4,13 @@
|
||||
# - released in the last year + current alpha/beta versions
|
||||
# - Firefox extended support release (ESR)
|
||||
# - with global utilization at or above 0.5%
|
||||
# - exclude dead browsers (no security maintenance for 2+ years)
|
||||
# - must support dynamic import of ES modules
|
||||
# - exclude browsers no longer being maintained
|
||||
# - exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data
|
||||
unreleased versions
|
||||
last 1 year
|
||||
Firefox ESR
|
||||
>= 0.5%
|
||||
>= 0.5% and supports es6-module-dynamic-import
|
||||
not dead
|
||||
not KaiOS > 0
|
||||
not QQAndroid > 0
|
||||
@@ -19,18 +20,23 @@ not UCAndroid > 0
|
||||
# Legacy builds are served when modern requirements are not met and support browsers:
|
||||
# - released in the last 7 years + current alpha/beta versionss
|
||||
# - with global utilization at or above 0.05%
|
||||
# - exclude dead browsers (no security maintenance for 2+ years)
|
||||
# - exclude Opera Mini which does not support web sockets
|
||||
# The lattermost query ensures that support for popular old browsers is not dropped too early
|
||||
# (e.g. IE 11, Android 4.4, or Samsung 4).
|
||||
#
|
||||
# In addition, legacy browsers must support some minimum features that cannot be polyfilled:
|
||||
# - ES5 (strict mode)
|
||||
# - web sockets to communicate with backend
|
||||
# - inline SVG used widely in buttons, widgets, etc.
|
||||
# - custom events used for most user interactions
|
||||
# - CSS flexbox used in the majority of the layout
|
||||
# Nearly all of these are redundant with the above rules.
|
||||
# As of May 2023, only web sockets must be added to the query.
|
||||
unreleased versions
|
||||
last 7 years
|
||||
>= 0.05%
|
||||
not dead
|
||||
not op_mini all
|
||||
>= 0.05% and supports websockets
|
||||
|
||||
[legacy-sw]
|
||||
# Same as legacy plus supports service workers
|
||||
unreleased versions
|
||||
last 7 years
|
||||
>= 0.05% and supports serviceworkers
|
||||
not dead
|
||||
not op_mini all
|
||||
>= 0.05% and supports websockets and supports serviceworkers
|
||||
|
36
.github/workflows/ci.yaml
vendored
@@ -26,24 +26,18 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4.1.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
- uses: actions/cache@v4.2.0
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: "node_modules"
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}
|
||||
- name: Install dependencies
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --immutable
|
||||
- name: Check for duplicate dependencies
|
||||
run: yarn dedupe --check
|
||||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
||||
- name: Setup lint cache
|
||||
uses: actions/cache@v4.2.0
|
||||
uses: actions/cache@v4.1.2
|
||||
with:
|
||||
path: |
|
||||
node_modules/.cache/prettier
|
||||
@@ -66,19 +60,11 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4.1.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
- uses: actions/cache@v4.2.0
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: "node_modules"
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}
|
||||
- name: Install dependencies
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --immutable
|
||||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data
|
||||
@@ -92,19 +78,11 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4.1.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
- uses: actions/cache@v4.2.0
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: "node_modules"
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}
|
||||
- name: Install dependencies
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --immutable
|
||||
- name: Build Application
|
||||
run: ./node_modules/.bin/gulp build-app
|
||||
@@ -124,19 +102,11 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4.1.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
- uses: actions/cache@v4.2.0
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: "node_modules"
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: ${{ runner.os }}-yarn-${{ hashFiles('.yarnrc.yml') }}-${{ steps.setup-node.outputs.node-version }}
|
||||
- name: Install dependencies
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --immutable
|
||||
- name: Build Application
|
||||
run: ./node_modules/.bin/gulp build-hassio
|
||||
|
2
.github/workflows/relative-ci.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send bundle stats and build information to RelativeCI
|
||||
uses: relative-ci/agent-action@v2.1.14
|
||||
uses: relative-ci/agent-action@v2.1.13
|
||||
with:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
58
.github/workflows/release.yaml
vendored
@@ -81,61 +81,3 @@ jobs:
|
||||
arch: amd64
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
requirements: "requirements.txt"
|
||||
|
||||
release-landing-page:
|
||||
name: Release landing-page frontend
|
||||
if: github.event.release.prerelease == false
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.1.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download Translations
|
||||
run: ./script/translations_download
|
||||
env:
|
||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||
- name: Build landing-page
|
||||
run: landing-page/script/build_landing_page
|
||||
- name: Tar folder
|
||||
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||
- name: Upload release asset
|
||||
uses: softprops/action-gh-release@v2.1.0
|
||||
with:
|
||||
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
||||
release-supervisor:
|
||||
name: Release supervisor frontend
|
||||
if: github.event.release.prerelease == false
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.1.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download Translations
|
||||
run: ./script/translations_download
|
||||
env:
|
||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||
- name: Build supervisor
|
||||
run: hassio/script/build_hassio
|
||||
- name: Tar folder
|
||||
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||
- name: Upload release asset
|
||||
uses: softprops/action-gh-release@v2.1.0
|
||||
with:
|
||||
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
3
.gitignore
vendored
@@ -50,6 +50,3 @@ src/cast/dev_const.ts
|
||||
|
||||
# Jetbrains
|
||||
/.idea/
|
||||
|
||||
# test coverage
|
||||
test/coverage/
|
||||
|
3
.vscode/extensions.json
vendored
@@ -4,7 +4,6 @@
|
||||
"esbenp.prettier-vscode",
|
||||
"runem.lit-plugin",
|
||||
"github.vscode-pull-request-github",
|
||||
"eamodio.gitlens",
|
||||
"vitest.explorer"
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
|
32
.vscode/tasks.json
vendored
@@ -100,38 +100,6 @@
|
||||
"instanceLimit": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Develop Landing Page",
|
||||
"type": "gulp",
|
||||
"task": "develop-landing-page",
|
||||
"problemMatcher": {
|
||||
"owner": "ha-build",
|
||||
"source": "ha-build",
|
||||
"fileLocation": "absolute",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
|
||||
"severity": 1,
|
||||
"file": 2,
|
||||
"message": 3,
|
||||
"line": 4,
|
||||
"column": 5
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "Changes detected. Starting compilation",
|
||||
"endsPattern": "Build done @"
|
||||
}
|
||||
},
|
||||
|
||||
"isBackground": true,
|
||||
"group": "build",
|
||||
"runOptions": {
|
||||
"instanceLimit": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Develop Demo",
|
||||
"type": "gulp",
|
||||
|
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.5.3.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.5.1.cjs
|
||||
|
@@ -147,7 +147,6 @@ const polyfillMap = {
|
||||
...Object.fromEntries(
|
||||
[
|
||||
"DateTimeFormat",
|
||||
"DurationFormat",
|
||||
"DisplayNames",
|
||||
"ListFormat",
|
||||
"NumberFormat",
|
||||
|
@@ -53,11 +53,6 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||
__SUPERVISOR__: false,
|
||||
__BACKWARDS_COMPAT__: false,
|
||||
__STATIC_PATH__: "/static/",
|
||||
__HASS_URL__: `\`${
|
||||
"HASS_URL" in process.env
|
||||
? process.env["HASS_URL"]
|
||||
: "${location.protocol}//${location.host}"
|
||||
}\``,
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
),
|
||||
@@ -157,6 +152,7 @@ module.exports.babelOptions = ({
|
||||
exclude: [
|
||||
// \\ for Windows, / for Mac OS and Linux
|
||||
/node_modules[\\/]core-js/,
|
||||
/node_modules[\\/]webpack[\\/]buildin/,
|
||||
],
|
||||
sourceMaps: !isTestBuild,
|
||||
overrides: [
|
||||
@@ -331,17 +327,4 @@ module.exports.config = {
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
landingPage({ isProdBuild, latestBuild }) {
|
||||
return {
|
||||
name: "landing-page" + nameSuffix(latestBuild),
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.landingPage_dir, "src/entrypoint.js"),
|
||||
},
|
||||
outputPath: outputPath(paths.landingPage_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@@ -2,22 +2,23 @@ const fs = require("fs");
|
||||
const path = require("path");
|
||||
const paths = require("./paths.cjs");
|
||||
|
||||
const isTrue = (value) => value === "1" || value?.toLowerCase() === "true";
|
||||
|
||||
module.exports = {
|
||||
useWDS() {
|
||||
return process.env.WDS === "1";
|
||||
},
|
||||
isProdBuild() {
|
||||
return (
|
||||
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
|
||||
);
|
||||
},
|
||||
isStatsBuild() {
|
||||
return isTrue(process.env.STATS);
|
||||
return process.env.STATS === "1";
|
||||
},
|
||||
isTestBuild() {
|
||||
return isTrue(process.env.IS_TEST);
|
||||
return process.env.IS_TEST === "true";
|
||||
},
|
||||
isNetlify() {
|
||||
return isTrue(process.env.NETLIFY);
|
||||
return process.env.NETLIFY === "true";
|
||||
},
|
||||
version() {
|
||||
const version = fs
|
||||
@@ -29,6 +30,6 @@ module.exports = {
|
||||
return version[1];
|
||||
},
|
||||
isDevContainer() {
|
||||
return isTrue(process.env.DEV_CONTAINER);
|
||||
return process.env.DEV_CONTAINER === "1";
|
||||
},
|
||||
};
|
||||
|
@@ -8,7 +8,8 @@ import "./gen-icons-json.js";
|
||||
import "./locale-data.js";
|
||||
import "./service-worker.js";
|
||||
import "./translations.js";
|
||||
import "./rspack.js";
|
||||
import "./wds.js";
|
||||
import "./webpack.js";
|
||||
|
||||
gulp.task(
|
||||
"develop-app",
|
||||
@@ -25,7 +26,7 @@ gulp.task(
|
||||
"build-locale-data"
|
||||
),
|
||||
"copy-static-app",
|
||||
"rspack-watch-app"
|
||||
env.useWDS() ? "wds-watch-app" : "webpack-watch-app"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -38,20 +39,9 @@ gulp.task(
|
||||
"clean",
|
||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||
"copy-static-app",
|
||||
"rspack-prod-app",
|
||||
"webpack-prod-app",
|
||||
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod"),
|
||||
// Don't compress running tests
|
||||
...(env.isTestBuild() || env.isStatsBuild() ? [] : ["compress-app"])
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"analyze-app",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.STATS = "1";
|
||||
},
|
||||
"clean",
|
||||
"rspack-prod-app"
|
||||
...(env.isTestBuild() ? [] : ["compress-app"])
|
||||
)
|
||||
);
|
||||
|
@@ -4,7 +4,7 @@ import "./entry-html.js";
|
||||
import "./gather-static.js";
|
||||
import "./service-worker.js";
|
||||
import "./translations.js";
|
||||
import "./rspack.js";
|
||||
import "./webpack.js";
|
||||
|
||||
gulp.task(
|
||||
"develop-cast",
|
||||
@@ -17,7 +17,7 @@ gulp.task(
|
||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||
"copy-static-cast",
|
||||
"gen-pages-cast-dev",
|
||||
"rspack-dev-server-cast"
|
||||
"webpack-dev-server-cast"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -31,7 +31,7 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||
"copy-static-cast",
|
||||
"rspack-prod-cast",
|
||||
"webpack-prod-cast",
|
||||
"gen-pages-cast-prod"
|
||||
)
|
||||
);
|
||||
|
@@ -38,14 +38,3 @@ gulp.task(
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-landing-page",
|
||||
gulp.parallel("clean-translations", async () =>
|
||||
deleteSync([
|
||||
paths.landingPage_output_root,
|
||||
paths.landingPage_build,
|
||||
paths.build_dir,
|
||||
])
|
||||
)
|
||||
);
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import { constants } from "node:zlib";
|
||||
import gulp from "gulp";
|
||||
import brotli from "gulp-brotli";
|
||||
import zopfli from "gulp-zopfli-green";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
const filesGlob = "*.{js,json,css,svg,xml}";
|
||||
@@ -12,6 +13,7 @@ const brotliOptions = {
|
||||
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
|
||||
},
|
||||
};
|
||||
const zopfliOptions = { threshold: 150 };
|
||||
|
||||
const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) =>
|
||||
gulp
|
||||
@@ -27,6 +29,20 @@ const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) =>
|
||||
.pipe(brotli(brotliOptions))
|
||||
.pipe(gulp.dest(rootDir));
|
||||
|
||||
const compressDistZopfli = (rootDir, modernDir, compressModern = false) =>
|
||||
gulp
|
||||
.src(
|
||||
[
|
||||
`${rootDir}/**/${filesGlob}`,
|
||||
compressModern ? undefined : `!${modernDir}/**/${filesGlob}`,
|
||||
`!${rootDir}/{sw-modern,service_worker}.js`,
|
||||
`${rootDir}/{authorize,onboarding}.html`,
|
||||
].filter(Boolean),
|
||||
{ base: rootDir }
|
||||
)
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(rootDir));
|
||||
|
||||
const compressAppBrotli = () =>
|
||||
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
|
||||
const compressHassioBrotli = () =>
|
||||
@@ -36,5 +52,17 @@ const compressHassioBrotli = () =>
|
||||
false
|
||||
);
|
||||
|
||||
gulp.task("compress-app", compressAppBrotli);
|
||||
gulp.task("compress-hassio", compressHassioBrotli);
|
||||
const compressAppZopfli = () =>
|
||||
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
|
||||
const compressHassioZopfli = () =>
|
||||
compressDistZopfli(
|
||||
paths.hassio_output_root,
|
||||
paths.hassio_output_latest,
|
||||
true
|
||||
);
|
||||
|
||||
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
|
||||
gulp.task(
|
||||
"compress-hassio",
|
||||
gulp.parallel(compressHassioBrotli, compressHassioZopfli)
|
||||
);
|
||||
|
@@ -5,7 +5,7 @@ import "./gather-static.js";
|
||||
import "./gen-icons-json.js";
|
||||
import "./service-worker.js";
|
||||
import "./translations.js";
|
||||
import "./rspack.js";
|
||||
import "./webpack.js";
|
||||
|
||||
gulp.task(
|
||||
"develop-demo",
|
||||
@@ -22,7 +22,7 @@ gulp.task(
|
||||
"build-locale-data"
|
||||
),
|
||||
"copy-static-demo",
|
||||
"rspack-dev-server-demo"
|
||||
"webpack-dev-server-demo"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -37,18 +37,7 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||
"copy-static-demo",
|
||||
"rspack-prod-demo",
|
||||
"webpack-prod-demo",
|
||||
"gen-pages-demo-prod"
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"analyze-demo",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.STATS = "1";
|
||||
},
|
||||
"clean",
|
||||
"rspack-prod-demo"
|
||||
)
|
||||
);
|
||||
|
@@ -11,6 +11,7 @@ import { minify } from "html-minifier-terser";
|
||||
import template from "lodash.template";
|
||||
import { dirname, extname, resolve } from "node:path";
|
||||
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
|
||||
import env from "../env.cjs";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
// macOS companion app has no way to obtain the Safari version used by WKWebView,
|
||||
@@ -55,6 +56,7 @@ const getCommonTemplateVars = () => {
|
||||
{ ignorePatch: true, allowHigherVersions: true }
|
||||
);
|
||||
return {
|
||||
useWDS: env.useWDS(),
|
||||
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
||||
};
|
||||
};
|
||||
@@ -90,11 +92,13 @@ const minifyHtml = (content, ext) => {
|
||||
};
|
||||
|
||||
// Function to generate a dev task for each project's configuration
|
||||
// Note Currently WDS paths are hard-coded to only work for app
|
||||
const genPagesDevTask =
|
||||
(
|
||||
pageEntries,
|
||||
inputRoot,
|
||||
outputRoot,
|
||||
useWDS = false,
|
||||
inputSub = "src/html",
|
||||
publicRoot = ""
|
||||
) =>
|
||||
@@ -105,13 +109,17 @@ const genPagesDevTask =
|
||||
resolve(inputRoot, inputSub, `${page}.template`),
|
||||
{
|
||||
...commonVars,
|
||||
latestEntryJS: entries.map(
|
||||
(entry) => `${publicRoot}/frontend_latest/${entry}.js`
|
||||
latestEntryJS: entries.map((entry) =>
|
||||
useWDS
|
||||
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
||||
: `${publicRoot}/frontend_latest/${entry}.js`
|
||||
),
|
||||
es5EntryJS: entries.map(
|
||||
(entry) => `${publicRoot}/frontend_es5/${entry}.js`
|
||||
),
|
||||
latestCustomPanelJS: `${publicRoot}/frontend_latest/custom-panel.js`,
|
||||
latestCustomPanelJS: useWDS
|
||||
? "http://localhost:8000/src/entrypoints/custom-panel.ts"
|
||||
: `${publicRoot}/frontend_latest/custom-panel.js`,
|
||||
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
||||
}
|
||||
);
|
||||
@@ -168,7 +176,12 @@ const APP_PAGE_ENTRIES = {
|
||||
|
||||
gulp.task(
|
||||
"gen-pages-app-dev",
|
||||
genPagesDevTask(APP_PAGE_ENTRIES, paths.polymer_dir, paths.app_output_root)
|
||||
genPagesDevTask(
|
||||
APP_PAGE_ENTRIES,
|
||||
paths.polymer_dir,
|
||||
paths.app_output_root,
|
||||
env.useWDS()
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
@@ -244,28 +257,6 @@ gulp.task(
|
||||
)
|
||||
);
|
||||
|
||||
const LANDING_PAGE_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
|
||||
|
||||
gulp.task(
|
||||
"gen-pages-landing-page-dev",
|
||||
genPagesDevTask(
|
||||
LANDING_PAGE_PAGE_ENTRIES,
|
||||
paths.landingPage_dir,
|
||||
paths.landingPage_output_root
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"gen-pages-landing-page-prod",
|
||||
genPagesProdTask(
|
||||
LANDING_PAGE_PAGE_ENTRIES,
|
||||
paths.landingPage_dir,
|
||||
paths.landingPage_output_root,
|
||||
paths.landingPage_output_latest,
|
||||
paths.landingPage_output_es5
|
||||
)
|
||||
);
|
||||
|
||||
const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] };
|
||||
|
||||
gulp.task(
|
||||
@@ -274,6 +265,7 @@ gulp.task(
|
||||
HASSIO_PAGE_ENTRIES,
|
||||
paths.hassio_dir,
|
||||
paths.hassio_output_root,
|
||||
undefined,
|
||||
"src",
|
||||
paths.hassio_publicPath
|
||||
)
|
||||
|
@@ -66,7 +66,7 @@ gulp.task("fetch-nightly-translations", async function () {
|
||||
tokenAuth = JSON.parse(await readFile(TOKEN_FILE, "utf-8"));
|
||||
} catch {
|
||||
if (!allowTokenSetup) {
|
||||
console.log("No token found so build will continue with English only");
|
||||
console.log("No token found so build wil continue with English only");
|
||||
return;
|
||||
}
|
||||
const auth = createOAuthDeviceAuth({
|
||||
|
@@ -11,7 +11,7 @@ import "./gather-static.js";
|
||||
import "./gen-icons-json.js";
|
||||
import "./service-worker.js";
|
||||
import "./translations.js";
|
||||
import "./rspack.js";
|
||||
import "./webpack.js";
|
||||
|
||||
gulp.task("gather-gallery-pages", async function gatherPages() {
|
||||
const pageDir = path.resolve(paths.gallery_dir, "src/pages");
|
||||
@@ -156,7 +156,7 @@ gulp.task(
|
||||
"copy-static-gallery",
|
||||
"gen-pages-gallery-dev",
|
||||
gulp.parallel(
|
||||
"rspack-dev-server-gallery",
|
||||
"webpack-dev-server-gallery",
|
||||
async function watchMarkdownFiles() {
|
||||
gulp.watch(
|
||||
[
|
||||
@@ -185,7 +185,7 @@ gulp.task(
|
||||
"gather-gallery-pages"
|
||||
),
|
||||
"copy-static-gallery",
|
||||
"rspack-prod-gallery",
|
||||
"webpack-prod-gallery",
|
||||
"gen-pages-gallery-prod"
|
||||
)
|
||||
);
|
||||
|
@@ -125,11 +125,6 @@ gulp.task("copy-translations-supervisor", async () => {
|
||||
copyTranslations(staticDir);
|
||||
});
|
||||
|
||||
gulp.task("copy-translations-landing-page", async () => {
|
||||
const staticDir = paths.landingPage_output_static;
|
||||
copyTranslations(staticDir);
|
||||
});
|
||||
|
||||
gulp.task("copy-static-supervisor", async () => {
|
||||
const staticDir = paths.hassio_output_static;
|
||||
copyLocaleData(staticDir);
|
||||
@@ -204,14 +199,3 @@ gulp.task("copy-static-gallery", async () => {
|
||||
copyLocaleData(paths.gallery_output_static);
|
||||
copyMdiIcons(paths.gallery_output_static);
|
||||
});
|
||||
|
||||
gulp.task("copy-static-landing-page", async () => {
|
||||
// Copy landing-page static files
|
||||
fs.copySync(
|
||||
path.resolve(paths.landingPage_dir, "public"),
|
||||
paths.landingPage_output_root
|
||||
);
|
||||
|
||||
copyFonts(paths.landingPage_output_static);
|
||||
copyTranslations(paths.landingPage_output_static);
|
||||
});
|
||||
|
@@ -6,7 +6,7 @@ import "./entry-html.js";
|
||||
import "./gather-static.js";
|
||||
import "./gen-icons-json.js";
|
||||
import "./translations.js";
|
||||
import "./rspack.js";
|
||||
import "./webpack.js";
|
||||
|
||||
gulp.task(
|
||||
"develop-hassio",
|
||||
@@ -21,7 +21,7 @@ gulp.task(
|
||||
"copy-translations-supervisor",
|
||||
"build-locale-data",
|
||||
"copy-static-supervisor",
|
||||
"rspack-watch-hassio"
|
||||
"webpack-watch-hassio"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ gulp.task(
|
||||
"copy-translations-supervisor",
|
||||
"build-locale-data",
|
||||
"copy-static-supervisor",
|
||||
"rspack-prod-hassio",
|
||||
"webpack-prod-hassio",
|
||||
"gen-pages-hassio-prod",
|
||||
...// Don't compress running tests
|
||||
(env.isTestBuild() ? [] : ["compress-hassio"])
|
||||
|
@@ -1,41 +0,0 @@
|
||||
import gulp from "gulp";
|
||||
import "./clean.js";
|
||||
import "./compress.js";
|
||||
import "./entry-html.js";
|
||||
import "./gather-static.js";
|
||||
import "./gen-icons-json.js";
|
||||
import "./translations.js";
|
||||
import "./rspack.js";
|
||||
|
||||
gulp.task(
|
||||
"develop-landing-page",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean-landing-page",
|
||||
"translations-enable-merge-backend",
|
||||
"build-landing-page-translations",
|
||||
"copy-translations-landing-page",
|
||||
"build-locale-data",
|
||||
"copy-static-landing-page",
|
||||
"gen-pages-landing-page-dev",
|
||||
"rspack-watch-landing-page"
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"build-landing-page",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean-landing-page",
|
||||
"build-landing-page-translations",
|
||||
"copy-translations-landing-page",
|
||||
"build-locale-data",
|
||||
"copy-static-landing-page",
|
||||
"rspack-prod-landing-page",
|
||||
"gen-pages-landing-page-prod"
|
||||
)
|
||||
);
|
@@ -172,14 +172,12 @@ const createMasterTranslation = () =>
|
||||
|
||||
const FRAGMENTS = ["base"];
|
||||
|
||||
const setFragment = (fragment) => async () => {
|
||||
FRAGMENTS[0] = fragment;
|
||||
const toggleSupervisorFragment = async () => {
|
||||
FRAGMENTS[0] = "supervisor";
|
||||
};
|
||||
|
||||
const panelFragment = (fragment) =>
|
||||
fragment !== "base" &&
|
||||
fragment !== "supervisor" &&
|
||||
fragment !== "landing-page";
|
||||
fragment !== "base" && fragment !== "supervisor";
|
||||
|
||||
const HASHES = new Map();
|
||||
|
||||
@@ -226,9 +224,6 @@ const createTranslations = async () => {
|
||||
case "supervisor":
|
||||
// Supervisor key is at the top level
|
||||
return [flatten(data.supervisor), ""];
|
||||
case "landing-page":
|
||||
// landing-page key is at the top level
|
||||
return [flatten(data["landing-page"]), ""];
|
||||
default:
|
||||
// Create a fragment with only the given panel
|
||||
return [
|
||||
@@ -327,10 +322,5 @@ gulp.task(
|
||||
|
||||
gulp.task(
|
||||
"build-supervisor-translations",
|
||||
gulp.series(setFragment("supervisor"), "build-translations")
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"build-landing-page-translations",
|
||||
gulp.series(setFragment("landing-page"), "build-translations")
|
||||
gulp.series(toggleSupervisorFragment, "build-translations")
|
||||
);
|
||||
|
10
build-scripts/gulp/wds.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import gulp from "gulp";
|
||||
import { startDevServer } from "@web/dev-server";
|
||||
|
||||
gulp.task("wds-watch-app", async () => {
|
||||
startDevServer({
|
||||
config: {
|
||||
watch: true,
|
||||
},
|
||||
});
|
||||
});
|
@@ -1,11 +1,11 @@
|
||||
// Tasks to run rspack.
|
||||
// Tasks to run webpack.
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import log from "fancy-log";
|
||||
import gulp from "gulp";
|
||||
import rspack from "@rspack/core";
|
||||
import { RspackDevServer } from "@rspack/dev-server";
|
||||
import webpack from "webpack";
|
||||
import WebpackDevServer from "webpack-dev-server";
|
||||
import env from "../env.cjs";
|
||||
import paths from "../paths.cjs";
|
||||
import {
|
||||
@@ -14,8 +14,7 @@ import {
|
||||
createDemoConfig,
|
||||
createGalleryConfig,
|
||||
createHassioConfig,
|
||||
createLandingPageConfig,
|
||||
} from "../rspack.cjs";
|
||||
} from "../webpack.cjs";
|
||||
|
||||
const bothBuilds = (createConfigFunc, params) => [
|
||||
createConfigFunc({ ...params, latestBuild: true }),
|
||||
@@ -31,7 +30,7 @@ const isWsl =
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* compiler: import("@rspack/core").Compiler,
|
||||
* compiler: import("webpack").Compiler,
|
||||
* contentBase: string,
|
||||
* port: number,
|
||||
* listenHost?: string
|
||||
@@ -42,13 +41,12 @@ const runDevServer = async ({
|
||||
contentBase,
|
||||
port,
|
||||
listenHost = undefined,
|
||||
proxy = undefined,
|
||||
}) => {
|
||||
if (listenHost === undefined) {
|
||||
// For dev container, we need to listen on all hosts
|
||||
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
|
||||
}
|
||||
const server = new RspackDevServer(
|
||||
const server = new WebpackDevServer(
|
||||
{
|
||||
hot: false,
|
||||
open: true,
|
||||
@@ -58,14 +56,13 @@ const runDevServer = async ({
|
||||
directory: contentBase,
|
||||
watch: true,
|
||||
},
|
||||
proxy,
|
||||
},
|
||||
compiler
|
||||
);
|
||||
|
||||
await server.start();
|
||||
// Server listening
|
||||
log("[rspack-dev-server]", `Project is running at http://localhost:${port}`);
|
||||
log("[webpack-dev-server]", `Project is running at http://localhost:${port}`);
|
||||
};
|
||||
|
||||
const doneHandler = (done) => (err, stats) => {
|
||||
@@ -90,16 +87,16 @@ const doneHandler = (done) => (err, stats) => {
|
||||
|
||||
const prodBuild = (conf) =>
|
||||
new Promise((resolve) => {
|
||||
rspack(
|
||||
webpack(
|
||||
conf,
|
||||
// Resolve promise when done. Because we pass a callback, rspack closes itself
|
||||
// Resolve promise when done. Because we pass a callback, webpack closes itself
|
||||
doneHandler(resolve)
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("rspack-watch-app", () => {
|
||||
gulp.task("webpack-watch-app", () => {
|
||||
// This command will run forever because we don't close compiler
|
||||
rspack(
|
||||
webpack(
|
||||
process.env.ES5
|
||||
? bothBuilds(createAppConfig, { isProdBuild: false })
|
||||
: createAppConfig({ isProdBuild: false, latestBuild: true })
|
||||
@@ -110,7 +107,7 @@ gulp.task("rspack-watch-app", () => {
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("rspack-prod-app", () =>
|
||||
gulp.task("webpack-prod-app", () =>
|
||||
prodBuild(
|
||||
bothBuilds(createAppConfig, {
|
||||
isProdBuild: true,
|
||||
@@ -120,9 +117,9 @@ gulp.task("rspack-prod-app", () =>
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("rspack-dev-server-demo", () =>
|
||||
gulp.task("webpack-dev-server-demo", () =>
|
||||
runDevServer({
|
||||
compiler: rspack(
|
||||
compiler: webpack(
|
||||
createDemoConfig({ isProdBuild: false, latestBuild: true })
|
||||
),
|
||||
contentBase: paths.demo_output_root,
|
||||
@@ -130,18 +127,17 @@ gulp.task("rspack-dev-server-demo", () =>
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task("rspack-prod-demo", () =>
|
||||
gulp.task("webpack-prod-demo", () =>
|
||||
prodBuild(
|
||||
bothBuilds(createDemoConfig, {
|
||||
isProdBuild: true,
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("rspack-dev-server-cast", () =>
|
||||
gulp.task("webpack-dev-server-cast", () =>
|
||||
runDevServer({
|
||||
compiler: rspack(
|
||||
compiler: webpack(
|
||||
createCastConfig({ isProdBuild: false, latestBuild: true })
|
||||
),
|
||||
contentBase: paths.cast_output_root,
|
||||
@@ -151,7 +147,7 @@ gulp.task("rspack-dev-server-cast", () =>
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task("rspack-prod-cast", () =>
|
||||
gulp.task("webpack-prod-cast", () =>
|
||||
prodBuild(
|
||||
bothBuilds(createCastConfig, {
|
||||
isProdBuild: true,
|
||||
@@ -159,9 +155,9 @@ gulp.task("rspack-prod-cast", () =>
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("rspack-watch-hassio", () => {
|
||||
gulp.task("webpack-watch-hassio", () => {
|
||||
// This command will run forever because we don't close compiler
|
||||
rspack(
|
||||
webpack(
|
||||
createHassioConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: true,
|
||||
@@ -174,7 +170,7 @@ gulp.task("rspack-watch-hassio", () => {
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("rspack-prod-hassio", () =>
|
||||
gulp.task("webpack-prod-hassio", () =>
|
||||
prodBuild(
|
||||
bothBuilds(createHassioConfig, {
|
||||
isProdBuild: true,
|
||||
@@ -184,9 +180,9 @@ gulp.task("rspack-prod-hassio", () =>
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("rspack-dev-server-gallery", () =>
|
||||
gulp.task("webpack-dev-server-gallery", () =>
|
||||
runDevServer({
|
||||
compiler: rspack(
|
||||
compiler: webpack(
|
||||
createGalleryConfig({ isProdBuild: false, latestBuild: true })
|
||||
),
|
||||
contentBase: paths.gallery_output_root,
|
||||
@@ -195,7 +191,7 @@ gulp.task("rspack-dev-server-gallery", () =>
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task("rspack-prod-gallery", () =>
|
||||
gulp.task("webpack-prod-gallery", () =>
|
||||
prodBuild(
|
||||
createGalleryConfig({
|
||||
isProdBuild: true,
|
||||
@@ -203,30 +199,3 @@ gulp.task("rspack-prod-gallery", () =>
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("rspack-watch-landing-page", () => {
|
||||
// This command will run forever because we don't close compiler
|
||||
rspack(
|
||||
process.env.ES5
|
||||
? bothBuilds(createLandingPageConfig, { isProdBuild: false })
|
||||
: createLandingPageConfig({ isProdBuild: false, latestBuild: true })
|
||||
).watch({ poll: isWsl }, doneHandler());
|
||||
|
||||
gulp.watch(
|
||||
path.join(paths.translations_src, "en.json"),
|
||||
gulp.series(
|
||||
"build-landing-page-translations",
|
||||
"copy-translations-landing-page"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("rspack-prod-landing-page", () =>
|
||||
prodBuild(
|
||||
bothBuilds(createLandingPageConfig, {
|
||||
isProdBuild: true,
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
isTestBuild: env.isTestBuild(),
|
||||
})
|
||||
)
|
||||
);
|
@@ -33,22 +33,6 @@ module.exports = {
|
||||
),
|
||||
gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"),
|
||||
|
||||
landingPage_dir: path.resolve(__dirname, "../landing-page"),
|
||||
landingPage_build: path.resolve(__dirname, "../landing-page/build"),
|
||||
landingPage_output_root: path.resolve(__dirname, "../landing-page/dist"),
|
||||
landingPage_output_latest: path.resolve(
|
||||
__dirname,
|
||||
"../landing-page/dist/frontend_latest"
|
||||
),
|
||||
landingPage_output_es5: path.resolve(
|
||||
__dirname,
|
||||
"../landing-page/dist/frontend_es5"
|
||||
),
|
||||
landingPage_output_static: path.resolve(
|
||||
__dirname,
|
||||
"../landing-page/dist/static"
|
||||
),
|
||||
|
||||
hassio_dir: path.resolve(__dirname, "../hassio"),
|
||||
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
|
||||
hassio_output_static: path.resolve(__dirname, "../hassio/build/static"),
|
||||
|
@@ -1,13 +1,16 @@
|
||||
const { existsSync } = require("fs");
|
||||
const path = require("path");
|
||||
const rspack = require("@rspack/core");
|
||||
const { RsdoctorRspackPlugin } = require("@rsdoctor/rspack-plugin");
|
||||
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 { WebpackManifestPlugin } = require("rspack-manifest-plugin");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const log = require("fancy-log");
|
||||
const WebpackBar = require("webpackbar/rspack");
|
||||
const WebpackBar = require("webpackbar");
|
||||
const {
|
||||
TransformAsyncModulesPlugin,
|
||||
} = require("transform-async-modules-webpack-plugin");
|
||||
const { dependencies } = require("../package.json");
|
||||
const paths = require("./paths.cjs");
|
||||
const bundle = require("./bundle.cjs");
|
||||
|
||||
@@ -25,7 +28,7 @@ class LogStartCompilePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
const createRspackConfig = ({
|
||||
const createWebpackConfig = ({
|
||||
name,
|
||||
entry,
|
||||
outputPath,
|
||||
@@ -99,18 +102,13 @@ const createRspackConfig = ({
|
||||
splitChunks: {
|
||||
// Disable splitting for web workers and worklets because imports of
|
||||
// external chunks are broken for:
|
||||
chunks: !isProdBuild
|
||||
? // improve incremental build speed, but blows up bundle size
|
||||
new RegExp(
|
||||
`^(?!(${Object.keys(entry).join("|")}|.*work(?:er|let))$)`
|
||||
)
|
||||
: // - ESM output: https://github.com/webpack/webpack/issues/17014
|
||||
// - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543
|
||||
(chunk) =>
|
||||
!chunk.canBeInitial() &&
|
||||
!new RegExp(
|
||||
`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`
|
||||
).test(chunk.name),
|
||||
// - ESM output: https://github.com/webpack/webpack/issues/17014
|
||||
// - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543
|
||||
chunks: (chunk) =>
|
||||
!chunk.canBeInitial() &&
|
||||
!new RegExp(`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`).test(
|
||||
chunk.name
|
||||
),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
@@ -119,10 +117,10 @@ const createRspackConfig = ({
|
||||
// Only include the JS of entrypoints
|
||||
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
||||
}),
|
||||
new rspack.DefinePlugin(
|
||||
new webpack.DefinePlugin(
|
||||
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
|
||||
),
|
||||
new rspack.IgnorePlugin({
|
||||
new webpack.IgnorePlugin({
|
||||
checkResource(resource, context) {
|
||||
// Only use ignore to intercept imports that we don't control
|
||||
// inside node_module dependencies.
|
||||
@@ -154,7 +152,7 @@ const createRspackConfig = ({
|
||||
);
|
||||
},
|
||||
}),
|
||||
new rspack.NormalModuleReplacementPlugin(
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
new RegExp(
|
||||
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
|
||||
),
|
||||
@@ -170,14 +168,10 @@ const createRspackConfig = ({
|
||||
stats: { assets: true, chunks: true, modules: true },
|
||||
transform: (stats) => JSON.stringify(filterStats(stats)),
|
||||
}),
|
||||
isProdBuild &&
|
||||
isStatsBuild &&
|
||||
new RsdoctorRspackPlugin({
|
||||
reportDir: path.join(paths.build_dir, "rsdoctor"),
|
||||
features: ["plugins", "bundle"],
|
||||
supports: {
|
||||
generateTileGraph: true,
|
||||
},
|
||||
!latestBuild &&
|
||||
new TransformAsyncModulesPlugin({
|
||||
browserslistEnv: "legacy",
|
||||
runtime: { version: dependencies["@babel/runtime"] },
|
||||
}),
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
@@ -216,6 +210,8 @@ const createRspackConfig = ({
|
||||
isProdBuild && !isStatsBuild ? "[id].[contenthash][ext]" : "[id][ext]",
|
||||
crossOriginLoading: "use-credentials",
|
||||
hashFunction: "xxhash64",
|
||||
hashDigest: "base64url",
|
||||
hashDigestLength: 11, // full length of 64 bit base64url
|
||||
path: outputPath,
|
||||
publicPath,
|
||||
// To silence warning in worker plugin
|
||||
@@ -257,17 +253,17 @@ const createAppConfig = ({
|
||||
isStatsBuild,
|
||||
isTestBuild,
|
||||
}) =>
|
||||
createRspackConfig(
|
||||
createWebpackConfig(
|
||||
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
|
||||
);
|
||||
|
||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
|
||||
createRspackConfig(
|
||||
createWebpackConfig(
|
||||
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
|
||||
);
|
||||
|
||||
const createCastConfig = ({ isProdBuild, latestBuild }) =>
|
||||
createRspackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
|
||||
createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
|
||||
|
||||
const createHassioConfig = ({
|
||||
isProdBuild,
|
||||
@@ -275,7 +271,7 @@ const createHassioConfig = ({
|
||||
isStatsBuild,
|
||||
isTestBuild,
|
||||
}) =>
|
||||
createRspackConfig(
|
||||
createWebpackConfig(
|
||||
bundle.config.hassio({
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
@@ -285,10 +281,7 @@ const createHassioConfig = ({
|
||||
);
|
||||
|
||||
const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
|
||||
createRspackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
|
||||
|
||||
const createLandingPageConfig = ({ isProdBuild, latestBuild }) =>
|
||||
createRspackConfig(bundle.config.landingPage({ isProdBuild, latestBuild }));
|
||||
createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
|
||||
|
||||
module.exports = {
|
||||
createAppConfig,
|
||||
@@ -296,6 +289,5 @@ module.exports = {
|
||||
createCastConfig,
|
||||
createHassioConfig,
|
||||
createGalleryConfig,
|
||||
createRspackConfig,
|
||||
createLandingPageConfig,
|
||||
createWebpackConfig,
|
||||
};
|
@@ -25,9 +25,9 @@ class HcLovelace extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public lovelaceConfig!: LovelaceConfig;
|
||||
|
||||
@property({ attribute: false }) public viewPath?: string | number | null;
|
||||
@property() public viewPath?: string | number | null;
|
||||
|
||||
@property({ attribute: false }) public urlPath: string | null = null;
|
||||
@property() public urlPath: string | null = null;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const index = this._viewIndex;
|
||||
|
@@ -144,10 +144,10 @@ export class HcMain extends HassElement {
|
||||
}
|
||||
|
||||
if (senderId) {
|
||||
this._sendMessage(senderId, status);
|
||||
this.sendMessage(senderId, status);
|
||||
} else {
|
||||
for (const sender of castContext.getSenders()) {
|
||||
this._sendMessage(sender.id, status);
|
||||
this.sendMessage(sender.id, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,10 +164,10 @@ export class HcMain extends HassElement {
|
||||
};
|
||||
|
||||
if (senderId) {
|
||||
this._sendMessage(senderId, error);
|
||||
this.sendMessage(senderId, error);
|
||||
} else {
|
||||
for (const sender of castContext.getSenders()) {
|
||||
this._sendMessage(sender.id, error);
|
||||
this.sendMessage(sender.id, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,7 +394,7 @@ export class HcMain extends HassElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _sendMessage(senderId: string, response: any) {
|
||||
private sendMessage(senderId: string, response: any) {
|
||||
castContext.sendCustomMessage(CAST_NS, senderId, response);
|
||||
}
|
||||
}
|
||||
|
8
cast/webpack.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import webpack from "../build-scripts/webpack.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
export default webpack.createCastConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
@@ -4,6 +4,11 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
./node_modules/.bin/gulp analyze-demo
|
||||
export STATS=1
|
||||
statsfile="compilation-stats-demo.json"
|
||||
|
||||
./node_modules/.bin/webpack-cli --profile --node-env=production --json=$statsfile
|
||||
npx webpack-bundle-analyzer $statsfile dist/frontend_latest
|
||||
rm -f $statsfile
|
||||
|
@@ -46,6 +46,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
|
||||
this.requestUpdate();
|
||||
});
|
||||
mgr.castContext.addEventListener(
|
||||
// eslint-disable-next-line no-undef
|
||||
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
||||
(ev) => {
|
||||
// On Android, opening a new session always results in SESSION_RESUMED.
|
||||
|
@@ -26,7 +26,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@state() private _switching = false;
|
||||
|
||||
private _hidden = window.localStorage.getItem("hide_demo_card");
|
||||
private _hidden = localStorage.hide_demo_card;
|
||||
|
||||
public getCardSize() {
|
||||
return this._hidden ? 0 : 2;
|
||||
|
11
demo/webpack.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import webpack from "../build-scripts/webpack.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
// File just used for stats builds
|
||||
const latestBuild = true;
|
||||
|
||||
export default webpack.createDemoConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
latestBuild,
|
||||
});
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import unusedImports from "eslint-plugin-unused-imports";
|
||||
import globals from "globals";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
@@ -7,10 +6,10 @@ import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = path.dirname(_filename);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: _dirname,
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
@@ -58,7 +57,7 @@ export default [
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
webpack: {
|
||||
config: "./rspack.config.cjs",
|
||||
config: "./webpack.config.cjs",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -114,10 +113,12 @@ export default [
|
||||
"@typescript-eslint/no-shadow": ["error"],
|
||||
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
"off",
|
||||
{
|
||||
selector: ["objectLiteralProperty", "objectLiteralMethod"],
|
||||
format: null,
|
||||
selector: "default",
|
||||
format: ["camelCase", "snake_case"],
|
||||
leadingUnderscore: "allow",
|
||||
trailingUnderscore: "allow",
|
||||
},
|
||||
{
|
||||
selector: ["variable"],
|
||||
@@ -125,27 +126,10 @@ export default [
|
||||
leadingUnderscore: "allow",
|
||||
trailingUnderscore: "allow",
|
||||
},
|
||||
{
|
||||
selector: ["variable"],
|
||||
modifiers: ["exported"],
|
||||
format: ["camelCase", "PascalCase", "UPPER_CASE"],
|
||||
},
|
||||
{
|
||||
selector: "typeLike",
|
||||
format: ["PascalCase"],
|
||||
},
|
||||
{
|
||||
selector: "method",
|
||||
modifiers: ["public"],
|
||||
format: ["camelCase"],
|
||||
leadingUnderscore: "forbid",
|
||||
},
|
||||
{
|
||||
selector: "method",
|
||||
modifiers: ["private"],
|
||||
format: ["camelCase"],
|
||||
leadingUnderscore: "require",
|
||||
},
|
||||
],
|
||||
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
@@ -162,16 +146,16 @@ export default [
|
||||
],
|
||||
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"lit/attribute-names": "error",
|
||||
"lit/attribute-names": "warn",
|
||||
"lit/attribute-value-entities": "off",
|
||||
"lit/no-template-map": "off",
|
||||
"lit/no-native-attributes": "error",
|
||||
"lit/no-this-assign-in-render": "error",
|
||||
"lit/no-native-attributes": "warn",
|
||||
"lit/no-this-assign-in-render": "warn",
|
||||
"lit-a11y/click-events-have-key-events": ["off"],
|
||||
"lit-a11y/no-autofocus": "off",
|
||||
"lit-a11y/alt-text": "error",
|
||||
"lit-a11y/anchor-is-valid": "error",
|
||||
"lit-a11y/role-has-required-aria-attrs": "error",
|
||||
"lit-a11y/alt-text": "warn",
|
||||
"lit-a11y/anchor-is-valid": "warn",
|
||||
"lit-a11y/role-has-required-aria-attrs": "warn",
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||
},
|
||||
|
@@ -9,7 +9,6 @@ import "../../../src/components/ha-card";
|
||||
|
||||
@customElement("demo-black-white-row")
|
||||
class DemoBlackWhiteRow extends LitElement {
|
||||
// eslint-disable-next-line lit/no-native-attributes
|
||||
@property() title!: string;
|
||||
|
||||
@property() value?: any;
|
||||
|
@@ -18,8 +18,7 @@ class DemoCard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public config!: DemoCardConfig;
|
||||
|
||||
@property({ attribute: "show-config", type: Boolean })
|
||||
public showConfig = false;
|
||||
@property({ type: Boolean }) public showConfig = false;
|
||||
|
||||
@state() private _size?: number;
|
||||
|
||||
|
@@ -44,11 +44,11 @@ class DemoCards extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _showConfigToggled(ev) {
|
||||
_showConfigToggled(ev) {
|
||||
this._showConfig = ev.target.checked;
|
||||
}
|
||||
|
||||
private _darkThemeToggled(ev) {
|
||||
_darkThemeToggled(ev) {
|
||||
applyThemesOnElement(this._container, { themes: {} } as any, "default", {
|
||||
dark: ev.target.checked,
|
||||
});
|
||||
|
@@ -10,10 +10,9 @@ import type { HomeAssistant } from "../../../src/types";
|
||||
class DemoMoreInfo extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId!: string;
|
||||
@property() public entityId!: string;
|
||||
|
||||
@property({ attribute: "show-config", type: Boolean })
|
||||
public showConfig = false;
|
||||
@property({ type: Boolean }) public showConfig = false;
|
||||
|
||||
render() {
|
||||
const state = this._getState(this.entityId, this.hass.states);
|
||||
@@ -24,7 +23,7 @@ class DemoMoreInfo extends LitElement {
|
||||
<state-card-content
|
||||
.stateObj=${state}
|
||||
.hass=${this.hass}
|
||||
in-dialog
|
||||
inDialog
|
||||
></state-card-content>
|
||||
|
||||
<more-info-content
|
||||
|
@@ -58,11 +58,11 @@ class DemoMoreInfos extends LitElement {
|
||||
}
|
||||
`;
|
||||
|
||||
private _showConfigToggled(ev) {
|
||||
_showConfigToggled(ev) {
|
||||
this._showConfig = ev.target.checked;
|
||||
}
|
||||
|
||||
private _darkThemeToggled(ev) {
|
||||
_darkThemeToggled(ev) {
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector("#container"),
|
||||
{
|
||||
|
@@ -182,7 +182,7 @@ class HaGallery extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _menuTapped() {
|
||||
_menuTapped() {
|
||||
this._drawer.open = !this._drawer.open;
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
@@ -14,6 +15,7 @@ import { HaDelayAction } from "../../../../src/panels/config/automation/action/t
|
||||
import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
|
||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
||||
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-activate_scene";
|
||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
||||
import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
|
||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||
@@ -31,6 +33,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
|
||||
{ name: "Service", actions: [HaServiceAction.defaultConfig] },
|
||||
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
|
||||
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
|
||||
{ name: "Scene", actions: [HaSceneAction.defaultConfig] },
|
||||
{ name: "Play media", actions: [HaPlayMediaAction.defaultConfig] },
|
||||
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
|
||||
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
|
||||
@@ -63,6 +66,11 @@ class DemoHaAutomationEditorAction extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const valueChanged = (ev) => {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.data[sampleIdx] = ev.detail.value;
|
||||
this.requestUpdate();
|
||||
};
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Disabled">
|
||||
@@ -87,7 +95,7 @@ class DemoHaAutomationEditorAction extends LitElement {
|
||||
.actions=${this.data[sampleIdx]}
|
||||
.sampleIdx=${sampleIdx}
|
||||
.disabled=${this._disabled}
|
||||
@value-changed=${this._handleValueChange}
|
||||
@value-changed=${valueChanged}
|
||||
></ha-automation-action>
|
||||
`
|
||||
)}
|
||||
@@ -97,12 +105,6 @@ class DemoHaAutomationEditorAction extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleValueChange(ev) {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.data[sampleIdx] = ev.detail.value;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
@@ -103,6 +104,11 @@ export class DemoAutomationEditorCondition extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const valueChanged = (ev) => {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.data[sampleIdx] = ev.detail.value;
|
||||
this.requestUpdate();
|
||||
};
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Disabled">
|
||||
@@ -127,7 +133,7 @@ export class DemoAutomationEditorCondition extends LitElement {
|
||||
.conditions=${this.data[sampleIdx]}
|
||||
.sampleIdx=${sampleIdx}
|
||||
.disabled=${this._disabled}
|
||||
@value-changed=${this._handleValueChange}
|
||||
@value-changed=${valueChanged}
|
||||
></ha-automation-condition>
|
||||
`
|
||||
)}
|
||||
@@ -137,12 +143,6 @@ export class DemoAutomationEditorCondition extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleValueChange(ev) {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.data[sampleIdx] = ev.detail.value;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
@@ -149,6 +149,11 @@ export class DemoAutomationEditorTrigger extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const valueChanged = (ev) => {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.data[sampleIdx] = ev.detail.value;
|
||||
this.requestUpdate();
|
||||
};
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Disabled">
|
||||
@@ -173,7 +178,7 @@ export class DemoAutomationEditorTrigger extends LitElement {
|
||||
.triggers=${this.data[sampleIdx]}
|
||||
.sampleIdx=${sampleIdx}
|
||||
.disabled=${this._disabled}
|
||||
@value-changed=${this._handleValueChange}
|
||||
@value-changed=${valueChanged}
|
||||
></ha-automation-trigger>
|
||||
`
|
||||
)}
|
||||
@@ -183,12 +188,6 @@ export class DemoAutomationEditorTrigger extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleValueChange(ev) {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.data[sampleIdx] = ev.detail.value;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
@@ -31,17 +31,22 @@ export class DemoAutomationTrace extends LitElement {
|
||||
<hat-script-graph
|
||||
.trace=${trace.trace}
|
||||
.selected=${this._selected[idx]}
|
||||
@graph-node-selected=${this._handleGraphNodeSelected}
|
||||
.sampleIdx=${idx}
|
||||
@graph-node-selected=${(ev) => {
|
||||
this._selected = { ...this._selected, [idx]: ev.detail.path };
|
||||
}}
|
||||
></hat-script-graph>
|
||||
<hat-trace-timeline
|
||||
allow-pick
|
||||
allowPick
|
||||
.hass=${this.hass}
|
||||
.trace=${trace.trace}
|
||||
.logbookEntries=${trace.logbookEntries}
|
||||
.selectedPath=${this._selected[idx]}
|
||||
@value-changed=${this._handleTimelineValueChanged}
|
||||
.sampleIdx=${idx}
|
||||
@value-changed=${(ev) => {
|
||||
this._selected = {
|
||||
...this._selected,
|
||||
[idx]: ev.detail.value,
|
||||
};
|
||||
}}
|
||||
></hat-trace-timeline>
|
||||
<button @click=${() => console.log(trace)}>Log trace</button>
|
||||
</div>
|
||||
@@ -58,16 +63,6 @@ export class DemoAutomationTrace extends LitElement {
|
||||
hass.updateTranslations("config", "en");
|
||||
}
|
||||
|
||||
private _handleTimelineValueChanged(ev) {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this._selected = { ...this._selected, [sampleIdx]: ev.detail.value };
|
||||
}
|
||||
|
||||
private _handleGraphNodeSelected(ev) {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this._selected = { ...this._selected, [sampleIdx]: ev.detail.path };
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
|
@@ -489,8 +489,14 @@ class DemoHaForm extends LitElement {
|
||||
.title=${info.title}
|
||||
.value=${this.data[idx]}
|
||||
.disabled=${this.disabled[idx]}
|
||||
@submitted=${this._handleSubmit}
|
||||
.sampleIdx=${idx}
|
||||
@submitted=${() => {
|
||||
this.disabled[idx] = true;
|
||||
this.requestUpdate();
|
||||
setTimeout(() => {
|
||||
this.disabled[idx] = false;
|
||||
this.requestUpdate();
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
${["light", "dark"].map(
|
||||
(slot) => html`
|
||||
@@ -505,8 +511,10 @@ class DemoHaForm extends LitElement {
|
||||
.computeLabel=${(schema) =>
|
||||
translations[schema.name] || schema.name}
|
||||
.computeHelper=${() => "Helper text"}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
.sampleIdx=${idx}
|
||||
@value-changed=${(e) => {
|
||||
this.data[idx] = e.detail.value;
|
||||
this.requestUpdate();
|
||||
}}
|
||||
></ha-form>
|
||||
`
|
||||
)}
|
||||
@@ -515,22 +523,6 @@ class DemoHaForm extends LitElement {
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleValueChanged(ev) {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.data[sampleIdx] = ev.detail.value;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _handleSubmit(ev) {
|
||||
const sampleIdx = ev.target.sampleIdx;
|
||||
this.disabled[sampleIdx] = true;
|
||||
this.requestUpdate();
|
||||
setTimeout(() => {
|
||||
this.disabled[sampleIdx] = false;
|
||||
this.requestUpdate();
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import "@material/mwc-button";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
@@ -590,6 +591,13 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
</div>
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
const data = this.data[idx];
|
||||
const valueChanged = (ev) => {
|
||||
this.data[idx] = {
|
||||
...data,
|
||||
[ev.target.key]: ev.detail.value,
|
||||
};
|
||||
this.requestUpdate();
|
||||
};
|
||||
return html`
|
||||
<demo-black-white-row .title=${info.name}>
|
||||
${["light", "dark"].map((slot) =>
|
||||
@@ -606,8 +614,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
.value=${data[key] ?? value!.default}
|
||||
.disabled=${this._disabled}
|
||||
.required=${this._required}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
.sampleIdx=${idx}
|
||||
@value-changed=${valueChanged}
|
||||
.helper=${this._helper ? "Helper text" : undefined}
|
||||
></ha-selector>
|
||||
</ha-settings-row>
|
||||
@@ -620,15 +627,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleValueChanged(ev) {
|
||||
const idx = ev.target.sampleIdx;
|
||||
this.data[idx] = {
|
||||
...this.data[idx],
|
||||
[ev.target.key]: ev.detail.value,
|
||||
};
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
@@ -86,11 +86,9 @@ const ENTITIES = [
|
||||
friendly_name: "Sensibo purifier",
|
||||
fan_modes: ["low", "high"],
|
||||
fan_mode: "low",
|
||||
swing_modes: ["both", "rangefull", "off"],
|
||||
swing_mode: "rangefull",
|
||||
swing_horizontal_modes: ["both", "rangefull", "off"],
|
||||
swing_horizontal_mode: "both",
|
||||
supported_features: 553,
|
||||
swing_modes: ["on", "off", "both", "vertical", "horizontal"],
|
||||
swing_mode: "vertical",
|
||||
supported_features: 41,
|
||||
}),
|
||||
getEntity("climate", "unavailable", "unavailable", {
|
||||
supported_features: 43,
|
||||
@@ -190,13 +188,11 @@ const CONFIGS = [
|
||||
- type: climate-swing-modes
|
||||
style: icons
|
||||
swing_modes:
|
||||
- 'both'
|
||||
- 'rangefull'
|
||||
- 'on'
|
||||
- 'off'
|
||||
swing_horizontal_modes:
|
||||
- 'both'
|
||||
- 'rangefull'
|
||||
- 'off'
|
||||
- 'vertical'
|
||||
- 'horizontal'
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@@ -4,7 +4,6 @@ import { customElement, query } from "lit/decorators";
|
||||
import { CoverEntityFeature } from "../../../../src/data/cover";
|
||||
import { LightColorMode } from "../../../../src/data/light";
|
||||
import { LockEntityFeature } from "../../../../src/data/lock";
|
||||
import { MediaPlayerEntityFeature } from "../../../../src/data/media-player";
|
||||
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
@@ -29,10 +28,6 @@ const ENTITIES = [
|
||||
device_class: "lock",
|
||||
supported_features: LockEntityFeature.OPEN,
|
||||
}),
|
||||
getEntity("media_player", "living_room", "playing", {
|
||||
friendly_name: "Living room speaker",
|
||||
supported_features: MediaPlayerEntityFeature.VOLUME_SET,
|
||||
}),
|
||||
getEntity("climate", "thermostat", "heat", {
|
||||
current_temperature: 73,
|
||||
min_temp: 45,
|
||||
@@ -83,19 +78,16 @@ const ENTITIES = [
|
||||
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
|
||||
preset_modes: ["home", "eco", "away"],
|
||||
swing_modes: ["auto", "1", "2", "3", "off"],
|
||||
switch_horizontal_modes: ["auto", "4", "5", "6", "off"],
|
||||
current_temperature: 23,
|
||||
target_temp_high: 24,
|
||||
target_temp_low: 21,
|
||||
fan_mode: "auto_low",
|
||||
preset_mode: "home",
|
||||
swing_mode: "auto",
|
||||
swing_horizontal_mode: "off",
|
||||
supported_features:
|
||||
ClimateEntityFeature.TURN_ON +
|
||||
ClimateEntityFeature.TURN_OFF +
|
||||
ClimateEntityFeature.SWING_MODE +
|
||||
ClimateEntityFeature.SWING_HORIZONTAL_MODE +
|
||||
ClimateEntityFeature.PRESET_MODE +
|
||||
ClimateEntityFeature.FAN_MODE +
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
|
||||
@@ -202,15 +194,6 @@ const CONFIGS = [
|
||||
- type: "lock-open-door"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Media player volume slider feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: media_player.living_room
|
||||
features:
|
||||
- type: "media-player-volume-slider"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Vacuum commands feature",
|
||||
config: `
|
||||
|
8
gallery/webpack.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import webpack from "../build-scripts/webpack.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
export default webpack.createGalleryConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
@@ -136,7 +136,7 @@ export class HassioAddonStore extends LitElement {
|
||||
this._manageRepositories(repositoryUrl);
|
||||
}
|
||||
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ export class HassioAddonStore extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _apiCalled(ev) {
|
||||
private apiCalled(ev) {
|
||||
if (ev.detail.success) {
|
||||
this._loadData();
|
||||
}
|
||||
|
@@ -315,7 +315,6 @@ class HassioAddonConfig extends LitElement {
|
||||
text: this.supervisor.localize("confirm.reset_options.text"),
|
||||
confirmText: this.supervisor.localize("common.reset_options"),
|
||||
dismissText: this.supervisor.localize("common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
|
@@ -58,7 +58,7 @@ export class HassioBackups extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
@property({ type: Boolean }) public isWide = false;
|
||||
|
||||
@state() private _selectedBackups: string[] = [];
|
||||
|
||||
@@ -74,7 +74,7 @@ export class HassioBackups extends LitElement {
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hass && this._firstUpdatedCalled) {
|
||||
this._fetchBackups();
|
||||
this.fetchBackups();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ export class HassioBackups extends LitElement {
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass && this.isConnected) {
|
||||
this._fetchBackups();
|
||||
this.fetchBackups();
|
||||
}
|
||||
this._firstUpdatedCalled = true;
|
||||
}
|
||||
@@ -198,7 +198,7 @@ export class HassioBackups extends LitElement {
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
clickable
|
||||
selectable
|
||||
has-fab
|
||||
hasFab
|
||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
||||
back-path=${atLeastVersion(this.hass.config.version, 2022, 5)
|
||||
? "/config/system"
|
||||
@@ -280,7 +280,7 @@ export class HassioBackups extends LitElement {
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._fetchBackups();
|
||||
this.fetchBackups();
|
||||
break;
|
||||
case 1:
|
||||
showHassioBackupLocationDialog(this, { supervisor: this.supervisor });
|
||||
@@ -303,13 +303,13 @@ export class HassioBackups extends LitElement {
|
||||
showHassioBackupDialog(this, {
|
||||
slug,
|
||||
supervisor: this.supervisor,
|
||||
onDelete: () => this._fetchBackups(),
|
||||
onDelete: () => this.fetchBackups(),
|
||||
}),
|
||||
reloadBackup: () => this._fetchBackups(),
|
||||
reloadBackup: () => this.fetchBackups(),
|
||||
});
|
||||
}
|
||||
|
||||
private async _fetchBackups() {
|
||||
private async fetchBackups() {
|
||||
this._isLoading = true;
|
||||
await reloadHassioBackups(this.hass);
|
||||
this._backups = await fetchHassioBackups(this.hass);
|
||||
@@ -323,7 +323,6 @@ export class HassioBackups extends LitElement {
|
||||
number: this._selectedBackups.length,
|
||||
}),
|
||||
confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
@@ -341,7 +340,7 @@ export class HassioBackups extends LitElement {
|
||||
});
|
||||
return;
|
||||
}
|
||||
await this._fetchBackups();
|
||||
await this.fetchBackups();
|
||||
this._dataTable.clearSelection();
|
||||
}
|
||||
|
||||
@@ -350,7 +349,7 @@ export class HassioBackups extends LitElement {
|
||||
showHassioBackupDialog(this, {
|
||||
slug,
|
||||
supervisor: this.supervisor,
|
||||
onDelete: () => this._fetchBackups(),
|
||||
onDelete: () => this.fetchBackups(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -366,7 +365,7 @@ export class HassioBackups extends LitElement {
|
||||
}
|
||||
showHassioCreateBackupDialog(this, {
|
||||
supervisor: this.supervisor!,
|
||||
onCreate: () => this._fetchBackups(),
|
||||
onCreate: () => this.fetchBackups(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -9,24 +9,23 @@ import type { HomeAssistant } from "../../../src/types";
|
||||
class HassioCardContent extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
// eslint-disable-next-line lit/no-native-attributes
|
||||
@property() public title!: string;
|
||||
|
||||
@property() public description?: string;
|
||||
|
||||
@property({ type: Boolean }) public available = true;
|
||||
|
||||
@property({ attribute: false }) public showTopbar = false;
|
||||
@property({ type: Boolean }) public showTopbar = false;
|
||||
|
||||
@property({ attribute: false }) public topbarClass?: string;
|
||||
@property() public topbarClass?: string;
|
||||
|
||||
@property({ attribute: false }) public iconTitle?: string;
|
||||
@property() public iconTitle?: string;
|
||||
|
||||
@property({ attribute: false }) public iconClass?: string;
|
||||
@property() public iconClass?: string;
|
||||
|
||||
@property() public icon = mdiHelpCircle;
|
||||
|
||||
@property({ attribute: false }) public iconImage?: string;
|
||||
@property() public iconImage?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
@@ -36,11 +35,7 @@ class HassioCardContent extends LitElement {
|
||||
${this.iconImage
|
||||
? html`
|
||||
<div class="icon_image ${this.iconClass}">
|
||||
<img
|
||||
src=${this.iconImage}
|
||||
.title=${this.iconTitle}
|
||||
alt=${this.iconTitle ?? ""}
|
||||
/>
|
||||
<img src=${this.iconImage} .title=${this.iconTitle} />
|
||||
<div></div>
|
||||
</div>
|
||||
`
|
||||
|
@@ -73,24 +73,23 @@ export class SupervisorBackupContent extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public backup?: HassioBackupDetail;
|
||||
|
||||
@property({ attribute: false })
|
||||
public backupType: HassioBackupDetail["type"] = "full";
|
||||
@property() public backupType: HassioBackupDetail["type"] = "full";
|
||||
|
||||
@property({ attribute: false }) public folders?: CheckboxItem[];
|
||||
|
||||
@property({ attribute: false }) public addons?: AddonCheckboxItem[];
|
||||
|
||||
@property({ attribute: false }) public homeAssistant = false;
|
||||
@property({ type: Boolean }) public homeAssistant = false;
|
||||
|
||||
@property({ attribute: false }) public backupHasPassword = false;
|
||||
@property({ type: Boolean }) public backupHasPassword = false;
|
||||
|
||||
@property({ type: Boolean }) public onboarding = false;
|
||||
|
||||
@property({ attribute: false }) public backupName = "";
|
||||
@property() public backupName = "";
|
||||
|
||||
@property({ attribute: false }) public backupPassword = "";
|
||||
@property() public backupPassword = "";
|
||||
|
||||
@property({ attribute: false }) public confirmBackupPassword = "";
|
||||
@property() public confirmBackupPassword = "";
|
||||
|
||||
@query("ha-textfield, ha-radio, ha-checkbox", true) private _focusTarget;
|
||||
|
||||
@@ -192,7 +191,7 @@ export class SupervisorBackupContent extends LitElement {
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@change=${this._toggleHomeAssistant}
|
||||
@change=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>`
|
||||
@@ -278,7 +277,7 @@ export class SupervisorBackupContent extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleHomeAssistant() {
|
||||
private toggleHomeAssistant() {
|
||||
this.homeAssistant = !this.homeAssistant;
|
||||
}
|
||||
|
||||
|
@@ -7,9 +7,9 @@ import "../../../src/components/ha-svg-icon";
|
||||
class SupervisorFormfieldLabel extends LitElement {
|
||||
@property({ type: String }) public label!: string;
|
||||
|
||||
@property({ attribute: false }) public imageUrl?: string;
|
||||
@property({ type: String }) public imageUrl?: string;
|
||||
|
||||
@property({ attribute: false }) public iconPath?: string;
|
||||
@property({ type: String }) public iconPath?: string;
|
||||
|
||||
@property({ type: String }) public version?: string;
|
||||
|
||||
|
@@ -76,7 +76,7 @@ class HassioDashboard extends LitElement {
|
||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
||||
back-path="/config"
|
||||
supervisor
|
||||
has-fab
|
||||
hasFab
|
||||
>
|
||||
<span slot="header">
|
||||
${this.supervisor.localize(
|
||||
|
@@ -302,7 +302,6 @@ class HassioBackupDialog
|
||||
text: supervisor!.localize("backup.confirm_delete_text"),
|
||||
confirmText: supervisor!.localize("backup.delete"),
|
||||
dismissText: supervisor!.localize("backup.cancel"),
|
||||
destructive: true,
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
|
@@ -95,7 +95,7 @@ class HassioDatadiskDialog extends LitElement {
|
||||
.label=${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.select_device"
|
||||
)}
|
||||
@selected=${this._selectDevice}
|
||||
@selected=${this._select_device}
|
||||
dialogInitialFocus
|
||||
>
|
||||
${this.devices.map(
|
||||
@@ -137,7 +137,7 @@ class HassioDatadiskDialog extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _selectDevice(ev) {
|
||||
private _select_device(ev) {
|
||||
this.selectedDevice = ev.target.value;
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,6 @@ import type { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
|
||||
class HassioMarkdownDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
// eslint-disable-next-line lit/no-native-attributes
|
||||
@property() public title!: string;
|
||||
|
||||
@property() public content!: string;
|
||||
|
@@ -394,7 +394,7 @@ export class DialogHassioNetwork
|
||||
`;
|
||||
}
|
||||
|
||||
private _toArray(data: string | string[]): string[] {
|
||||
_toArray(data: string | string[]): string[] {
|
||||
if (Array.isArray(data)) {
|
||||
if (data && typeof data[0] === "string") {
|
||||
data = data[0];
|
||||
@@ -409,7 +409,7 @@ export class DialogHassioNetwork
|
||||
return data;
|
||||
}
|
||||
|
||||
private _toString(data: string | string[]): string {
|
||||
_toString(data: string | string[]): string {
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ class HassioIngressView extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public ingressPanel = false;
|
||||
@property({ type: Boolean }) public ingressPanel = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
|
@@ -58,10 +58,10 @@ const SUPERVISOR_UPDATE_NAMES = {
|
||||
supervisor: "Home Assistant Supervisor",
|
||||
};
|
||||
|
||||
type UpdateType = "os" | "supervisor" | "core" | "addon";
|
||||
type updateType = "os" | "supervisor" | "core" | "addon";
|
||||
|
||||
const changelogUrl = (
|
||||
entry: UpdateType,
|
||||
entry: updateType,
|
||||
version: string
|
||||
): string | undefined => {
|
||||
if (entry === "addon") {
|
||||
@@ -99,7 +99,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addonSlug?: string;
|
||||
|
||||
@state() private _updateType?: UpdateType;
|
||||
@state() private _updateType?: updateType;
|
||||
|
||||
@state() private _changelogContent?: string;
|
||||
|
||||
@@ -222,7 +222,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
const updateType = ["core", "os", "supervisor"].includes(pathPart)
|
||||
? pathPart
|
||||
: "addon";
|
||||
this._updateType = updateType as UpdateType;
|
||||
this._updateType = updateType as updateType;
|
||||
|
||||
switch (updateType) {
|
||||
case "addon":
|
||||
|
8
hassio/webpack.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import webpack from "../build-scripts/webpack.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
export default webpack.createHassioConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
@@ -1,44 +0,0 @@
|
||||
# Home Assistant OS Landingpage
|
||||
|
||||
On initial startup of Home Assistant, HAOS needs to download Home Assistant core before the setup can start.
|
||||
In this time the [home-assistant/landingpage](https://github.com/home-assistant/landingpage) is serving a "Preparing Home Assistant" page.
|
||||
|
||||
## Functionality
|
||||
|
||||
- Progress bar to show download
|
||||
- Show / hide supervisor logs
|
||||
- Links
|
||||
- Read our Vision
|
||||
- Join our community
|
||||
- Download our app
|
||||
- DNS issue handler
|
||||
- if the supervisor is not able to connect to the internet
|
||||
- Show actions to set dns to google or cloudflare to resolve the issue
|
||||
- Error handler
|
||||
- if something with the installation goes wrong, we show the logs
|
||||
|
||||
## Develop
|
||||
|
||||
It is similar to the core frontend dev.
|
||||
|
||||
- frontend repo is building stuff
|
||||
- landingpage repo can set the frontend repo path and serve the dev frontend
|
||||
|
||||
### landingpage dev server
|
||||
|
||||
- clone [home-assistant/landingpage](https://github.com/home-assistant/landingpage)
|
||||
- Add frontend repo as mount to your devcontainer config
|
||||
- please do not commit this changes, you can remove it after initial dev container build, because the build will keep the options as long as you don't rebuild it.
|
||||
- `"mounts": ["source=/path/to/hass/frontend,target=/workspaces/frontend,type=bind,consistency=cached"]`
|
||||
- use the dev container
|
||||
- start the dev server with following optional env vars:
|
||||
- `SUPERVISOR_HOST` to have real supervisor data, you can [setup a supervisor remote API access](https://developers.home-assistant.io/docs/supervisor/development/#supervisor-api-access) and set the host of your supervisor. e.g.: `SUPERVISOR_HOST=192.168.0.20:8888`
|
||||
- `SUPERVISOR_TOKEN` the supervisor api token you get from the Remote API proxy Addon Logs
|
||||
- `FRONTEND_PATH` the path inside your container should be `/workspaces/frontend`
|
||||
- example: `SUPERVISOR_TOKEN=abc123 SUPERVISOR_HOST=192.168.0.20:8888 FRONTEND_PATH=/workspaces/frontend go run main.go http.go mdns.go`
|
||||
- You can also add this into your devcontainer settings, but then it's not so flexible to change if you want to test something else.
|
||||
|
||||
### frontend dev server
|
||||
|
||||
- install all dependencies
|
||||
- run `landing-page/script/develop`
|
@@ -1,8 +0,0 @@
|
||||
import rootConfig from "../eslint.config.mjs";
|
||||
|
||||
export default [
|
||||
...rootConfig,
|
||||
{
|
||||
rules: {},
|
||||
},
|
||||
];
|
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 15 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="92" height="136" viewBox="0 0 92 136" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M76.38 104.9H3.03C2.24231 104.9 1.48688 105.213 0.929897 105.77C0.372914 106.327 0.0600052 107.082 0.0600052 107.87V132.47C0.0600052 133.258 0.372914 134.013 0.929897 134.57C1.48688 135.127 2.24231 135.44 3.03 135.44H12.36C13.1477 135.44 13.9031 135.127 14.4601 134.57C15.0171 134.013 15.33 133.258 15.33 132.47V120.17H76.39V132.47C76.39 133.258 76.7029 134.013 77.2599 134.57C77.8169 135.127 78.5723 135.44 79.36 135.44H88.69C89.4777 135.44 90.2331 135.127 90.7901 134.57C91.3471 134.013 91.66 133.258 91.66 132.47V107.87C91.66 107.082 91.3471 106.327 90.7901 105.77C90.2331 105.213 89.4777 104.9 88.69 104.9H76.39H76.38ZM50.04 2.24996C47.73 -0.0600439 43.95 -0.0600439 41.65 2.24996L4.25 39.65C1.94 41.96 0.0500031 46.52 0.0500031 49.78V83.7C0.0500031 86.96 2.72 89.64 5.99 89.64H85.7C88.96 89.64 91.64 86.97 91.64 83.7V49.78C91.64 46.52 89.75 41.96 87.44 39.65L50.04 2.24996Z" fill="#18BCF2"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1011 B |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,10 +0,0 @@
|
||||
<svg width="75" height="79" viewBox="0 0 75 79" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M73.8393 17.4898C72.6973 9.00165 65.2994 2.31235 56.5296 1.01614C55.05 0.797115 49.4441 0 36.4582 0H36.3612C23.3717 0 20.585 0.797115 19.1054 1.01614C10.5798 2.27644 2.79399 8.28712 0.904997 16.8758C-0.00358524 21.1056 -0.100549 25.7949 0.0682394 30.0965C0.308852 36.2651 0.355538 42.423 0.91577 48.5665C1.30307 52.6474 1.97872 56.6957 2.93763 60.6812C4.73325 68.042 12.0019 74.1676 19.1233 76.6666C26.7478 79.2728 34.9474 79.7055 42.8039 77.9162C43.6682 77.7151 44.5217 77.4817 45.3645 77.216C47.275 76.6092 49.5123 75.9305 51.1571 74.7385C51.1797 74.7217 51.1982 74.7001 51.2112 74.6753C51.2243 74.6504 51.2316 74.6229 51.2325 74.5948V68.6416C51.2321 68.6154 51.2259 68.5896 51.2142 68.5661C51.2025 68.5426 51.1858 68.522 51.1651 68.5058C51.1444 68.4896 51.1204 68.4783 51.0948 68.4726C51.0692 68.4669 51.0426 68.467 51.0171 68.4729C45.9835 69.675 40.8254 70.2777 35.6502 70.2682C26.7439 70.2682 24.3486 66.042 23.6626 64.2826C23.1113 62.762 22.7612 61.1759 22.6212 59.5646C22.6197 59.5375 22.6247 59.5105 22.6357 59.4857C22.6466 59.4609 22.6633 59.4391 22.6843 59.422C22.7053 59.4048 22.73 59.3929 22.7565 59.3871C22.783 59.3813 22.8104 59.3818 22.8367 59.3886C27.7864 60.5826 32.8604 61.1853 37.9522 61.1839C39.1768 61.1839 40.3978 61.1839 41.6224 61.1516C46.7435 61.008 52.1411 60.7459 57.1796 59.7621C57.3053 59.7369 57.431 59.7154 57.5387 59.6831C65.4861 58.157 73.0493 53.3672 73.8178 41.2381C73.8465 40.7606 73.9184 36.2364 73.9184 35.7409C73.9219 34.0569 74.4606 23.7949 73.8393 17.4898Z" fill="url(#paint0_linear_549_34)"/>
|
||||
<path d="M61.2484 27.0263V48.114H52.8916V27.6475C52.8916 23.3388 51.096 21.1413 47.4437 21.1413C43.4287 21.1413 41.4177 23.7409 41.4177 28.8755V40.0782H33.1111V28.8755C33.1111 23.7409 31.0965 21.1413 27.0815 21.1413C23.4507 21.1413 21.6371 23.3388 21.6371 27.6475V48.114H13.2839V27.0263C13.2839 22.7176 14.384 19.2946 16.5843 16.7572C18.8539 14.2258 21.8311 12.926 25.5264 12.926C29.8036 12.926 33.0357 14.5705 35.1905 17.8559L37.2698 21.346L39.3527 17.8559C41.5074 14.5705 44.7395 12.926 49.0095 12.926C52.7013 12.926 55.6784 14.2258 57.9553 16.7572C60.1531 19.2922 61.2508 22.7152 61.2484 27.0263Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_549_34" x1="37.0692" y1="0" x2="37.0692" y2="79" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6364FF"/>
|
||||
<stop offset="1" stop-color="#563ACC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 430 B |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 69 KiB |
@@ -1,9 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Run the landing-page
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
./node_modules/.bin/gulp build-landing-page
|
@@ -1,9 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Run the landing-page
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
./node_modules/.bin/gulp develop-landing-page
|
@@ -1,334 +0,0 @@
|
||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import { mdiArrowCollapseDown, mdiDownload } from "@mdi/js";
|
||||
// eslint-disable-next-line import/extensions
|
||||
import { IntersectionController } from "@lit-labs/observers/intersection-controller.js";
|
||||
import { LitElement, type PropertyValues, css, html, nothing } from "lit";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type {
|
||||
LandingPageKeys,
|
||||
LocalizeFunc,
|
||||
} from "../../../src/common/translations/localize";
|
||||
import "../../../src/components/ha-button";
|
||||
import "../../../src/components/ha-icon-button";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import "../../../src/components/ha-ansi-to-html";
|
||||
import "../../../src/components/ha-alert";
|
||||
import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html";
|
||||
import {
|
||||
getObserverLogs,
|
||||
downloadUrl as observerLogsDownloadUrl,
|
||||
} from "../data/observer";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { fileDownload } from "../../../src/util/file_download";
|
||||
import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor";
|
||||
|
||||
const ERROR_CHECK = /^[\d\s-:]+(ERROR|CRITICAL)(.*)/gm;
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"landing-page-error": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const SCHEDULE_FETCH_OBSERVER_LOGS = 5;
|
||||
|
||||
@customElement("landing-page-logs")
|
||||
class LandingPageLogs extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public localize!: LocalizeFunc<LandingPageKeys>;
|
||||
|
||||
@query("ha-ansi-to-html") private _ansiToHtmlElement?: HaAnsiToHtml;
|
||||
|
||||
@query(".logs") private _logElement?: HTMLElement;
|
||||
|
||||
@query("#scroll-bottom-marker")
|
||||
private _scrollBottomMarkerElement?: HTMLElement;
|
||||
|
||||
@state() private _show = false;
|
||||
|
||||
@state() private _scrolledToBottomController =
|
||||
new IntersectionController<boolean>(this, {
|
||||
callback(this: IntersectionController<boolean>, entries) {
|
||||
return entries[0].isIntersecting;
|
||||
},
|
||||
});
|
||||
|
||||
@state() private _error = false;
|
||||
|
||||
@state() private _newLogsIndicator?: boolean;
|
||||
|
||||
@state() private _logLinesCount = 0;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="actions">
|
||||
<ha-button @click=${this._toggleLogDetails}>
|
||||
${this.localize(this._show ? "hide_details" : "show_details")}
|
||||
</ha-button>
|
||||
${this._show
|
||||
? html`<ha-icon-button
|
||||
.label=${this.localize("logs.download_logs")}
|
||||
.path=${mdiDownload}
|
||||
@click=${this._downloadLogs}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._error
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this.localize("logs.fetch_error")}
|
||||
>
|
||||
<ha-button @click=${this._startLogStream}>
|
||||
${this.localize("logs.retry")}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
<div
|
||||
class=${classMap({
|
||||
logs: true,
|
||||
hidden: !this._show,
|
||||
})}
|
||||
>
|
||||
<ha-ansi-to-html></ha-ansi-to-html>
|
||||
<div id="scroll-bottom-marker"></div>
|
||||
</div>
|
||||
<ha-button
|
||||
class="new-logs-indicator ${classMap({
|
||||
visible:
|
||||
(this._show &&
|
||||
this._newLogsIndicator &&
|
||||
!this._scrolledToBottomController.value) ||
|
||||
false,
|
||||
})}"
|
||||
@click=${this._scrollToBottom}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiArrowCollapseDown} slot="icon"></ha-svg-icon>
|
||||
${this.localize("logs.scroll_down_button")}
|
||||
<ha-svg-icon
|
||||
.path=${mdiArrowCollapseDown}
|
||||
slot="trailingIcon"
|
||||
></ha-svg-icon>
|
||||
</ha-button>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
this._scrolledToBottomController.observe(this._scrollBottomMarkerElement!);
|
||||
|
||||
this._startLogStream();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (this._newLogsIndicator && this._scrolledToBottomController.value) {
|
||||
this._newLogsIndicator = false;
|
||||
}
|
||||
|
||||
if (changedProps.has("_show") && this._show) {
|
||||
this._scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleLogDetails() {
|
||||
this._show = !this._show;
|
||||
}
|
||||
|
||||
private _scrollToBottom(): void {
|
||||
if (this._logElement) {
|
||||
this._newLogsIndicator = false;
|
||||
this._logElement!.scrollTo(0, this._logElement!.scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private _displayLogs(logs: string, tempLogLine = "", clear = false): string {
|
||||
if (clear) {
|
||||
this._ansiToHtmlElement?.clear();
|
||||
this._logLinesCount = 0;
|
||||
}
|
||||
|
||||
const showError = ERROR_CHECK.test(logs);
|
||||
|
||||
const scrolledToBottom = this._scrolledToBottomController.value;
|
||||
const lines = `${tempLogLine}${logs}`
|
||||
.split("\n")
|
||||
.filter((line) => line.trim() !== "");
|
||||
|
||||
// handle edge case where the last line is not complete
|
||||
if (logs.endsWith("\n")) {
|
||||
tempLogLine = "";
|
||||
} else {
|
||||
tempLogLine = lines.splice(-1, 1)[0];
|
||||
}
|
||||
|
||||
if (lines.length) {
|
||||
this._ansiToHtmlElement?.parseLinesToColoredPre(lines);
|
||||
this._logLinesCount += lines.length;
|
||||
}
|
||||
|
||||
if (showError) {
|
||||
fireEvent(this, "landing-page-error");
|
||||
this._show = true;
|
||||
}
|
||||
|
||||
if (showError || (scrolledToBottom && this._logElement)) {
|
||||
this._scrollToBottom();
|
||||
} else {
|
||||
this._newLogsIndicator = true;
|
||||
}
|
||||
|
||||
return tempLogLine;
|
||||
}
|
||||
|
||||
private async _startLogStream() {
|
||||
this._error = false;
|
||||
this._newLogsIndicator = false;
|
||||
this._ansiToHtmlElement?.clear();
|
||||
|
||||
try {
|
||||
const response = await getSupervisorLogsFollow();
|
||||
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error("No stream body found");
|
||||
}
|
||||
|
||||
let tempLogLine = "";
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let done = false;
|
||||
|
||||
while (!done) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { value, done: readerDone } = await reader.read();
|
||||
done = readerDone;
|
||||
|
||||
if (value) {
|
||||
const chunk = decoder.decode(value, { stream: !done });
|
||||
tempLogLine = this._displayLogs(chunk, tempLogLine);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
|
||||
// fallback to observerlogs if there is a problem with supervisor
|
||||
this._loadObserverLogs();
|
||||
}
|
||||
}
|
||||
|
||||
private _scheduleObserverLogs() {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
// check if supervisor logs are available
|
||||
const superVisorLogsResponse = await getSupervisorLogs(1);
|
||||
if (superVisorLogsResponse.ok) {
|
||||
this._startLogStream();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore and continue with observer logs
|
||||
}
|
||||
this._loadObserverLogs();
|
||||
}, SCHEDULE_FETCH_OBSERVER_LOGS * 1000);
|
||||
}
|
||||
|
||||
private async _loadObserverLogs() {
|
||||
try {
|
||||
const response = await getObserverLogs();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Error fetching observer logs");
|
||||
}
|
||||
|
||||
const logs = await response.text();
|
||||
|
||||
this._displayLogs(logs, "", true);
|
||||
|
||||
this._scheduleObserverLogs();
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._error = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _downloadLogs() {
|
||||
const timeString = new Date().toISOString().replace(/:/g, "-");
|
||||
|
||||
fileDownload(observerLogsDownloadUrl, `observer_${timeString}.log`);
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ha-alert {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions ha-icon-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -4px;
|
||||
--icon-primary-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.logs {
|
||||
width: 100%;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.logs.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.new-logs-indicator {
|
||||
--mdc-theme-primary: var(--text-primary-color);
|
||||
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 0;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 8px;
|
||||
|
||||
transition: height 0.4s ease-out;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.new-logs-indicator.visible {
|
||||
height: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"landing-page-logs": LandingPageLogs;
|
||||
}
|
||||
}
|
@@ -1,186 +0,0 @@
|
||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import {
|
||||
type CSSResultGroup,
|
||||
LitElement,
|
||||
type PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type {
|
||||
LandingPageKeys,
|
||||
LocalizeFunc,
|
||||
} from "../../../src/common/translations/localize";
|
||||
import "../../../src/components/ha-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import {
|
||||
ALTERNATIVE_DNS_SERVERS,
|
||||
getSupervisorNetworkInfo,
|
||||
setSupervisorNetworkDns,
|
||||
} from "../data/supervisor";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
|
||||
const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5;
|
||||
|
||||
@customElement("landing-page-network")
|
||||
class LandingPageNetwork extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public localize!: LocalizeFunc<LandingPageKeys>;
|
||||
|
||||
@state() private _networkIssue = false;
|
||||
|
||||
@state() private _getNetworkInfoError = false;
|
||||
|
||||
@state() private _dnsPrimaryInterfaceNameservers?: string;
|
||||
|
||||
@state() private _dnsPrimaryInterface?: string;
|
||||
|
||||
protected render() {
|
||||
if (!this._networkIssue && !this._getNetworkInfoError) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (this._getNetworkInfoError) {
|
||||
return html`
|
||||
<ha-alert alert-type="error">
|
||||
<p>${this.localize("network_issue.error_get_network_info")}</p>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-alert
|
||||
alert-type="warning"
|
||||
.title=${this.localize("network_issue.title")}
|
||||
>
|
||||
<p>
|
||||
${this.localize("network_issue.description", {
|
||||
dns: this._dnsPrimaryInterfaceNameservers || "?",
|
||||
})}
|
||||
</p>
|
||||
<p>${this.localize("network_issue.resolve_different")}</p>
|
||||
${!this._dnsPrimaryInterfaceNameservers
|
||||
? html`
|
||||
<p>
|
||||
<b>${this.localize("network_issue.no_primary_interface")} </b>
|
||||
</p>
|
||||
`
|
||||
: nothing}
|
||||
<div class="actions">
|
||||
${ALTERNATIVE_DNS_SERVERS.map(
|
||||
({ translationKey }, key) =>
|
||||
html`<ha-button
|
||||
.index=${key}
|
||||
.disabled=${!this._dnsPrimaryInterfaceNameservers}
|
||||
@click=${this._setDns}
|
||||
>${this.localize(translationKey)}</ha-button
|
||||
>`
|
||||
)}
|
||||
</div>
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(_changedProperties);
|
||||
this._fetchSupervisorInfo();
|
||||
}
|
||||
|
||||
private _scheduleFetchSupervisorInfo() {
|
||||
setTimeout(
|
||||
() => this._fetchSupervisorInfo(),
|
||||
SCHEDULE_FETCH_NETWORK_INFO_SECONDS * 1000
|
||||
);
|
||||
}
|
||||
|
||||
private async _fetchSupervisorInfo() {
|
||||
let data;
|
||||
try {
|
||||
const response = await getSupervisorNetworkInfo();
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch network info");
|
||||
}
|
||||
|
||||
({ data } = await response.json());
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._getNetworkInfoError = true;
|
||||
this._dnsPrimaryInterfaceNameservers = undefined;
|
||||
this._dnsPrimaryInterface = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this._getNetworkInfoError = false;
|
||||
|
||||
const primaryInterface = data.interfaces.find(
|
||||
(intf) => intf.primary && intf.enabled
|
||||
);
|
||||
if (primaryInterface) {
|
||||
this._dnsPrimaryInterfaceNameservers = [
|
||||
...(primaryInterface.ipv4?.nameservers || []),
|
||||
...(primaryInterface.ipv6?.nameservers || []),
|
||||
].join(", ");
|
||||
|
||||
this._dnsPrimaryInterface = primaryInterface.interface;
|
||||
} else {
|
||||
this._dnsPrimaryInterfaceNameservers = undefined;
|
||||
this._dnsPrimaryInterface = undefined;
|
||||
}
|
||||
|
||||
if (!data.host_internet) {
|
||||
this._networkIssue = true;
|
||||
} else {
|
||||
this._networkIssue = false;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this._networkIssue,
|
||||
});
|
||||
this._scheduleFetchSupervisorInfo();
|
||||
}
|
||||
|
||||
private async _setDns(ev) {
|
||||
const index = ev.target?.index;
|
||||
try {
|
||||
const response = await setSupervisorNetworkDns(
|
||||
index,
|
||||
this._dnsPrimaryInterface!
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to set DNS");
|
||||
}
|
||||
this._networkIssue = false;
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
showAlertDialog(this, {
|
||||
title: this.localize("network_issue.failed"),
|
||||
warning: true,
|
||||
text: `${this.localize(
|
||||
"network_issue.set_dns_failed"
|
||||
)}${err?.message ? ` ${this.localize("network_issue.error")}: ${err.message}` : ""}`,
|
||||
confirmText: this.localize("network_issue.close"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"landing-page-network": LandingPageNetwork;
|
||||
}
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
export async function getObserverLogs() {
|
||||
return fetch("/observer/logs");
|
||||
}
|
||||
|
||||
export const downloadUrl = "/observer/logs";
|
@@ -1,59 +0,0 @@
|
||||
import type { LandingPageKeys } from "../../../src/common/translations/localize";
|
||||
|
||||
export const ALTERNATIVE_DNS_SERVERS: {
|
||||
ipv4: string[];
|
||||
ipv6: string[];
|
||||
translationKey: LandingPageKeys;
|
||||
}[] = [
|
||||
{
|
||||
ipv4: ["1.1.1.1", "1.0.0.1"],
|
||||
ipv6: ["2606:4700:4700::1111", "2606:4700:4700::1001"],
|
||||
translationKey: "network_issue.use_cloudflare",
|
||||
},
|
||||
{
|
||||
ipv4: ["8.8.8.8", "8.8.4.4"],
|
||||
ipv6: ["2001:4860:4860::8888", "2001:4860:4860::8844"],
|
||||
translationKey: "network_issue.use_google",
|
||||
},
|
||||
];
|
||||
|
||||
export async function getSupervisorLogs(lines = 100) {
|
||||
return fetch(`/supervisor/supervisor/logs?lines=${lines}`, {
|
||||
headers: {
|
||||
Accept: "text/plain",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSupervisorLogsFollow(lines = 500) {
|
||||
return fetch(`/supervisor/supervisor/logs/follow?lines=${lines}`, {
|
||||
headers: {
|
||||
Accept: "text/plain",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSupervisorNetworkInfo() {
|
||||
return fetch("/supervisor/network/info");
|
||||
}
|
||||
|
||||
export const setSupervisorNetworkDns = async (
|
||||
dnsServerIndex: number,
|
||||
primaryInterface: string
|
||||
) =>
|
||||
fetch(`/supervisor/network/interface/${primaryInterface}/update`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
ipv4: {
|
||||
method: "auto",
|
||||
nameservers: ALTERNATIVE_DNS_SERVERS[dnsServerIndex].ipv4,
|
||||
},
|
||||
ipv6: {
|
||||
method: "auto",
|
||||
nameservers: ALTERNATIVE_DNS_SERVERS[dnsServerIndex].ipv6,
|
||||
},
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
@@ -1,3 +0,0 @@
|
||||
import "./ha-landing-page";
|
||||
|
||||
import("../../src/resources/ha-style");
|
@@ -1,182 +0,0 @@
|
||||
import "@material/mwc-linear-progress";
|
||||
import { type PropertyValues, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../src/components/ha-alert";
|
||||
import { haStyle } from "../../src/resources/styles";
|
||||
import "../../src/onboarding/onboarding-welcome-links";
|
||||
import "./components/landing-page-network";
|
||||
import "./components/landing-page-logs";
|
||||
import { extractSearchParam } from "../../src/common/url/search-params";
|
||||
import { onBoardingStyles } from "../../src/onboarding/styles";
|
||||
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
||||
import { LandingPageBaseElement } from "./landing-page-base-element";
|
||||
|
||||
const SCHEDULE_CORE_CHECK_SECONDS = 5;
|
||||
|
||||
@customElement("ha-landing-page")
|
||||
class HaLandingPage extends LandingPageBaseElement {
|
||||
@property({ attribute: false }) public translationFragment = "landing-page";
|
||||
|
||||
@state() private _networkIssue = false;
|
||||
|
||||
@state() private _supervisorError = false;
|
||||
|
||||
private _mobileApp =
|
||||
extractSearchParam("redirect_uri") === "homeassistant://auth-callback";
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<h1>${this.localize("header")}</h1>
|
||||
${!this._networkIssue && !this._supervisorError
|
||||
? html`
|
||||
<p>${this.localize("subheader")}</p>
|
||||
<mwc-linear-progress indeterminate></mwc-linear-progress>
|
||||
`
|
||||
: nothing}
|
||||
<landing-page-network
|
||||
@value-changed=${this._networkInfoChanged}
|
||||
.localize=${this.localize}
|
||||
></landing-page-network>
|
||||
|
||||
${this._supervisorError
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this.localize("error_title")}
|
||||
>
|
||||
${this.localize("error_description")}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
<landing-page-logs
|
||||
.localize=${this.localize}
|
||||
@landing-page-error=${this._showError}
|
||||
></landing-page-logs>
|
||||
</div>
|
||||
</ha-card>
|
||||
<onboarding-welcome-links
|
||||
.localize=${this.localize}
|
||||
.mobileApp=${this._mobileApp}
|
||||
></onboarding-welcome-links>
|
||||
<div class="footer">
|
||||
<ha-language-picker
|
||||
.value=${this.language}
|
||||
.label=${""}
|
||||
native-name
|
||||
@value-changed=${this._languageChanged}
|
||||
inline-arrow
|
||||
></ha-language-picker>
|
||||
<a
|
||||
href="https://www.home-assistant.io/getting-started/onboarding/"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>${this.localize("ui.panel.page-onboarding.help")}</a
|
||||
>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
|
||||
if (window.innerWidth > 450) {
|
||||
import("../../src/resources/particles");
|
||||
}
|
||||
import("../../src/components/ha-language-picker");
|
||||
|
||||
this._scheduleCoreCheck();
|
||||
}
|
||||
|
||||
private _scheduleCoreCheck() {
|
||||
setTimeout(
|
||||
() => this._checkCoreAvailability(),
|
||||
SCHEDULE_CORE_CHECK_SECONDS * 1000
|
||||
);
|
||||
}
|
||||
|
||||
private async _checkCoreAvailability() {
|
||||
try {
|
||||
const response = await fetch("/manifest.json");
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
}
|
||||
} finally {
|
||||
this._scheduleCoreCheck();
|
||||
}
|
||||
}
|
||||
|
||||
private _showError() {
|
||||
this._supervisorError = true;
|
||||
}
|
||||
|
||||
private _networkInfoChanged(ev: CustomEvent) {
|
||||
this._networkIssue = ev.detail.value;
|
||||
}
|
||||
|
||||
private _languageChanged(ev: CustomEvent) {
|
||||
const language = ev.detail.value;
|
||||
if (language !== this.language && language) {
|
||||
this.language = language;
|
||||
try {
|
||||
window.localStorage.setItem(
|
||||
"selectedLanguage",
|
||||
JSON.stringify(language)
|
||||
);
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyle,
|
||||
onBoardingStyles,
|
||||
css`
|
||||
.footer {
|
||||
padding-top: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
ha-card .card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
ha-alert p {
|
||||
text-align: unset;
|
||||
}
|
||||
ha-language-picker {
|
||||
display: block;
|
||||
width: 200px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
--ha-select-height: 40px;
|
||||
--mdc-select-fill-color: none;
|
||||
--mdc-select-label-ink-color: var(--primary-text-color, #212121);
|
||||
--mdc-select-ink-color: var(--primary-text-color, #212121);
|
||||
--mdc-select-idle-line-color: transparent;
|
||||
--mdc-select-hover-line-color: transparent;
|
||||
--mdc-select-dropdown-icon-color: var(--primary-text-color, #212121);
|
||||
--mdc-shape-small: 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
margin-right: 16px;
|
||||
margin-inline-end: 16px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-landing-page": HaLandingPage;
|
||||
}
|
||||
}
|
@@ -1,53 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Home Assistant</title>
|
||||
<%= renderTemplate("../../../src/html/_header.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
|
||||
<style>
|
||||
html {
|
||||
background-color: var(--primary-background-color, #fafafa);
|
||||
color: var(--primary-text-color, #212121);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: var(--primary-background-color, #111111);
|
||||
color: var(--primary-text-color, #e1e1e1);
|
||||
}
|
||||
}
|
||||
body {
|
||||
height: auto;
|
||||
padding: 32px 0;
|
||||
}
|
||||
.content {
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.header img {
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body id="particles">
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<img src="/static/icons/favicon-192x192.png" alt="Home Assistant" />
|
||||
</div>
|
||||
<ha-landing-page></ha-landing-page>
|
||||
</div>
|
||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||
</body>
|
||||
</html>
|
@@ -1,74 +0,0 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import {
|
||||
computeLocalize,
|
||||
type LandingPageKeys,
|
||||
type LocalizeFunc,
|
||||
} from "../../src/common/translations/localize";
|
||||
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||
import type { Constructor, Resources } from "../../src/types";
|
||||
import {
|
||||
getLocalLanguage,
|
||||
getTranslation,
|
||||
} from "../../src/util/common-translation";
|
||||
import { computeDirectionStyles } from "../../src/common/util/compute_rtl";
|
||||
import themesMixin from "../../src/state/themes-mixin";
|
||||
import { translationMetadata } from "../../src/resources/translations-metadata";
|
||||
import type { HassBaseEl } from "../../src/state/hass-base-mixin";
|
||||
|
||||
export class LandingPageBaseElement extends themesMixin(
|
||||
ProvideHassLitMixin(LitElement) as unknown as Constructor<HassBaseEl>
|
||||
) {
|
||||
// Initialized to empty will prevent undefined errors if called before connected to DOM.
|
||||
@property({ attribute: false })
|
||||
public localize: LocalizeFunc<LandingPageKeys> = () => "";
|
||||
|
||||
// Use browser language setup before login.
|
||||
@property() public language?: string = getLocalLanguage();
|
||||
|
||||
@state() private _resources?: Resources;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._initializeLocalize();
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
if (changedProperties.get("language")) {
|
||||
this._resources = undefined;
|
||||
this._initializeLocalize();
|
||||
}
|
||||
|
||||
if (
|
||||
this.language &&
|
||||
this._resources &&
|
||||
(changedProperties.has("language") || changedProperties.has("_resources"))
|
||||
) {
|
||||
this._setLocalize();
|
||||
}
|
||||
}
|
||||
|
||||
private async _initializeLocalize() {
|
||||
if (this._resources || !this.language) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = await getTranslation(null, this.language);
|
||||
this._resources = {
|
||||
[this.language]: data,
|
||||
};
|
||||
}
|
||||
|
||||
private async _setLocalize() {
|
||||
this.localize = await computeLocalize(
|
||||
this.constructor.prototype,
|
||||
this.language!,
|
||||
this._resources!
|
||||
);
|
||||
computeDirectionStyles(
|
||||
translationMetadata.translations[this.language!].isRTL,
|
||||
this
|
||||
);
|
||||
}
|
||||
}
|
85
package.json
@@ -8,7 +8,7 @@
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "script/build_frontend",
|
||||
"lint:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --max-warnings=0",
|
||||
"lint:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore",
|
||||
"format:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --fix",
|
||||
"lint:prettier": "prettier . --cache --check",
|
||||
"format:prettier": "prettier . --cache --write",
|
||||
@@ -19,8 +19,7 @@
|
||||
"postinstall": "husky",
|
||||
"prepack": "pinst --disable",
|
||||
"postpack": "pinst --enable",
|
||||
"test": "vitest run --config test/vitest.config.ts",
|
||||
"test:coverage": "vitest run --config test/vitest.config.ts --coverage"
|
||||
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\""
|
||||
},
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,21 +29,20 @@
|
||||
"@braintree/sanitize-url": "7.1.0",
|
||||
"@codemirror/autocomplete": "6.18.3",
|
||||
"@codemirror/commands": "6.7.1",
|
||||
"@codemirror/language": "6.10.6",
|
||||
"@codemirror/language": "6.10.3",
|
||||
"@codemirror/legacy-modes": "6.4.2",
|
||||
"@codemirror/search": "6.5.8",
|
||||
"@codemirror/search": "6.5.7",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.35.2",
|
||||
"@codemirror/view": "6.34.3",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.16.6",
|
||||
"@formatjs/intl-displaynames": "6.8.6",
|
||||
"@formatjs/intl-durationformat": "0.6.5",
|
||||
"@formatjs/intl-getcanonicallocales": "2.5.3",
|
||||
"@formatjs/intl-listformat": "7.7.6",
|
||||
"@formatjs/intl-locale": "4.2.6",
|
||||
"@formatjs/intl-numberformat": "8.14.6",
|
||||
"@formatjs/intl-pluralrules": "5.3.6",
|
||||
"@formatjs/intl-relativetimeformat": "11.4.6",
|
||||
"@formatjs/intl-datetimeformat": "6.16.4",
|
||||
"@formatjs/intl-displaynames": "6.8.4",
|
||||
"@formatjs/intl-getcanonicallocales": "2.5.2",
|
||||
"@formatjs/intl-listformat": "7.7.4",
|
||||
"@formatjs/intl-locale": "4.2.4",
|
||||
"@formatjs/intl-numberformat": "8.14.4",
|
||||
"@formatjs/intl-pluralrules": "5.3.4",
|
||||
"@formatjs/intl-relativetimeformat": "11.4.4",
|
||||
"@fullcalendar/core": "6.1.15",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"@fullcalendar/interaction": "6.1.15",
|
||||
@@ -54,7 +52,7 @@
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.7",
|
||||
"@lit-labs/observers": "2.0.4",
|
||||
"@lit-labs/observers": "2.0.2",
|
||||
"@lit-labs/virtualizer": "2.0.14",
|
||||
"@lrnwebcomponents/simple-tooltip": "8.0.2",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
@@ -91,8 +89,8 @@
|
||||
"@polymer/polymer": "3.5.2",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.5.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.5.5",
|
||||
"@vaadin/combo-box": "24.5.3",
|
||||
"@vaadin/vaadin-themable-mixin": "24.5.3",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -101,7 +99,7 @@
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"barcode-detector": "2.3.1",
|
||||
"chart.js": "4.4.7",
|
||||
"chart.js": "4.4.6",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.39.0",
|
||||
@@ -111,20 +109,19 @@
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"element-internals-polyfill": "1.3.12",
|
||||
"element-internals-polyfill": "1.3.11",
|
||||
"fuse.js": "7.0.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||
"home-assistant-js-websocket": "9.4.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.7.8",
|
||||
"intl-messageformat": "10.7.6",
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
"lit": "2.8.0",
|
||||
"lit-html": "2.8.0",
|
||||
"luxon": "3.5.0",
|
||||
"marked": "15.0.3",
|
||||
"marked": "15.0.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
@@ -162,25 +159,24 @@
|
||||
"@babel/preset-env": "7.26.0",
|
||||
"@babel/preset-typescript": "7.26.0",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.17.0",
|
||||
"@koa/cors": "5.0.0",
|
||||
"@lokalise/node-api": "12.8.0",
|
||||
"@octokit/auth-oauth-device": "7.1.1",
|
||||
"@octokit/plugin-retry": "7.1.2",
|
||||
"@octokit/rest": "21.0.2",
|
||||
"@rsdoctor/rspack-plugin": "0.4.11",
|
||||
"@rspack/cli": "1.1.5",
|
||||
"@rspack/core": "1.1.5",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.20",
|
||||
"@types/chromecast-caf-sender": "1.0.11",
|
||||
"@types/chromecast-caf-receiver": "6.0.18",
|
||||
"@types/chromecast-caf-sender": "1.0.10",
|
||||
"@types/color-name": "2.0.0",
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/html-minifier-terser": "7.0.2",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/leaflet": "1.9.15",
|
||||
"@types/leaflet": "1.9.14",
|
||||
"@types/leaflet-draw": "1.0.11",
|
||||
"@types/lodash.merge": "4.6.9",
|
||||
"@types/luxon": "3.4.2",
|
||||
"@types/mocha": "10.0.10",
|
||||
"@types/mocha": "10.0.9",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/serve-handler": "6.1.4",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
@@ -189,12 +185,13 @@
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||
"@typescript-eslint/parser": "7.18.0",
|
||||
"@vitest/coverage-v8": "2.1.8",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"babel-loader": "9.2.1",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
"chai": "5.1.2",
|
||||
"del": "8.0.0",
|
||||
"eslint": "9.16.0",
|
||||
"eslint": "9.15.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "18.0.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
@@ -211,29 +208,34 @@
|
||||
"gulp-brotli": "3.0.0",
|
||||
"gulp-json-transform": "0.5.0",
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "25.0.1",
|
||||
"husky": "9.1.6",
|
||||
"instant-mocha": "1.5.3",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.10",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.14",
|
||||
"magic-string": "0.30.12",
|
||||
"map-stream": "0.0.7",
|
||||
"mocha": "10.8.2",
|
||||
"object-hash": "3.0.0",
|
||||
"open": "10.1.0",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.4.2",
|
||||
"rspack-manifest-plugin": "5.0.2",
|
||||
"prettier": "3.3.3",
|
||||
"serve-handler": "6.1.6",
|
||||
"sinon": "19.0.2",
|
||||
"systemjs": "6.15.1",
|
||||
"tar": "7.4.3",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.7.2",
|
||||
"vitest": "2.1.8",
|
||||
"typescript": "5.6.3",
|
||||
"webpack": "5.96.1",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.1.0",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||
@@ -243,11 +245,10 @@
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.2#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||
"lit": "2.8.0",
|
||||
"lit-html": "2.8.0",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"globals": "15.13.0"
|
||||
"globals": "15.12.0"
|
||||
},
|
||||
"packageManager": "yarn@4.5.3"
|
||||
"packageManager": "yarn@4.5.1"
|
||||
}
|
||||
|
@@ -1,3 +0,0 @@
|
||||
<svg width="92" height="136" viewBox="0 0 92 136" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M76.38 104.9H3.03C2.24231 104.9 1.48688 105.213 0.929897 105.77C0.372914 106.327 0.0600052 107.082 0.0600052 107.87V132.47C0.0600052 133.258 0.372914 134.013 0.929897 134.57C1.48688 135.127 2.24231 135.44 3.03 135.44H12.36C13.1477 135.44 13.9031 135.127 14.4601 134.57C15.0171 134.013 15.33 133.258 15.33 132.47V120.17H76.39V132.47C76.39 133.258 76.7029 134.013 77.2599 134.57C77.8169 135.127 78.5723 135.44 79.36 135.44H88.69C89.4777 135.44 90.2331 135.127 90.7901 134.57C91.3471 134.013 91.66 133.258 91.66 132.47V107.87C91.66 107.082 91.3471 106.327 90.7901 105.77C90.2331 105.213 89.4777 104.9 88.69 104.9H76.39H76.38ZM50.04 2.24996C47.73 -0.0600439 43.95 -0.0600439 41.65 2.24996L4.25 39.65C1.94 41.96 0.0500031 46.52 0.0500031 49.78V83.7C0.0500031 86.96 2.72 89.64 5.99 89.64H85.7C88.96 89.64 91.64 86.97 91.64 83.7V49.78C91.64 46.52 89.75 41.96 87.44 39.65L50.04 2.24996Z" fill="#18BCF2"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1011 B |
@@ -1,10 +0,0 @@
|
||||
<svg width="75" height="79" viewBox="0 0 75 79" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M73.8393 17.4898C72.6973 9.00165 65.2994 2.31235 56.5296 1.01614C55.05 0.797115 49.4441 0 36.4582 0H36.3612C23.3717 0 20.585 0.797115 19.1054 1.01614C10.5798 2.27644 2.79399 8.28712 0.904997 16.8758C-0.00358524 21.1056 -0.100549 25.7949 0.0682394 30.0965C0.308852 36.2651 0.355538 42.423 0.91577 48.5665C1.30307 52.6474 1.97872 56.6957 2.93763 60.6812C4.73325 68.042 12.0019 74.1676 19.1233 76.6666C26.7478 79.2728 34.9474 79.7055 42.8039 77.9162C43.6682 77.7151 44.5217 77.4817 45.3645 77.216C47.275 76.6092 49.5123 75.9305 51.1571 74.7385C51.1797 74.7217 51.1982 74.7001 51.2112 74.6753C51.2243 74.6504 51.2316 74.6229 51.2325 74.5948V68.6416C51.2321 68.6154 51.2259 68.5896 51.2142 68.5661C51.2025 68.5426 51.1858 68.522 51.1651 68.5058C51.1444 68.4896 51.1204 68.4783 51.0948 68.4726C51.0692 68.4669 51.0426 68.467 51.0171 68.4729C45.9835 69.675 40.8254 70.2777 35.6502 70.2682C26.7439 70.2682 24.3486 66.042 23.6626 64.2826C23.1113 62.762 22.7612 61.1759 22.6212 59.5646C22.6197 59.5375 22.6247 59.5105 22.6357 59.4857C22.6466 59.4609 22.6633 59.4391 22.6843 59.422C22.7053 59.4048 22.73 59.3929 22.7565 59.3871C22.783 59.3813 22.8104 59.3818 22.8367 59.3886C27.7864 60.5826 32.8604 61.1853 37.9522 61.1839C39.1768 61.1839 40.3978 61.1839 41.6224 61.1516C46.7435 61.008 52.1411 60.7459 57.1796 59.7621C57.3053 59.7369 57.431 59.7154 57.5387 59.6831C65.4861 58.157 73.0493 53.3672 73.8178 41.2381C73.8465 40.7606 73.9184 36.2364 73.9184 35.7409C73.9219 34.0569 74.4606 23.7949 73.8393 17.4898Z" fill="url(#paint0_linear_549_34)"/>
|
||||
<path d="M61.2484 27.0263V48.114H52.8916V27.6475C52.8916 23.3388 51.096 21.1413 47.4437 21.1413C43.4287 21.1413 41.4177 23.7409 41.4177 28.8755V40.0782H33.1111V28.8755C33.1111 23.7409 31.0965 21.1413 27.0815 21.1413C23.4507 21.1413 21.6371 23.3388 21.6371 27.6475V48.114H13.2839V27.0263C13.2839 22.7176 14.384 19.2946 16.5843 16.7572C18.8539 14.2258 21.8311 12.926 25.5264 12.926C29.8036 12.926 33.0357 14.5705 35.1905 17.8559L37.2698 21.346L39.3527 17.8559C41.5074 14.5705 44.7395 12.926 49.0095 12.926C52.7013 12.926 55.6784 14.2258 57.9553 16.7572C60.1531 19.2922 61.2508 22.7152 61.2484 27.0263Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_549_34" x1="37.0692" y1="0" x2="37.0692" y2="79" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6364FF"/>
|
||||
<stop offset="1" stop-color="#563ACC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20241127.0"
|
||||
version = "20241106.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -43,6 +43,12 @@
|
||||
"description": "Group date-fns with dependent timezone package",
|
||||
"groupName": "date-fns",
|
||||
"matchPackageNames": ["date-fns", "date-fns-tz"]
|
||||
},
|
||||
{
|
||||
"description": "Group and temporarily disable WDS packages",
|
||||
"groupName": "Web Dev Server",
|
||||
"enabled": false,
|
||||
"matchPackageNames": ["@web/dev-server{/,}**"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -1,70 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This script can be used to develop and test the frontend without having to
|
||||
# link the build in a running core instance through the frontend/development_repo setting.
|
||||
#
|
||||
# WARNING:
|
||||
# If you have an active login session in the frontend. The core that was used
|
||||
# as a backend during the time of the login remains used until you logout again.
|
||||
# So if you reuse the url hosting the frontend, you will need to logout before
|
||||
# it will actually start using the core backend configured by this script.
|
||||
#
|
||||
# If you run this script without parameters, the frontend will be accessible under http://localhost:8124.
|
||||
# And it will use the core instance running under http://localhost:8123 as a backend.
|
||||
# Note that from a devcontainer, the frontend will be accessible under port 8124 on the host container.
|
||||
# Inside the devcontainer it will be accessible under port 8123 instead.
|
||||
# The core instance endpoint remains the same in both cases, as this is resolved from the browser.
|
||||
#
|
||||
# You can change the core instance the frontend connects to by passing the -c option.
|
||||
# For example: script/develop_and_serve -c https://myhost.duckdns.org:8123
|
||||
# This will also work for existing production core instances.
|
||||
# It does not need to be a development version hosted locally.
|
||||
#
|
||||
# You can change the port the frontend is served on by passing the -p option.
|
||||
# For example: script/develop_and_serve -p 8654
|
||||
# Note that if you are running from a devcontainer, you will need to setup
|
||||
# port forwarding as well if you want to access it from the container host.
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# parse input paramters
|
||||
if [ -n "$DEVCONTAINER" ]; then
|
||||
frontendPort=8123
|
||||
else
|
||||
frontendPort=8124
|
||||
fi
|
||||
|
||||
coreUrl=http://localhost:8123
|
||||
|
||||
while getopts p:c:h flag
|
||||
do
|
||||
case "${flag}" in
|
||||
p) frontendPort=${OPTARG};;
|
||||
c) coreUrl="${OPTARG}";;
|
||||
h) echo Documentation can be found inside "$0" && exit 0;;
|
||||
*) echo Documentation can be found inside "$0" && exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
# display used settings
|
||||
if [ -n "$DEVCONTAINER" ]; then
|
||||
echo Frontend is available inside container as http://localhost:${frontendPort}
|
||||
if [ 8123 -eq $frontendPort ]; then
|
||||
echo Frontend is available on container host as http://localhost:8124
|
||||
fi
|
||||
else
|
||||
echo Frontend is hosted on http://localhost:${frontendPort}
|
||||
fi
|
||||
echo Core is used from ${coreUrl}
|
||||
|
||||
# build the frontend so it connects to the passed core
|
||||
HASS_URL="$coreUrl" ./script/develop &
|
||||
|
||||
# serve the frontend
|
||||
yarn dlx serve -l $frontendPort ./hass_frontend -s &
|
||||
|
||||
# keep the script running while serving
|
||||
wait
|
@@ -6,4 +6,9 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
./node_modules/.bin/gulp analyze-app
|
||||
export STATS=1
|
||||
statsfile="compilation-stats.json"
|
||||
|
||||
./node_modules/.bin/webpack-cli --profile --node-env=production --json=$statsfile
|
||||
npx webpack-bundle-analyzer $statsfile hass_frontend/frontend_latest
|
||||
rm -f $statsfile
|
||||
|
@@ -30,17 +30,17 @@ type State = "loading" | "error" | "step";
|
||||
export class HaAuthFlow extends LitElement {
|
||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
||||
|
||||
@property({ attribute: false }) public clientId?: string;
|
||||
@property() public clientId?: string;
|
||||
|
||||
@property({ attribute: false }) public redirectUri?: string;
|
||||
@property() public redirectUri?: string;
|
||||
|
||||
@property({ attribute: false }) public oauth2State?: string;
|
||||
@property() public oauth2State?: string;
|
||||
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||
|
||||
@property({ attribute: false }) public initStoreToken = false;
|
||||
@property({ type: Boolean }) public initStoreToken = false;
|
||||
|
||||
@state() private _storeToken = false;
|
||||
|
||||
|
@@ -21,13 +21,13 @@ const appNames = {
|
||||
|
||||
@customElement("ha-authorize")
|
||||
export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
@property({ attribute: false }) public clientId?: string;
|
||||
@property() public clientId?: string;
|
||||
|
||||
@property({ attribute: false }) public redirectUri?: string;
|
||||
@property() public redirectUri?: string;
|
||||
|
||||
@property({ attribute: false }) public oauth2State?: string;
|
||||
@property() public oauth2State?: string;
|
||||
|
||||
@property({ attribute: false }) public translationFragment = "page-authorize";
|
||||
@property() public translationFragment = "page-authorize";
|
||||
|
||||
@state() private _authProvider?: AuthProvider;
|
||||
|
||||
@@ -202,9 +202,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
<ha-language-picker
|
||||
.value=${this.language}
|
||||
.label=${""}
|
||||
native-name
|
||||
nativeName
|
||||
@value-changed=${this._languageChanged}
|
||||
inline-arrow
|
||||
></ha-language-picker>
|
||||
<a
|
||||
href="https://www.home-assistant.io/docs/authentication/"
|
||||
@@ -327,7 +326,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
this.language = language;
|
||||
|
||||
try {
|
||||
window.localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
||||
localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
|
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* Get all possible combinations of an array
|
||||
* @param arr - The array to get combinations of
|
||||
* @returns A multidimensional array of all possible combinations
|
||||
*/
|
||||
export function getAllCombinations<T>(arr: T[]) {
|
||||
return arr.reduce<T[][]>(
|
||||
(combinations, element) =>
|
||||
|
@@ -1,9 +1,5 @@
|
||||
type NonUndefined<T> = T extends undefined ? never : T;
|
||||
|
||||
/**
|
||||
* Ensure that the input is an array or wrap it in an array
|
||||
* @param value - The value to ensure is an array
|
||||
*/
|
||||
export function ensureArray(value: undefined): undefined;
|
||||
export function ensureArray<T>(value: T | T[]): NonUndefined<T>[];
|
||||
export function ensureArray<T>(value: T | readonly T[]): NonUndefined<T>[];
|
||||
|
@@ -1,8 +1,4 @@
|
||||
/**
|
||||
* Creates a type predicate function for determining if an array literal includes a given value
|
||||
* @param array - The array to check
|
||||
* @returns A type predicate function
|
||||
*/
|
||||
// Creates a type predicate function for determining if an array literal includes a given value
|
||||
export const arrayLiteralIncludes =
|
||||
<T extends readonly unknown[]>(array: T) =>
|
||||
(searchElement: unknown, fromIndex?: number): searchElement is T[number] =>
|
||||
|