mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-25 08:07:25 +00:00
Compare commits
93
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1605ba196 | ||
|
|
2240d019f5 | ||
|
|
cb11c6b3ea | ||
|
|
5893559951 | ||
|
|
8408d25cef | ||
|
|
1ac2ffcf02 | ||
|
|
6b6c38c2c8 | ||
|
|
e55df73a91 | ||
|
|
360c2cbfa3 | ||
|
|
aba96674f3 | ||
|
|
5c3d85fc90 | ||
|
|
6486b7fd4c | ||
|
|
5f3e980de0 | ||
|
|
d0edbec5fb | ||
|
|
5d46963e8a | ||
|
|
321f441b63 | ||
|
|
d55bade070 | ||
|
|
6ba6b821f5 | ||
|
|
b3dedae115 | ||
|
|
5a1070c30f | ||
|
|
40664997e1 | ||
|
|
c6e83cb7c0 | ||
|
|
e7e27e794c | ||
|
|
1073dbe6ab | ||
|
|
2bd9b5a015 | ||
|
|
bc09febd2c | ||
|
|
b2a87c90a2 | ||
|
|
d6dbbcb0de | ||
|
|
9ccb5360b3 | ||
|
|
0187c4faff | ||
|
|
605172a0bc | ||
|
|
8565a0d911 | ||
|
|
61c8d23a7e | ||
|
|
5e3487ed59 | ||
|
|
d5a161769c | ||
|
|
1692f9c2dd | ||
|
|
0cbac8bb44 | ||
|
|
35a81e7f11 | ||
|
|
ac64d293e7 | ||
|
|
708b8787c5 | ||
|
|
2bddd151eb | ||
|
|
43a585187c | ||
|
|
324658a36b | ||
|
|
dd9a9b34d1 | ||
|
|
2ab0e40952 | ||
|
|
dfea80ae96 | ||
|
|
6e38f5accf | ||
|
|
7c952d92bf | ||
|
|
2fae0d2d95 | ||
|
|
67ab63f00e | ||
|
|
719f9c28af | ||
|
|
035d621109 | ||
|
|
791f3b896d | ||
|
|
fe2172a660 | ||
|
|
640fbd616b | ||
|
|
900efe8a36 | ||
|
|
5bd92d04d9 | ||
|
|
b15684bcbd | ||
|
|
a93222dbb2 | ||
|
|
20744e90a0 | ||
|
|
32777b4259 | ||
|
|
271120999c | ||
|
|
68fe13a67d | ||
|
|
f3606014c6 | ||
|
|
efbf4482b2 | ||
|
|
21a3b4f8e2 | ||
|
|
de23b2d046 | ||
|
|
bd8f436c1d | ||
|
|
e963735dba | ||
|
|
46c981103d | ||
|
|
f6d02d8fc6 | ||
|
|
e08f691510 | ||
|
|
af9199aaff | ||
|
|
8576b13f74 | ||
|
|
2270d8a795 | ||
|
|
f4dcce6d6c | ||
|
|
b802a410b9 | ||
|
|
9e3d339ec5 | ||
|
|
fb97a98b97 | ||
|
|
72773f3bc8 | ||
|
|
b0fd93e0c3 | ||
|
|
7aa2ec78f2 | ||
|
|
047e856a61 | ||
|
|
dbe209e3f2 | ||
|
|
e0d23ee6cf | ||
|
|
7a35f46370 | ||
|
|
4a4465efb6 | ||
|
|
3a112531cc | ||
|
|
456209dded | ||
|
|
2556b0d157 | ||
|
|
1e8903fd76 | ||
|
|
ad9f18c231 | ||
|
|
63e3de00cb |
@@ -73,8 +73,7 @@ jobs:
|
||||
matrix:
|
||||
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
|
||||
tag:
|
||||
- "3.8-alpine3.12"
|
||||
- "3.9-alpine3.13"
|
||||
- "3.9-alpine3.14"
|
||||
steps:
|
||||
- name: Download requirements.txt
|
||||
uses: actions/download-artifact@v2
|
||||
|
||||
+15
-15
@@ -5,32 +5,32 @@ require("./translations");
|
||||
|
||||
gulp.task(
|
||||
"clean",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([paths.app_output_root, paths.build_dir]);
|
||||
})
|
||||
gulp.parallel("clean-translations", () =>
|
||||
del([paths.app_output_root, paths.build_dir])
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-demo",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([paths.demo_output_root, paths.build_dir]);
|
||||
})
|
||||
gulp.parallel("clean-translations", () =>
|
||||
del([paths.demo_output_root, paths.build_dir])
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-cast",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([paths.cast_output_root, paths.build_dir]);
|
||||
})
|
||||
gulp.parallel("clean-translations", () =>
|
||||
del([paths.cast_output_root, paths.build_dir])
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("clean-hassio", function cleanOutputAndBuildDir() {
|
||||
return del([paths.hassio_output_root, paths.build_dir]);
|
||||
});
|
||||
gulp.task("clean-hassio", () =>
|
||||
del([paths.hassio_output_root, paths.build_dir])
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-gallery",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([paths.gallery_output_root, paths.build_dir]);
|
||||
})
|
||||
gulp.parallel("clean-translations", () =>
|
||||
del([paths.gallery_output_root, paths.build_dir])
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const crypto = require("crypto");
|
||||
const del = require("del");
|
||||
const path = require("path");
|
||||
@@ -26,13 +28,6 @@ gulp.task("translations-enable-merge-backend", (done) => {
|
||||
done();
|
||||
});
|
||||
|
||||
String.prototype.rsplit = function (sep, maxsplit) {
|
||||
var split = this.split(sep);
|
||||
return maxsplit
|
||||
? [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit))
|
||||
: split;
|
||||
};
|
||||
|
||||
// Panel translations which should be split from the core translations.
|
||||
const TRANSLATION_FRAGMENTS = Object.keys(
|
||||
require("../../src/translations/en.json").ui.panel
|
||||
@@ -40,7 +35,7 @@ const TRANSLATION_FRAGMENTS = Object.keys(
|
||||
|
||||
function recursiveFlatten(prefix, data) {
|
||||
let output = {};
|
||||
Object.keys(data).forEach(function (key) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (typeof data[key] === "object") {
|
||||
output = {
|
||||
...output,
|
||||
@@ -101,15 +96,19 @@ function lokaliseTransform(data, original, file) {
|
||||
if (value instanceof Object) {
|
||||
output[key] = lokaliseTransform(value, original, file);
|
||||
} else {
|
||||
output[key] = value.replace(re_key_reference, (match, key) => {
|
||||
const replace = key.split("::").reduce((tr, k) => {
|
||||
output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
|
||||
const replace = lokalise_key.split("::").reduce((tr, k) => {
|
||||
if (!tr) {
|
||||
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
|
||||
throw Error(
|
||||
`Invalid key placeholder ${lokalise_key} in ${file.path}`
|
||||
);
|
||||
}
|
||||
return tr[k];
|
||||
}, original);
|
||||
if (typeof replace !== "string") {
|
||||
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
|
||||
throw Error(
|
||||
`Invalid key placeholder ${lokalise_key} in ${file.path}`
|
||||
);
|
||||
}
|
||||
return replace;
|
||||
});
|
||||
@@ -118,9 +117,7 @@ function lokaliseTransform(data, original, file) {
|
||||
return output;
|
||||
}
|
||||
|
||||
gulp.task("clean-translations", function () {
|
||||
return del([workDir]);
|
||||
});
|
||||
gulp.task("clean-translations", () => del([workDir]));
|
||||
|
||||
gulp.task("ensure-translations-build-dir", (done) => {
|
||||
if (!fs.existsSync(workDir)) {
|
||||
@@ -129,7 +126,7 @@ gulp.task("ensure-translations-build-dir", (done) => {
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("create-test-metadata", function (cb) {
|
||||
gulp.task("create-test-metadata", (cb) => {
|
||||
fs.writeFile(
|
||||
workDir + "/testMetadata.json",
|
||||
JSON.stringify({
|
||||
@@ -143,17 +140,13 @@ gulp.task("create-test-metadata", function (cb) {
|
||||
|
||||
gulp.task(
|
||||
"create-test-translation",
|
||||
gulp.series("create-test-metadata", function createTestTranslation() {
|
||||
return gulp
|
||||
gulp.series("create-test-metadata", () =>
|
||||
gulp
|
||||
.src(path.join(paths.translations_src, "en.json"))
|
||||
.pipe(
|
||||
transform(function (data, file) {
|
||||
return recursiveEmpty(data);
|
||||
})
|
||||
)
|
||||
.pipe(transform((data, _file) => recursiveEmpty(data)))
|
||||
.pipe(rename("test.json"))
|
||||
.pipe(gulp.dest(workDir));
|
||||
})
|
||||
.pipe(gulp.dest(workDir))
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -165,7 +158,7 @@ gulp.task(
|
||||
* project is buildable immediately after merging new translation keys, since
|
||||
* the Lokalise update to translations/en.json will not happen immediately.
|
||||
*/
|
||||
gulp.task("build-master-translation", function () {
|
||||
gulp.task("build-master-translation", () => {
|
||||
const src = [path.join(paths.translations_src, "en.json")];
|
||||
|
||||
if (mergeBackend) {
|
||||
@@ -174,11 +167,7 @@ gulp.task("build-master-translation", function () {
|
||||
|
||||
return gulp
|
||||
.src(src)
|
||||
.pipe(
|
||||
transform(function (data, file) {
|
||||
return lokaliseTransform(data, data, file);
|
||||
})
|
||||
)
|
||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||
.pipe(
|
||||
merge({
|
||||
fileName: "translationMaster.json",
|
||||
@@ -187,18 +176,14 @@ gulp.task("build-master-translation", function () {
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
|
||||
gulp.task("build-merged-translations", function () {
|
||||
return gulp
|
||||
gulp.task("build-merged-translations", () =>
|
||||
gulp
|
||||
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
|
||||
allowEmpty: true,
|
||||
})
|
||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||
.pipe(
|
||||
transform(function (data, file) {
|
||||
return lokaliseTransform(data, data, file);
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
foreach(function (stream, file) {
|
||||
foreach((stream, file) => {
|
||||
// For each language generate a merged json file. It begins with the master
|
||||
// translation as a failsafe for untranslated strings, and merges all parent
|
||||
// tags into one file for each specific subtag
|
||||
@@ -230,17 +215,17 @@ gulp.task("build-merged-translations", function () {
|
||||
)
|
||||
.pipe(gulp.dest(fullDir));
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
|
||||
var taskName;
|
||||
let taskName;
|
||||
|
||||
const splitTasks = [];
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
taskName = "build-translation-fragment-" + fragment;
|
||||
gulp.task(taskName, function () {
|
||||
gulp.task(taskName, () =>
|
||||
// Return only the translations for this fragment.
|
||||
return gulp
|
||||
gulp
|
||||
.src(fullDir + "/*.json")
|
||||
.pipe(
|
||||
transform((data) => ({
|
||||
@@ -251,18 +236,18 @@ TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
},
|
||||
}))
|
||||
)
|
||||
.pipe(gulp.dest(workDir + "/" + fragment));
|
||||
});
|
||||
.pipe(gulp.dest(workDir + "/" + fragment))
|
||||
);
|
||||
splitTasks.push(taskName);
|
||||
});
|
||||
|
||||
taskName = "build-translation-core";
|
||||
gulp.task(taskName, function () {
|
||||
gulp.task(taskName, () =>
|
||||
// Remove the fragment translations from the core translation.
|
||||
return gulp
|
||||
gulp
|
||||
.src(fullDir + "/*.json")
|
||||
.pipe(
|
||||
transform((data, file) => {
|
||||
transform((data, _file) => {
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
delete data.ui.panel[fragment];
|
||||
});
|
||||
@@ -270,14 +255,14 @@ gulp.task(taskName, function () {
|
||||
return data;
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(coreDir));
|
||||
});
|
||||
.pipe(gulp.dest(coreDir))
|
||||
);
|
||||
|
||||
splitTasks.push(taskName);
|
||||
|
||||
gulp.task("build-flattened-translations", function () {
|
||||
gulp.task("build-flattened-translations", () =>
|
||||
// Flatten the split versions of our translations, and move them into outDir
|
||||
return gulp
|
||||
gulp
|
||||
.src(
|
||||
TRANSLATION_FRAGMENTS.map(
|
||||
(fragment) => workDir + "/" + fragment + "/*.json"
|
||||
@@ -285,41 +270,45 @@ gulp.task("build-flattened-translations", function () {
|
||||
{ base: workDir }
|
||||
)
|
||||
.pipe(
|
||||
transform(function (data) {
|
||||
transform((data) =>
|
||||
// Polymer.AppLocalizeBehavior requires flattened json
|
||||
return flatten(data);
|
||||
})
|
||||
flatten(data)
|
||||
)
|
||||
)
|
||||
.pipe(
|
||||
rename((filePath) => {
|
||||
if (filePath.dirname === "core") {
|
||||
filePath.dirname = "";
|
||||
}
|
||||
// In dev we create the file with the fake hash in the filename
|
||||
if (!env.isProdBuild()) {
|
||||
filePath.basename += "-dev";
|
||||
}
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(outDir));
|
||||
});
|
||||
.pipe(gulp.dest(outDir))
|
||||
);
|
||||
|
||||
const fingerprints = {};
|
||||
|
||||
gulp.task(
|
||||
"build-translation-fingerprints",
|
||||
function fingerprintTranslationFiles() {
|
||||
// Fingerprint full file of each language
|
||||
const files = fs.readdirSync(fullDir);
|
||||
gulp.task("build-translation-fingerprints", () => {
|
||||
// Fingerprint full file of each language
|
||||
const files = fs.readdirSync(fullDir);
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
fingerprints[files[i].split(".")[0]] = {
|
||||
// In dev we create fake hashes
|
||||
hash: env.isProdBuild()
|
||||
? crypto
|
||||
.createHash("md5")
|
||||
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
|
||||
.digest("hex")
|
||||
: "dev",
|
||||
};
|
||||
}
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
fingerprints[files[i].split(".")[0]] = {
|
||||
// In dev we create fake hashes
|
||||
hash: env.isProdBuild()
|
||||
? crypto
|
||||
.createHash("md5")
|
||||
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
|
||||
.digest("hex")
|
||||
: "dev",
|
||||
};
|
||||
}
|
||||
|
||||
// In dev we create the file with the fake hash in the filename
|
||||
if (env.isProdBuild()) {
|
||||
mapFiles(outDir, ".json", (filename) => {
|
||||
const parsed = path.parse(filename);
|
||||
|
||||
@@ -335,35 +324,35 @@ gulp.task(
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
const stream = source("translationFingerprints.json");
|
||||
stream.write(JSON.stringify(fingerprints));
|
||||
process.nextTick(() => stream.end());
|
||||
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
|
||||
}
|
||||
);
|
||||
|
||||
gulp.task("build-translation-fragment-supervisor", function () {
|
||||
return gulp
|
||||
const stream = source("translationFingerprints.json");
|
||||
stream.write(JSON.stringify(fingerprints));
|
||||
process.nextTick(() => stream.end());
|
||||
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
|
||||
});
|
||||
|
||||
gulp.task("build-translation-fragment-supervisor", () =>
|
||||
gulp
|
||||
.src(fullDir + "/*.json")
|
||||
.pipe(transform((data) => data.supervisor))
|
||||
.pipe(gulp.dest(workDir + "/supervisor"));
|
||||
});
|
||||
.pipe(gulp.dest(workDir + "/supervisor"))
|
||||
);
|
||||
|
||||
gulp.task("build-translation-flatten-supervisor", function () {
|
||||
return gulp
|
||||
gulp.task("build-translation-flatten-supervisor", () =>
|
||||
gulp
|
||||
.src(workDir + "/supervisor/*.json")
|
||||
.pipe(
|
||||
transform(function (data) {
|
||||
transform((data) =>
|
||||
// Polymer.AppLocalizeBehavior requires flattened json
|
||||
return flatten(data);
|
||||
})
|
||||
flatten(data)
|
||||
)
|
||||
)
|
||||
.pipe(gulp.dest(outDir));
|
||||
});
|
||||
.pipe(gulp.dest(outDir))
|
||||
);
|
||||
|
||||
gulp.task("build-translation-write-metadata", function writeMetadata() {
|
||||
return gulp
|
||||
gulp.task("build-translation-write-metadata", () =>
|
||||
gulp
|
||||
.src(
|
||||
[
|
||||
path.join(paths.translations_src, "translationMetadata.json"),
|
||||
@@ -374,13 +363,14 @@ gulp.task("build-translation-write-metadata", function writeMetadata() {
|
||||
)
|
||||
.pipe(merge({}))
|
||||
.pipe(
|
||||
transform(function (data) {
|
||||
transform((data) => {
|
||||
const newData = {};
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
// Filter out translations without native name.
|
||||
if (value.nativeName) {
|
||||
newData[key] = value;
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`Skipping language ${key}. Native name was not translated.`
|
||||
);
|
||||
@@ -396,19 +386,26 @@ gulp.task("build-translation-write-metadata", function writeMetadata() {
|
||||
}))
|
||||
)
|
||||
.pipe(rename("translationMetadata.json"))
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
.pipe(gulp.dest(workDir))
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"create-translations",
|
||||
gulp.series(
|
||||
env.isProdBuild() ? (done) => done() : "create-test-translation",
|
||||
"build-master-translation",
|
||||
"build-merged-translations",
|
||||
gulp.parallel(...splitTasks),
|
||||
"build-flattened-translations"
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"build-translations",
|
||||
gulp.series(
|
||||
"clean-translations",
|
||||
"ensure-translations-build-dir",
|
||||
env.isProdBuild() ? (done) => done() : "create-test-translation",
|
||||
"build-master-translation",
|
||||
"build-merged-translations",
|
||||
gulp.parallel(...splitTasks),
|
||||
"build-flattened-translations",
|
||||
"create-translations",
|
||||
"build-translation-fingerprints",
|
||||
"build-translation-write-metadata"
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
// Tasks to run webpack.
|
||||
const fs = require("fs");
|
||||
const gulp = require("gulp");
|
||||
@@ -44,7 +45,7 @@ const runDevServer = ({
|
||||
open: true,
|
||||
watchContentBase: true,
|
||||
contentBase,
|
||||
}).listen(port, listenHost, function (err) {
|
||||
}).listen(port, listenHost, (err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
@@ -65,6 +66,7 @@ const doneHandler = (done) => (err, stats) => {
|
||||
}
|
||||
|
||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(stats.toString("minimal"));
|
||||
}
|
||||
|
||||
@@ -90,16 +92,10 @@ gulp.task("webpack-watch-app", () => {
|
||||
process.env.ES5
|
||||
? bothBuilds(createAppConfig, { isProdBuild: false })
|
||||
: createAppConfig({ isProdBuild: false, latestBuild: true })
|
||||
).watch(
|
||||
{
|
||||
ignored: /build-translations/,
|
||||
poll: isWsl,
|
||||
},
|
||||
doneHandler()
|
||||
);
|
||||
).watch({ poll: isWsl }, doneHandler());
|
||||
gulp.watch(
|
||||
path.join(paths.translations_src, "en.json"),
|
||||
gulp.series("build-translations", "copy-translations-app")
|
||||
gulp.series("create-translations", "copy-translations-app")
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -23,7 +23,6 @@ import { mockTranslations } from "./stubs/translations";
|
||||
import { mockEnergy } from "./stubs/energy";
|
||||
import { mockConfig } from "./stubs/config";
|
||||
import { energyEntities } from "./stubs/entities";
|
||||
import { mockForecastSolar } from "./stubs/forecast_solar";
|
||||
|
||||
class HaDemo extends HomeAssistantAppEl {
|
||||
protected async _initializeHass() {
|
||||
@@ -52,7 +51,6 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
mockMediaPlayer(hass);
|
||||
mockFrontend(hass);
|
||||
mockEnergy(hass);
|
||||
mockForecastSolar(hass);
|
||||
mockConfig(hass);
|
||||
mockPersistentNotification(hass);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { format, startOfToday, startOfTomorrow } from "date-fns";
|
||||
import { EnergySolarForecasts } from "../../../src/data/energy";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
@@ -44,11 +46,11 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
stat_energy_from: "sensor.solar_production",
|
||||
config_entry_solar_forecast: ["solar_forecast"],
|
||||
},
|
||||
{
|
||||
/* {
|
||||
type: "battery",
|
||||
stat_energy_from: "sensor.battery_output",
|
||||
stat_energy_to: "sensor.battery_input",
|
||||
},
|
||||
}, */
|
||||
{
|
||||
type: "gas",
|
||||
stat_energy_from: "sensor.energy_gas",
|
||||
@@ -80,4 +82,53 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
],
|
||||
}));
|
||||
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
|
||||
const todayString = format(startOfToday(), "yyyy-MM-dd");
|
||||
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
|
||||
hass.mockWS(
|
||||
"energy/solar_forecast",
|
||||
(): EnergySolarForecasts => ({
|
||||
solar_forecast: {
|
||||
wh_hours: {
|
||||
[`${todayString}T06:00:00`]: 0,
|
||||
[`${todayString}T06:23:00`]: 6,
|
||||
[`${todayString}T06:45:00`]: 39,
|
||||
[`${todayString}T07:00:00`]: 28,
|
||||
[`${todayString}T08:00:00`]: 208,
|
||||
[`${todayString}T09:00:00`]: 352,
|
||||
[`${todayString}T10:00:00`]: 544,
|
||||
[`${todayString}T11:00:00`]: 748,
|
||||
[`${todayString}T12:00:00`]: 1259,
|
||||
[`${todayString}T13:00:00`]: 1361,
|
||||
[`${todayString}T14:00:00`]: 1373,
|
||||
[`${todayString}T15:00:00`]: 1370,
|
||||
[`${todayString}T16:00:00`]: 1186,
|
||||
[`${todayString}T17:00:00`]: 937,
|
||||
[`${todayString}T18:00:00`]: 652,
|
||||
[`${todayString}T19:00:00`]: 370,
|
||||
[`${todayString}T20:00:00`]: 155,
|
||||
[`${todayString}T21:48:00`]: 24,
|
||||
[`${todayString}T22:36:00`]: 0,
|
||||
[`${tomorrowString}T06:01:00`]: 0,
|
||||
[`${tomorrowString}T06:23:00`]: 9,
|
||||
[`${tomorrowString}T06:45:00`]: 47,
|
||||
[`${tomorrowString}T07:00:00`]: 48,
|
||||
[`${tomorrowString}T08:00:00`]: 473,
|
||||
[`${tomorrowString}T09:00:00`]: 827,
|
||||
[`${tomorrowString}T10:00:00`]: 1153,
|
||||
[`${tomorrowString}T11:00:00`]: 1413,
|
||||
[`${tomorrowString}T12:00:00`]: 1590,
|
||||
[`${tomorrowString}T13:00:00`]: 1652,
|
||||
[`${tomorrowString}T14:00:00`]: 1612,
|
||||
[`${tomorrowString}T15:00:00`]: 1438,
|
||||
[`${tomorrowString}T16:00:00`]: 1149,
|
||||
[`${tomorrowString}T17:00:00`]: 830,
|
||||
[`${tomorrowString}T18:00:00`]: 542,
|
||||
[`${tomorrowString}T19:00:00`]: 311,
|
||||
[`${tomorrowString}T20:00:00`]: 140,
|
||||
[`${tomorrowString}T21:47:00`]: 22,
|
||||
[`${tomorrowString}T22:34:00`]: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { format, startOfToday, startOfTomorrow } from "date-fns";
|
||||
import { ForecastSolarForecast } from "../../../src/data/forecast_solar";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockForecastSolar = (hass: MockHomeAssistant) => {
|
||||
const todayString = format(startOfToday(), "yyyy-MM-dd");
|
||||
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
|
||||
hass.mockWS(
|
||||
"forecast_solar/forecasts",
|
||||
(): Record<string, ForecastSolarForecast> => ({
|
||||
solar_forecast: {
|
||||
wh_hours: {
|
||||
[`${todayString}T06:00:00`]: 0,
|
||||
[`${todayString}T06:23:00`]: 6,
|
||||
[`${todayString}T06:45:00`]: 39,
|
||||
[`${todayString}T07:00:00`]: 28,
|
||||
[`${todayString}T08:00:00`]: 208,
|
||||
[`${todayString}T09:00:00`]: 352,
|
||||
[`${todayString}T10:00:00`]: 544,
|
||||
[`${todayString}T11:00:00`]: 748,
|
||||
[`${todayString}T12:00:00`]: 1259,
|
||||
[`${todayString}T13:00:00`]: 1361,
|
||||
[`${todayString}T14:00:00`]: 1373,
|
||||
[`${todayString}T15:00:00`]: 1370,
|
||||
[`${todayString}T16:00:00`]: 1186,
|
||||
[`${todayString}T17:00:00`]: 937,
|
||||
[`${todayString}T18:00:00`]: 652,
|
||||
[`${todayString}T19:00:00`]: 370,
|
||||
[`${todayString}T20:00:00`]: 155,
|
||||
[`${todayString}T21:48:00`]: 24,
|
||||
[`${todayString}T22:36:00`]: 0,
|
||||
[`${tomorrowString}T06:01:00`]: 0,
|
||||
[`${tomorrowString}T06:23:00`]: 9,
|
||||
[`${tomorrowString}T06:45:00`]: 47,
|
||||
[`${tomorrowString}T07:00:00`]: 48,
|
||||
[`${tomorrowString}T08:00:00`]: 473,
|
||||
[`${tomorrowString}T09:00:00`]: 827,
|
||||
[`${tomorrowString}T10:00:00`]: 1153,
|
||||
[`${tomorrowString}T11:00:00`]: 1413,
|
||||
[`${tomorrowString}T12:00:00`]: 1590,
|
||||
[`${tomorrowString}T13:00:00`]: 1652,
|
||||
[`${tomorrowString}T14:00:00`]: 1612,
|
||||
[`${tomorrowString}T15:00:00`]: 1438,
|
||||
[`${tomorrowString}T16:00:00`]: 1149,
|
||||
[`${tomorrowString}T17:00:00`]: 830,
|
||||
[`${tomorrowString}T18:00:00`]: 542,
|
||||
[`${tomorrowString}T19:00:00`]: 311,
|
||||
[`${tomorrowString}T20:00:00`]: 140,
|
||||
[`${tomorrowString}T21:47:00`]: 22,
|
||||
[`${tomorrowString}T22:34:00`]: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-card";
|
||||
|
||||
const alerts: {
|
||||
title?: string;
|
||||
description: string | TemplateResult;
|
||||
type: "info" | "warning" | "error" | "success";
|
||||
dismissable?: boolean;
|
||||
action?: string;
|
||||
rtl?: boolean;
|
||||
}[] = [
|
||||
{
|
||||
title: "Test info alert",
|
||||
description: "This is a test info alert with a title and description",
|
||||
type: "info",
|
||||
},
|
||||
{
|
||||
title: "Test warning alert",
|
||||
description: "This is a test warning alert with a title and description",
|
||||
type: "warning",
|
||||
},
|
||||
{
|
||||
title: "Test error alert",
|
||||
description: "This is a test error alert with a title and description",
|
||||
type: "error",
|
||||
},
|
||||
{
|
||||
title: "Test warning with long string",
|
||||
description:
|
||||
"sensor.lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum",
|
||||
type: "warning",
|
||||
},
|
||||
{
|
||||
title: "Test success alert",
|
||||
description: "This is a test success alert with a title and description",
|
||||
type: "success",
|
||||
},
|
||||
{
|
||||
description: "This is a test info alert with description only",
|
||||
type: "info",
|
||||
},
|
||||
{
|
||||
description:
|
||||
"This is a test warning alert with a rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really long description only",
|
||||
type: "warning",
|
||||
},
|
||||
{
|
||||
title: "Error with description and list",
|
||||
description: html`<p>
|
||||
This is a test error alert with a title, description and a list
|
||||
</p>
|
||||
<ul>
|
||||
<li>List item #1</li>
|
||||
<li>List item #2</li>
|
||||
<li>List item #3</li>
|
||||
</ul>`,
|
||||
type: "error",
|
||||
},
|
||||
{
|
||||
title: "Test dismissable alert",
|
||||
description: "This is a test success alert that can be dismissable",
|
||||
type: "success",
|
||||
dismissable: true,
|
||||
},
|
||||
{
|
||||
description: "Dismissable information",
|
||||
type: "info",
|
||||
dismissable: true,
|
||||
},
|
||||
{
|
||||
title: "Error with action",
|
||||
description: "This is a test error alert with action",
|
||||
type: "error",
|
||||
action: "restart",
|
||||
},
|
||||
{
|
||||
title: "Unsaved data",
|
||||
description: "You have unsaved data",
|
||||
type: "warning",
|
||||
action: "save",
|
||||
},
|
||||
{
|
||||
description: "Dismissable information (RTL)",
|
||||
type: "info",
|
||||
dismissable: true,
|
||||
rtl: true,
|
||||
},
|
||||
{
|
||||
title: "Error with action",
|
||||
description: "This is a test error alert with action (RTL)",
|
||||
type: "error",
|
||||
action: "restart",
|
||||
rtl: true,
|
||||
},
|
||||
{
|
||||
title: "Test success alert (RTL)",
|
||||
description: "This is a test success alert with a title and description",
|
||||
type: "success",
|
||||
rtl: true,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-ha-alert")
|
||||
export class DemoHaAlert extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card header="ha-alert demo">
|
||||
${alerts.map(
|
||||
(alert) => html`
|
||||
<ha-alert
|
||||
.title=${alert.title || ""}
|
||||
.alertType=${alert.type}
|
||||
.dismissable=${alert.dismissable || false}
|
||||
.actionText=${alert.action || ""}
|
||||
.rtl=${alert.rtl || false}
|
||||
>
|
||||
${alert.description}
|
||||
</ha-alert>
|
||||
`
|
||||
)}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
.condition {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
span {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-ha-alert": DemoHaAlert;
|
||||
}
|
||||
}
|
||||
@@ -172,6 +172,14 @@ class HaGallery extends PolymerElement {
|
||||
this.$.notifications.showDialog({ message: ev.detail.message })
|
||||
);
|
||||
|
||||
this.addEventListener("alert-dismissed-clicked", () =>
|
||||
this.$.notifications.showDialog({ message: "Alert dismissed clicked" })
|
||||
);
|
||||
|
||||
this.addEventListener("alert-action-clicked", () =>
|
||||
this.$.notifications.showDialog({ message: "Alert action clicked" })
|
||||
);
|
||||
|
||||
this.addEventListener("hass-more-info", (ev) => {
|
||||
if (ev.detail.entityId) {
|
||||
this.$.notifications.showDialog({
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
@@ -53,7 +54,9 @@ class HassioAddonAudio extends LitElement {
|
||||
.header=${this.supervisor.localize("addon.configuration.audio.header")}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<paper-dropdown-menu
|
||||
.label=${this.supervisor.localize(
|
||||
@@ -117,10 +120,6 @@ class HassioAddonAudio extends LitElement {
|
||||
paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
paper-item {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/ha-form";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
@@ -135,17 +136,19 @@ class HassioAddonConfig extends LitElement {
|
||||
@value-changed=${this._configChanged}
|
||||
.yamlSchema=${ADDON_YAML_SCHEMA}
|
||||
></ha-yaml-editor>`}
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${!this._yamlMode ||
|
||||
(this._canShowSchema && this.addon.schema) ||
|
||||
this._valid
|
||||
? ""
|
||||
: html`
|
||||
<div class="errors">
|
||||
<ha-alert alert-type="error">
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.options.invalid_yaml"
|
||||
)}
|
||||
</div>
|
||||
</ha-alert>
|
||||
`}
|
||||
</div>
|
||||
${hasHiddenOptions
|
||||
@@ -324,13 +327,7 @@ class HassioAddonConfig extends LitElement {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-top: 16px;
|
||||
}
|
||||
.syntaxerror {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.card-menu {
|
||||
float: right;
|
||||
z-index: 3;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
@@ -62,7 +63,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
@@ -225,10 +228,6 @@ class HassioAddonNetwork extends LitElement {
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "../../../../src/components/ha-card";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -38,7 +39,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
return html`
|
||||
<div class="content">
|
||||
<ha-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<ha-markdown .content=${this._content}></ha-markdown>`
|
||||
|
||||
@@ -23,6 +23,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../../src/common/navigate";
|
||||
import "../../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-label-badge";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
@@ -143,14 +144,14 @@ class HassioAddonInfo extends LitElement {
|
||||
this.addon.arch
|
||||
)
|
||||
? html`
|
||||
<p class="warning">
|
||||
<ha-alert alert-type="warning">
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_arch"
|
||||
)}
|
||||
</p>
|
||||
</ha-alert>
|
||||
`
|
||||
: html`
|
||||
<p class="warning">
|
||||
<ha-alert alert-type="warning">
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_arch",
|
||||
"core_version_installed",
|
||||
@@ -158,7 +159,7 @@ class HassioAddonInfo extends LitElement {
|
||||
"core_version_needed",
|
||||
addonStoreInfo.homeassistant
|
||||
)}
|
||||
</p>
|
||||
</ha-alert>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -569,21 +570,23 @@ class HassioAddonInfo extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${!this.addon.version && addonStoreInfo && !this.addon.available
|
||||
? !addonArchIsSupported(
|
||||
this.supervisor.info.supported_arch,
|
||||
this.addon.arch
|
||||
)
|
||||
? html`
|
||||
<p class="warning">
|
||||
<ha-alert alert-type="warning">
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_arch"
|
||||
)}
|
||||
</p>
|
||||
</ha-alert>
|
||||
`
|
||||
: html`
|
||||
<p class="warning">
|
||||
<ha-alert alert-type="warning">
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_version",
|
||||
"core_version_installed",
|
||||
@@ -591,7 +594,7 @@ class HassioAddonInfo extends LitElement {
|
||||
"core_version_needed",
|
||||
addonStoreInfo!.homeassistant
|
||||
)}
|
||||
</p>
|
||||
</ha-alert>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
fetchHassioAddonLogs,
|
||||
@@ -34,7 +35,9 @@ class HassioAddonLogs extends LitElement {
|
||||
return html`
|
||||
<h1>${this.addon.name}</h1>
|
||||
<ha-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<hassio-ansi-to-html
|
||||
@@ -60,10 +63,6 @@ class HassioAddonLogs extends LitElement {
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import { compare } from "../../../src/common/string/compare";
|
||||
import { stringCompare } from "../../../src/common/string/compare";
|
||||
import "../../../src/components/ha-card";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
@@ -33,7 +33,7 @@ class HassioAddons extends LitElement {
|
||||
</ha-card>
|
||||
`
|
||||
: this.supervisor.supervisor.addons
|
||||
.sort((a, b) => compare(a.name, b.name))
|
||||
.sort((a, b) => stringCompare(a.name, b.name))
|
||||
.map(
|
||||
(addon) => html`
|
||||
<ha-card .addon=${addon} @click=${this._addonTapped}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { slugify } from "../../../../src/common/string/slugify";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
import "../../../../src/components/ha-header-bar";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
@@ -89,7 +90,9 @@ class HassioBackupDialog
|
||||
.localize=${this._dialogParams.localize}
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<mwc-button
|
||||
.disabled=${this._restoringBackup}
|
||||
@@ -280,9 +283,9 @@ class HassioBackupDialog
|
||||
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/remove`
|
||||
? `backups/${this._backup!.slug}`
|
||||
: `snapshots/${this._backup!.slug}/remove`
|
||||
}`
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
@@ -62,7 +63,9 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||
${this._dialogParams.supervisor.localize("common.close")}
|
||||
</mwc-button>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/common/search/search-input";
|
||||
import { compare } from "../../../../src/common/string/compare";
|
||||
import { stringCompare } from "../../../../src/common/string/compare";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-expansion-panel";
|
||||
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
||||
@@ -27,7 +27,7 @@ const _filterDevices = memoizeOne(
|
||||
.toLocaleLowerCase()
|
||||
.includes(filter))
|
||||
)
|
||||
.sort((a, b) => compare(a.name, b.name))
|
||||
.sort((a, b) => stringCompare(a.name, b.name))
|
||||
);
|
||||
|
||||
@customElement("dialog-hassio-hardware")
|
||||
|
||||
@@ -10,6 +10,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { cache } from "lit/directives/cache";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-expansion-panel";
|
||||
@@ -251,9 +252,9 @@ export class DialogHassioNetwork
|
||||
`
|
||||
: ""}
|
||||
${this._dirty
|
||||
? html`<div class="warning">
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.supervisor.localize("dialog.network.warning")}
|
||||
</div>`
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
|
||||
@@ -9,6 +9,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
@@ -75,7 +76,9 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
this._dialogParams!.supervisor.localize("dialog.repositories.title")
|
||||
)}
|
||||
>
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<div class="form">
|
||||
${repositories.length
|
||||
? repositories.map(
|
||||
|
||||
@@ -2,6 +2,7 @@ import "@material/mwc-button/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
@@ -128,7 +129,9 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
this._dialogParams.name
|
||||
)}
|
||||
</p>`}
|
||||
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
@@ -19,6 +18,7 @@ import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
configSyncOS,
|
||||
dataDiskMove,
|
||||
rebootHost,
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
@@ -180,21 +180,27 @@ class HassioHostInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleMenuAction}
|
||||
>
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<mwc-icon-button slot="trigger">
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item>
|
||||
<mwc-list-item @click=${() => this._handleMenuAction("hardware")}>
|
||||
${this.supervisor.localize("system.host.hardware")}
|
||||
</mwc-list-item>
|
||||
${this.supervisor.host.features.includes("haos")
|
||||
? html`<mwc-list-item>
|
||||
? html`<mwc-list-item
|
||||
@click=${() => this._handleMenuAction("import_from_usb")}
|
||||
>
|
||||
${this.supervisor.localize("system.host.import_from_usb")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${this.supervisor.host.features.includes("agent")
|
||||
? html`<mwc-list-item
|
||||
@click=${() => this._handleMenuAction("data_disk_move")}
|
||||
>
|
||||
${this.supervisor.localize("system.host.data_disk_move")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -216,14 +222,17 @@ class HassioHostInfo extends LitElement {
|
||||
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
|
||||
});
|
||||
|
||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
private async _handleMenuAction(action: string) {
|
||||
switch (action) {
|
||||
case "hardware":
|
||||
await this._showHardware();
|
||||
break;
|
||||
case 1:
|
||||
case "import_from_usb":
|
||||
await this._importFromUSB();
|
||||
break;
|
||||
case "data_disk_move":
|
||||
await this._dataDiskMove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +404,34 @@ class HassioHostInfo extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _dataDiskMove(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.supervisor.localize("system.host.data_disk_move"),
|
||||
text: html`${this.supervisor.localize(
|
||||
"dialog.data_disk_move.description",
|
||||
{ current_path: this.supervisor.os.data_disk }
|
||||
)} <br /><br />${this.supervisor.localize(
|
||||
"dialog.data_disk_move.confirm_text"
|
||||
)}`,
|
||||
confirmText: this.supervisor.localize("dialog.data_disk_move.move"),
|
||||
dismissText: this.supervisor.localize("dialog.data_disk_move.cancel"),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await dataDiskMove(this.hass);
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor.localize("system.host.failed_to_move"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
fireEvent(this, "supervisor-collection-refresh", {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import "../../../src/components/ha-switch";
|
||||
@@ -170,31 +171,25 @@ class HassioSupervisorInfo extends LitElement {
|
||||
></ha-switch>
|
||||
</ha-settings-row>`
|
||||
: ""
|
||||
: html`<div class="error">
|
||||
: html`<ha-alert
|
||||
alert-type="warning"
|
||||
.actionText=${this.supervisor.localize("common.learn_more")}
|
||||
@alert-action-clicked=${this._unsupportedDialog}
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.unsupported_title"
|
||||
)}
|
||||
<button
|
||||
class="link"
|
||||
.title=${this.supervisor.localize("common.learn_more")}
|
||||
@click=${this._unsupportedDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>`}
|
||||
</ha-alert>`}
|
||||
${!this.supervisor.supervisor.healthy
|
||||
? html`<div class="error">
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.actionText=${this.supervisor.localize("common.learn_more")}
|
||||
@alert-action-clicked=${this._unhealthyDialog}
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.unhealthy_title"
|
||||
)}
|
||||
<button
|
||||
class="link"
|
||||
.title=${this.supervisor.localize("common.learn_more")}
|
||||
@click=${this._unhealthyDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>`
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="metrics-block">
|
||||
|
||||
@@ -5,6 +5,7 @@ import "@polymer/paper-listbox/paper-listbox";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-card";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||
@@ -67,7 +68,9 @@ class HassioSupervisorLog extends LitElement {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-dropdown-menu
|
||||
@@ -154,10 +157,6 @@ class HassioSupervisorLog extends LitElement {
|
||||
padding: 0 2%;
|
||||
width: 96%;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
+45
-44
@@ -22,26 +22,26 @@
|
||||
"author": "Paulus Schoutsen <[email protected]> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.1",
|
||||
"@codemirror/commands": "^0.18.0",
|
||||
"@codemirror/gutter": "^0.18.0",
|
||||
"@codemirror/highlight": "^0.18.0",
|
||||
"@codemirror/history": "^0.18.0",
|
||||
"@codemirror/legacy-modes": "^0.18.0",
|
||||
"@codemirror/rectangular-selection": "^0.18.0",
|
||||
"@codemirror/search": "^0.18.0",
|
||||
"@codemirror/state": "^0.18.0",
|
||||
"@codemirror/stream-parser": "^0.18.0",
|
||||
"@codemirror/text": "^0.18.0",
|
||||
"@codemirror/view": "^0.18.0",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.5.10",
|
||||
"@formatjs/intl-locale": "^2.4.28",
|
||||
"@formatjs/intl-pluralrules": "^4.0.22",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
"@fullcalendar/core": "5.1.0",
|
||||
"@fullcalendar/daygrid": "5.1.0",
|
||||
"@fullcalendar/interaction": "5.1.0",
|
||||
"@fullcalendar/list": "5.1.0",
|
||||
"@braintree/sanitize-url": "^5.0.2",
|
||||
"@codemirror/commands": "^0.19.2",
|
||||
"@codemirror/gutter": "^0.19.1",
|
||||
"@codemirror/highlight": "^0.19.2",
|
||||
"@codemirror/history": "^0.19.0",
|
||||
"@codemirror/legacy-modes": "^0.19.0",
|
||||
"@codemirror/rectangular-selection": "^0.19.0",
|
||||
"@codemirror/search": "^0.19.0",
|
||||
"@codemirror/state": "^0.19.1",
|
||||
"@codemirror/stream-parser": "^0.19.1",
|
||||
"@codemirror/text": "^0.19.2",
|
||||
"@codemirror/view": "^0.19.4",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.7.3",
|
||||
"@formatjs/intl-locale": "^2.4.37",
|
||||
"@formatjs/intl-pluralrules": "^4.1.3",
|
||||
"@fullcalendar/common": "5.9.0",
|
||||
"@fullcalendar/core": "5.9.0",
|
||||
"@fullcalendar/daygrid": "5.9.0",
|
||||
"@fullcalendar/interaction": "5.9.0",
|
||||
"@fullcalendar/list": "5.9.0",
|
||||
"@lit-labs/virtualizer": "patch:@lit-labs/[email protected]#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
|
||||
"@material/chips": "12.0.0-canary.22d29cbb4.0",
|
||||
"@material/data-table": "12.0.0-canary.22d29cbb4.0",
|
||||
@@ -61,8 +61,8 @@
|
||||
"@material/mwc-tab": "0.22.1",
|
||||
"@material/mwc-tab-bar": "0.22.1",
|
||||
"@material/top-app-bar": "12.0.0-canary.22d29cbb4.0",
|
||||
"@mdi/js": "5.9.55",
|
||||
"@mdi/svg": "5.9.55",
|
||||
"@mdi/js": "6.1.95",
|
||||
"@mdi/svg": "6.1.95",
|
||||
"@polymer/app-layout": "^3.1.0",
|
||||
"@polymer/iron-flex-layout": "^3.0.1",
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
@@ -88,9 +88,9 @@
|
||||
"@polymer/paper-toast": "^3.0.1",
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.4.1",
|
||||
"@thomasloven/round-slider": "0.5.2",
|
||||
"@vaadin/vaadin-combo-box": "^20.0.1",
|
||||
"@vaadin/vaadin-date-picker": "^20.0.1",
|
||||
"@thomasloven/round-slider": "0.5.4",
|
||||
"@vaadin/vaadin-combo-box": "^20.0.4",
|
||||
"@vaadin/vaadin-date-picker": "^20.0.4",
|
||||
"@vibrant/color": "^3.2.1-alpha.1",
|
||||
"@vibrant/core": "^3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
|
||||
@@ -99,36 +99,35 @@
|
||||
"chart.js": "^3.3.2",
|
||||
"comlink": "^4.3.1",
|
||||
"core-js": "^3.15.2",
|
||||
"cropperjs": "^1.5.11",
|
||||
"date-fns": "^2.22.1",
|
||||
"cropperjs": "^1.5.12",
|
||||
"date-fns": "^2.23.0",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fecha": "^4.2.0",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.0.7",
|
||||
"hls.js": "^1.0.10",
|
||||
"home-assistant-js-websocket": "^5.11.1",
|
||||
"idb-keyval": "^5.0.5",
|
||||
"intl-messageformat": "^9.6.16",
|
||||
"idb-keyval": "^5.1.3",
|
||||
"intl-messageformat": "^9.9.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"leaflet": "^1.7.1",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
"lit": "^2.0.0-rc.2",
|
||||
"lit": "^2.0.0-rc.3",
|
||||
"lit-vaadin-helpers": "^0.1.3",
|
||||
"marked": "^2.0.5",
|
||||
"mdn-polyfills": "^5.16.0",
|
||||
"marked": "^3.0.2",
|
||||
"memoize-one": "^5.2.1",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "^0.3.1",
|
||||
"proxy-polyfill": "^0.3.2",
|
||||
"punycode": "^2.1.1",
|
||||
"qrcode": "^1.4.4",
|
||||
"regenerator-runtime": "^0.13.8",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sortablejs": "^1.10.2",
|
||||
"sortablejs": "^1.14.0",
|
||||
"superstruct": "^0.15.2",
|
||||
"tinykeys": "^1.1.3",
|
||||
"tsparticles": "^1.19.2",
|
||||
"tsparticles": "^1.34.0",
|
||||
"unfetch": "^4.1.0",
|
||||
"vis-data": "^7.1.2",
|
||||
"vis-network": "^8.5.4",
|
||||
@@ -164,12 +163,12 @@
|
||||
"@rollup/plugin-replace": "^2.3.2",
|
||||
"@types/chromecast-caf-receiver": "5.0.12",
|
||||
"@types/chromecast-caf-sender": "^1.0.3",
|
||||
"@types/js-yaml": "^4.0.1",
|
||||
"@types/leaflet": "^1.7.0",
|
||||
"@types/leaflet-draw": "^1.0.3",
|
||||
"@types/marked": "^2.0.3",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/sortablejs": "^1.10.6",
|
||||
"@types/js-yaml": "^4",
|
||||
"@types/leaflet": "^1",
|
||||
"@types/leaflet-draw": "^1",
|
||||
"@types/marked": "^2",
|
||||
"@types/mocha": "^8",
|
||||
"@types/sortablejs": "^1",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
||||
"@typescript-eslint/parser": "^4.28.3",
|
||||
@@ -233,8 +232,10 @@
|
||||
"resolutions": {
|
||||
"@polymer/polymer": "patch:@polymer/[email protected]#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"lit-html": "2.0.0-rc.3",
|
||||
"lit-element": "3.0.0-rc.2"
|
||||
"lit": "^2.0.0-rc.3",
|
||||
"lit-html": "2.0.0-rc.4",
|
||||
"lit-element": "3.0.0-rc.3",
|
||||
"@lit/reactive-element": "1.0.0-rc.3"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210813.0",
|
||||
version="20210911.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/frontend",
|
||||
author="The Home Assistant Authors",
|
||||
|
||||
@@ -8,10 +8,6 @@ import {
|
||||
AuthUrlSearchParams,
|
||||
fetchAuthProviders,
|
||||
} from "../data/auth";
|
||||
import {
|
||||
DiscoveryInformation,
|
||||
fetchDiscoveryInformation,
|
||||
} from "../data/discovery";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./ha-auth-flow";
|
||||
@@ -29,8 +25,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
@state() private _authProviders?: AuthProvider[];
|
||||
|
||||
@state() private _discovery?: DiscoveryInformation;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.translationFragment = "page-authorize";
|
||||
@@ -58,17 +52,14 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
// the name with a bold tag.
|
||||
const loggingInWith = document.createElement("div");
|
||||
loggingInWith.innerText = this.localize(
|
||||
this._discovery?.location_name
|
||||
? "ui.panel.page-authorize.logging_in_to_with"
|
||||
: "ui.panel.page-authorize.logging_in_with",
|
||||
"locationName",
|
||||
"LOCATION",
|
||||
"ui.panel.page-authorize.logging_in_with",
|
||||
"authProviderName",
|
||||
"NAME"
|
||||
);
|
||||
loggingInWith.innerHTML = loggingInWith.innerHTML
|
||||
.replace("**LOCATION**", `<b>${this._discovery?.location_name}</b>`)
|
||||
.replace("**NAME**", `<b>${this._authProvider!.name}</b>`);
|
||||
loggingInWith.innerHTML = loggingInWith.innerHTML.replace(
|
||||
"**NAME**",
|
||||
`<b>${this._authProvider!.name}</b>`
|
||||
);
|
||||
|
||||
const inactiveProviders = this._authProviders.filter(
|
||||
(prv) => prv !== this._authProvider
|
||||
@@ -108,7 +99,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchAuthProviders();
|
||||
this._fetchDiscoveryInfo();
|
||||
|
||||
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
applyThemesOnElement(
|
||||
@@ -144,10 +134,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchDiscoveryInfo() {
|
||||
this._discovery = await fetchDiscoveryInformation();
|
||||
}
|
||||
|
||||
private async _fetchAuthProviders() {
|
||||
// Fetch auth providers
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const COLORS = [
|
||||
"#377eb8",
|
||||
"#44739e",
|
||||
"#984ea3",
|
||||
"#00d2d5",
|
||||
"#ff7f00",
|
||||
|
||||
+12
-63
@@ -56,19 +56,31 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
};
|
||||
|
||||
export const FIXED_DEVICE_CLASS_ICONS = {
|
||||
aqi: "hass:air-filter",
|
||||
current: "hass:current-ac",
|
||||
carbon_dioxide: "mdi:molecule-co2",
|
||||
carbon_monoxide: "mdi:molecule-co",
|
||||
date: "hass:calendar",
|
||||
energy: "hass:lightning-bolt",
|
||||
gas: "hass:gas-cylinder",
|
||||
humidity: "hass:water-percent",
|
||||
illuminance: "hass:brightness-5",
|
||||
nitrogen_dioxide: "mdi:molecule",
|
||||
nitrogen_monoxide: "mdi:molecule",
|
||||
nitrous_oxide: "mdi:molecule",
|
||||
ozone: "mdi:molecule",
|
||||
temperature: "hass:thermometer",
|
||||
monetary: "mdi:cash",
|
||||
pm25: "mdi:molecule",
|
||||
pm1: "mdi:molecule",
|
||||
pm10: "mdi:molecule",
|
||||
pressure: "hass:gauge",
|
||||
power: "hass:flash",
|
||||
power_factor: "hass:angle-acute",
|
||||
signal_strength: "hass:wifi",
|
||||
sulphur_dioxide: "mdi:molecule",
|
||||
timestamp: "hass:clock",
|
||||
volatile_organic_compounds: "mdi:molecule",
|
||||
voltage: "hass:sine-wave",
|
||||
};
|
||||
|
||||
@@ -157,66 +169,3 @@ export const UNIT_F = "°F";
|
||||
|
||||
/** Entity ID of the default view. */
|
||||
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
|
||||
|
||||
/** HA Color Pallete. */
|
||||
export const HA_COLOR_PALETTE = [
|
||||
"ff0029",
|
||||
"66a61e",
|
||||
"377eb8",
|
||||
"984ea3",
|
||||
"00d2d5",
|
||||
"ff7f00",
|
||||
"af8d00",
|
||||
"7f80cd",
|
||||
"b3e900",
|
||||
"c42e60",
|
||||
"a65628",
|
||||
"f781bf",
|
||||
"8dd3c7",
|
||||
"bebada",
|
||||
"fb8072",
|
||||
"80b1d3",
|
||||
"fdb462",
|
||||
"fccde5",
|
||||
"bc80bd",
|
||||
"ffed6f",
|
||||
"c4eaff",
|
||||
"cf8c00",
|
||||
"1b9e77",
|
||||
"d95f02",
|
||||
"e7298a",
|
||||
"e6ab02",
|
||||
"a6761d",
|
||||
"0097ff",
|
||||
"00d067",
|
||||
"f43600",
|
||||
"4ba93b",
|
||||
"5779bb",
|
||||
"927acc",
|
||||
"97ee3f",
|
||||
"bf3947",
|
||||
"9f5b00",
|
||||
"f48758",
|
||||
"8caed6",
|
||||
"f2b94f",
|
||||
"eff26e",
|
||||
"e43872",
|
||||
"d9b100",
|
||||
"9d7a00",
|
||||
"698cff",
|
||||
"d9d9d9",
|
||||
"00d27e",
|
||||
"d06800",
|
||||
"009f82",
|
||||
"c49200",
|
||||
"cbe8ff",
|
||||
"fecddf",
|
||||
"c27eb6",
|
||||
"8cd2ce",
|
||||
"c4b8d9",
|
||||
"f883b0",
|
||||
"a49100",
|
||||
"f48800",
|
||||
"27d0df",
|
||||
"a04a9b",
|
||||
];
|
||||
|
||||
@@ -16,7 +16,7 @@ const formatDateTimeMem = memoizeOne(
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
@@ -34,7 +34,7 @@ const formatDateTimeWithSecondsMem = memoizeOne(
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
|
||||
@@ -13,7 +13,7 @@ export const formatTime = toLocaleTimeStringSupportsOptions
|
||||
const formatTimeMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
hour: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
@@ -28,7 +28,7 @@ export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
||||
const formatTimeWithSecondsMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
hour: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
@@ -45,7 +45,7 @@ const formatTimeWeekdayMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
weekday: "long",
|
||||
hour: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
|
||||
@@ -22,8 +22,9 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
case "gas":
|
||||
case "problem":
|
||||
case "safety":
|
||||
case "smoke":
|
||||
return is_off ? "hass:check-circle" : "hass:alert-circle";
|
||||
case "smoke":
|
||||
return is_off ? "hass:check-circle" : "hass:smoke";
|
||||
case "heat":
|
||||
return is_off ? "hass:thermometer" : "hass:fire";
|
||||
case "light":
|
||||
@@ -44,6 +45,8 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
return is_off ? "hass:home-outline" : "hass:home";
|
||||
case "sound":
|
||||
return is_off ? "hass:music-note-off" : "hass:music-note";
|
||||
case "update":
|
||||
return is_off ? "mdi:package" : "mdi:package-up";
|
||||
case "vibration":
|
||||
return is_off ? "hass:crop-portrait" : "hass:vibrate";
|
||||
case "window":
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
|
||||
import { batteryIcon } from "./battery_icon";
|
||||
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
|
||||
|
||||
export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
|
||||
const dclass = stateObj?.attributes.device_class;
|
||||
@@ -10,7 +11,7 @@ export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
|
||||
return FIXED_DEVICE_CLASS_ICONS[dclass];
|
||||
}
|
||||
|
||||
if (dclass === "battery") {
|
||||
if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
|
||||
return stateObj ? batteryIcon(stateObj) : "hass:battery";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const compare = (a: string, b: string) => {
|
||||
export const stringCompare = (a: string, b: string) => {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
@@ -9,5 +9,5 @@ export const compare = (a: string, b: string) => {
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const caseInsensitiveCompare = (a: string, b: string) =>
|
||||
compare(a.toLowerCase(), b.toLowerCase());
|
||||
export const caseInsensitiveStringCompare = (a: string, b: string) =>
|
||||
stringCompare(a.toLowerCase(), b.toLowerCase());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { refine, string } from "superstruct";
|
||||
|
||||
const isCustomType = (value: string) => value.startsWith("custom:");
|
||||
export const isCustomType = (value: string) => value.startsWith("custom:");
|
||||
|
||||
export const customType = () =>
|
||||
refine(string(), "custom element type", isCustomType);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export const groupBy = <T>(
|
||||
list: T[],
|
||||
keySelector: (item: T) => string
|
||||
): { [key: string]: T[] } => {
|
||||
const result = {};
|
||||
for (const item of list) {
|
||||
const key = keySelector(item);
|
||||
if (key in result) {
|
||||
result[key].push(item);
|
||||
} else {
|
||||
result[key] = [item];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
@@ -61,6 +61,11 @@ export default class HaChartBase extends LitElement {
|
||||
this.chart.config.type = this.chartType;
|
||||
}
|
||||
if (changedProps.has("data")) {
|
||||
if (this._hiddenDatasets.size) {
|
||||
this.data.datasets.forEach((dataset, index) => {
|
||||
dataset.hidden = this._hiddenDatasets.has(index);
|
||||
});
|
||||
}
|
||||
this.chart.data = this.data;
|
||||
}
|
||||
if (changedProps.has("options")) {
|
||||
@@ -238,9 +243,19 @@ export default class HaChartBase extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
public updateChart = (): void => {
|
||||
public updateChart = (
|
||||
mode:
|
||||
| "resize"
|
||||
| "reset"
|
||||
| "none"
|
||||
| "hide"
|
||||
| "show"
|
||||
| "normal"
|
||||
| "active"
|
||||
| undefined
|
||||
): void => {
|
||||
if (this.chart) {
|
||||
this.chart.update();
|
||||
this.chart.update(mode);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export const createCurrencyListEl = () => {
|
||||
"BSD",
|
||||
"BTN",
|
||||
"BWP",
|
||||
"BYN",
|
||||
"BYR",
|
||||
"BZD",
|
||||
"CAD",
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface DataTableSortColumnData {
|
||||
sortable?: boolean;
|
||||
filterable?: boolean;
|
||||
filterKey?: string;
|
||||
valueColumn?: string;
|
||||
direction?: SortingDirection;
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@ export interface DataTableColumnData extends DataTableSortColumnData {
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
type ClonedDataTableColumnData = Omit<DataTableColumnData, "title"> & {
|
||||
export type ClonedDataTableColumnData = Omit<DataTableColumnData, "title"> & {
|
||||
title?: TemplateResult | string;
|
||||
};
|
||||
|
||||
@@ -455,7 +456,7 @@ export class HaDataTable extends LitElement {
|
||||
const prom = this._sortColumn
|
||||
? sortData(
|
||||
filteredData,
|
||||
this._sortColumns,
|
||||
this._sortColumns[this._sortColumn],
|
||||
this._sortDirection,
|
||||
this._sortColumn
|
||||
)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { expose } from "comlink";
|
||||
import "proxy-polyfill";
|
||||
import type {
|
||||
ClonedDataTableColumnData,
|
||||
DataTableRowData,
|
||||
DataTableSortColumnData,
|
||||
SortableColumnContainer,
|
||||
SortingDirection,
|
||||
} from "./ha-data-table";
|
||||
@@ -19,7 +19,11 @@ const filterData = (
|
||||
const [key, column] = columnEntry;
|
||||
if (column.filterable) {
|
||||
if (
|
||||
String(column.filterKey ? row[key][column.filterKey] : row[key])
|
||||
String(
|
||||
column.filterKey
|
||||
? row[column.valueColumn || key][column.filterKey]
|
||||
: row[column.valueColumn || key]
|
||||
)
|
||||
.toUpperCase()
|
||||
.includes(filter)
|
||||
) {
|
||||
@@ -33,7 +37,7 @@ const filterData = (
|
||||
|
||||
const sortData = (
|
||||
data: DataTableRowData[],
|
||||
column: DataTableSortColumnData,
|
||||
column: ClonedDataTableColumnData,
|
||||
direction: SortingDirection,
|
||||
sortColumn: string
|
||||
) =>
|
||||
@@ -44,12 +48,12 @@ const sortData = (
|
||||
}
|
||||
|
||||
let valA = column.filterKey
|
||||
? a[sortColumn][column.filterKey]
|
||||
: a[sortColumn];
|
||||
? a[column.valueColumn || sortColumn][column.filterKey]
|
||||
: a[column.valueColumn || sortColumn];
|
||||
|
||||
let valB = column.filterKey
|
||||
? b[sortColumn][column.filterKey]
|
||||
: b[sortColumn];
|
||||
? b[column.valueColumn || sortColumn][column.filterKey]
|
||||
: b[column.valueColumn || sortColumn];
|
||||
|
||||
if (typeof valA === "string") {
|
||||
valA = valA.toUpperCase();
|
||||
|
||||
@@ -20,7 +20,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
@@ -226,7 +226,10 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
const sorted = Object.keys(devicesByArea)
|
||||
.sort((a, b) =>
|
||||
compare(devicesByArea[a].name || "", devicesByArea[b].name || "")
|
||||
stringCompare(
|
||||
devicesByArea[a].name || "",
|
||||
devicesByArea[b].name || ""
|
||||
)
|
||||
)
|
||||
.map((key) => devicesByArea[key]);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
import { mdiCheck } from "@mdi/js";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
@@ -242,7 +242,9 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
if (outputDevices.length === 1) {
|
||||
return outputDevices;
|
||||
}
|
||||
return outputDevices.sort((a, b) => compare(a.name || "", b.name || ""));
|
||||
return outputDevices.sort((a, b) =>
|
||||
stringCompare(a.name || "", b.name || "")
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -106,19 +106,24 @@ export class HaStateLabelBadge extends LitElement {
|
||||
|
||||
private _computeValue(domain: string, entityState: HassEntity) {
|
||||
switch (domain) {
|
||||
case "alarm_control_panel":
|
||||
case "binary_sensor":
|
||||
case "device_tracker":
|
||||
case "person":
|
||||
case "updater":
|
||||
case "scene":
|
||||
case "sun":
|
||||
case "alarm_control_panel":
|
||||
case "timer":
|
||||
case "updater":
|
||||
return null;
|
||||
// @ts-expect-error we don't break and go to default
|
||||
case "sensor":
|
||||
if (entityState.attributes.device_class === "moon__phase") {
|
||||
return null;
|
||||
}
|
||||
// eslint-disable-next-line: disable=no-fallthrough
|
||||
default:
|
||||
return entityState.attributes.device_class === "moon__phase"
|
||||
? null
|
||||
: entityState.state === UNKNOWN
|
||||
return entityState.state === UNKNOWN ||
|
||||
entityState.state === UNAVAILABLE
|
||||
? "-"
|
||||
: entityState.attributes.unit_of_measurement
|
||||
? formatNumber(entityState.state, this.hass!.locale)
|
||||
@@ -160,16 +165,19 @@ export class HaStateLabelBadge extends LitElement {
|
||||
case "device_tracker":
|
||||
case "updater":
|
||||
case "person":
|
||||
case "scene":
|
||||
case "sun":
|
||||
return stateIcon(entityState);
|
||||
case "timer":
|
||||
return entityState.state === "active"
|
||||
? "hass:timer-outline"
|
||||
: "hass:timer-off-outline";
|
||||
default:
|
||||
return entityState?.attributes.device_class === "moon__phase"
|
||||
case "sensor":
|
||||
return entityState.attributes.device_class === "moon__phase"
|
||||
? stateIcon(entityState)
|
||||
: null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import { getStatisticIds, StatisticsMetaData } from "../../data/history";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@@ -165,7 +165,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
}
|
||||
|
||||
if (output.length > 1) {
|
||||
output.sort((a, b) => compare(a.name || "", b.name || ""));
|
||||
output.sort((a, b) => stringCompare(a.name || "", b.name || ""));
|
||||
}
|
||||
|
||||
output.push({
|
||||
|
||||
@@ -129,38 +129,39 @@ export class StateBadge extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
border-radius: 50%;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
background-size: cover;
|
||||
line-height: 40px;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
:host(:not([icon]):focus) {
|
||||
border: 2px solid var(--divider-color);
|
||||
}
|
||||
:host([icon]:focus) {
|
||||
background: var(--divider-color);
|
||||
}
|
||||
ha-icon {
|
||||
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
|
||||
}
|
||||
.missing {
|
||||
color: #fce588;
|
||||
}
|
||||
|
||||
${iconColorCSS}
|
||||
`;
|
||||
return [
|
||||
iconColorCSS,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
border-radius: 50%;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
background-size: cover;
|
||||
line-height: 40px;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
:host(:not([icon]):focus) {
|
||||
border: 2px solid var(--divider-color);
|
||||
}
|
||||
:host([icon]:focus) {
|
||||
background: var(--divider-color);
|
||||
}
|
||||
ha-icon {
|
||||
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
|
||||
}
|
||||
.missing {
|
||||
color: #fce588;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { HassioAddonInfo } from "../data/hassio/addon";
|
||||
import { fetchHassioSupervisorInfo } from "../data/hassio/supervisor";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
@@ -97,7 +97,7 @@ class HaAddonPicker extends LitElement {
|
||||
if (isComponentLoaded(this.hass, "hassio")) {
|
||||
const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
this._addons = supervisorInfo.addons.sort((a, b) =>
|
||||
compare(a.name, b.name)
|
||||
stringCompare(a.name, b.name)
|
||||
);
|
||||
} else {
|
||||
showAlertDialog(this, {
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiAlertOutline,
|
||||
mdiCheckboxMarkedCircleOutline,
|
||||
mdiClose,
|
||||
mdiInformationOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
const ALERT_ICONS = {
|
||||
info: mdiInformationOutline,
|
||||
warning: mdiAlertOutline,
|
||||
error: mdiAlertCircleOutline,
|
||||
success: mdiCheckboxMarkedCircleOutline,
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"alert-dismissed-clicked": undefined;
|
||||
"alert-action-clicked": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-alert")
|
||||
class HaAlert extends LitElement {
|
||||
@property() public title = "";
|
||||
|
||||
@property({ attribute: "alert-type" }) public alertType:
|
||||
| "info"
|
||||
| "warning"
|
||||
| "error"
|
||||
| "success" = "info";
|
||||
|
||||
@property({ attribute: "action-text" }) public actionText = "";
|
||||
|
||||
@property({ type: Boolean }) public dismissable = false;
|
||||
|
||||
@property({ type: Boolean }) public rtl = false;
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<div
|
||||
class="issue-type ${classMap({
|
||||
rtl: this.rtl,
|
||||
[this.alertType]: true,
|
||||
})}"
|
||||
>
|
||||
<div class="icon">
|
||||
<ha-svg-icon .path=${ALERT_ICONS[this.alertType]}></ha-svg-icon>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div
|
||||
class="main-content ${classMap({
|
||||
"no-title": !this.title,
|
||||
})}"
|
||||
>
|
||||
${this.title ? html`<div class="title">${this.title}</div>` : ""}
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="action">
|
||||
${this.actionText
|
||||
? html`<mwc-button
|
||||
@click=${this._action_clicked}
|
||||
.label=${this.actionText}
|
||||
></mwc-button>`
|
||||
: this.dismissable
|
||||
? html`<mwc-icon-button
|
||||
@click=${this._dismiss_clicked}
|
||||
aria-label="Dismiss alert"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}> </ha-svg-icon>
|
||||
</mwc-icon-button> `
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _dismiss_clicked() {
|
||||
fireEvent(this, "alert-dismissed-clicked");
|
||||
}
|
||||
|
||||
private _action_clicked() {
|
||||
fireEvent(this, "alert-action-clicked");
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.issue-type {
|
||||
position: relative;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
margin: 4px 0;
|
||||
}
|
||||
.issue-type.rtl {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.issue-type::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
border-radius: 4px;
|
||||
}
|
||||
.icon {
|
||||
margin: 4px 8px;
|
||||
width: 24px;
|
||||
}
|
||||
.main-content.no-title {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.issue-type.rtl > .content {
|
||||
flex-direction: row-reverse;
|
||||
text-align: right;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.main-content {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.title {
|
||||
font-weight: bold;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
mwc-button {
|
||||
--mdc-theme-primary: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.action {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.issue-type.info > .icon {
|
||||
color: var(--info-color);
|
||||
}
|
||||
.issue-type.info::before {
|
||||
background-color: var(--info-color);
|
||||
}
|
||||
|
||||
.issue-type.warning > .icon {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
.issue-type.warning::before {
|
||||
background-color: var(--warning-color);
|
||||
}
|
||||
|
||||
.issue-type.error > .icon {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.issue-type.error::before {
|
||||
background-color: var(--error-color);
|
||||
}
|
||||
|
||||
.issue-type.success > .icon {
|
||||
color: var(--success-color);
|
||||
}
|
||||
.issue-type.success::before {
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-alert": HaAlert;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
@@ -33,7 +33,7 @@ class HaBluePrintPicker extends LitElement {
|
||||
...(blueprint as Blueprint).metadata,
|
||||
path,
|
||||
}));
|
||||
return result.sort((a, b) => compare(a.name, b.name));
|
||||
return result.sort((a, b) => stringCompare(a.name, b.name));
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import type { Button } from "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, queryAll } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { ToggleButton } from "../types";
|
||||
@@ -17,6 +18,8 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public dense = false;
|
||||
|
||||
@queryAll("mwc-button") private _buttons?: Button[];
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div>
|
||||
@@ -48,6 +51,16 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated() {
|
||||
// Work around Safari default margin that is not reset in mwc-button as of aug 2021
|
||||
this._buttons?.forEach(async (button) => {
|
||||
await button.updateComplete;
|
||||
(
|
||||
button.shadowRoot!.querySelector("button") as HTMLButtonElement
|
||||
).style.margin = "0";
|
||||
});
|
||||
}
|
||||
|
||||
private _handleClick(ev): void {
|
||||
this.active = ev.currentTarget.value;
|
||||
fireEvent(this, "value-changed", { value: this.active });
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import {
|
||||
@@ -41,6 +40,32 @@ class HaCameraStream extends LitElement {
|
||||
|
||||
@state() private _url?: string;
|
||||
|
||||
@state() private _connected = false;
|
||||
|
||||
public willUpdate(changedProps: PropertyValues): void {
|
||||
if (
|
||||
changedProps.has("stateObj") &&
|
||||
!this._shouldRenderMJPEG &&
|
||||
this.stateObj &&
|
||||
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
|
||||
this.stateObj.entity_id
|
||||
) {
|
||||
this._forceMJPEG = undefined;
|
||||
this._url = undefined;
|
||||
this._getStreamUrl();
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._connected = true;
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._connected = false;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.stateObj) {
|
||||
return html``;
|
||||
@@ -50,10 +75,11 @@ class HaCameraStream extends LitElement {
|
||||
${__DEMO__ || this._shouldRenderMJPEG
|
||||
? html`
|
||||
<img
|
||||
@load=${this._elementResized}
|
||||
.src=${__DEMO__
|
||||
? this.stateObj!.attributes.entity_picture
|
||||
: computeMJPEGStreamUrl(this.stateObj)}
|
||||
? this.stateObj!.attributes.entity_picture!
|
||||
: this._connected
|
||||
? computeMJPEGStreamUrl(this.stateObj)
|
||||
: ""}
|
||||
.alt=${`Preview of the ${computeStateName(
|
||||
this.stateObj
|
||||
)} camera.`}
|
||||
@@ -75,13 +101,6 @@ class HaCameraStream extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("stateObj") && !this._shouldRenderMJPEG) {
|
||||
this._forceMJPEG = undefined;
|
||||
this._getStreamUrl();
|
||||
}
|
||||
}
|
||||
|
||||
private get _shouldRenderMJPEG() {
|
||||
return (
|
||||
this._forceMJPEG === this.stateObj!.entity_id ||
|
||||
@@ -107,10 +126,6 @@ class HaCameraStream extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _elementResized() {
|
||||
fireEvent(this, "iron-resize");
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host,
|
||||
|
||||
@@ -32,17 +32,19 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
||||
.autoValidate=${this.schema.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<span suffix="" slot="suffix">${this.suffix}</span>
|
||||
<span suffix slot="suffix">${this.suffix}</span>
|
||||
</paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.data || 0;
|
||||
return this.data;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const value = Number((ev.target as PaperInputElement).value);
|
||||
const value: number | undefined = (ev.target as PaperInputElement).value
|
||||
? Number((ev.target as PaperInputElement).value)
|
||||
: undefined;
|
||||
if (this._value === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,11 @@ class HaHLSPlayer extends LitElement {
|
||||
|
||||
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min"))
|
||||
.default;
|
||||
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hlsSupported = Hls.isSupported();
|
||||
|
||||
if (!hlsSupported) {
|
||||
@@ -115,6 +120,10 @@ class HaHLSPlayer extends LitElement {
|
||||
const useExoPlayer = await useExoPlayerPromise;
|
||||
const masterPlaylist = await (await masterPlaylistPromise).text();
|
||||
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse playlist assuming it is a master playlist. Match group 1 is whether hevc, match group 2 is regular playlist url
|
||||
// See https://tools.ietf.org/html/rfc8216 for HLS spec details
|
||||
const playlistRegexp =
|
||||
|
||||
+329
-1
@@ -29,7 +29,335 @@ interface DeprecatedIcon {
|
||||
};
|
||||
}
|
||||
|
||||
const mdiDeprecatedIcons: DeprecatedIcon = {};
|
||||
const mdiDeprecatedIcons: DeprecatedIcon = {
|
||||
"adobe-acrobat": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
adobe: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"amazon-alexa": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
amazon: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"android-auto": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"android-debug-bridge": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"apple-airplay": {
|
||||
newName: "cast-variant",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
application: {
|
||||
newName: "application-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"application-cog": {
|
||||
newName: "application-cog-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"application-settings": {
|
||||
newName: "application-settings-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
bandcamp: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
battlenet: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
blogger: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"bolnisi-cross": {
|
||||
newName: "cross-bolnisi",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"boom-gate-up": {
|
||||
newName: "boom-gate-arrow-up",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"boom-gate-up-outline": {
|
||||
newName: "boom-gate-arrow-up-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"boom-gate-down": {
|
||||
newName: "boom-gate-arrow-down",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"boom-gate-down-outline": {
|
||||
newName: "boom-gate-arrow-down-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
buddhism: {
|
||||
newName: "dharmachakra",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
buffer: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"cash-usd-outline": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"cash-usd": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"cellphone-android": {
|
||||
newName: "cellphone",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"cellphone-erase": {
|
||||
newName: "cellphone-remove",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"cellphone-iphone": {
|
||||
newName: "cellphone",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"celtic-cross": {
|
||||
newName: "cross-celtic",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
christianity: {
|
||||
newName: "cross",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"christianity-outline": {
|
||||
newName: "cross-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"concourse-ci": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"currency-usd-circle": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"currency-usd-circle-outline": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"do-not-disturb-off": {
|
||||
newName: "minus-circle-off",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"do-not-disturb": {
|
||||
newName: "minus-circle",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
douban: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
face: {
|
||||
newName: "face-man",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"face-outline": {
|
||||
newName: "face-man-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"face-profile-woman": {
|
||||
newName: "face-woman-profile",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"face-shimmer": {
|
||||
newName: "face-man-shimmer",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"face-shimmer-outline": {
|
||||
newName: "face-man-shimmer-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"file-pdf": {
|
||||
newName: "file-pdf-box",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"file-pdf-outline": {
|
||||
newName: "file-pdf-box",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"file-pdf-box-outline": {
|
||||
newName: "file-pdf-box",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"flash-circle": {
|
||||
newName: "lightning-bolt-circle",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"floor-lamp-variant": {
|
||||
newName: "floor-lamp-torchiere-variant",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
gif: {
|
||||
newName: "file-gif-box",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"google-photos": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
gradient: {
|
||||
newName: "gradient-vertical",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
hand: {
|
||||
newName: "hand-front-right",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"hand-left": {
|
||||
newName: "hand-back-left",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"hand-right": {
|
||||
newName: "hand-back-right",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
hinduism: {
|
||||
newName: "om",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"home-currency-usd": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
iframe: {
|
||||
newName: "application-brackets",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-outline": {
|
||||
newName: "application-brackets-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-array": {
|
||||
newName: "application-array",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-array-outline": {
|
||||
newName: "application-array-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-braces": {
|
||||
newName: "application-braces",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-braces-outline": {
|
||||
newName: "application-braces-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-parentheses": {
|
||||
newName: "application-parentheses",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-parentheses-outline": {
|
||||
newName: "application-parentheses-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-variable": {
|
||||
newName: "application-variable",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"iframe-variable-outline": {
|
||||
newName: "application-variable-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
islam: {
|
||||
newName: "star-crescent",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
judaism: {
|
||||
newName: "star-david",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"laptop-chromebook": {
|
||||
newName: "laptop",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"laptop-mac": {
|
||||
newName: "laptop",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"laptop-windows": {
|
||||
newName: "laptop",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"microsoft-edge-legacy": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"microsoft-yammer": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"monitor-clean": {
|
||||
newName: "monitor-shimmer",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"pdf-box": {
|
||||
newName: "file-pdf-box",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
pharmacy: {
|
||||
newName: "mortar-pestle-plus",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"plus-one": {
|
||||
newName: "numeric-positive-1",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"poll-box": {
|
||||
newName: "chart-box",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"poll-box-outline": {
|
||||
newName: "chart-box-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
sparkles: {
|
||||
newName: "shimmer",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"tablet-ipad": {
|
||||
newName: "tablet",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
teach: {
|
||||
newName: "human-male-board",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
telegram: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"television-clean": {
|
||||
newName: "television-shimmer",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"text-subject": {
|
||||
newName: "text-long",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"twitter-retweet": {
|
||||
newName: "repeat-variant",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
untappd: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
vk: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"voice-off": {
|
||||
newName: "account-voice-off",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"xamarian-outline": {
|
||||
newName: "xamarian",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
xing: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"y-combinator": {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
};
|
||||
|
||||
const chunks: Chunks = {};
|
||||
|
||||
|
||||
@@ -5,15 +5,18 @@ import {
|
||||
HassServiceTarget,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
import {
|
||||
fetchIntegrationManifest,
|
||||
IntegrationManifest,
|
||||
} from "../data/integration";
|
||||
import { Selector } from "../data/selector";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import "./ha-checkbox";
|
||||
import "./ha-selector/ha-selector";
|
||||
import "./ha-service-picker";
|
||||
@@ -53,6 +56,8 @@ export class HaServiceControl extends LitElement {
|
||||
|
||||
@state() private _checkedKeys = new Set();
|
||||
|
||||
@state() private _manifest?: IntegrationManifest;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>) {
|
||||
@@ -72,6 +77,19 @@ export class HaServiceControl extends LitElement {
|
||||
this.hass.services
|
||||
);
|
||||
|
||||
// Fetch the manifest if we have a service selected and the service domain changed.
|
||||
// If no service is selected, clear the manifest.
|
||||
if (this.value?.service) {
|
||||
if (
|
||||
!oldValue?.service ||
|
||||
computeDomain(this.value.service) !== computeDomain(oldValue.service)
|
||||
) {
|
||||
this._fetchManifest(computeDomain(this.value?.service));
|
||||
}
|
||||
} else {
|
||||
this._manifest = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
serviceData &&
|
||||
"target" in serviceData &&
|
||||
@@ -177,12 +195,9 @@ export class HaServiceControl extends LitElement {
|
||||
></ha-service-picker>
|
||||
<div class="description">
|
||||
<p>${serviceData?.description}</p>
|
||||
${this.value?.service
|
||||
${this._manifest
|
||||
? html` <a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
"/integrations/" + computeDomain(this.value?.service)
|
||||
)}"
|
||||
href="${this._manifest.documentation}"
|
||||
title="${this.hass.localize(
|
||||
"ui.components.service-control.integration_doc"
|
||||
)}"
|
||||
@@ -387,6 +402,15 @@ export class HaServiceControl extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _fetchManifest(integration: string) {
|
||||
this._manifest = undefined;
|
||||
try {
|
||||
this._manifest = await fetchIntegrationManifest(this.hass, integration);
|
||||
} catch (err) {
|
||||
// Ignore if loading manifest fails. Probably bad JSON in manifest
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-settings-row {
|
||||
|
||||
@@ -29,7 +29,7 @@ import { LocalStorage } from "../common/decorators/local-storage";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { ActionHandlerDetail } from "../data/lovelace";
|
||||
import {
|
||||
@@ -96,7 +96,7 @@ const defaultPanelSorter = (
|
||||
}
|
||||
|
||||
if (aLovelace && bLovelace) {
|
||||
return compare(a.title!, b.title!);
|
||||
return stringCompare(a.title!, b.title!);
|
||||
}
|
||||
if (aLovelace && !bLovelace) {
|
||||
return -1;
|
||||
@@ -118,7 +118,7 @@ const defaultPanelSorter = (
|
||||
return 1;
|
||||
}
|
||||
// both not built in, sort by title
|
||||
return compare(a.title!, b.title!);
|
||||
return stringCompare(a.title!, b.title!);
|
||||
};
|
||||
|
||||
const computePanels = memoizeOne(
|
||||
|
||||
@@ -90,10 +90,12 @@ export class HatGraphNode extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(var(--hat-graph-node-size) + var(--hat-graph-spacing));
|
||||
height: calc(var(--hat-graph-node-size) + var(--hat-graph-spacing) + 1);
|
||||
height: calc(
|
||||
var(--hat-graph-node-size) + var(--hat-graph-spacing) + 1px
|
||||
);
|
||||
}
|
||||
:host([graphStart]) {
|
||||
height: calc(var(--hat-graph-node-size) + 2);
|
||||
height: calc(var(--hat-graph-node-size) + 2px);
|
||||
}
|
||||
:host([track]) {
|
||||
--stroke-clr: var(--track-clr);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
mdiAsterisk,
|
||||
mdiCallSplit,
|
||||
mdiCheckboxBlankOutline,
|
||||
mdiCheckBoxOutline,
|
||||
mdiCheckboxOutline,
|
||||
mdiChevronDown,
|
||||
mdiChevronRight,
|
||||
mdiChevronUp,
|
||||
@@ -167,27 +167,31 @@ export class HatScriptGraph extends LitElement {
|
||||
<div class="graph-container" ?track=${track_this}>
|
||||
<hat-graph-node
|
||||
.iconPath=${!trace || track_this
|
||||
? mdiCheckBoxOutline
|
||||
? mdiCheckboxOutline
|
||||
: mdiCheckboxBlankOutline}
|
||||
@focus=${this.selectNode(config, branch_path)}
|
||||
?track=${track_this}
|
||||
?active=${this.selected === branch_path}
|
||||
></hat-graph-node>
|
||||
${ensureArray(branch.sequence).map((action, j) =>
|
||||
this.render_action_node(
|
||||
action,
|
||||
`${branch_path}/sequence/${j}`
|
||||
)
|
||||
)}
|
||||
${branch.sequence !== null
|
||||
? ensureArray(branch.sequence).map((action, j) =>
|
||||
this.render_action_node(
|
||||
action,
|
||||
`${branch_path}/sequence/${j}`
|
||||
)
|
||||
)
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
: ""}
|
||||
<div ?track=${track_default}>
|
||||
<hat-graph-spacer ?track=${track_default}></hat-graph-spacer>
|
||||
${ensureArray(config.default)?.map((action, i) =>
|
||||
this.render_action_node(action, `${path}/default/${i}`)
|
||||
)}
|
||||
${config.default !== null
|
||||
? ensureArray(config.default)?.map((action, i) =>
|
||||
this.render_action_node(action, `${path}/default/${i}`)
|
||||
)
|
||||
: ""}
|
||||
</div>
|
||||
</hat-graph-branch>
|
||||
`;
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import relativeTime from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { toggleAttribute } from "../../common/dom/toggle_attribute";
|
||||
import { LogbookEntry } from "../../data/logbook";
|
||||
import {
|
||||
ChooseAction,
|
||||
@@ -552,7 +553,7 @@ export class HaAutomationTracer extends LitElement {
|
||||
this.shadowRoot!.querySelectorAll<HaTimeline>(
|
||||
"ha-timeline[data-path]"
|
||||
).forEach((el) => {
|
||||
el.toggleAttribute("selected", this.selectedPath === el.dataset.path);
|
||||
toggleAttribute(el, "selected", this.selectedPath === el.dataset.path);
|
||||
if (!this.allowPick || el.tabIndex === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import { fetchUsers, User } from "../../data/user";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-icon-button";
|
||||
@@ -33,7 +33,7 @@ class HaUserPicker extends LitElement {
|
||||
|
||||
return users
|
||||
.filter((user) => !user.system_generated)
|
||||
.sort((a, b) => compare(a.name, b.name));
|
||||
.sort((a, b) => stringCompare(a.name, b.name));
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
@@ -46,7 +46,7 @@ const fetchAreaRegistry = (conn: Connection) =>
|
||||
})
|
||||
.then((areas) =>
|
||||
(areas as AreaRegistryEntry[]).sort((ent1, ent2) =>
|
||||
compare(ent1.name, ent2.name)
|
||||
stringCompare(ent1.name, ent2.name)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HA_COLOR_PALETTE } from "../common/const";
|
||||
import { getColorByIndex } from "../common/color/colors";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import type { CalendarEvent, HomeAssistant } from "../types";
|
||||
@@ -81,5 +81,5 @@ export const getCalendars = (hass: HomeAssistant): Calendar[] =>
|
||||
.map((eid, idx) => ({
|
||||
entity_id: eid,
|
||||
name: computeStateName(hass.states[eid]),
|
||||
backgroundColor: `#${HA_COLOR_PALETTE[idx % HA_COLOR_PALETTE.length]}`,
|
||||
backgroundColor: getColorByIndex(idx),
|
||||
}));
|
||||
|
||||
+16
-18
@@ -36,30 +36,28 @@ export interface Stream {
|
||||
export const computeMJPEGStreamUrl = (entity: CameraEntity) =>
|
||||
`/api/camera_proxy_stream/${entity.entity_id}?token=${entity.attributes.access_token}`;
|
||||
|
||||
export const fetchThumbnailUrlWithCache = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
width: number,
|
||||
height: number
|
||||
) =>
|
||||
timeCachePromiseFunc(
|
||||
"_cameraTmbUrl",
|
||||
9000,
|
||||
fetchThumbnailUrl,
|
||||
hass,
|
||||
entityId,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
export const fetchThumbnailUrl = async (
|
||||
export const fetchThumbnailUrlWithCache = async (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
width: number,
|
||||
height: number
|
||||
) => {
|
||||
const base_url = await timeCachePromiseFunc(
|
||||
"_cameraTmbUrl",
|
||||
9000,
|
||||
fetchThumbnailUrl,
|
||||
hass,
|
||||
entityId
|
||||
);
|
||||
return `${base_url}&width=${width}&height=${height}`;
|
||||
};
|
||||
|
||||
export const fetchThumbnailUrl = async (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
) => {
|
||||
const path = await getSignedPath(hass, `/api/camera_proxy/${entityId}`);
|
||||
return hass.hassUrl(`${path.path}&width=${width}&height=${height}`);
|
||||
return hass.hassUrl(path.path);
|
||||
};
|
||||
|
||||
export const fetchStreamUrl = async (
|
||||
|
||||
@@ -6,6 +6,7 @@ import { DataEntryFlowProgress, DataEntryFlowStep } from "./data_entry_flow";
|
||||
import { domainToName } from "./integration";
|
||||
|
||||
export const DISCOVERY_SOURCES = [
|
||||
"usb",
|
||||
"unignore",
|
||||
"dhcp",
|
||||
"homekit",
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
export interface DiscoveryInformation {
|
||||
uuid: string;
|
||||
base_url: string | null;
|
||||
external_url: string | null;
|
||||
internal_url: string | null;
|
||||
location_name: string;
|
||||
installation_type: string;
|
||||
requires_api_password: boolean;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export const fetchDiscoveryInformation =
|
||||
async (): Promise<DiscoveryInformation> => {
|
||||
const response = await fetch("/api/discovery_info", { method: "GET" });
|
||||
return response.json();
|
||||
};
|
||||
+32
-11
@@ -6,6 +6,7 @@ import {
|
||||
startOfYesterday,
|
||||
} from "date-fns";
|
||||
import { Collection, getCollection } from "home-assistant-js-websocket";
|
||||
import { groupBy } from "../common/util/group-by";
|
||||
import { subscribeOne } from "../common/util/subscribe-one";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { ConfigEntry, getConfigEntries } from "./config_entries";
|
||||
@@ -62,6 +63,13 @@ export const emptyGasEnergyPreference = (): GasSourceTypeEnergyPreference => ({
|
||||
number_energy_price: null,
|
||||
});
|
||||
|
||||
interface EnergySolarForecast {
|
||||
wh_hours: Record<string, number>;
|
||||
}
|
||||
export type EnergySolarForecasts = {
|
||||
[config_entry_id: string]: EnergySolarForecast;
|
||||
};
|
||||
|
||||
export interface DeviceConsumptionEnergyPreference {
|
||||
// This is an ever increasing value
|
||||
stat_consumption: string;
|
||||
@@ -142,6 +150,18 @@ export interface EnergyPreferences {
|
||||
|
||||
export interface EnergyInfo {
|
||||
cost_sensors: Record<string, string>;
|
||||
solar_forecast_domains: string[];
|
||||
}
|
||||
|
||||
export interface EnergyValidationIssue {
|
||||
type: string;
|
||||
identifier: string;
|
||||
value?: unknown;
|
||||
}
|
||||
|
||||
export interface EnergyPreferencesValidation {
|
||||
energy_sources: EnergyValidationIssue[][];
|
||||
device_consumption: EnergyValidationIssue[][];
|
||||
}
|
||||
|
||||
export const getEnergyInfo = (hass: HomeAssistant) =>
|
||||
@@ -149,6 +169,11 @@ export const getEnergyInfo = (hass: HomeAssistant) =>
|
||||
type: "energy/info",
|
||||
});
|
||||
|
||||
export const getEnergyPreferenceValidation = (hass: HomeAssistant) =>
|
||||
hass.callWS<EnergyPreferencesValidation>({
|
||||
type: "energy/validate",
|
||||
});
|
||||
|
||||
export const getEnergyPreferences = (hass: HomeAssistant) =>
|
||||
hass.callWS<EnergyPreferences>({
|
||||
type: "energy/get_prefs",
|
||||
@@ -173,17 +198,8 @@ interface EnergySourceByType {
|
||||
gas?: GasSourceTypeEnergyPreference[];
|
||||
}
|
||||
|
||||
export const energySourcesByType = (prefs: EnergyPreferences) => {
|
||||
const types: EnergySourceByType = {};
|
||||
for (const source of prefs.energy_sources) {
|
||||
if (source.type in types) {
|
||||
types[source.type]!.push(source as any);
|
||||
} else {
|
||||
types[source.type] = [source as any];
|
||||
}
|
||||
}
|
||||
return types;
|
||||
};
|
||||
export const energySourcesByType = (prefs: EnergyPreferences) =>
|
||||
groupBy(prefs.energy_sources, (item) => item.type) as EnergySourceByType;
|
||||
|
||||
export interface EnergyData {
|
||||
start: Date;
|
||||
@@ -432,3 +448,8 @@ export const getEnergyDataCollection = (
|
||||
};
|
||||
return collection;
|
||||
};
|
||||
|
||||
export const getEnergySolarForecasts = (hass: HomeAssistant) =>
|
||||
hass.callWS<EnergySolarForecasts>({
|
||||
type: "energy/solar_forecast",
|
||||
});
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface ForecastSolarForecast {
|
||||
wh_hours: Record<string, number>;
|
||||
}
|
||||
|
||||
export const getForecastSolarForecasts = (hass: HomeAssistant) =>
|
||||
hass.callWS<Record<string, ForecastSolarForecast>>({
|
||||
type: "forecast_solar/forecasts",
|
||||
});
|
||||
@@ -159,9 +159,11 @@ export const removeBackup = async (hass: HomeAssistant, slug: string) => {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/${
|
||||
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
|
||||
}/${slug}/remove`,
|
||||
method: "post",
|
||||
atLeastVersion(hass.config.version, 2021, 9)
|
||||
? `backups/${slug}`
|
||||
: `snapshots/${slug}/remove`
|
||||
}`,
|
||||
method: atLeastVersion(hass.config.version, 2021, 9) ? "delete" : "post",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface HassioHassOSInfo {
|
||||
update_available: boolean;
|
||||
version_latest: string | null;
|
||||
version: string | null;
|
||||
data_disk: string;
|
||||
}
|
||||
|
||||
export const fetchHassioHostInfo = async (
|
||||
@@ -113,6 +114,19 @@ export const configSyncOS = async (hass: HomeAssistant) => {
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/config/sync");
|
||||
};
|
||||
|
||||
export const dataDiskMove = async (hass: HomeAssistant) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/os/datadisk/move",
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/datadisk/move");
|
||||
};
|
||||
|
||||
export const changeHostOptions = async (hass: HomeAssistant, options: any) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
|
||||
+10
-5
@@ -239,14 +239,17 @@ export const computeHistory = (
|
||||
return;
|
||||
}
|
||||
|
||||
const stateWithUnit = stateInfo.find(
|
||||
(state) => state.attributes && "unit_of_measurement" in state.attributes
|
||||
const stateWithUnitorStateClass = stateInfo.find(
|
||||
(state) =>
|
||||
state.attributes &&
|
||||
("unit_of_measurement" in state.attributes ||
|
||||
"state_class" in state.attributes)
|
||||
);
|
||||
|
||||
let unit: string | undefined;
|
||||
|
||||
if (stateWithUnit) {
|
||||
unit = stateWithUnit.attributes.unit_of_measurement;
|
||||
if (stateWithUnitorStateClass) {
|
||||
unit = stateWithUnitorStateClass.attributes.unit_of_measurement || " ";
|
||||
} else {
|
||||
unit = {
|
||||
climate: hass.config.unit_system.temperature,
|
||||
@@ -291,13 +294,15 @@ export const fetchStatistics = (
|
||||
hass: HomeAssistant,
|
||||
startTime: Date,
|
||||
endTime?: Date,
|
||||
statistic_ids?: string[]
|
||||
statistic_ids?: string[],
|
||||
period: "hour" | "5minute" = "hour"
|
||||
) =>
|
||||
hass.callWS<Statistics>({
|
||||
type: "history/statistics_during_period",
|
||||
start_time: startTime.toISOString(),
|
||||
end_time: endTime?.toISOString(),
|
||||
statistic_ids,
|
||||
period,
|
||||
});
|
||||
|
||||
export const calculateStatisticSumGrowth = (
|
||||
|
||||
+1
-17
@@ -2,7 +2,6 @@ import { clear, get, set, createStore, promisifyRequest } from "idb-keyval";
|
||||
import { promiseTimeout } from "../common/util/promise-timeout";
|
||||
import { iconMetadata } from "../resources/icon-metadata";
|
||||
import { IconMeta } from "../types";
|
||||
import { isSafari } from "../util/is_safari";
|
||||
|
||||
export interface Icons {
|
||||
[key: string]: string;
|
||||
@@ -39,22 +38,7 @@ export const getIcon = (iconName: string) =>
|
||||
toRead = [];
|
||||
});
|
||||
|
||||
let readIconPromise: Promise<void>;
|
||||
|
||||
if (isSafari && (indexedDB as any).databases) {
|
||||
let intervalId: number;
|
||||
readIconPromise = new Promise<void>((resolveTry) => {
|
||||
const tryIdb = () => (indexedDB as any).databases().finally(resolveTry);
|
||||
intervalId = window.setInterval(tryIdb, 100);
|
||||
tryIdb();
|
||||
})
|
||||
.then(() => readIcons())
|
||||
.finally(() => clearInterval(intervalId));
|
||||
} else {
|
||||
readIconPromise = readIcons();
|
||||
}
|
||||
|
||||
promiseTimeout(1000, readIconPromise).catch((e) => {
|
||||
promiseTimeout(1000, readIcons()).catch((e) => {
|
||||
// Firefox in private mode doesn't support IDB
|
||||
// Safari sometime doesn't open the DB so we time out
|
||||
for (const [, , reject_] of toRead) {
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
|
||||
export interface InstallationType {
|
||||
installation_type:
|
||||
| "Home Assistant Operating System"
|
||||
| "Home Assistant Container"
|
||||
| "Home Assistant Supervised"
|
||||
| "Home Assistant Core"
|
||||
| "Unknown";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface OnboardingCoreConfigStepResponse {}
|
||||
|
||||
@@ -65,3 +74,15 @@ export const onboardIntegrationStep = (
|
||||
"onboarding/integration",
|
||||
params
|
||||
);
|
||||
|
||||
export const fetchInstallationType = async (): Promise<InstallationType> => {
|
||||
const response = await fetch("/api/onboarding/installation_type", {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
throw Error("unauthorized");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const scanUSBDevices = (hass: HomeAssistant) =>
|
||||
hass.callWS({ type: "usb/scan" });
|
||||
+23
-2
@@ -24,12 +24,22 @@ export interface ZWaveJSController {
|
||||
is_heal_network_active: boolean;
|
||||
}
|
||||
|
||||
export interface ZWaveJSNode {
|
||||
export interface ZWaveJSNodeStatus {
|
||||
node_id: number;
|
||||
ready: boolean;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface ZwaveJSNodeMetadata {
|
||||
node_id: number;
|
||||
exclusion: string;
|
||||
inclusion: string;
|
||||
manual: string;
|
||||
wakeup: string;
|
||||
reset: string;
|
||||
device_database_url: string;
|
||||
}
|
||||
|
||||
export interface ZWaveJSNodeConfigParams {
|
||||
[key: string]: ZWaveJSNodeConfigParam;
|
||||
}
|
||||
@@ -132,13 +142,24 @@ export const fetchNodeStatus = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
node_id: number
|
||||
): Promise<ZWaveJSNode> =>
|
||||
): Promise<ZWaveJSNodeStatus> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/node_status",
|
||||
entry_id,
|
||||
node_id,
|
||||
});
|
||||
|
||||
export const fetchNodeMetadata = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
node_id: number
|
||||
): Promise<ZwaveJSNodeMetadata> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/node_metadata",
|
||||
entry_id,
|
||||
node_id,
|
||||
});
|
||||
|
||||
export const fetchNodeConfigParameters = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { html } from "lit";
|
||||
import { caseInsensitiveCompare } from "../../common/string/compare";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
createConfigFlow,
|
||||
deleteConfigFlow,
|
||||
@@ -29,7 +29,7 @@ export const showConfigFlowDialog = (
|
||||
]);
|
||||
|
||||
return handlers.sort((handlerA, handlerB) =>
|
||||
caseInsensitiveCompare(
|
||||
caseInsensitiveStringCompare(
|
||||
domainToName(hass.localize, handlerA),
|
||||
domainToName(hass.localize, handlerB)
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import "../../../components/ha-attributes";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import {
|
||||
VacuumEntity,
|
||||
VACUUM_SUPPORT_BATTERY,
|
||||
@@ -98,31 +99,35 @@ class MoreInfoVacuum extends LitElement {
|
||||
"fan_speed,fan_speed_list,status,battery_level,battery_icon";
|
||||
|
||||
return html`
|
||||
<div class="flex-horizontal">
|
||||
${supportsFeature(stateObj, VACUUM_SUPPORT_STATUS)
|
||||
? html`
|
||||
<div>
|
||||
<span class="status-subtitle"
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.vacuum.status"
|
||||
)}:
|
||||
</span>
|
||||
<span><strong>${stateObj.attributes.status}</strong></span>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${supportsFeature(stateObj, VACUUM_SUPPORT_BATTERY)
|
||||
? html`
|
||||
<div>
|
||||
<span>
|
||||
<ha-icon .icon=${stateObj.attributes.battery_icon}></ha-icon>
|
||||
${stateObj.attributes.battery_level} %
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
${stateObj.state !== UNAVAILABLE
|
||||
? html` <div class="flex-horizontal">
|
||||
${supportsFeature(stateObj, VACUUM_SUPPORT_STATUS)
|
||||
? html`
|
||||
<div>
|
||||
<span class="status-subtitle"
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.vacuum.status"
|
||||
)}:
|
||||
</span>
|
||||
<span><strong>${stateObj.attributes.status}</strong></span>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${supportsFeature(stateObj, VACUUM_SUPPORT_BATTERY) &&
|
||||
stateObj.attributes.battery_level
|
||||
? html`
|
||||
<div>
|
||||
<span>
|
||||
${stateObj.attributes.battery_level} %
|
||||
<ha-icon
|
||||
.icon=${stateObj.attributes.battery_icon}
|
||||
></ha-icon>
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>`
|
||||
: ""}
|
||||
${VACUUM_COMMANDS.some((item) => item.isVisible(stateObj))
|
||||
? html`
|
||||
<div>
|
||||
@@ -145,6 +150,7 @@ class MoreInfoVacuum extends LitElement {
|
||||
.title=${this.hass!.localize(
|
||||
`ui.dialogs.more_info_control.vacuum.${item.translationKey}`
|
||||
)}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
@@ -161,6 +167,7 @@ class MoreInfoVacuum extends LitElement {
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.vacuum.fan_speed"
|
||||
)}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
|
||||
@@ -25,7 +25,7 @@ import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { domainIcon } from "../../common/entity/domain_icon";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../common/search/search-input";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
fuzzyFilterSort,
|
||||
ScorableTextItem,
|
||||
@@ -299,6 +299,10 @@ export class QuickBar extends LitElement {
|
||||
|
||||
private _handleSelected(ev: SingleSelectedEvent) {
|
||||
const index = ev.detail.index;
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = ((ev.target as List).items[index] as any).item;
|
||||
this.processItemAndCloseDialog(item, index);
|
||||
}
|
||||
@@ -395,7 +399,7 @@ export class QuickBar extends LitElement {
|
||||
};
|
||||
})
|
||||
.sort((a, b) =>
|
||||
compare(a.primaryText.toLowerCase(), b.primaryText.toLowerCase())
|
||||
stringCompare(a.primaryText.toLowerCase(), b.primaryText.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -405,7 +409,7 @@ export class QuickBar extends LitElement {
|
||||
...this._generateServerControlCommands(),
|
||||
...this._generateNavigationCommands(),
|
||||
].sort((a, b) =>
|
||||
compare(
|
||||
stringCompare(
|
||||
a.strings.join(" ").toLowerCase(),
|
||||
b.strings.join(" ").toLowerCase()
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ import { subscribePanels } from "../data/ws-panels";
|
||||
import { subscribeThemes } from "../data/ws-themes";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import type { ExternalAuth } from "../external_app/external_auth";
|
||||
import "../resources/array.flat.polyfill";
|
||||
import "../resources/safari-14-attachshadow-patch";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { MAIN_WINDOW_NAME } from "../data/main_window";
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"content" in document.createElement("template"))) {
|
||||
document.write("<script src='/static/polyfills/webcomponents-bundle.js'><"+"/script>");
|
||||
}
|
||||
var isS11_12 = /.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent);
|
||||
var isS11_12 = /(?:.*(?:iPhone|iPad).*OS (?:11|12)_\d)|(?:.*Version\/(?:11|12)(?:\.\d+)*.*Safari\/)/.test(navigator.userAgent);
|
||||
</script>
|
||||
|
||||
@@ -211,6 +211,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
if (!this.hass!.connection.connected) {
|
||||
return;
|
||||
}
|
||||
window.stop();
|
||||
this.hass!.connection.suspend();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,12 @@ import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
import { subscribeOne } from "../common/util/subscribe-one";
|
||||
import { AuthUrlSearchParams, hassUrl } from "../data/auth";
|
||||
import {
|
||||
DiscoveryInformation,
|
||||
fetchDiscoveryInformation,
|
||||
} from "../data/discovery";
|
||||
import {
|
||||
InstallationType,
|
||||
fetchOnboardingOverview,
|
||||
OnboardingResponses,
|
||||
OnboardingStep,
|
||||
onboardIntegrationStep,
|
||||
fetchInstallationType,
|
||||
} from "../data/onboarding";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
@@ -71,7 +69,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
|
||||
@state() private _steps?: OnboardingStep[];
|
||||
|
||||
@state() private _discoveryInformation?: DiscoveryInformation;
|
||||
@state() private _installation_type?: InstallationType;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const step = this._curStep()!;
|
||||
@@ -92,7 +90,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
? html`<onboarding-restore-backup
|
||||
.localize=${this.localize}
|
||||
.restoring=${this._restoring}
|
||||
.discoveryInformation=${this._discoveryInformation}
|
||||
.installtionType=${this._installation_type}
|
||||
@restoring=${this._restoringBackup}
|
||||
>
|
||||
</onboarding-restore-backup>`
|
||||
@@ -130,7 +128,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchOnboardingSteps();
|
||||
this._fetchDiscoveryInformation();
|
||||
this._fetchInstallationType();
|
||||
import("./onboarding-integrations");
|
||||
import("./onboarding-core-config");
|
||||
registerServiceWorker(this, false);
|
||||
@@ -174,9 +172,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
this._restoring = true;
|
||||
}
|
||||
|
||||
private async _fetchDiscoveryInformation(): Promise<void> {
|
||||
private async _fetchInstallationType(): Promise<void> {
|
||||
try {
|
||||
const response = await fetchDiscoveryInformation();
|
||||
const response = await fetchInstallationType();
|
||||
this._supervisor = [
|
||||
"Home Assistant OS",
|
||||
"Home Assistant Supervised",
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
|
||||
import {
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
} from "../data/config_flow";
|
||||
import { DataEntryFlowProgress } from "../data/data_entry_flow";
|
||||
import { domainToName } from "../data/integration";
|
||||
import { scanUSBDevices } from "../data/usb";
|
||||
import {
|
||||
loadConfigFlowDialog,
|
||||
showConfigFlowDialog,
|
||||
@@ -103,7 +105,7 @@ class OnboardingIntegrations extends LitElement {
|
||||
}
|
||||
);
|
||||
const content = [...entries, ...discovered]
|
||||
.sort((a, b) => compare(a[0], b[0]))
|
||||
.sort((a, b) => stringCompare(a[0], b[0]))
|
||||
.map((item) => item[1]);
|
||||
|
||||
return html`
|
||||
@@ -134,6 +136,7 @@ class OnboardingIntegrations extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._scanUSBDevices();
|
||||
loadConfigFlowDialog();
|
||||
this._loadConfigEntries();
|
||||
/* polyfill for paper-dropdown */
|
||||
@@ -159,6 +162,13 @@ class OnboardingIntegrations extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _scanUSBDevices() {
|
||||
if (!isComponentLoaded(this.hass, "usb")) {
|
||||
return;
|
||||
}
|
||||
await scanUSBDevices(this.hass);
|
||||
}
|
||||
|
||||
private async _loadConfigEntries() {
|
||||
const entries = await getConfigEntries(this.hass!);
|
||||
// We filter out the config entry for the local weather and rpi_power.
|
||||
|
||||
@@ -6,14 +6,11 @@ import { showHassioBackupDialog } from "../../hassio/src/dialogs/backup/show-dia
|
||||
import { showBackupUploadDialog } from "../../hassio/src/dialogs/backup/show-dialog-backup-upload";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-card";
|
||||
import {
|
||||
DiscoveryInformation,
|
||||
fetchDiscoveryInformation,
|
||||
} from "../data/discovery";
|
||||
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
||||
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import "./onboarding-loading";
|
||||
import { fetchInstallationType, InstallationType } from "../data/onboarding";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@@ -30,7 +27,7 @@ class OnboardingRestoreBackup extends ProvideHassLitMixin(LitElement) {
|
||||
@property({ type: Boolean }) public restoring = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public discoveryInformation?: DiscoveryInformation;
|
||||
public installationType?: InstallationType;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return this.restoring
|
||||
@@ -64,17 +61,11 @@ class OnboardingRestoreBackup extends ProvideHassLitMixin(LitElement) {
|
||||
private async _checkRestoreStatus(): Promise<void> {
|
||||
if (this.restoring) {
|
||||
try {
|
||||
const response = await fetchDiscoveryInformation();
|
||||
|
||||
if (
|
||||
!this.discoveryInformation ||
|
||||
this.discoveryInformation.uuid !== response.uuid
|
||||
) {
|
||||
// When the UUID changes, the restore is complete
|
||||
await fetchInstallationType();
|
||||
} catch (err) {
|
||||
if ((err as Error).message === "unauthorized") {
|
||||
window.location.replace("/");
|
||||
}
|
||||
} catch (err) {
|
||||
// We fully expected issues with fetching info untill restore is complete.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import memoize from "memoize-one";
|
||||
import { useAmPm } from "../../common/datetime/use_am_pm";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button-toggle-group";
|
||||
import "../../components/ha-icon-button";
|
||||
@@ -214,6 +215,11 @@ export class HAFullCalendar extends LitElement {
|
||||
...defaultFullCalendarConfig,
|
||||
locale: this.hass.language,
|
||||
initialView: this.initialView,
|
||||
eventTimeFormat: {
|
||||
hour: useAmPm(this.hass.locale) ? "numeric" : "2-digit",
|
||||
minute: useAmPm(this.hass.locale) ? "numeric" : "2-digit",
|
||||
hour12: useAmPm(this.hass.locale),
|
||||
},
|
||||
};
|
||||
|
||||
config.dateClick = (info) => this._handleDateClick(info);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
@@ -104,7 +104,7 @@ class HaConfigAreas extends HassRouterPage {
|
||||
private _loadData() {
|
||||
getConfigEntries(this.hass).then((configEntries) => {
|
||||
this._configEntries = configEntries.sort((conf1, conf2) =>
|
||||
compare(conf1.title, conf2.title)
|
||||
stringCompare(conf1.title, conf2.title)
|
||||
);
|
||||
});
|
||||
if (this._unsubs) {
|
||||
|
||||
@@ -252,6 +252,9 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _handleUiModeNotAvailable(ev: CustomEvent) {
|
||||
// Prevent possible parent action-row from switching to yamlMode
|
||||
ev.stopPropagation();
|
||||
|
||||
this._warnings = handleStructError(this.hass, ev.detail).warnings;
|
||||
if (!this._yamlMode) {
|
||||
this._yamlMode = true;
|
||||
|
||||
@@ -53,6 +53,9 @@ export default class HaAutomationAction extends LitElement {
|
||||
}
|
||||
|
||||
private _move(ev: CustomEvent) {
|
||||
// Prevent possible parent action-row from also moving
|
||||
ev.stopPropagation();
|
||||
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = ev.detail.direction === "up" ? index - 1 : index + 1;
|
||||
const actions = this.actions.concat();
|
||||
|
||||
@@ -53,7 +53,8 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
|
||||
protected render() {
|
||||
const blueprint = this._blueprint;
|
||||
return html`<ha-config-section vertical .isWide=${this.isWide}>
|
||||
return html`
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html` <span slot="header">${this.config.alias}</span> `
|
||||
: ""}
|
||||
@@ -118,81 +119,80 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section vertical .isWide=${this.isWide}>
|
||||
<span slot="header"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.header"
|
||||
)}</span
|
||||
>
|
||||
<ha-card>
|
||||
<div class="blueprint-picker-container">
|
||||
${this._blueprints
|
||||
? Object.keys(this._blueprints).length
|
||||
? html`
|
||||
<ha-blueprint-picker
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
|
||||
)}
|
||||
.blueprints=${this._blueprints}
|
||||
.value=${this.config.use_blueprint.path}
|
||||
@value-changed=${this._blueprintChanged}
|
||||
></ha-blueprint-picker>
|
||||
`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.no_blueprints"
|
||||
)
|
||||
: html`<ha-circular-progress active></ha-circular-progress>`}
|
||||
</div>
|
||||
<ha-card
|
||||
class="blueprint"
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.header"
|
||||
)}
|
||||
>
|
||||
<div class="blueprint-picker-container">
|
||||
${this._blueprints
|
||||
? Object.keys(this._blueprints).length
|
||||
? html`
|
||||
<ha-blueprint-picker
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
|
||||
)}
|
||||
.blueprints=${this._blueprints}
|
||||
.value=${this.config.use_blueprint.path}
|
||||
@value-changed=${this._blueprintChanged}
|
||||
></ha-blueprint-picker>
|
||||
`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.no_blueprints"
|
||||
)
|
||||
: html`<ha-circular-progress active></ha-circular-progress>`}
|
||||
</div>
|
||||
|
||||
${this.config.use_blueprint.path
|
||||
? blueprint && "error" in blueprint
|
||||
? html`<p class="warning padding">
|
||||
There is an error in this Blueprint: ${blueprint.error}
|
||||
</p>`
|
||||
: html`${blueprint?.metadata.description
|
||||
? html`<ha-markdown
|
||||
class="card-content"
|
||||
breaks
|
||||
.content=${blueprint.metadata.description}
|
||||
></ha-markdown>`
|
||||
: ""}
|
||||
${blueprint?.metadata?.input &&
|
||||
Object.keys(blueprint.metadata.input).length
|
||||
? Object.entries(blueprint.metadata.input).map(
|
||||
([key, value]) =>
|
||||
html`<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">${value?.name || key}</span>
|
||||
<span slot="description">${value?.description}</span>
|
||||
${value?.selector
|
||||
? html`<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${value.selector}
|
||||
.key=${key}
|
||||
.value=${(this.config.use_blueprint.input &&
|
||||
this.config.use_blueprint.input[key]) ??
|
||||
value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
></ha-selector>`
|
||||
: html`<paper-input
|
||||
.key=${key}
|
||||
required
|
||||
.value=${(this.config.use_blueprint.input &&
|
||||
this.config.use_blueprint.input[key]) ??
|
||||
value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
no-label-float
|
||||
></paper-input>`}
|
||||
</ha-settings-row>`
|
||||
)
|
||||
: html`<p class="padding">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.no_inputs"
|
||||
)}
|
||||
</p>`}`
|
||||
: ""}
|
||||
</ha-card>
|
||||
</ha-config-section>`;
|
||||
${this.config.use_blueprint.path
|
||||
? blueprint && "error" in blueprint
|
||||
? html`<p class="warning padding">
|
||||
There is an error in this Blueprint: ${blueprint.error}
|
||||
</p>`
|
||||
: html`${blueprint?.metadata.description
|
||||
? html`<ha-markdown
|
||||
class="card-content"
|
||||
breaks
|
||||
.content=${blueprint.metadata.description}
|
||||
></ha-markdown>`
|
||||
: ""}
|
||||
${blueprint?.metadata?.input &&
|
||||
Object.keys(blueprint.metadata.input).length
|
||||
? Object.entries(blueprint.metadata.input).map(
|
||||
([key, value]) =>
|
||||
html`<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">${value?.name || key}</span>
|
||||
<span slot="description">${value?.description}</span>
|
||||
${value?.selector
|
||||
? html`<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${value.selector}
|
||||
.key=${key}
|
||||
.value=${(this.config.use_blueprint.input &&
|
||||
this.config.use_blueprint.input[key]) ??
|
||||
value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
></ha-selector>`
|
||||
: html`<paper-input
|
||||
.key=${key}
|
||||
required
|
||||
.value=${(this.config.use_blueprint.input &&
|
||||
this.config.use_blueprint.input[key]) ??
|
||||
value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
no-label-float
|
||||
></paper-input>`}
|
||||
</ha-settings-row>`
|
||||
)
|
||||
: html`<p class="padding">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.no_inputs"
|
||||
)}
|
||||
</p>`}`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _getBlueprints() {
|
||||
@@ -267,11 +267,15 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card.blueprint {
|
||||
max-width: 1040px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
.padding {
|
||||
padding: 16px;
|
||||
}
|
||||
.blueprint-picker-container {
|
||||
padding: 16px;
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
h3 {
|
||||
margin: 16px;
|
||||
|
||||
@@ -490,9 +490,12 @@ export class HaAutomationTrace extends LitElement {
|
||||
border-right: 1px solid var(--divider-color);
|
||||
overflow-x: auto;
|
||||
max-width: 50%;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
:host([narrow]) .graph {
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { fetchTags, Tag } from "../../../../../data/tag";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { TriggerElement } from "../ha-automation-trigger-row";
|
||||
import "../../../../../components/ha-paper-dropdown-menu";
|
||||
import { caseInsensitiveStringCompare } from "../../../../../common/string/compare";
|
||||
|
||||
@customElement("ha-automation-trigger-tag")
|
||||
export class HaTagTrigger extends LitElement implements TriggerElement {
|
||||
@@ -54,6 +55,9 @@ export class HaTagTrigger extends LitElement implements TriggerElement {
|
||||
|
||||
private async _fetchTags() {
|
||||
this._tags = await fetchTags(this.hass);
|
||||
this._tags.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(a.name || a.id, b.name || b.id)
|
||||
);
|
||||
}
|
||||
|
||||
private _tagChanged(ev) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { caseInsensitiveCompare } from "../../../../common/string/compare";
|
||||
import { caseInsensitiveStringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-switch";
|
||||
@@ -139,7 +139,7 @@ export class CloudTTSPref extends LitElement {
|
||||
|
||||
languages.push([lang, label]);
|
||||
}
|
||||
return languages.sort((a, b) => caseInsensitiveCompare(a[1], b[1]));
|
||||
return languages.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
|
||||
});
|
||||
|
||||
private getSupportedGenders = memoizeOne(
|
||||
@@ -160,7 +160,7 @@ export class CloudTTSPref extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
return genders.sort((a, b) => caseInsensitiveCompare(a[1], b[1]));
|
||||
return genders.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
generateFilter,
|
||||
isEmptyFilter,
|
||||
} from "../../../../common/entity/entity_filter";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/entity/state-info";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
@@ -295,7 +295,7 @@ class CloudAlexa extends LitElement {
|
||||
entities.sort((a, b) => {
|
||||
const stateA = this.hass.states[a.entity_id];
|
||||
const stateB = this.hass.states[b.entity_id];
|
||||
return compare(
|
||||
return stringCompare(
|
||||
stateA ? computeStateName(stateA) : a.entity_id,
|
||||
stateB ? computeStateName(stateB) : b.entity_id
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
generateFilter,
|
||||
isEmptyFilter,
|
||||
} from "../../../../common/entity/entity_filter";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import "../../../../components/entity/state-info";
|
||||
import "../../../../components/ha-button-menu";
|
||||
@@ -330,7 +330,7 @@ class CloudGoogleAssistant extends LitElement {
|
||||
entities.sort((a, b) => {
|
||||
const stateA = this.hass.states[a.entity_id];
|
||||
const stateB = this.hass.states[b.entity_id];
|
||||
return compare(
|
||||
return stringCompare(
|
||||
stateA ? computeStateName(stateA) : a.entity_id,
|
||||
stateB ? computeStateName(stateB) : b.entity_id
|
||||
);
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ import {
|
||||
fetchNodeStatus,
|
||||
getIdentifiersFromDevice,
|
||||
nodeStatus,
|
||||
ZWaveJSNode,
|
||||
ZWaveJSNodeStatus,
|
||||
ZWaveJSNodeIdentifiers,
|
||||
} from "../../../../../../data/zwave_js";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
@@ -36,7 +36,7 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
||||
|
||||
@state() private _nodeId?: number;
|
||||
|
||||
@state() private _node?: ZWaveJSNode;
|
||||
@state() private _node?: ZWaveJSNodeStatus;
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("device")) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-icon-next";
|
||||
@@ -103,7 +103,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
stateName: this._computeEntityName(entity),
|
||||
}))
|
||||
.sort((ent1, ent2) =>
|
||||
compare(
|
||||
stringCompare(
|
||||
ent1.stateName || `zzz${ent1.entity_id}`,
|
||||
ent2.stateName || `zzz${ent2.entity_id}`
|
||||
)
|
||||
|
||||
@@ -212,6 +212,10 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
this._batteryEntity(device.id, deviceEntityLookup),
|
||||
this._batteryChargingEntity(device.id, deviceEntityLookup),
|
||||
],
|
||||
battery_level:
|
||||
this.hass.states[
|
||||
this._batteryEntity(device.id, deviceEntityLookup) || ""
|
||||
]?.state,
|
||||
}));
|
||||
|
||||
this._numHiddenDevices = startLength - outputDevices.length;
|
||||
@@ -286,9 +290,11 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
columns.battery_entity = {
|
||||
title: this.hass.localize("ui.panel.config.devices.data_table.battery"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
type: "numeric",
|
||||
width: narrow ? "95px" : "15%",
|
||||
maxWidth: "95px",
|
||||
valueColumn: "battery_level",
|
||||
template: (batteryEntityPair: DeviceRowData["battery_entity"]) => {
|
||||
const battery =
|
||||
batteryEntityPair && batteryEntityPair[0]
|
||||
|
||||
@@ -10,7 +10,8 @@ import "../../../../components/ha-settings-row";
|
||||
import {
|
||||
BatterySourceTypeEnergyPreference,
|
||||
EnergyPreferences,
|
||||
energySourcesByType,
|
||||
EnergyPreferencesValidation,
|
||||
EnergyValidationIssue,
|
||||
saveEnergyPreferences,
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
@@ -21,6 +22,7 @@ import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { documentationUrl } from "../../../../util/documentation-url";
|
||||
import { showEnergySettingsBatteryDialog } from "../dialogs/show-dialogs-energy";
|
||||
import "./ha-energy-validation-result";
|
||||
import { energyCardStyles } from "./styles";
|
||||
|
||||
@customElement("ha-energy-battery-settings")
|
||||
@@ -30,10 +32,23 @@ export class EnergyBatterySettings extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public preferences!: EnergyPreferences;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const types = energySourcesByType(this.preferences);
|
||||
@property({ attribute: false })
|
||||
public validationResult?: EnergyPreferencesValidation;
|
||||
|
||||
const batterySources = types.battery || [];
|
||||
protected render(): TemplateResult {
|
||||
const batterySources: BatterySourceTypeEnergyPreference[] = [];
|
||||
const batteryValidation: EnergyValidationIssue[][] = [];
|
||||
|
||||
this.preferences.energy_sources.forEach((source, idx) => {
|
||||
if (source.type !== "battery") {
|
||||
return;
|
||||
}
|
||||
batterySources.push(source);
|
||||
|
||||
if (this.validationResult) {
|
||||
batteryValidation.push(this.validationResult.energy_sources[idx]);
|
||||
}
|
||||
});
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
@@ -54,6 +69,16 @@ export class EnergyBatterySettings extends LitElement {
|
||||
)}</a
|
||||
>
|
||||
</p>
|
||||
${batteryValidation.map(
|
||||
(result) =>
|
||||
html`
|
||||
<ha-energy-validation-result
|
||||
.hass=${this.hass}
|
||||
.issues=${result}
|
||||
></ha-energy-validation-result>
|
||||
`
|
||||
)}
|
||||
|
||||
<h3>Battery systems</h3>
|
||||
${batterySources.map((source) => {
|
||||
const fromEntityState = this.hass.states[source.stat_energy_from];
|
||||
|
||||
@@ -9,6 +9,7 @@ import "../../../../components/ha-card";
|
||||
import {
|
||||
DeviceConsumptionEnergyPreference,
|
||||
EnergyPreferences,
|
||||
EnergyPreferencesValidation,
|
||||
saveEnergyPreferences,
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
@@ -19,6 +20,7 @@ import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { documentationUrl } from "../../../../util/documentation-url";
|
||||
import { showEnergySettingsDeviceDialog } from "../dialogs/show-dialogs-energy";
|
||||
import "./ha-energy-validation-result";
|
||||
import { energyCardStyles } from "./styles";
|
||||
|
||||
@customElement("ha-energy-device-settings")
|
||||
@@ -28,6 +30,9 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public preferences!: EnergyPreferences;
|
||||
|
||||
@property({ attribute: false })
|
||||
public validationResult?: EnergyPreferencesValidation;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card>
|
||||
@@ -55,6 +60,15 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
)}</a
|
||||
>
|
||||
</p>
|
||||
${this.validationResult?.device_consumption.map(
|
||||
(result) =>
|
||||
html`
|
||||
<ha-energy-validation-result
|
||||
.hass=${this.hass}
|
||||
.issues=${result}
|
||||
></ha-energy-validation-result>
|
||||
`
|
||||
)}
|
||||
<h3>Devices</h3>
|
||||
${this.preferences.device_consumption.map((device) => {
|
||||
const entityState = this.hass.states[device.stat_consumption];
|
||||
|
||||
@@ -7,9 +7,10 @@ import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import "../../../../components/ha-card";
|
||||
import {
|
||||
EnergyPreferences,
|
||||
energySourcesByType,
|
||||
saveEnergyPreferences,
|
||||
GasSourceTypeEnergyPreference,
|
||||
EnergyPreferencesValidation,
|
||||
EnergyValidationIssue,
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
@@ -19,6 +20,7 @@ import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { documentationUrl } from "../../../../util/documentation-url";
|
||||
import { showEnergySettingsGasDialog } from "../dialogs/show-dialogs-energy";
|
||||
import "./ha-energy-validation-result";
|
||||
import { energyCardStyles } from "./styles";
|
||||
|
||||
@customElement("ha-energy-gas-settings")
|
||||
@@ -28,10 +30,23 @@ export class EnergyGasSettings extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public preferences!: EnergyPreferences;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const types = energySourcesByType(this.preferences);
|
||||
@property({ attribute: false })
|
||||
public validationResult?: EnergyPreferencesValidation;
|
||||
|
||||
const gasSources = types.gas || [];
|
||||
protected render(): TemplateResult {
|
||||
const gasSources: GasSourceTypeEnergyPreference[] = [];
|
||||
const gasValidation: EnergyValidationIssue[][] = [];
|
||||
|
||||
this.preferences.energy_sources.forEach((source, idx) => {
|
||||
if (source.type !== "gas") {
|
||||
return;
|
||||
}
|
||||
gasSources.push(source);
|
||||
|
||||
if (this.validationResult) {
|
||||
gasValidation.push(this.validationResult.energy_sources[idx]);
|
||||
}
|
||||
});
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
@@ -50,6 +65,15 @@ export class EnergyGasSettings extends LitElement {
|
||||
>${this.hass.localize("ui.panel.config.energy.gas.learn_more")}</a
|
||||
>
|
||||
</p>
|
||||
${gasValidation.map(
|
||||
(result) =>
|
||||
html`
|
||||
<ha-energy-validation-result
|
||||
.hass=${this.hass}
|
||||
.issues=${result}
|
||||
></ha-energy-validation-result>
|
||||
`
|
||||
)}
|
||||
<h3>Gas consumption</h3>
|
||||
${gasSources.map((source) => {
|
||||
const entityState = this.hass.states[source.stat_energy_from];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user