mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-26 14:27:20 +00:00
Merge pull request #10136 from home-assistant/dev
This commit is contained in:
commit
31b69147f4
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@ -30,7 +30,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
- name: Build resources
|
- name: Build resources
|
||||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
|
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-demos
|
||||||
- name: Run eslint
|
- name: Run eslint
|
||||||
run: yarn run lint:eslint
|
run: yarn run lint:eslint
|
||||||
- name: Run tsc
|
- name: Run tsc
|
||||||
@ -53,6 +53,8 @@ jobs:
|
|||||||
run: yarn install
|
run: yarn install
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
- name: Build resources
|
||||||
|
run: ./node_modules/.bin/gulp build-translations build-locale-data
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: yarn run test
|
run: yarn run test
|
||||||
build:
|
build:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
# build
|
# build
|
||||||
build
|
build
|
||||||
build-translations/*
|
|
||||||
hass_frontend/*
|
hass_frontend/*
|
||||||
dist
|
dist
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
build
|
build
|
||||||
build-translations/*
|
|
||||||
translations/*
|
translations/*
|
||||||
node_modules/*
|
node_modules/*
|
||||||
hass_frontend/*
|
hass_frontend/*
|
||||||
|
@ -5,6 +5,7 @@ const env = require("../env");
|
|||||||
|
|
||||||
require("./clean.js");
|
require("./clean.js");
|
||||||
require("./translations.js");
|
require("./translations.js");
|
||||||
|
require("./locale-data.js");
|
||||||
require("./gen-icons-json.js");
|
require("./gen-icons-json.js");
|
||||||
require("./gather-static.js");
|
require("./gather-static.js");
|
||||||
require("./compress.js");
|
require("./compress.js");
|
||||||
@ -26,7 +27,8 @@ gulp.task(
|
|||||||
"gen-icons-json",
|
"gen-icons-json",
|
||||||
"gen-pages-dev",
|
"gen-pages-dev",
|
||||||
"gen-index-app-dev",
|
"gen-index-app-dev",
|
||||||
"build-translations"
|
"build-translations",
|
||||||
|
"build-locale-data"
|
||||||
),
|
),
|
||||||
"copy-static-app",
|
"copy-static-app",
|
||||||
env.useWDS()
|
env.useWDS()
|
||||||
@ -44,7 +46,7 @@ gulp.task(
|
|||||||
process.env.NODE_ENV = "production";
|
process.env.NODE_ENV = "production";
|
||||||
},
|
},
|
||||||
"clean",
|
"clean",
|
||||||
gulp.parallel("gen-icons-json", "build-translations"),
|
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||||
"copy-static-app",
|
"copy-static-app",
|
||||||
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
||||||
// Don't compress running tests
|
// Don't compress running tests
|
||||||
|
@ -18,7 +18,7 @@ gulp.task(
|
|||||||
},
|
},
|
||||||
"clean-cast",
|
"clean-cast",
|
||||||
"translations-enable-merge-backend",
|
"translations-enable-merge-backend",
|
||||||
gulp.parallel("gen-icons-json", "build-translations"),
|
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||||
"copy-static-cast",
|
"copy-static-cast",
|
||||||
"gen-index-cast-dev",
|
"gen-index-cast-dev",
|
||||||
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
||||||
@ -33,7 +33,7 @@ gulp.task(
|
|||||||
},
|
},
|
||||||
"clean-cast",
|
"clean-cast",
|
||||||
"translations-enable-merge-backend",
|
"translations-enable-merge-backend",
|
||||||
gulp.parallel("gen-icons-json", "build-translations"),
|
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||||
"copy-static-cast",
|
"copy-static-cast",
|
||||||
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
|
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
|
||||||
"gen-index-cast-prod"
|
"gen-index-cast-prod"
|
||||||
|
@ -20,7 +20,12 @@ gulp.task(
|
|||||||
},
|
},
|
||||||
"clean-demo",
|
"clean-demo",
|
||||||
"translations-enable-merge-backend",
|
"translations-enable-merge-backend",
|
||||||
gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
|
gulp.parallel(
|
||||||
|
"gen-icons-json",
|
||||||
|
"gen-index-demo-dev",
|
||||||
|
"build-translations",
|
||||||
|
"build-locale-data"
|
||||||
|
),
|
||||||
"copy-static-demo",
|
"copy-static-demo",
|
||||||
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
|
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
|
||||||
)
|
)
|
||||||
@ -35,7 +40,7 @@ gulp.task(
|
|||||||
"clean-demo",
|
"clean-demo",
|
||||||
// Cast needs to be backwards compatible and older HA has no translations
|
// Cast needs to be backwards compatible and older HA has no translations
|
||||||
"translations-enable-merge-backend",
|
"translations-enable-merge-backend",
|
||||||
gulp.parallel("gen-icons-json", "build-translations"),
|
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||||
"copy-static-demo",
|
"copy-static-demo",
|
||||||
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
|
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
|
||||||
"gen-index-demo-prod"
|
"gen-index-demo-prod"
|
||||||
|
@ -51,6 +51,7 @@ gulp.task(
|
|||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
"gen-icons-json",
|
"gen-icons-json",
|
||||||
"build-translations",
|
"build-translations",
|
||||||
|
"build-locale-data",
|
||||||
"gather-gallery-demos"
|
"gather-gallery-demos"
|
||||||
),
|
),
|
||||||
"copy-static-gallery",
|
"copy-static-gallery",
|
||||||
@ -70,6 +71,7 @@ gulp.task(
|
|||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
"gen-icons-json",
|
"gen-icons-json",
|
||||||
"build-translations",
|
"build-translations",
|
||||||
|
"build-locale-data",
|
||||||
"gather-gallery-demos"
|
"gather-gallery-demos"
|
||||||
),
|
),
|
||||||
"copy-static-gallery",
|
"copy-static-gallery",
|
||||||
|
@ -22,11 +22,18 @@ function copyTranslations(staticDir) {
|
|||||||
|
|
||||||
// Translation output
|
// Translation output
|
||||||
fs.copySync(
|
fs.copySync(
|
||||||
polyPath("build-translations/output"),
|
polyPath("build/translations/output"),
|
||||||
staticPath("translations")
|
staticPath("translations")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyLocaleData(staticDir) {
|
||||||
|
const staticPath = genStaticPath(staticDir);
|
||||||
|
|
||||||
|
// Locale data output
|
||||||
|
fs.copySync(polyPath("build/locale-data"), staticPath("locale-data"));
|
||||||
|
}
|
||||||
|
|
||||||
function copyMdiIcons(staticDir) {
|
function copyMdiIcons(staticDir) {
|
||||||
const staticPath = genStaticPath(staticDir);
|
const staticPath = genStaticPath(staticDir);
|
||||||
|
|
||||||
@ -84,6 +91,11 @@ function copyMapPanel(staticDir) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gulp.task("copy-locale-data", async () => {
|
||||||
|
const staticDir = paths.app_output_static;
|
||||||
|
copyLocaleData(staticDir);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task("copy-translations-app", async () => {
|
gulp.task("copy-translations-app", async () => {
|
||||||
const staticDir = paths.app_output_static;
|
const staticDir = paths.app_output_static;
|
||||||
copyTranslations(staticDir);
|
copyTranslations(staticDir);
|
||||||
@ -94,6 +106,11 @@ gulp.task("copy-translations-supervisor", async () => {
|
|||||||
copyTranslations(staticDir);
|
copyTranslations(staticDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task("copy-locale-data-supervisor", async () => {
|
||||||
|
const staticDir = paths.hassio_output_static;
|
||||||
|
copyLocaleData(staticDir);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task("copy-static-app", async () => {
|
gulp.task("copy-static-app", async () => {
|
||||||
const staticDir = paths.app_output_static;
|
const staticDir = paths.app_output_static;
|
||||||
// Basic static files
|
// Basic static files
|
||||||
@ -103,6 +120,7 @@ gulp.task("copy-static-app", async () => {
|
|||||||
copyPolyfills(staticDir);
|
copyPolyfills(staticDir);
|
||||||
copyFonts(staticDir);
|
copyFonts(staticDir);
|
||||||
copyTranslations(staticDir);
|
copyTranslations(staticDir);
|
||||||
|
copyLocaleData(staticDir);
|
||||||
copyMdiIcons(staticDir);
|
copyMdiIcons(staticDir);
|
||||||
|
|
||||||
// Panel assets
|
// Panel assets
|
||||||
@ -123,6 +141,7 @@ gulp.task("copy-static-demo", async () => {
|
|||||||
copyMapPanel(paths.demo_output_static);
|
copyMapPanel(paths.demo_output_static);
|
||||||
copyFonts(paths.demo_output_static);
|
copyFonts(paths.demo_output_static);
|
||||||
copyTranslations(paths.demo_output_static);
|
copyTranslations(paths.demo_output_static);
|
||||||
|
copyLocaleData(paths.demo_output_static);
|
||||||
copyMdiIcons(paths.demo_output_static);
|
copyMdiIcons(paths.demo_output_static);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,6 +156,7 @@ gulp.task("copy-static-cast", async () => {
|
|||||||
copyMapPanel(paths.cast_output_static);
|
copyMapPanel(paths.cast_output_static);
|
||||||
copyFonts(paths.cast_output_static);
|
copyFonts(paths.cast_output_static);
|
||||||
copyTranslations(paths.cast_output_static);
|
copyTranslations(paths.cast_output_static);
|
||||||
|
copyLocaleData(paths.cast_output_static);
|
||||||
copyMdiIcons(paths.cast_output_static);
|
copyMdiIcons(paths.cast_output_static);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -152,5 +172,6 @@ gulp.task("copy-static-gallery", async () => {
|
|||||||
copyMapPanel(paths.gallery_output_static);
|
copyMapPanel(paths.gallery_output_static);
|
||||||
copyFonts(paths.gallery_output_static);
|
copyFonts(paths.gallery_output_static);
|
||||||
copyTranslations(paths.gallery_output_static);
|
copyTranslations(paths.gallery_output_static);
|
||||||
|
copyLocaleData(paths.gallery_output_static);
|
||||||
copyMdiIcons(paths.gallery_output_static);
|
copyMdiIcons(paths.gallery_output_static);
|
||||||
});
|
});
|
||||||
|
@ -24,6 +24,8 @@ gulp.task(
|
|||||||
"gen-index-hassio-dev",
|
"gen-index-hassio-dev",
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
"copy-translations-supervisor",
|
"copy-translations-supervisor",
|
||||||
|
"build-locale-data",
|
||||||
|
"copy-locale-data-supervisor",
|
||||||
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
|
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -38,6 +40,8 @@ gulp.task(
|
|||||||
"gen-icons-json",
|
"gen-icons-json",
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
"copy-translations-supervisor",
|
"copy-translations-supervisor",
|
||||||
|
"build-locale-data",
|
||||||
|
"copy-locale-data-supervisor",
|
||||||
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
||||||
"gen-index-hassio-prod",
|
"gen-index-hassio-prod",
|
||||||
...// Don't compress running tests
|
...// Don't compress running tests
|
||||||
|
77
build-scripts/gulp/locale-data.js
Executable file
77
build-scripts/gulp/locale-data.js
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
|
const del = require("del");
|
||||||
|
const path = require("path");
|
||||||
|
const gulp = require("gulp");
|
||||||
|
const fs = require("fs");
|
||||||
|
const merge = require("gulp-merge-json");
|
||||||
|
const rename = require("gulp-rename");
|
||||||
|
const transform = require("gulp-json-transform");
|
||||||
|
const paths = require("../paths");
|
||||||
|
|
||||||
|
const outDir = "build/locale-data";
|
||||||
|
|
||||||
|
gulp.task("clean-locale-data", () => del([outDir]));
|
||||||
|
|
||||||
|
gulp.task("ensure-locale-data-build-dir", (done) => {
|
||||||
|
if (!fs.existsSync(outDir)) {
|
||||||
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
const modules = {
|
||||||
|
"intl-relativetimeformat": "RelativeTimeFormat",
|
||||||
|
"intl-datetimeformat": "DateTimeFormat",
|
||||||
|
"intl-numberformat": "NumberFormat",
|
||||||
|
};
|
||||||
|
|
||||||
|
gulp.task("create-locale-data", (done) => {
|
||||||
|
const translationMeta = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.join(paths.translations_src, "translationMetadata.json")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Object.entries(modules).forEach(([module, className]) => {
|
||||||
|
Object.keys(translationMeta).forEach((lang) => {
|
||||||
|
try {
|
||||||
|
const localeData = String(
|
||||||
|
fs.readFileSync(
|
||||||
|
require.resolve(`@formatjs/${module}/locale-data/${lang}.js`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
new RegExp(
|
||||||
|
`\\/\\*\\s*@generated\\s*\\*\\/\\s*\\/\\/\\s*prettier-ignore\\s*if\\s*\\(Intl\\.${className}\\s*&&\\s*typeof\\s*Intl\\.${className}\\.__addLocaleData\\s*===\\s*'function'\\)\\s*{\\s*Intl\\.${className}\\.__addLocaleData\\(`,
|
||||||
|
"im"
|
||||||
|
),
|
||||||
|
""
|
||||||
|
)
|
||||||
|
.replace(/\)\s*}/im, "");
|
||||||
|
// make sure we have valid JSON
|
||||||
|
JSON.parse(localeData);
|
||||||
|
if (!fs.existsSync(path.join(outDir, module))) {
|
||||||
|
fs.mkdirSync(path.join(outDir, module), { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(outDir, `${module}/${lang}.json`),
|
||||||
|
localeData
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== "MODULE_NOT_FOUND") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task(
|
||||||
|
"build-locale-data",
|
||||||
|
gulp.series(
|
||||||
|
"clean-locale-data",
|
||||||
|
"ensure-locale-data-build-dir",
|
||||||
|
"create-locale-data"
|
||||||
|
)
|
||||||
|
);
|
@ -17,7 +17,7 @@ const paths = require("../paths");
|
|||||||
|
|
||||||
const inFrontendDir = "translations/frontend";
|
const inFrontendDir = "translations/frontend";
|
||||||
const inBackendDir = "translations/backend";
|
const inBackendDir = "translations/backend";
|
||||||
const workDir = "build-translations";
|
const workDir = "build/translations";
|
||||||
const fullDir = workDir + "/full";
|
const fullDir = workDir + "/full";
|
||||||
const coreDir = workDir + "/core";
|
const coreDir = workDir + "/core";
|
||||||
const outDir = workDir + "/output";
|
const outDir = workDir + "/output";
|
||||||
@ -121,7 +121,7 @@ gulp.task("clean-translations", () => del([workDir]));
|
|||||||
|
|
||||||
gulp.task("ensure-translations-build-dir", (done) => {
|
gulp.task("ensure-translations-build-dir", (done) => {
|
||||||
if (!fs.existsSync(workDir)) {
|
if (!fs.existsSync(workDir)) {
|
||||||
fs.mkdirSync(workDir);
|
fs.mkdirSync(workDir, { recursive: true });
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -336,6 +336,14 @@ gulp.task("build-translation-fragment-supervisor", () =>
|
|||||||
gulp
|
gulp
|
||||||
.src(fullDir + "/*.json")
|
.src(fullDir + "/*.json")
|
||||||
.pipe(transform((data) => data.supervisor))
|
.pipe(transform((data) => data.supervisor))
|
||||||
|
.pipe(
|
||||||
|
rename((filePath) => {
|
||||||
|
// In dev we create the file with the fake hash in the filename
|
||||||
|
if (!env.isProdBuild()) {
|
||||||
|
filePath.basename += "-dev";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
.pipe(gulp.dest(workDir + "/supervisor"))
|
.pipe(gulp.dest(workDir + "/supervisor"))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -35,26 +35,29 @@ const isWsl =
|
|||||||
* listenHost?: string
|
* listenHost?: string
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
const runDevServer = ({
|
const runDevServer = async ({
|
||||||
compiler,
|
compiler,
|
||||||
contentBase,
|
contentBase,
|
||||||
port,
|
port,
|
||||||
listenHost = "localhost",
|
listenHost = "localhost",
|
||||||
}) =>
|
}) => {
|
||||||
new WebpackDevServer(compiler, {
|
const server = new WebpackDevServer(
|
||||||
open: true,
|
{
|
||||||
watchContentBase: true,
|
open: true,
|
||||||
contentBase,
|
host: listenHost,
|
||||||
}).listen(port, listenHost, (err) => {
|
port,
|
||||||
if (err) {
|
static: {
|
||||||
throw err;
|
directory: contentBase,
|
||||||
}
|
watch: true,
|
||||||
// Server listening
|
},
|
||||||
log(
|
},
|
||||||
"[webpack-dev-server]",
|
compiler
|
||||||
`Project is running at http://localhost:${port}`
|
);
|
||||||
);
|
|
||||||
});
|
await server.start();
|
||||||
|
// Server listening
|
||||||
|
log("[webpack-dev-server]", `Project is running at http://localhost:${port}`);
|
||||||
|
};
|
||||||
|
|
||||||
const doneHandler = (done) => (err, stats) => {
|
const doneHandler = (done) => (err, stats) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -107,13 +110,13 @@ gulp.task("webpack-prod-app", () =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task("webpack-dev-server-demo", () => {
|
gulp.task("webpack-dev-server-demo", () =>
|
||||||
runDevServer({
|
runDevServer({
|
||||||
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
|
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
|
||||||
contentBase: paths.demo_output_root,
|
contentBase: paths.demo_output_root,
|
||||||
port: 8090,
|
port: 8090,
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
gulp.task("webpack-prod-demo", () =>
|
gulp.task("webpack-prod-demo", () =>
|
||||||
prodBuild(
|
prodBuild(
|
||||||
@ -123,15 +126,15 @@ gulp.task("webpack-prod-demo", () =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task("webpack-dev-server-cast", () => {
|
gulp.task("webpack-dev-server-cast", () =>
|
||||||
runDevServer({
|
runDevServer({
|
||||||
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
|
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
|
||||||
contentBase: paths.cast_output_root,
|
contentBase: paths.cast_output_root,
|
||||||
port: 8080,
|
port: 8080,
|
||||||
// Accessible from the network, because that's how Cast hits it.
|
// Accessible from the network, because that's how Cast hits it.
|
||||||
listenHost: "0.0.0.0",
|
listenHost: "0.0.0.0",
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
gulp.task("webpack-prod-cast", () =>
|
gulp.task("webpack-prod-cast", () =>
|
||||||
prodBuild(
|
prodBuild(
|
||||||
@ -148,7 +151,7 @@ gulp.task("webpack-watch-hassio", () => {
|
|||||||
isProdBuild: false,
|
isProdBuild: false,
|
||||||
latestBuild: true,
|
latestBuild: true,
|
||||||
})
|
})
|
||||||
).watch({ ignored: /build-translations/, poll: isWsl }, doneHandler());
|
).watch({ ignored: /build/, poll: isWsl }, doneHandler());
|
||||||
|
|
||||||
gulp.watch(
|
gulp.watch(
|
||||||
path.join(paths.translations_src, "en.json"),
|
path.join(paths.translations_src, "en.json"),
|
||||||
@ -164,14 +167,14 @@ gulp.task("webpack-prod-hassio", () =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task("webpack-dev-server-gallery", () => {
|
gulp.task("webpack-dev-server-gallery", () =>
|
||||||
runDevServer({
|
runDevServer({
|
||||||
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
|
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
|
||||||
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
|
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
|
||||||
contentBase: paths.gallery_output_root,
|
contentBase: paths.gallery_output_root,
|
||||||
port: 8100,
|
port: 8100,
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
gulp.task("webpack-prod-gallery", () =>
|
gulp.task("webpack-prod-gallery", () =>
|
||||||
prodBuild(
|
prodBuild(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable lit/no-template-arrow */
|
||||||
import { html, css, LitElement, TemplateResult } from "lit";
|
import { html, css, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable lit/no-template-arrow */
|
||||||
import { html, css, LitElement, TemplateResult } from "lit";
|
import { html, css, LitElement, TemplateResult } from "lit";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import "../../../src/components/trace/hat-script-graph";
|
import "../../../src/components/trace/hat-script-graph";
|
||||||
|
212
gallery/src/demos/demo-ha-form.ts
Normal file
212
gallery/src/demos/demo-ha-form.ts
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/* eslint-disable lit/no-template-arrow */
|
||||||
|
import { LitElement, TemplateResult, css, html } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import "../../../src/components/ha-form/ha-form";
|
||||||
|
import "../../../src/components/ha-card";
|
||||||
|
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||||
|
import type { HaFormSchema } from "../../../src/components/ha-form/ha-form";
|
||||||
|
|
||||||
|
const SCHEMAS: {
|
||||||
|
title: string;
|
||||||
|
translations?: Record<string, string>;
|
||||||
|
error?: Record<string, string>;
|
||||||
|
schema: HaFormSchema[];
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
title: "Authentication",
|
||||||
|
translations: {
|
||||||
|
username: "Username",
|
||||||
|
password: "Password",
|
||||||
|
invalid_login: "Invalid login",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
base: "invalid_login",
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: "string",
|
||||||
|
name: "username",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "string",
|
||||||
|
name: "password",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "One of each",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: "constant",
|
||||||
|
value: "Constant Value",
|
||||||
|
name: "constant",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "boolean",
|
||||||
|
name: "bool",
|
||||||
|
optional: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "integer",
|
||||||
|
name: "int",
|
||||||
|
optional: true,
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "string",
|
||||||
|
name: "string",
|
||||||
|
optional: true,
|
||||||
|
default: "Default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
["default", "default"],
|
||||||
|
["other", "other"],
|
||||||
|
],
|
||||||
|
name: "select",
|
||||||
|
optional: true,
|
||||||
|
default: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "multi_select",
|
||||||
|
options: {
|
||||||
|
default: "Default",
|
||||||
|
other: "Other",
|
||||||
|
},
|
||||||
|
name: "multi",
|
||||||
|
optional: true,
|
||||||
|
default: ["default"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Multi select",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: "multi_select",
|
||||||
|
options: {
|
||||||
|
default: "Default",
|
||||||
|
other: "Other",
|
||||||
|
},
|
||||||
|
name: "multi",
|
||||||
|
optional: true,
|
||||||
|
default: ["default"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "multi_select",
|
||||||
|
options: {
|
||||||
|
default: "Default",
|
||||||
|
other: "Other",
|
||||||
|
uno: "mas",
|
||||||
|
one: "more",
|
||||||
|
and: "another_one",
|
||||||
|
option: "1000",
|
||||||
|
},
|
||||||
|
name: "multi",
|
||||||
|
optional: true,
|
||||||
|
default: ["default"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-ha-form")
|
||||||
|
class DemoHaForm extends LitElement {
|
||||||
|
private lightModeData: any = [];
|
||||||
|
|
||||||
|
private darkModeData: any = [];
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${SCHEMAS.map((info, idx) => {
|
||||||
|
const translations = info.translations || {};
|
||||||
|
const computeLabel = (schema) =>
|
||||||
|
translations[schema.name] || schema.name;
|
||||||
|
const computeError = (error) => translations[error] || error;
|
||||||
|
|
||||||
|
return [
|
||||||
|
[this.lightModeData, "light"],
|
||||||
|
[this.darkModeData, "dark"],
|
||||||
|
].map(
|
||||||
|
([data, type]) => html`
|
||||||
|
<div class="row" data-type=${type}>
|
||||||
|
<ha-card .header=${info.title}>
|
||||||
|
<div class="card-content">
|
||||||
|
<ha-form
|
||||||
|
.data=${data[idx]}
|
||||||
|
.schema=${info.schema}
|
||||||
|
.error=${info.error}
|
||||||
|
.computeError=${computeError}
|
||||||
|
.computeLabel=${computeLabel}
|
||||||
|
@value-changed=${(e) => {
|
||||||
|
data[idx] = e.detail.value;
|
||||||
|
this.requestUpdate();
|
||||||
|
}}
|
||||||
|
></ha-form>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
<pre>${JSON.stringify(data[idx], undefined, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.shadowRoot!.querySelectorAll("[data-type=dark]").forEach((el) => {
|
||||||
|
applyThemesOnElement(
|
||||||
|
el,
|
||||||
|
{
|
||||||
|
default_theme: "default",
|
||||||
|
default_dark_theme: "default",
|
||||||
|
themes: {},
|
||||||
|
darkMode: false,
|
||||||
|
},
|
||||||
|
"default",
|
||||||
|
{ dark: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
.row {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 800px;
|
||||||
|
display: flex;
|
||||||
|
padding: 50px;
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 384px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
width: 400px;
|
||||||
|
margin: 0 16px;
|
||||||
|
overflow: auto;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
.row {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-ha-form": DemoHaForm;
|
||||||
|
}
|
||||||
|
}
|
@ -181,9 +181,7 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.homeAssistant}
|
.checked=${this.homeAssistant}
|
||||||
@click=${() => {
|
@click=${this.toggleHomeAssistant}
|
||||||
this.homeAssistant = !this.homeAssistant;
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
@ -272,6 +270,10 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toggleHomeAssistant() {
|
||||||
|
this.homeAssistant = !this.homeAssistant;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.partial-picker ha-formfield {
|
.partial-picker ha-formfield {
|
||||||
|
@ -28,6 +28,7 @@ import "../../components/supervisor-backup-content";
|
|||||||
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
|
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
|
||||||
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
|
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
|
||||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||||
|
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
|
||||||
|
|
||||||
@customElement("dialog-hassio-backup")
|
@customElement("dialog-hassio-backup")
|
||||||
class HassioBackupDialog
|
class HassioBackupDialog
|
||||||
@ -107,7 +108,7 @@ class HassioBackupDialog
|
|||||||
fixed
|
fixed
|
||||||
slot="primaryAction"
|
slot="primaryAction"
|
||||||
@action=${this._handleMenuAction}
|
@action=${this._handleMenuAction}
|
||||||
@closed=${(ev: Event) => ev.stopPropagation()}
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
<mwc-icon-button slot="trigger" alt="menu">
|
<mwc-icon-button slot="trigger" alt="menu">
|
||||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||||
|
@ -65,32 +65,21 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
open
|
open
|
||||||
scrimClickAction
|
scrimClickAction
|
||||||
escapeKeyAction
|
escapeKeyAction
|
||||||
|
.heading=${this.moving
|
||||||
|
? this.dialogParams.supervisor.localize("dialog.datadisk_move.moving")
|
||||||
|
: this.dialogParams.supervisor.localize("dialog.datadisk_move.title")}
|
||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
?hideActions=${this.moving}
|
?hideActions=${this.moving}
|
||||||
>
|
>
|
||||||
${this.moving
|
${this.moving
|
||||||
? html`<slot name="heading">
|
? html` <ha-circular-progress alt="Moving" size="large" active>
|
||||||
<h2 id="title" class="header_title">
|
|
||||||
${this.dialogParams.supervisor.localize(
|
|
||||||
"dialog.datadisk_move.moving"
|
|
||||||
)}
|
|
||||||
</h2>
|
|
||||||
</slot>
|
|
||||||
<ha-circular-progress alt="Moving" size="large" active>
|
|
||||||
</ha-circular-progress>
|
</ha-circular-progress>
|
||||||
<p class="progress-text">
|
<p class="progress-text">
|
||||||
${this.dialogParams.supervisor.localize(
|
${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.moving_desc"
|
"dialog.datadisk_move.moving_desc"
|
||||||
)}
|
)}
|
||||||
</p>`
|
</p>`
|
||||||
: html`<slot name="heading">
|
: html` ${this.devices?.length
|
||||||
<h2 id="title" class="header_title">
|
|
||||||
${this.dialogParams.supervisor.localize(
|
|
||||||
"dialog.datadisk_move.title"
|
|
||||||
)}
|
|
||||||
</h2>
|
|
||||||
</slot>
|
|
||||||
${this.devices?.length
|
|
||||||
? html`
|
? html`
|
||||||
${this.dialogParams.supervisor.localize(
|
${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.description",
|
"dialog.datadisk_move.description",
|
||||||
|
@ -184,23 +184,34 @@ class HassioHostInfo extends LitElement {
|
|||||||
<mwc-icon-button slot="trigger">
|
<mwc-icon-button slot="trigger">
|
||||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||||
</mwc-icon-button>
|
</mwc-icon-button>
|
||||||
<mwc-list-item @click=${() => this._handleMenuAction("hardware")}>
|
<mwc-list-item
|
||||||
|
.action=${"hardware"}
|
||||||
|
@click=${this._handleMenuAction}
|
||||||
|
>
|
||||||
${this.supervisor.localize("system.host.hardware")}
|
${this.supervisor.localize("system.host.hardware")}
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
${this.supervisor.host.features.includes("haos")
|
${this.supervisor.host.features.includes("haos")
|
||||||
? html`<mwc-list-item
|
? html`
|
||||||
@click=${() => this._handleMenuAction("import_from_usb")}
|
<mwc-list-item
|
||||||
|
.action=${"import_from_usb"}
|
||||||
|
@click=${this._handleMenuAction}
|
||||||
>
|
>
|
||||||
${this.supervisor.localize("system.host.import_from_usb")}
|
${this.supervisor.localize("system.host.import_from_usb")}
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
${this.supervisor.host.features.includes("os_agent") &&
|
${this.supervisor.host.features.includes("os_agent") &&
|
||||||
atLeastVersion(this.supervisor.host.agent_version, 1, 2, 0)
|
atLeastVersion(this.supervisor.host.agent_version, 1, 2, 0)
|
||||||
? html`<mwc-list-item
|
? html`
|
||||||
@click=${() => this._handleMenuAction("move_datadisk")}
|
<mwc-list-item
|
||||||
>
|
.action=${"move_datadisk"}
|
||||||
${this.supervisor.localize("system.host.move_datadisk")}
|
@click=${this._handleMenuAction}
|
||||||
</mwc-list-item>`
|
>
|
||||||
: ""} `
|
${this.supervisor.localize(
|
||||||
|
"system.host.move_datadisk"
|
||||||
|
)}
|
||||||
|
</mwc-list-item>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
</div>
|
</div>
|
||||||
@ -223,8 +234,8 @@ class HassioHostInfo extends LitElement {
|
|||||||
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
|
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
|
||||||
});
|
});
|
||||||
|
|
||||||
private async _handleMenuAction(action: string) {
|
private async _handleMenuAction(ev) {
|
||||||
switch (action) {
|
switch ((ev.target as any).action) {
|
||||||
case "hardware":
|
case "hardware":
|
||||||
await this._showHardware();
|
await this._showHardware();
|
||||||
break;
|
break;
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20210930.0",
|
version="20211002.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/frontend",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -4,6 +4,7 @@ import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-rel
|
|||||||
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
|
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
|
||||||
import IntlMessageFormat from "intl-messageformat";
|
import IntlMessageFormat from "intl-messageformat";
|
||||||
import { Resources } from "../../types";
|
import { Resources } from "../../types";
|
||||||
|
import { getLocalLanguage } from "../../util/hass-translation";
|
||||||
|
|
||||||
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
||||||
interface FormatType {
|
interface FormatType {
|
||||||
@ -15,37 +16,32 @@ export interface FormatsType {
|
|||||||
time: FormatType;
|
time: FormatType;
|
||||||
}
|
}
|
||||||
|
|
||||||
let loadedPolyfillLocale: Set<string> | undefined;
|
const loadedPolyfillLocale = new Set();
|
||||||
|
|
||||||
const polyfillPluralRules = shouldPolyfillPluralRules();
|
|
||||||
const polyfillRelativeTime = shouldPolyfillRelativeTime();
|
|
||||||
const polyfillDateTime = shouldPolyfillDateTime();
|
|
||||||
|
|
||||||
const polyfills: Promise<any>[] = [];
|
const polyfills: Promise<any>[] = [];
|
||||||
if (__BUILD__ === "latest") {
|
if (__BUILD__ === "latest") {
|
||||||
if (shouldPolyfillLocale()) {
|
if (shouldPolyfillLocale()) {
|
||||||
polyfills.push(import("@formatjs/intl-locale/polyfill"));
|
polyfills.push(import("@formatjs/intl-locale/polyfill"));
|
||||||
}
|
}
|
||||||
if (polyfillPluralRules) {
|
if (shouldPolyfillPluralRules()) {
|
||||||
polyfills.push(import("@formatjs/intl-pluralrules/polyfill"));
|
polyfills.push(import("@formatjs/intl-pluralrules/polyfill"));
|
||||||
|
polyfills.push(import("@formatjs/intl-pluralrules/locale-data/en"));
|
||||||
}
|
}
|
||||||
if (polyfillRelativeTime) {
|
if (shouldPolyfillRelativeTime()) {
|
||||||
polyfills.push(import("@formatjs/intl-relativetimeformat/polyfill"));
|
polyfills.push(import("@formatjs/intl-relativetimeformat/polyfill"));
|
||||||
}
|
}
|
||||||
if (polyfillDateTime) {
|
if (shouldPolyfillDateTime()) {
|
||||||
polyfills.push(import("@formatjs/intl-datetimeformat/polyfill"));
|
polyfills.push(import("@formatjs/intl-datetimeformat/polyfill"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let polyfillLoaded = polyfills.length === 0;
|
export const polyfillsLoaded =
|
||||||
export const polyfillsLoaded = polyfillLoaded
|
polyfills.length === 0
|
||||||
? undefined
|
? undefined
|
||||||
: Promise.all(polyfills).then(() => {
|
: Promise.all(polyfills).then(() =>
|
||||||
loadedPolyfillLocale = new Set();
|
// Load the default language
|
||||||
polyfillLoaded = true;
|
loadPolyfillLocales(getLocalLanguage())
|
||||||
// Load English so it becomes the default
|
);
|
||||||
return loadPolyfillLocales("en");
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapted from Polymer app-localize-behavior.
|
* Adapted from Polymer app-localize-behavior.
|
||||||
@ -74,11 +70,11 @@ export const computeLocalize = async (
|
|||||||
resources: Resources,
|
resources: Resources,
|
||||||
formats?: FormatsType
|
formats?: FormatsType
|
||||||
): Promise<LocalizeFunc> => {
|
): Promise<LocalizeFunc> => {
|
||||||
if (!polyfillLoaded) {
|
if (polyfillsLoaded) {
|
||||||
await polyfillsLoaded;
|
await polyfillsLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPolyfillLocales(language);
|
await loadPolyfillLocales(language);
|
||||||
|
|
||||||
// Everytime any of the parameters change, invalidate the strings cache.
|
// Everytime any of the parameters change, invalidate the strings cache.
|
||||||
cache._localizationCache = {};
|
cache._localizationCache = {};
|
||||||
@ -132,19 +128,44 @@ export const computeLocalize = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const loadPolyfillLocales = async (language: string) => {
|
export const loadPolyfillLocales = async (language: string) => {
|
||||||
if (!loadedPolyfillLocale || loadedPolyfillLocale.has(language)) {
|
if (loadedPolyfillLocale.has(language)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loadedPolyfillLocale.add(language);
|
loadedPolyfillLocale.add(language);
|
||||||
try {
|
try {
|
||||||
if (polyfillPluralRules) {
|
if (
|
||||||
await import(`@formatjs/intl-pluralrules/locale-data/${language}`);
|
Intl.NumberFormat &&
|
||||||
|
// @ts-ignore
|
||||||
|
typeof Intl.NumberFormat.__addLocaleData === "function"
|
||||||
|
) {
|
||||||
|
const result = await fetch(
|
||||||
|
`/static/locale-data/intl-numberformat/${language}.json`
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
Intl.NumberFormat.__addLocaleData(await result.json());
|
||||||
}
|
}
|
||||||
if (polyfillRelativeTime) {
|
if (
|
||||||
await import(`@formatjs/intl-relativetimeformat/locale-data/${language}`);
|
// @ts-expect-error
|
||||||
|
Intl.RelativeTimeFormat &&
|
||||||
|
// @ts-ignore
|
||||||
|
typeof Intl.RelativeTimeFormat.__addLocaleData === "function"
|
||||||
|
) {
|
||||||
|
const result = await fetch(
|
||||||
|
`/static/locale-data/intl-relativetimeformat/${language}.json`
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
Intl.RelativeTimeFormat.__addLocaleData(await result.json());
|
||||||
}
|
}
|
||||||
if (polyfillDateTime) {
|
if (
|
||||||
await import(`@formatjs/intl-datetimeformat/locale-data/${language}`);
|
Intl.DateTimeFormat &&
|
||||||
|
// @ts-ignore
|
||||||
|
typeof Intl.DateTimeFormat.__addLocaleData === "function"
|
||||||
|
) {
|
||||||
|
const result = await fetch(
|
||||||
|
`/static/locale-data/intl-datetimeformat/${language}.json`
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
Intl.DateTimeFormat.__addLocaleData(await result.json());
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
// Ignore
|
// Ignore
|
||||||
|
@ -21,6 +21,7 @@ const BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED = new Set([
|
|||||||
"garage_door",
|
"garage_door",
|
||||||
"gas",
|
"gas",
|
||||||
"lock",
|
"lock",
|
||||||
|
"motion",
|
||||||
"opening",
|
"opening",
|
||||||
"problem",
|
"problem",
|
||||||
"safety",
|
"safety",
|
||||||
|
@ -39,8 +39,8 @@ export class HaAreaSelector extends LitElement {
|
|||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
no-add
|
no-add
|
||||||
.deviceFilter=${(device) => this._filterDevices(device)}
|
.deviceFilter=${this._filterDevices}
|
||||||
.entityFilter=${(entity) => this._filterEntities(entity)}
|
.entityFilter=${this._filterEntities}
|
||||||
.includeDeviceClasses=${this.selector.area.entity?.device_class
|
.includeDeviceClasses=${this.selector.area.entity?.device_class
|
||||||
? [this.selector.area.entity.device_class]
|
? [this.selector.area.entity.device_class]
|
||||||
: undefined}
|
: undefined}
|
||||||
@ -51,16 +51,16 @@ export class HaAreaSelector extends LitElement {
|
|||||||
></ha-area-picker>`;
|
></ha-area-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filterEntities(entity: EntityRegistryEntry): boolean {
|
private _filterEntities = (entity: EntityRegistryEntry): boolean => {
|
||||||
if (this.selector.area.entity?.integration) {
|
if (this.selector.area.entity?.integration) {
|
||||||
if (entity.platform !== this.selector.area.entity.integration) {
|
if (entity.platform !== this.selector.area.entity.integration) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
||||||
|
|
||||||
private _filterDevices(device: DeviceRegistryEntry): boolean {
|
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||||
if (
|
if (
|
||||||
this.selector.area.device?.manufacturer &&
|
this.selector.area.device?.manufacturer &&
|
||||||
device.manufacturer !== this.selector.area.device.manufacturer
|
device.manufacturer !== this.selector.area.device.manufacturer
|
||||||
@ -84,7 +84,7 @@ export class HaAreaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
||||||
|
|
||||||
private async _loadConfigEntries() {
|
private async _loadConfigEntries() {
|
||||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
HassEntityBase,
|
HassEntityBase,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
|
|
||||||
export enum LightColorModes {
|
export const enum LightColorModes {
|
||||||
UNKNOWN = "unknown",
|
UNKNOWN = "unknown",
|
||||||
ONOFF = "onoff",
|
ONOFF = "onoff",
|
||||||
BRIGHTNESS = "brightness",
|
BRIGHTNESS = "brightness",
|
||||||
|
@ -2,7 +2,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { DeviceRegistryEntry } from "./device_registry";
|
import { DeviceRegistryEntry } from "./device_registry";
|
||||||
|
|
||||||
export enum InclusionStrategy {
|
export const enum InclusionStrategy {
|
||||||
/**
|
/**
|
||||||
* Always uses Security S2 if supported, otherwise uses Security S0 for certain devices which don't work without encryption and uses no encryption otherwise.
|
* Always uses Security S2 if supported, otherwise uses Security S0 for certain devices which don't work without encryption and uses no encryption otherwise.
|
||||||
*
|
*
|
||||||
@ -83,6 +83,7 @@ export interface ZWaveJSNodeStatus {
|
|||||||
node_id: number;
|
node_id: number;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
status: number;
|
status: number;
|
||||||
|
is_secure: boolean | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZwaveJSNodeMetadata {
|
export interface ZwaveJSNodeMetadata {
|
||||||
@ -154,7 +155,7 @@ export interface ZWaveJSRemovedNode {
|
|||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NodeStatus {
|
export const enum NodeStatus {
|
||||||
Unknown,
|
Unknown,
|
||||||
Asleep,
|
Asleep,
|
||||||
Awake,
|
Awake,
|
||||||
|
@ -116,6 +116,14 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
|||||||
? this.hass.localize("ui.common.yes")
|
? this.hass.localize("ui.common.yes")
|
||||||
: this.hass.localize("ui.common.no")}
|
: this.hass.localize("ui.common.no")}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
${this.hass.localize("ui.panel.config.zwave_js.device_info.is_secure")}:
|
||||||
|
${this._node.is_secure === true
|
||||||
|
? this.hass.localize("ui.common.yes")
|
||||||
|
: this._node.is_secure === false
|
||||||
|
? this.hass.localize("ui.common.no")
|
||||||
|
: this.hass.localize("ui.panel.config.zwave_js.device_info.unknown")}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ class PanelDeveloperTools extends LitElement {
|
|||||||
}
|
}
|
||||||
developer-tools-router {
|
developer-tools-router {
|
||||||
display: block;
|
display: block;
|
||||||
height: calc(100vh - 112px);
|
height: calc(100vh - 104px);
|
||||||
}
|
}
|
||||||
ha-tabs {
|
ha-tabs {
|
||||||
margin-left: max(env(safe-area-inset-left), 24px);
|
margin-left: max(env(safe-area-inset-left), 24px);
|
||||||
|
@ -2,6 +2,7 @@ import "@material/mwc-button/mwc-button";
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import "../../../components/data-table/ha-data-table";
|
import "../../../components/data-table/ha-data-table";
|
||||||
@ -32,63 +33,70 @@ class HaPanelDevStatistics extends LitElement {
|
|||||||
this._validateStatistics();
|
this._validateStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _columns: DataTableColumnContainer = {
|
/* eslint-disable lit/no-template-arrow */
|
||||||
state: {
|
private _columns = memoizeOne(
|
||||||
title: "Entity",
|
(localize): DataTableColumnContainer => ({
|
||||||
sortable: true,
|
state: {
|
||||||
filterable: true,
|
title: "Entity",
|
||||||
grows: true,
|
sortable: true,
|
||||||
template: (entityState, data: any) =>
|
filterable: true,
|
||||||
html`${entityState
|
grows: true,
|
||||||
? computeStateName(entityState)
|
template: (entityState, data: any) =>
|
||||||
: data.statistic_id}`,
|
html`${entityState
|
||||||
},
|
? computeStateName(entityState)
|
||||||
statistic_id: {
|
: data.statistic_id}`,
|
||||||
title: "Statistic id",
|
},
|
||||||
sortable: true,
|
statistic_id: {
|
||||||
filterable: true,
|
title: "Statistic id",
|
||||||
hidden: this.narrow,
|
sortable: true,
|
||||||
width: "30%",
|
filterable: true,
|
||||||
},
|
hidden: this.narrow,
|
||||||
unit_of_measurement: {
|
width: "30%",
|
||||||
title: "Unit",
|
},
|
||||||
sortable: true,
|
unit_of_measurement: {
|
||||||
filterable: true,
|
title: "Unit",
|
||||||
width: "10%",
|
sortable: true,
|
||||||
},
|
filterable: true,
|
||||||
issues: {
|
width: "10%",
|
||||||
title: "Issue",
|
},
|
||||||
sortable: true,
|
issues: {
|
||||||
filterable: true,
|
title: "Issue",
|
||||||
direction: "asc",
|
sortable: true,
|
||||||
width: "30%",
|
filterable: true,
|
||||||
template: (issues) =>
|
direction: "asc",
|
||||||
html`${issues
|
width: "30%",
|
||||||
? issues.map(
|
template: (issues) =>
|
||||||
(issue) =>
|
html`${issues
|
||||||
this.hass.localize(
|
? issues.map(
|
||||||
`ui.panel.developer-tools.tabs.statistics.issues.${issue.type}`,
|
(issue) =>
|
||||||
issue.data
|
localize(
|
||||||
) || issue.type
|
`ui.panel.developer-tools.tabs.statistics.issues.${issue.type}`,
|
||||||
)
|
issue.data
|
||||||
: ""}`,
|
) || issue.type
|
||||||
},
|
)
|
||||||
fix: {
|
: ""}`,
|
||||||
title: "",
|
},
|
||||||
template: (_, data: any) =>
|
fix: {
|
||||||
html`${data.issues
|
title: "",
|
||||||
? html`<mwc-button @click=${this._fixIssue} .data=${data.issues}
|
template: (_, data: any) =>
|
||||||
>Fix issue</mwc-button
|
html`${data.issues
|
||||||
>`
|
? html`<mwc-button
|
||||||
: ""}`,
|
@click=${(ev) => this._fixIssue(ev)}
|
||||||
width: "113px",
|
.data=${data.issues}
|
||||||
},
|
>
|
||||||
};
|
Fix issue
|
||||||
|
</mwc-button>`
|
||||||
|
: ""}`,
|
||||||
|
width: "113px",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
/* eslint-enable lit/no-template-arrow */
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-data-table
|
<ha-data-table
|
||||||
.columns=${this._columns}
|
.columns=${this._columns(this.hass.localize)}
|
||||||
.data=${this._data}
|
.data=${this._data}
|
||||||
noDataText="No issues found!"
|
noDataText="No issues found!"
|
||||||
id="statistic_id"
|
id="statistic_id"
|
||||||
@ -123,11 +131,11 @@ class HaPanelDevStatistics extends LitElement {
|
|||||||
if (issue.type === "unsupported_unit") {
|
if (issue.type === "unsupported_unit") {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: "Unsupported unit",
|
title: "Unsupported unit",
|
||||||
text: html`The unit of your entity is not a suppported unit for the
|
text: html`The unit of your entity is not a supported unit for the
|
||||||
device class of the entity, ${issue.data.device_class}.
|
device class of the entity, ${issue.data.device_class}.
|
||||||
<br />Statistics can not be generated until this entity has a
|
<br />Statistics can not be generated until this entity has a
|
||||||
supported unit. <br /><br />If this unit was provided by an
|
supported unit.<br /><br />If this unit was provided by an
|
||||||
integration, this is a bug. Please report an issue. <br /><br />If you
|
integration, this is a bug. Please report an issue.<br /><br />If you
|
||||||
have set this unit yourself, and want to have statistics generated,
|
have set this unit yourself, and want to have statistics generated,
|
||||||
make sure the unit matched the device class. The supported units are
|
make sure the unit matched the device class. The supported units are
|
||||||
documented in the
|
documented in the
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
ChartDataset,
|
ChartDataset,
|
||||||
ChartOptions,
|
ChartOptions,
|
||||||
ParsedDataType,
|
ParsedDataType,
|
||||||
|
ScatterDataPoint,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import { getRelativePosition } from "chart.js/helpers";
|
import { getRelativePosition } from "chart.js/helpers";
|
||||||
import { addHours } from "date-fns";
|
import { addHours } from "date-fns";
|
||||||
@ -21,11 +22,7 @@ import {
|
|||||||
import "../../../../components/chart/ha-chart-base";
|
import "../../../../components/chart/ha-chart-base";
|
||||||
import type HaChartBase from "../../../../components/chart/ha-chart-base";
|
import type HaChartBase from "../../../../components/chart/ha-chart-base";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import {
|
import { EnergyData, getEnergyDataCollection } from "../../../../data/energy";
|
||||||
DeviceConsumptionEnergyPreference,
|
|
||||||
EnergyData,
|
|
||||||
getEnergyDataCollection,
|
|
||||||
} from "../../../../data/energy";
|
|
||||||
import {
|
import {
|
||||||
calculateStatisticSumGrowth,
|
calculateStatisticSumGrowth,
|
||||||
fetchStatistics,
|
fetchStatistics,
|
||||||
@ -52,8 +49,6 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
|
|
||||||
@query("ha-chart-base") private _chart?: HaChartBase;
|
@query("ha-chart-base") private _chart?: HaChartBase;
|
||||||
|
|
||||||
private _deviceConsumptionPrefs: DeviceConsumptionEnergyPreference[] = [];
|
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
return [
|
return [
|
||||||
getEnergyDataCollection(this.hass, {
|
getEnergyDataCollection(this.hass, {
|
||||||
@ -110,11 +105,11 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
ticks: {
|
ticks: {
|
||||||
autoSkip: false,
|
autoSkip: false,
|
||||||
callback: (index) => {
|
callback: (index) => {
|
||||||
const devicePref = this._deviceConsumptionPrefs[index];
|
const entityId = (
|
||||||
const entity = this.hass.states[devicePref.stat_consumption];
|
this._chartData.datasets[0].data[index] as ScatterDataPoint
|
||||||
return entity
|
).y;
|
||||||
? computeStateName(entity)
|
const entity = this.hass.states[entityId];
|
||||||
: devicePref.stat_consumption;
|
return entity ? computeStateName(entity) : entityId;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -160,8 +155,6 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
);
|
);
|
||||||
|
|
||||||
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||||
this._deviceConsumptionPrefs = energyData.prefs.device_consumption;
|
|
||||||
|
|
||||||
this._data = await fetchStatistics(
|
this._data = await fetchStatistics(
|
||||||
this.hass,
|
this.hass,
|
||||||
addHours(energyData.start, -1),
|
addHours(energyData.start, -1),
|
||||||
|
@ -23,7 +23,7 @@ const DEFAULT_FILTER = "grayscale(100%)";
|
|||||||
const MAX_IMAGE_WIDTH = 640;
|
const MAX_IMAGE_WIDTH = 640;
|
||||||
const ASPECT_RATIO_DEFAULT = 9 / 16;
|
const ASPECT_RATIO_DEFAULT = 9 / 16;
|
||||||
|
|
||||||
enum LoadState {
|
const enum LoadState {
|
||||||
Loading = 1,
|
Loading = 1,
|
||||||
Loaded = 2,
|
Loaded = 2,
|
||||||
Error = 3,
|
Error = 3,
|
||||||
|
@ -31,6 +31,9 @@ const REDIRECTS: Redirects = {
|
|||||||
developer_events: {
|
developer_events: {
|
||||||
redirect: "/developer-tools/event",
|
redirect: "/developer-tools/event",
|
||||||
},
|
},
|
||||||
|
developer_statistics: {
|
||||||
|
redirect: "/developer-tools/statistics",
|
||||||
|
},
|
||||||
config: {
|
config: {
|
||||||
redirect: "/config",
|
redirect: "/config",
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as translationMetadata_ from "../../build-translations/translationMetadata.json";
|
import * as translationMetadata_ from "../../build/translations/translationMetadata.json";
|
||||||
import { TranslationMetadata } from "../types.js";
|
import { TranslationMetadata } from "../types.js";
|
||||||
|
|
||||||
export const translationMetadata = (translationMetadata_ as any)
|
export const translationMetadata = (translationMetadata_ as any)
|
||||||
|
@ -2761,7 +2761,9 @@
|
|||||||
"device_config": "Configure Device",
|
"device_config": "Configure Device",
|
||||||
"reinterview_device": "Re-interview Device",
|
"reinterview_device": "Re-interview Device",
|
||||||
"heal_node": "Heal Device",
|
"heal_node": "Heal Device",
|
||||||
"remove_failed": "Remove Failed Device"
|
"remove_failed": "Remove Failed Device",
|
||||||
|
"is_secure": "Secure",
|
||||||
|
"unknown": "Unknown"
|
||||||
},
|
},
|
||||||
"node_config": {
|
"node_config": {
|
||||||
"header": "Z-Wave Device Configuration",
|
"header": "Z-Wave Device Configuration",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user