mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
commit
038f7b43d5
@ -980,12 +980,12 @@ export const demoEntitiesArsaboo: () => Entity[] = () =>
|
|||||||
entity_id: "climate.upstairs",
|
entity_id: "climate.upstairs",
|
||||||
state: "auto",
|
state: "auto",
|
||||||
attributes: {
|
attributes: {
|
||||||
current_temperature: 66,
|
current_temperature: 22,
|
||||||
min_temp: 45,
|
min_temp: 15,
|
||||||
max_temp: 95,
|
max_temp: 30,
|
||||||
temperature: null,
|
temperature: null,
|
||||||
target_temp_high: 79,
|
target_temp_high: 24,
|
||||||
target_temp_low: 66,
|
target_temp_low: 20,
|
||||||
fan_mode: "auto",
|
fan_mode: "auto",
|
||||||
fan_list: ["auto", "on"],
|
fan_list: ["auto", "on"],
|
||||||
operation_mode: "auto",
|
operation_mode: "auto",
|
||||||
|
@ -19,7 +19,7 @@ module.exports = {
|
|||||||
devtool: isProd ? "cheap-source-map" : "inline-source-map",
|
devtool: isProd ? "cheap-source-map" : "inline-source-map",
|
||||||
entry: {
|
entry: {
|
||||||
main: "./src/entrypoint.ts",
|
main: "./src/entrypoint.ts",
|
||||||
compatibility: "../src/entrypoints/compatibility.js",
|
compatibility: "../src/entrypoints/compatibility.ts",
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
@ -44,7 +44,7 @@ function transformXMLtoPolymer(name, xml) {
|
|||||||
|
|
||||||
// Given an iconset name and icon names, generate a polymer iconset
|
// Given an iconset name and icon names, generate a polymer iconset
|
||||||
function generateIconset(name, iconNames) {
|
function generateIconset(name, iconNames) {
|
||||||
const iconDefs = iconNames
|
const iconDefs = Array.from(iconNames)
|
||||||
.map((name) => {
|
.map((name) => {
|
||||||
const iconDef = loadIcon(name);
|
const iconDef = loadIcon(name);
|
||||||
if (!iconDef) {
|
if (!iconDef) {
|
||||||
@ -95,18 +95,27 @@ function findIcons(path, iconsetName) {
|
|||||||
}
|
}
|
||||||
mapFiles(path, ".js", processFile);
|
mapFiles(path, ".js", processFile);
|
||||||
mapFiles(path, ".ts", processFile);
|
mapFiles(path, ".ts", processFile);
|
||||||
return Array.from(icons);
|
return icons;
|
||||||
}
|
}
|
||||||
|
|
||||||
function genHassIcons() {
|
function genHassIcons() {
|
||||||
const iconNames = findIcons("./src", "hass").concat(BUILT_IN_PANEL_ICONS);
|
const iconNames = findIcons("./src", "hass");
|
||||||
fs.existsSync(OUTPUT_DIR) || fs.mkdirSync(OUTPUT_DIR);
|
BUILT_IN_PANEL_ICONS.forEach((name) => iconNames.add(name));
|
||||||
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||||
|
fs.mkdirSync(OUTPUT_DIR);
|
||||||
|
}
|
||||||
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset("hass", iconNames));
|
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset("hass", iconNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
gulp.task("gen-icons-mdi", () => genMDIIcons());
|
gulp.task("gen-icons-mdi", (done) => {
|
||||||
gulp.task("gen-icons-hass", () => genHassIcons());
|
genMDIIcons();
|
||||||
gulp.task("gen-icons", ["gen-icons-hass", "gen-icons-mdi"], () => {});
|
done();
|
||||||
|
});
|
||||||
|
gulp.task("gen-icons-hass", (done) => {
|
||||||
|
genHassIcons();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
gulp.task("gen-icons", gulp.series("gen-icons-hass", "gen-icons-mdi"));
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
findIcons,
|
findIcons,
|
||||||
|
@ -118,198 +118,219 @@ tasks.push(taskName);
|
|||||||
* the Lokalise update to translations/en.json will not happen immediately.
|
* the Lokalise update to translations/en.json will not happen immediately.
|
||||||
*/
|
*/
|
||||||
taskName = "build-master-translation";
|
taskName = "build-master-translation";
|
||||||
gulp.task(taskName, ["clean-translations"], function() {
|
gulp.task(
|
||||||
return gulp
|
taskName,
|
||||||
.src("src/translations/en.json")
|
gulp.series("clean-translations", function() {
|
||||||
.pipe(
|
return gulp
|
||||||
transform(function(data, file) {
|
.src("src/translations/en.json")
|
||||||
return lokalise_transform(data, data);
|
.pipe(
|
||||||
})
|
transform(function(data, file) {
|
||||||
)
|
return lokalise_transform(data, data);
|
||||||
.pipe(rename("translationMaster.json"))
|
})
|
||||||
.pipe(gulp.dest(workDir));
|
)
|
||||||
});
|
.pipe(rename("translationMaster.json"))
|
||||||
|
.pipe(gulp.dest(workDir));
|
||||||
|
})
|
||||||
|
);
|
||||||
tasks.push(taskName);
|
tasks.push(taskName);
|
||||||
|
|
||||||
taskName = "build-merged-translations";
|
taskName = "build-merged-translations";
|
||||||
gulp.task(taskName, ["build-master-translation"], function() {
|
gulp.task(
|
||||||
return gulp.src(inDir + "/*.json").pipe(
|
taskName,
|
||||||
foreach(function(stream, file) {
|
gulp.series("build-master-translation", function() {
|
||||||
// For each language generate a merged json file. It begins with the master
|
return gulp.src(inDir + "/*.json").pipe(
|
||||||
// translation as a failsafe for untranslated strings, and merges all parent
|
foreach(function(stream, file) {
|
||||||
// tags into one file for each specific subtag
|
// For each language generate a merged json file. It begins with the master
|
||||||
//
|
// translation as a failsafe for untranslated strings, and merges all parent
|
||||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
// tags into one file for each specific subtag
|
||||||
// Will be OK for now as long as we don't have anything more complicated
|
//
|
||||||
// than a base translation + region.
|
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
||||||
const tr = path.basename(file.history[0], ".json");
|
// Will be OK for now as long as we don't have anything more complicated
|
||||||
const subtags = tr.split("-");
|
// than a base translation + region.
|
||||||
const src = [workDir + "/translationMaster.json"];
|
const tr = path.basename(file.history[0], ".json");
|
||||||
for (let i = 1; i <= subtags.length; i++) {
|
const subtags = tr.split("-");
|
||||||
const lang = subtags.slice(0, i).join("-");
|
const src = [workDir + "/translationMaster.json"];
|
||||||
src.push(inDir + "/" + lang + ".json");
|
for (let i = 1; i <= subtags.length; i++) {
|
||||||
}
|
const lang = subtags.slice(0, i).join("-");
|
||||||
return gulp
|
src.push(inDir + "/" + lang + ".json");
|
||||||
.src(src)
|
}
|
||||||
.pipe(transform((data) => emptyFilter(data)))
|
return gulp
|
||||||
.pipe(
|
.src(src, { allowEmpty: true })
|
||||||
merge({
|
.pipe(transform((data) => emptyFilter(data)))
|
||||||
fileName: tr + ".json",
|
.pipe(
|
||||||
})
|
merge({
|
||||||
)
|
fileName: tr + ".json",
|
||||||
.pipe(gulp.dest(fullDir));
|
})
|
||||||
})
|
)
|
||||||
);
|
.pipe(gulp.dest(fullDir));
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
tasks.push(taskName);
|
tasks.push(taskName);
|
||||||
|
|
||||||
const splitTasks = [];
|
const splitTasks = [];
|
||||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||||
taskName = "build-translation-fragment-" + fragment;
|
taskName = "build-translation-fragment-" + fragment;
|
||||||
gulp.task(taskName, ["build-merged-translations"], function() {
|
gulp.task(
|
||||||
// Return only the translations for this fragment.
|
taskName,
|
||||||
return gulp
|
gulp.series("build-merged-translations", function() {
|
||||||
.src(fullDir + "/*.json")
|
// Return only the translations for this fragment.
|
||||||
.pipe(
|
return gulp
|
||||||
transform((data) => ({
|
.src(fullDir + "/*.json")
|
||||||
ui: {
|
.pipe(
|
||||||
panel: {
|
transform((data) => ({
|
||||||
[fragment]: data.ui.panel[fragment],
|
ui: {
|
||||||
|
panel: {
|
||||||
|
[fragment]: data.ui.panel[fragment],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}))
|
||||||
}))
|
)
|
||||||
)
|
.pipe(gulp.dest(workDir + "/" + fragment));
|
||||||
.pipe(gulp.dest(workDir + "/" + fragment));
|
})
|
||||||
});
|
);
|
||||||
tasks.push(taskName);
|
tasks.push(taskName);
|
||||||
splitTasks.push(taskName);
|
splitTasks.push(taskName);
|
||||||
});
|
});
|
||||||
|
|
||||||
taskName = "build-translation-core";
|
taskName = "build-translation-core";
|
||||||
gulp.task(taskName, ["build-merged-translations"], function() {
|
gulp.task(
|
||||||
// Remove the fragment translations from the core translation.
|
taskName,
|
||||||
return gulp
|
gulp.series("build-merged-translations", function() {
|
||||||
.src(fullDir + "/*.json")
|
// Remove the fragment translations from the core translation.
|
||||||
.pipe(
|
return gulp
|
||||||
transform((data) => {
|
.src(fullDir + "/*.json")
|
||||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
.pipe(
|
||||||
delete data.ui.panel[fragment];
|
transform((data) => {
|
||||||
});
|
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||||
return data;
|
delete data.ui.panel[fragment];
|
||||||
})
|
});
|
||||||
)
|
return data;
|
||||||
.pipe(gulp.dest(coreDir));
|
})
|
||||||
});
|
)
|
||||||
|
.pipe(gulp.dest(coreDir));
|
||||||
|
})
|
||||||
|
);
|
||||||
tasks.push(taskName);
|
tasks.push(taskName);
|
||||||
splitTasks.push(taskName);
|
splitTasks.push(taskName);
|
||||||
|
|
||||||
taskName = "build-flattened-translations";
|
taskName = "build-flattened-translations";
|
||||||
gulp.task(taskName, splitTasks, function() {
|
gulp.task(
|
||||||
// Flatten the split versions of our translations, and move them into outDir
|
taskName,
|
||||||
return gulp
|
gulp.series(...splitTasks, function() {
|
||||||
.src(
|
// Flatten the split versions of our translations, and move them into outDir
|
||||||
TRANSLATION_FRAGMENTS.map(
|
return gulp
|
||||||
(fragment) => workDir + "/" + fragment + "/*.json"
|
.src(
|
||||||
).concat(coreDir + "/*.json"),
|
TRANSLATION_FRAGMENTS.map(
|
||||||
{ base: workDir }
|
(fragment) => workDir + "/" + fragment + "/*.json"
|
||||||
)
|
).concat(coreDir + "/*.json"),
|
||||||
.pipe(
|
{ base: workDir }
|
||||||
transform(function(data) {
|
)
|
||||||
// Polymer.AppLocalizeBehavior requires flattened json
|
.pipe(
|
||||||
return flatten(data);
|
transform(function(data) {
|
||||||
})
|
// Polymer.AppLocalizeBehavior requires flattened json
|
||||||
)
|
return flatten(data);
|
||||||
.pipe(minify())
|
})
|
||||||
.pipe(hashFilename())
|
)
|
||||||
.pipe(
|
.pipe(minify())
|
||||||
rename((filePath) => {
|
.pipe(hashFilename())
|
||||||
if (filePath.dirname === "core") {
|
.pipe(
|
||||||
filePath.dirname = "";
|
rename((filePath) => {
|
||||||
}
|
if (filePath.dirname === "core") {
|
||||||
})
|
filePath.dirname = "";
|
||||||
)
|
}
|
||||||
.pipe(gulp.dest(outDir));
|
})
|
||||||
});
|
)
|
||||||
|
.pipe(gulp.dest(outDir));
|
||||||
|
})
|
||||||
|
);
|
||||||
tasks.push(taskName);
|
tasks.push(taskName);
|
||||||
|
|
||||||
taskName = "build-translation-fingerprints";
|
taskName = "build-translation-fingerprints";
|
||||||
gulp.task(taskName, ["build-flattened-translations"], function() {
|
gulp.task(
|
||||||
return gulp
|
taskName,
|
||||||
.src(outDir + "/**/*.json")
|
gulp.series("build-flattened-translations", function() {
|
||||||
.pipe(
|
return gulp
|
||||||
rename({
|
.src(outDir + "/**/*.json")
|
||||||
extname: "",
|
.pipe(
|
||||||
})
|
rename({
|
||||||
)
|
extname: "",
|
||||||
.pipe(
|
})
|
||||||
hash({
|
)
|
||||||
algorithm: "md5",
|
.pipe(
|
||||||
hashLength: 32,
|
hash({
|
||||||
template: "<%= name %>.json",
|
algorithm: "md5",
|
||||||
})
|
hashLength: 32,
|
||||||
)
|
template: "<%= name %>.json",
|
||||||
.pipe(hash.manifest("translationFingerprints.json"))
|
})
|
||||||
.pipe(
|
)
|
||||||
transform(function(data) {
|
.pipe(hash.manifest("translationFingerprints.json"))
|
||||||
// After generating fingerprints of our translation files, consolidate
|
.pipe(
|
||||||
// all translation fragment fingerprints under the translation name key
|
transform(function(data) {
|
||||||
const newData = {};
|
// After generating fingerprints of our translation files, consolidate
|
||||||
Object.entries(data).forEach(([key, value]) => {
|
// all translation fragment fingerprints under the translation name key
|
||||||
const [path, _md5] = key.rsplit("-", 1);
|
const newData = {};
|
||||||
// let translation = key;
|
Object.entries(data).forEach(([key, value]) => {
|
||||||
let translation = path;
|
const [path, _md5] = key.rsplit("-", 1);
|
||||||
const parts = translation.split("/");
|
// let translation = key;
|
||||||
if (parts.length === 2) {
|
let translation = path;
|
||||||
translation = parts[1];
|
const parts = translation.split("/");
|
||||||
}
|
if (parts.length === 2) {
|
||||||
if (!(translation in newData)) {
|
translation = parts[1];
|
||||||
newData[translation] = {
|
}
|
||||||
fingerprints: {},
|
if (!(translation in newData)) {
|
||||||
};
|
newData[translation] = {
|
||||||
}
|
fingerprints: {},
|
||||||
newData[translation].fingerprints[path] = value;
|
};
|
||||||
});
|
}
|
||||||
return newData;
|
newData[translation].fingerprints[path] = value;
|
||||||
})
|
});
|
||||||
)
|
return newData;
|
||||||
.pipe(gulp.dest(workDir));
|
})
|
||||||
});
|
)
|
||||||
|
.pipe(gulp.dest(workDir));
|
||||||
|
})
|
||||||
|
);
|
||||||
tasks.push(taskName);
|
tasks.push(taskName);
|
||||||
|
|
||||||
taskName = "build-translations";
|
taskName = "build-translations";
|
||||||
gulp.task(taskName, ["build-translation-fingerprints"], function() {
|
gulp.task(
|
||||||
return gulp
|
taskName,
|
||||||
.src([
|
gulp.series("build-translation-fingerprints", function() {
|
||||||
"src/translations/translationMetadata.json",
|
return gulp
|
||||||
workDir + "/translationFingerprints.json",
|
.src([
|
||||||
])
|
"src/translations/translationMetadata.json",
|
||||||
.pipe(merge({}))
|
workDir + "/translationFingerprints.json",
|
||||||
.pipe(
|
])
|
||||||
transform(function(data) {
|
.pipe(merge({}))
|
||||||
const newData = {};
|
.pipe(
|
||||||
Object.entries(data).forEach(([key, value]) => {
|
transform(function(data) {
|
||||||
// Filter out translations without native name.
|
const newData = {};
|
||||||
if (data[key].nativeName) {
|
Object.entries(data).forEach(([key, value]) => {
|
||||||
newData[key] = data[key];
|
// Filter out translations without native name.
|
||||||
} else {
|
if (data[key].nativeName) {
|
||||||
console.warn(
|
newData[key] = data[key];
|
||||||
`Skipping language ${key}. Native name was not translated.`
|
} else {
|
||||||
);
|
console.warn(
|
||||||
}
|
`Skipping language ${key}. Native name was not translated.`
|
||||||
if (data[key]) newData[key] = value;
|
);
|
||||||
});
|
}
|
||||||
return newData;
|
if (data[key]) newData[key] = value;
|
||||||
})
|
});
|
||||||
)
|
return newData;
|
||||||
.pipe(
|
})
|
||||||
transform((data) => ({
|
)
|
||||||
fragments: TRANSLATION_FRAGMENTS,
|
.pipe(
|
||||||
translations: data,
|
transform((data) => ({
|
||||||
}))
|
fragments: TRANSLATION_FRAGMENTS,
|
||||||
)
|
translations: data,
|
||||||
.pipe(rename("translationMetadata.json"))
|
}))
|
||||||
.pipe(gulp.dest(workDir));
|
)
|
||||||
});
|
.pipe(rename("translationMetadata.json"))
|
||||||
|
.pipe(gulp.dest(workDir));
|
||||||
|
})
|
||||||
|
);
|
||||||
tasks.push(taskName);
|
tasks.push(taskName);
|
||||||
|
|
||||||
module.exports = tasks;
|
module.exports = tasks;
|
||||||
|
@ -6,10 +6,13 @@ const {
|
|||||||
genMDIIcons,
|
genMDIIcons,
|
||||||
} = require("../../gulp/tasks/gen-icons.js");
|
} = require("../../gulp/tasks/gen-icons.js");
|
||||||
|
|
||||||
const MENU_BUTTON_ICON = "menu";
|
|
||||||
|
|
||||||
function genHassioIcons() {
|
function genHassioIcons() {
|
||||||
const iconNames = findIcons("./src", "hassio").concat(MENU_BUTTON_ICON);
|
const iconNames = findIcons("./src", "hassio");
|
||||||
|
|
||||||
|
for (const item of findIcons("../src", "hassio")) {
|
||||||
|
iconNames.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync("./hassio-icons.html", generateIconset("hassio", iconNames));
|
fs.writeFileSync("./hassio-icons.html", generateIconset("hassio", iconNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
import "@polymer/paper-card/paper-card";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../components/hassio-card-content";
|
|
||||||
import "../resources/hassio-style";
|
|
||||||
import NavigateMixin from "../../../src/mixins/navigate-mixin";
|
|
||||||
|
|
||||||
class HassioAddonRepository extends NavigateMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex ha-style hassio-style">
|
|
||||||
paper-card {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.not_available {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
a.repo {
|
|
||||||
display: block;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<template is="dom-if" if="[[addons.length]]">
|
|
||||||
<div class="card-group">
|
|
||||||
<div class="title">
|
|
||||||
[[repo.name]]
|
|
||||||
<div class="description">
|
|
||||||
Maintained by [[repo.maintainer]]
|
|
||||||
<a class="repo" href="[[repo.url]]" target="_blank"
|
|
||||||
>[[repo.url]]</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template
|
|
||||||
is="dom-repeat"
|
|
||||||
items="[[addons]]"
|
|
||||||
as="addon"
|
|
||||||
sort="sortAddons"
|
|
||||||
>
|
|
||||||
<paper-card class$="[[computeClass(addon)]]" on-click="addonTapped">
|
|
||||||
<div class="card-content">
|
|
||||||
<hassio-card-content
|
|
||||||
hass="[[hass]]"
|
|
||||||
title="[[addon.name]]"
|
|
||||||
description="[[addon.description]]"
|
|
||||||
available="[[addon.available]]"
|
|
||||||
icon="[[computeIcon(addon)]]"
|
|
||||||
icon-title="[[computeIconTitle(addon)]]"
|
|
||||||
icon-class="[[computeIconClass(addon)]]"
|
|
||||||
></hassio-card-content>
|
|
||||||
</div>
|
|
||||||
</paper-card>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
repo: Object,
|
|
||||||
addons: Array,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sortAddons(a, b) {
|
|
||||||
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeIcon(addon) {
|
|
||||||
return addon.installed && addon.installed !== addon.version
|
|
||||||
? "hassio:arrow-up-bold-circle"
|
|
||||||
: "hassio:puzzle";
|
|
||||||
}
|
|
||||||
|
|
||||||
computeIconTitle(addon) {
|
|
||||||
if (addon.installed)
|
|
||||||
return addon.installed !== addon.version
|
|
||||||
? "New version available"
|
|
||||||
: "Add-on is installed";
|
|
||||||
return addon.available
|
|
||||||
? "Add-on is not installed"
|
|
||||||
: "Add-on is not available on your system";
|
|
||||||
}
|
|
||||||
|
|
||||||
computeIconClass(addon) {
|
|
||||||
if (addon.installed)
|
|
||||||
return addon.installed !== addon.version ? "update" : "installed";
|
|
||||||
return !addon.available ? "not_available" : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
computeClass(addon) {
|
|
||||||
return !addon.available ? "not_available" : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
addonTapped(ev) {
|
|
||||||
this.navigate(`/hassio/addon/${ev.model.addon.slug}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hassio-addon-repository", HassioAddonRepository);
|
|
112
hassio/src/addon-store/hassio-addon-repository.ts
Normal file
112
hassio/src/addon-store/hassio-addon-repository.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
CSSResultArray,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/paper-card/paper-card";
|
||||||
|
|
||||||
|
import "../components/hassio-card-content";
|
||||||
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
import {
|
||||||
|
HassioAddonInfo,
|
||||||
|
HassioAddonRepository,
|
||||||
|
} from "../../../src/data/hassio";
|
||||||
|
import { navigate } from "../../../src/common/navigate";
|
||||||
|
|
||||||
|
class HassioAddonRepositoryEl extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public repo!: HassioAddonRepository;
|
||||||
|
@property() public addons!: HassioAddonInfo[];
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
const repo = this.repo;
|
||||||
|
return html`
|
||||||
|
<div class="card-group">
|
||||||
|
<div class="title">
|
||||||
|
${repo.name}
|
||||||
|
<div class="description">
|
||||||
|
Maintained by ${repo.maintainer}<br />
|
||||||
|
<a class="repo" href=${repo.url} target="_blank">${repo.url}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this.addons
|
||||||
|
.sort((a, b) =>
|
||||||
|
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(addon) => html`
|
||||||
|
<paper-card
|
||||||
|
.addon=${addon}
|
||||||
|
class=${addon.available ? "" : "not_available"}
|
||||||
|
@click=${this.addonTapped}
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
<hassio-card-content
|
||||||
|
.hass=${this.hass}
|
||||||
|
.title=${addon.name}
|
||||||
|
.description=${addon.description}
|
||||||
|
.available=${addon.available}
|
||||||
|
.icon=${this.computeIcon(addon)}
|
||||||
|
.iconTitle=${this.computeIconTitle(addon)}
|
||||||
|
.iconClass=${this.computeIconClass(addon)}
|
||||||
|
></hassio-card-content>
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeIcon(addon) {
|
||||||
|
return addon.installed && addon.installed !== addon.version
|
||||||
|
? "hassio:arrow-up-bold-circle"
|
||||||
|
: "hassio:puzzle";
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeIconTitle(addon) {
|
||||||
|
if (addon.installed) {
|
||||||
|
return addon.installed !== addon.version
|
||||||
|
? "New version available"
|
||||||
|
: "Add-on is installed";
|
||||||
|
}
|
||||||
|
return addon.available
|
||||||
|
? "Add-on is not installed"
|
||||||
|
: "Add-on is not available on your system";
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeIconClass(addon) {
|
||||||
|
if (addon.installed) {
|
||||||
|
return addon.installed !== addon.version ? "update" : "installed";
|
||||||
|
}
|
||||||
|
return !addon.available ? "not_available" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private addonTapped(ev) {
|
||||||
|
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultArray {
|
||||||
|
return [
|
||||||
|
hassioStyle,
|
||||||
|
css`
|
||||||
|
paper-card {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.not_available {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
a.repo {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("hassio-addon-repository", HassioAddonRepositoryEl);
|
@ -1,92 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "./hassio-addon-repository";
|
|
||||||
import "./hassio-repositories-editor";
|
|
||||||
|
|
||||||
class HassioAddonStore extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex ha-style">
|
|
||||||
hassio-addon-repository {
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<hassio-repositories-editor
|
|
||||||
hass="[[hass]]"
|
|
||||||
repos="[[repos]]"
|
|
||||||
></hassio-repositories-editor>
|
|
||||||
|
|
||||||
<template is="dom-repeat" items="[[repos]]" as="repo" sort="sortRepos">
|
|
||||||
<hassio-addon-repository
|
|
||||||
hass="[[hass]]"
|
|
||||||
repo="[[repo]]"
|
|
||||||
addons="[[computeAddons(repo.slug)]]"
|
|
||||||
></hassio-addon-repository>
|
|
||||||
</template>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
addons: Array,
|
|
||||||
repos: Array,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
|
||||||
this.loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
apiCalled(ev) {
|
|
||||||
if (ev.detail.success) {
|
|
||||||
this.loadData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sortRepos(a, b) {
|
|
||||||
if (a.slug === "local") {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (b.slug === "local") {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (a.slug === "core") {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (b.slug === "core") {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeAddons(repo) {
|
|
||||||
return this.addons.filter(function(addon) {
|
|
||||||
return addon.repository === repo;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadData() {
|
|
||||||
this.hass.callApi("get", "hassio/addons").then(
|
|
||||||
(info) => {
|
|
||||||
this.addons = info.data.addons;
|
|
||||||
this.repos = info.data.repositories;
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.addons = [];
|
|
||||||
this.repos = [];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshData() {
|
|
||||||
this.hass.callApi("post", "hassio/addons/reload").then(() => {
|
|
||||||
this.loadData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hassio-addon-store", HassioAddonStore);
|
|
116
hassio/src/addon-store/hassio-addon-store.ts
Normal file
116
hassio/src/addon-store/hassio-addon-store.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import "./hassio-addon-repository";
|
||||||
|
import "./hassio-repositories-editor";
|
||||||
|
import { TemplateResult, html } from "lit-html";
|
||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
import {
|
||||||
|
HassioAddonRepository,
|
||||||
|
HassioAddonInfo,
|
||||||
|
fetchHassioAddonsInfo,
|
||||||
|
reloadHassioAddons,
|
||||||
|
} from "../../../src/data/hassio";
|
||||||
|
import "../../../src/layouts/loading-screen";
|
||||||
|
|
||||||
|
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||||
|
if (a.slug === "local") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b.slug === "local") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (a.slug === "core") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b.slug === "core") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HassioAddonStore extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() private _addons?: HassioAddonInfo[];
|
||||||
|
@property() private _repos?: HassioAddonRepository[];
|
||||||
|
|
||||||
|
public async refreshData() {
|
||||||
|
this._repos = undefined;
|
||||||
|
this._addons = undefined;
|
||||||
|
await reloadHassioAddons(this.hass);
|
||||||
|
await this._loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this._addons || !this._repos) {
|
||||||
|
return html`
|
||||||
|
<loading-screen></loading-screen>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
const repos: TemplateResult[] = [];
|
||||||
|
|
||||||
|
for (const repo of this._repos) {
|
||||||
|
const addons = this._addons!.filter(
|
||||||
|
(addon) => addon.repository === repo.slug
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addons.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
repos.push(html`
|
||||||
|
<hassio-addon-repository
|
||||||
|
.hass=${this.hass}
|
||||||
|
.repo=${repo}
|
||||||
|
.addons=${addons}
|
||||||
|
></hassio-addon-repository>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hassio-repositories-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.repos=${this._repos}
|
||||||
|
></hassio-repositories-editor>
|
||||||
|
|
||||||
|
${repos}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||||
|
this._loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private apiCalled(ev) {
|
||||||
|
if (ev.detail.success) {
|
||||||
|
this._loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadData() {
|
||||||
|
try {
|
||||||
|
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||||
|
this._repos = addonsInfo.repositories;
|
||||||
|
this._repos.sort(sortRepos);
|
||||||
|
this._addons = addonsInfo.addons;
|
||||||
|
} catch (err) {
|
||||||
|
alert("Failed to fetch add-on info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
hassio-addon-repository {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("hassio-addon-store", HassioAddonStore);
|
@ -1,120 +0,0 @@
|
|||||||
import "@polymer/iron-icon/iron-icon";
|
|
||||||
import "@polymer/paper-card/paper-card";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../../src/components/buttons/ha-call-api-button";
|
|
||||||
import "../components/hassio-card-content";
|
|
||||||
import "../resources/hassio-style";
|
|
||||||
|
|
||||||
class HassioRepositoriesEditor extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style hassio-style">
|
|
||||||
.add {
|
|
||||||
padding: 12px 16px;
|
|
||||||
}
|
|
||||||
iron-icon {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
margin-right: 16px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
paper-input {
|
|
||||||
width: calc(100% - 49px);
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="card-group">
|
|
||||||
<div class="title">
|
|
||||||
Repositories
|
|
||||||
<div class="description">
|
|
||||||
Configure which add-on repositories to fetch data from:
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template
|
|
||||||
id="list"
|
|
||||||
is="dom-repeat"
|
|
||||||
items="[[repoList]]"
|
|
||||||
as="repo"
|
|
||||||
sort="sortRepos"
|
|
||||||
>
|
|
||||||
<paper-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<hassio-card-content
|
|
||||||
hass="[[hass]]"
|
|
||||||
title="[[repo.name]]"
|
|
||||||
description="[[repo.url]]"
|
|
||||||
icon="hassio:github-circle"
|
|
||||||
></hassio-card-content>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<ha-call-api-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
path="hassio/supervisor/options"
|
|
||||||
data="[[computeRemoveRepoData(repoList, repo.url)]]"
|
|
||||||
class="warning"
|
|
||||||
>Remove</ha-call-api-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</paper-card>
|
|
||||||
</template>
|
|
||||||
<paper-card>
|
|
||||||
<div class="card-content add">
|
|
||||||
<iron-icon icon="hassio:github-circle"></iron-icon>
|
|
||||||
<paper-input
|
|
||||||
label="Add new repository by URL"
|
|
||||||
value="{{repoUrl}}"
|
|
||||||
></paper-input>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<ha-call-api-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
path="hassio/supervisor/options"
|
|
||||||
data="[[computeAddRepoData(repoList, repoUrl)]]"
|
|
||||||
>Add</ha-call-api-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</paper-card>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
repos: {
|
|
||||||
type: Array,
|
|
||||||
observer: "reposChanged",
|
|
||||||
},
|
|
||||||
repoList: Array,
|
|
||||||
repoUrl: String,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
reposChanged(repos) {
|
|
||||||
this.repoList = repos.filter(
|
|
||||||
(repo) => repo.slug !== "core" && repo.slug !== "local"
|
|
||||||
);
|
|
||||||
this.repoUrl = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
sortRepos(a, b) {
|
|
||||||
return a.name < b.name ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeRemoveRepoData(repoList, url) {
|
|
||||||
const list = repoList
|
|
||||||
.filter((repo) => repo.url !== url)
|
|
||||||
.map((repo) => repo.source);
|
|
||||||
return { addons_repositories: list };
|
|
||||||
}
|
|
||||||
|
|
||||||
computeAddRepoData(repoList, url) {
|
|
||||||
const list = repoList ? repoList.map((repo) => repo.source) : [];
|
|
||||||
list.push(url);
|
|
||||||
return { addons_repositories: list };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hassio-repositories-editor", HassioRepositoriesEditor);
|
|
148
hassio/src/addon-store/hassio-repositories-editor.ts
Normal file
148
hassio/src/addon-store/hassio-repositories-editor.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
CSSResultArray,
|
||||||
|
css,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/iron-icon/iron-icon";
|
||||||
|
import "@polymer/paper-card/paper-card";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
|
import "../../../src/components/buttons/ha-call-api-button";
|
||||||
|
import "../components/hassio-card-content";
|
||||||
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
import { HassioAddonRepository } from "../../../src/data/hassio";
|
||||||
|
import { PolymerChangedEvent } from "../../../src/polymer-types";
|
||||||
|
import { repeat } from "lit-html/directives/repeat";
|
||||||
|
|
||||||
|
@customElement("hassio-repositories-editor")
|
||||||
|
class HassioRepositoriesEditor extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public repos!: HassioAddonRepository[];
|
||||||
|
@property() private _repoUrl = "";
|
||||||
|
|
||||||
|
private _sortedRepos = memoizeOne((repos: HassioAddonRepository[]) =>
|
||||||
|
repos
|
||||||
|
.filter((repo) => repo.slug !== "core" && repo.slug !== "local")
|
||||||
|
.sort((a, b) => (a.name < b.name ? -1 : 1))
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
const repos = this._sortedRepos(this.repos);
|
||||||
|
return html`
|
||||||
|
<div class="card-group">
|
||||||
|
<div class="title">
|
||||||
|
Repositories
|
||||||
|
<div class="description">
|
||||||
|
Configure which add-on repositories to fetch data from:
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${// Use repeat so that the fade-out from call-service-api-button
|
||||||
|
// stays with the correct repo after we add/delete one.
|
||||||
|
repeat(
|
||||||
|
repos,
|
||||||
|
(repo) => repo.slug,
|
||||||
|
(repo) => html`
|
||||||
|
<paper-card>
|
||||||
|
<div class="card-content">
|
||||||
|
<hassio-card-content
|
||||||
|
.hass=${this.hass}
|
||||||
|
.title=${repo.name}
|
||||||
|
.description=${repo.url}
|
||||||
|
icon="hassio:github-circle"
|
||||||
|
></hassio-card-content>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-call-api-button
|
||||||
|
path="hassio/supervisor/options"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this.computeRemoveRepoData(repos, repo.url)}
|
||||||
|
class="warning"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</ha-call-api-button>
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
|
||||||
|
<paper-card>
|
||||||
|
<div class="card-content add">
|
||||||
|
<iron-icon icon="hassio:github-circle"></iron-icon>
|
||||||
|
<paper-input
|
||||||
|
label="Add new repository by URL"
|
||||||
|
.value=${this._repoUrl}
|
||||||
|
@value-changed=${this._urlChanged}
|
||||||
|
></paper-input>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-call-api-button
|
||||||
|
path="hassio/supervisor/options"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this.computeAddRepoData(repos, this._repoUrl)}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</ha-call-api-button>
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
|
||||||
|
if (changedProps.has("repos")) {
|
||||||
|
this._repoUrl = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _urlChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
this._repoUrl = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeRemoveRepoData(repoList, url) {
|
||||||
|
const list = repoList
|
||||||
|
.filter((repo) => repo.url !== url)
|
||||||
|
.map((repo) => repo.source);
|
||||||
|
return { addons_repositories: list };
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeAddRepoData(repoList, url) {
|
||||||
|
const list = repoList ? repoList.map((repo) => repo.source) : [];
|
||||||
|
list.push(url);
|
||||||
|
return { addons_repositories: list };
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultArray {
|
||||||
|
return [
|
||||||
|
hassioStyle,
|
||||||
|
css`
|
||||||
|
.add {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
iron-icon {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
margin-right: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
paper-input {
|
||||||
|
width: calc(100% - 49px);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-repositories-editor": HassioRepositoriesEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,9 @@ import "../../../src/components/ha-markdown";
|
|||||||
import "../../../src/components/buttons/ha-call-api-button";
|
import "../../../src/components/buttons/ha-call-api-button";
|
||||||
import "../../../src/resources/ha-style";
|
import "../../../src/resources/ha-style";
|
||||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||||
|
import { navigate } from "../../../src/common/navigate";
|
||||||
|
|
||||||
|
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||||
import "../components/hassio-card-content";
|
import "../components/hassio-card-content";
|
||||||
|
|
||||||
const PERMIS_DESC = {
|
const PERMIS_DESC = {
|
||||||
@ -59,6 +61,11 @@ const PERMIS_DESC = {
|
|||||||
description:
|
description:
|
||||||
"An addon can authenticate users against Home Assistant, allowing add-ons to give users the possibility to log into applications running inside add-ons, using their Home Assistant username/password. This badge indicates if the add-on author requests this capability.",
|
"An addon can authenticate users against Home Assistant, allowing add-ons to give users the possibility to log into applications running inside add-ons, using their Home Assistant username/password. This badge indicates if the add-on author requests this capability.",
|
||||||
},
|
},
|
||||||
|
ingress: {
|
||||||
|
title: "Ingress",
|
||||||
|
description:
|
||||||
|
"This add-on is using Ingress to embed its interface securely into Home Assistant.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||||
@ -161,12 +168,18 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
icon="hassio:arrow-up-bold-circle"
|
icon="hassio:arrow-up-bold-circle"
|
||||||
icon-class="update"
|
icon-class="update"
|
||||||
></hassio-card-content>
|
></hassio-card-content>
|
||||||
|
<template is="dom-if" if="[[!addon.available]]">
|
||||||
|
<p>This update is no longer compatible with your system.</p>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-call-api-button
|
<ha-call-api-button
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
path="hassio/addons/[[addonSlug]]/update"
|
path="hassio/addons/[[addonSlug]]/update"
|
||||||
>Update</ha-call-api-button
|
disabled="[[!addon.available]]"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</ha-call-api-button
|
||||||
>
|
>
|
||||||
<template is="dom-if" if="[[addon.changelog]]">
|
<template is="dom-if" if="[[addon.changelog]]">
|
||||||
<mwc-button on-click="openChangelog">Changelog</mwc-button>
|
<mwc-button on-click="openChangelog">Changelog</mwc-button>
|
||||||
@ -310,6 +323,15 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
description=""
|
description=""
|
||||||
></ha-label-badge>
|
></ha-label-badge>
|
||||||
</template>
|
</template>
|
||||||
|
<template is="dom-if" if="[[addon.ingress]]">
|
||||||
|
<ha-label-badge
|
||||||
|
on-click="showMoreInfo"
|
||||||
|
id="ingress"
|
||||||
|
icon="hassio:cursor-default-click-outline"
|
||||||
|
label="ingress"
|
||||||
|
description=""
|
||||||
|
></ha-label-badge>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<template is="dom-if" if="[[addon.version]]">
|
<template is="dom-if" if="[[addon.version]]">
|
||||||
<div class="state">
|
<div class="state">
|
||||||
@ -371,7 +393,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
is="dom-if"
|
is="dom-if"
|
||||||
if="[[computeShowWebUI(addon.webui, isRunning)]]"
|
if="[[computeShowWebUI(addon.ingress, addon.webui, isRunning)]]"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="[[pathWebui(addon.webui)]]"
|
href="[[pathWebui(addon.webui)]]"
|
||||||
@ -381,6 +403,16 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
><mwc-button>Open web UI</mwc-button></a
|
><mwc-button>Open web UI</mwc-button></a
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
|
<template
|
||||||
|
is="dom-if"
|
||||||
|
if="[[computeShowIngressUI(addon.ingress, isRunning)]]"
|
||||||
|
>
|
||||||
|
<mwc-button
|
||||||
|
tabindex="-1"
|
||||||
|
class="right"
|
||||||
|
on-click="openIngress"
|
||||||
|
>Open web UI</mwc-button>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template is="dom-if" if="[[!addon.version]]">
|
<template is="dom-if" if="[[!addon.version]]">
|
||||||
<template is="dom-if" if="[[!addon.available]]">
|
<template is="dom-if" if="[[!addon.available]]">
|
||||||
@ -448,8 +480,16 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
return webui && webui.replace("[HOST]", document.location.hostname);
|
return webui && webui.replace("[HOST]", document.location.hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
computeShowWebUI(webui, isRunning) {
|
computeShowWebUI(ingress, webui, isRunning) {
|
||||||
return webui && isRunning;
|
return !ingress && webui && isRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
openIngress() {
|
||||||
|
navigate(this, `/hassio/ingress/${this.addon.slug}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
computeShowIngressUI(ingress, isRunning) {
|
||||||
|
return ingress && isRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
computeStartOnBoot(state) {
|
computeStartOnBoot(state) {
|
||||||
@ -484,7 +524,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
|
|
||||||
showMoreInfo(e) {
|
showMoreInfo(e) {
|
||||||
const id = e.target.getAttribute("id");
|
const id = e.target.getAttribute("id");
|
||||||
this.fire("hassio-markdown-dialog", {
|
showHassioMarkdownDialog(this, {
|
||||||
title: PERMIS_DESC[id].title,
|
title: PERMIS_DESC[id].title,
|
||||||
content: PERMIS_DESC[id].description,
|
content: PERMIS_DESC[id].description,
|
||||||
});
|
});
|
||||||
@ -495,7 +535,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
|||||||
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
|
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
|
||||||
.then((resp) => resp, () => "Error getting changelog")
|
.then((resp) => resp, () => "Error getting changelog")
|
||||||
.then((content) => {
|
.then((content) => {
|
||||||
this.fire("hassio-markdown-dialog", {
|
showHassioMarkdownDialog(this, {
|
||||||
title: "Changelog",
|
title: "Changelog",
|
||||||
content: content,
|
content: content,
|
||||||
});
|
});
|
||||||
|
@ -37,6 +37,7 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Container</th>
|
<th>Container</th>
|
||||||
<th>Host</th>
|
<th>Host</th>
|
||||||
|
<th>Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
<template is="dom-repeat" items="[[config]]">
|
<template is="dom-repeat" items="[[config]]">
|
||||||
<tr>
|
<tr>
|
||||||
@ -47,6 +48,7 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
|||||||
no-label-float=""
|
no-label-float=""
|
||||||
></paper-input>
|
></paper-input>
|
||||||
</td>
|
</td>
|
||||||
|
<td>[[item.description]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -89,9 +91,11 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
|||||||
if (!addon) return;
|
if (!addon) return;
|
||||||
|
|
||||||
const network = addon.network || {};
|
const network = addon.network || {};
|
||||||
|
const description = addon.network_description || {};
|
||||||
const items = Object.keys(network).map((key) => ({
|
const items = Object.keys(network).map((key) => ({
|
||||||
container: key,
|
container: key,
|
||||||
host: network[key],
|
host: network[key],
|
||||||
|
description: description[key],
|
||||||
}));
|
}));
|
||||||
this.config = items.sort(function(el1, el2) {
|
this.config = items.sort(function(el1, el2) {
|
||||||
return el1.host - el2.host;
|
return el1.host - el2.host;
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import "@polymer/app-route/app-route";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
import "../../../src/components/ha-menu-button";
|
|
||||||
import "../../../src/resources/ha-style";
|
|
||||||
import "../hassio-markdown-dialog";
|
|
||||||
import "./hassio-addon-audio";
|
import "./hassio-addon-audio";
|
||||||
import "./hassio-addon-config";
|
import "./hassio-addon-config";
|
||||||
import "./hassio-addon-info";
|
import "./hassio-addon-info";
|
||||||
@ -18,7 +14,7 @@ import "./hassio-addon-network";
|
|||||||
class HassioAddonView extends PolymerElement {
|
class HassioAddonView extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style include="iron-flex ha-style">
|
<style>
|
||||||
:host {
|
:host {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
--paper-card-header-color: var(--primary-text-color);
|
--paper-card-header-color: var(--primary-text-color);
|
||||||
@ -51,35 +47,19 @@ class HassioAddonView extends PolymerElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<app-route
|
<hass-subpage header="Hass.io: add-on details" hassio>
|
||||||
route="[[route]]"
|
|
||||||
pattern="/addon/:slug"
|
|
||||||
data="{{routeData}}"
|
|
||||||
active="{{routeMatches}}"
|
|
||||||
></app-route>
|
|
||||||
<app-header-layout has-scrolling-region="">
|
|
||||||
<app-header fixed="" slot="header">
|
|
||||||
<app-toolbar>
|
|
||||||
<ha-menu-button hassio></ha-menu-button>
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hassio:arrow-left"
|
|
||||||
on-click="backTapped"
|
|
||||||
></paper-icon-button>
|
|
||||||
<div main-title="">Hass.io: add-on details</div>
|
|
||||||
</app-toolbar>
|
|
||||||
</app-header>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<hassio-addon-info
|
<hassio-addon-info
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
addon="[[addon]]"
|
addon="[[addon]]"
|
||||||
addon-slug="[[routeData.slug]]"
|
addon-slug="[[addonSlug]]"
|
||||||
></hassio-addon-info>
|
></hassio-addon-info>
|
||||||
|
|
||||||
<template is="dom-if" if="[[addon.version]]">
|
<template is="dom-if" if="[[addon.version]]">
|
||||||
<hassio-addon-config
|
<hassio-addon-config
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
addon="[[addon]]"
|
addon="[[addon]]"
|
||||||
addon-slug="[[routeData.slug]]"
|
addon-slug="[[addonSlug]]"
|
||||||
></hassio-addon-config>
|
></hassio-addon-config>
|
||||||
|
|
||||||
<template is="dom-if" if="[[addon.audio]]">
|
<template is="dom-if" if="[[addon.audio]]">
|
||||||
@ -93,50 +73,38 @@ class HassioAddonView extends PolymerElement {
|
|||||||
<hassio-addon-network
|
<hassio-addon-network
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
addon="[[addon]]"
|
addon="[[addon]]"
|
||||||
addon-slug="[[routeData.slug]]"
|
addon-slug="[[addonSlug]]"
|
||||||
></hassio-addon-network>
|
></hassio-addon-network>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<hassio-addon-logs
|
<hassio-addon-logs
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
addon-slug="[[routeData.slug]]"
|
addon-slug="[[addonSlug]]"
|
||||||
></hassio-addon-logs>
|
></hassio-addon-logs>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</app-header-layout>
|
</hass-subpage>
|
||||||
|
|
||||||
<hassio-markdown-dialog
|
|
||||||
title="[[markdownTitle]]"
|
|
||||||
content="[[markdownContent]]"
|
|
||||||
></hassio-markdown-dialog>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
hass: Object,
|
hass: Object,
|
||||||
route: Object,
|
route: {
|
||||||
routeData: {
|
|
||||||
type: Object,
|
type: Object,
|
||||||
observer: "routeDataChanged",
|
observer: "routeDataChanged",
|
||||||
},
|
},
|
||||||
routeMatches: Boolean,
|
addonSlug: {
|
||||||
addon: Object,
|
|
||||||
|
|
||||||
markdownTitle: String,
|
|
||||||
markdownContent: {
|
|
||||||
type: String,
|
type: String,
|
||||||
value: "",
|
computed: "_computeSlug(route)",
|
||||||
},
|
},
|
||||||
|
addon: Object,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ready() {
|
ready() {
|
||||||
super.ready();
|
super.ready();
|
||||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||||
this.addEventListener("hassio-markdown-dialog", (ev) =>
|
|
||||||
this.openMarkdown(ev)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apiCalled(ev) {
|
apiCalled(ev) {
|
||||||
@ -145,15 +113,15 @@ class HassioAddonView extends PolymerElement {
|
|||||||
if (!path) return;
|
if (!path) return;
|
||||||
|
|
||||||
if (path.substr(path.lastIndexOf("/") + 1) === "uninstall") {
|
if (path.substr(path.lastIndexOf("/") + 1) === "uninstall") {
|
||||||
this.backTapped();
|
history.back();
|
||||||
} else {
|
} else {
|
||||||
this.routeDataChanged(this.routeData);
|
this.routeDataChanged(this.route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routeDataChanged(routeData) {
|
routeDataChanged(routeData) {
|
||||||
if (!this.routeMatches || !routeData || !routeData.slug) return;
|
const addon = routeData.path.substr(1);
|
||||||
this.hass.callApi("get", `hassio/addons/${routeData.slug}/info`).then(
|
this.hass.callApi("get", `hassio/addons/${addon}/info`).then(
|
||||||
(info) => {
|
(info) => {
|
||||||
this.addon = info.data;
|
this.addon = info.data;
|
||||||
},
|
},
|
||||||
@ -163,16 +131,8 @@ class HassioAddonView extends PolymerElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
backTapped() {
|
_computeSlug(route) {
|
||||||
history.back();
|
return route.path.substr(1);
|
||||||
}
|
|
||||||
|
|
||||||
openMarkdown(ev) {
|
|
||||||
this.setProperties({
|
|
||||||
markdownTitle: ev.detail.title,
|
|
||||||
markdownContent: ev.detail.content,
|
|
||||||
});
|
|
||||||
this.shadowRoot.querySelector("hassio-markdown-dialog").openDialog();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
import "@polymer/iron-icon/iron-icon";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../../src/components/ha-relative-time";
|
|
||||||
|
|
||||||
class HassioCardContent extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
iron-icon {
|
|
||||||
margin-right: 16px;
|
|
||||||
margin-top: 16px;
|
|
||||||
float: left;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
iron-icon.update {
|
|
||||||
color: var(--paper-orange-400);
|
|
||||||
}
|
|
||||||
iron-icon.running,
|
|
||||||
iron-icon.installed {
|
|
||||||
color: var(--paper-green-400);
|
|
||||||
}
|
|
||||||
iron-icon.hassupdate,
|
|
||||||
iron-icon.snapshot {
|
|
||||||
color: var(--paper-item-icon-color);
|
|
||||||
}
|
|
||||||
iron-icon.not_available {
|
|
||||||
color: var(--google-red-500);
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.addition {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
height: 2.4em;
|
|
||||||
line-height: 1.2em;
|
|
||||||
}
|
|
||||||
ha-relative-time {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<iron-icon
|
|
||||||
icon="[[icon]]"
|
|
||||||
class\$="[[iconClass]]"
|
|
||||||
title="[[iconTitle]]"
|
|
||||||
></iron-icon>
|
|
||||||
<div>
|
|
||||||
<div class="title">[[title]]</div>
|
|
||||||
<div class="addition">
|
|
||||||
<template is="dom-if" if="[[description]]">
|
|
||||||
[[description]]
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[!available]]">
|
|
||||||
(Not available)
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[datetime]]">
|
|
||||||
<ha-relative-time
|
|
||||||
hass="[[hass]]"
|
|
||||||
class="addition"
|
|
||||||
datetime="[[datetime]]"
|
|
||||||
></ha-relative-time>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
available: Boolean,
|
|
||||||
datetime: String,
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
value: "hass:help-circle",
|
|
||||||
},
|
|
||||||
iconTitle: String,
|
|
||||||
iconClass: String,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define("hassio-card-content", HassioCardContent);
|
|
97
hassio/src/components/hassio-card-content.ts
Normal file
97
hassio/src/components/hassio-card-content.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/iron-icon/iron-icon";
|
||||||
|
|
||||||
|
import "../../../src/components/ha-relative-time";
|
||||||
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
|
||||||
|
@customElement("hassio-card-content")
|
||||||
|
class HassioCardContent extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public title!: string;
|
||||||
|
@property() public description?: string;
|
||||||
|
@property({ type: Boolean }) public available: boolean = true;
|
||||||
|
@property() public datetime?: string;
|
||||||
|
@property() public iconTitle?: string;
|
||||||
|
@property() public iconClass?: string;
|
||||||
|
@property() public icon = "hass:help-circle";
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<iron-icon
|
||||||
|
class=${this.iconClass}
|
||||||
|
.icon=${this.icon}
|
||||||
|
.title=${this.iconTitle}
|
||||||
|
></iron-icon>
|
||||||
|
<div>
|
||||||
|
<div class="title">${this.title}</div>
|
||||||
|
<div class="addition">
|
||||||
|
${this.description} ${this.available ? undefined : " (Not available"}
|
||||||
|
${this.datetime
|
||||||
|
? html`
|
||||||
|
<ha-relative-time
|
||||||
|
.hass=${this.hass}
|
||||||
|
class="addition"
|
||||||
|
.datetime=${this.datetime}
|
||||||
|
></ha-relative-time>
|
||||||
|
`
|
||||||
|
: undefined}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
iron-icon {
|
||||||
|
margin-right: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
float: left;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
iron-icon.update {
|
||||||
|
color: var(--paper-orange-400);
|
||||||
|
}
|
||||||
|
iron-icon.running,
|
||||||
|
iron-icon.installed {
|
||||||
|
color: var(--paper-green-400);
|
||||||
|
}
|
||||||
|
iron-icon.hassupdate,
|
||||||
|
iron-icon.snapshot {
|
||||||
|
color: var(--paper-item-icon-color);
|
||||||
|
}
|
||||||
|
iron-icon.not_available {
|
||||||
|
color: var(--google-red-500);
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.addition {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 2.4em;
|
||||||
|
line-height: 1.2em;
|
||||||
|
}
|
||||||
|
ha-relative-time {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-card-content": HassioCardContent;
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "./hassio-addons";
|
|
||||||
import "./hassio-hass-update";
|
|
||||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
|
||||||
|
|
||||||
class HassioDashboard extends EventsMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex ha-style">
|
|
||||||
.content {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="content">
|
|
||||||
<hassio-hass-update
|
|
||||||
hass="[[hass]]"
|
|
||||||
hass-info="[[hassInfo]]"
|
|
||||||
></hassio-hass-update>
|
|
||||||
<hassio-addons
|
|
||||||
hass="[[hass]]"
|
|
||||||
addons="[[supervisorInfo.addons]]"
|
|
||||||
></hassio-addons>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
supervisorInfo: Object,
|
|
||||||
hassInfo: Object,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hassio-dashboard", HassioDashboard);
|
|
52
hassio/src/dashboard/hassio-dashboard.ts
Normal file
52
hassio/src/dashboard/hassio-dashboard.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
|
import "./hassio-addons";
|
||||||
|
import "./hassio-hass-update";
|
||||||
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
import {
|
||||||
|
HassioSupervisorInfo,
|
||||||
|
HassioHomeAssistantInfo,
|
||||||
|
} from "../../../src/data/hassio";
|
||||||
|
|
||||||
|
@customElement("hassio-dashboard")
|
||||||
|
class HassioDashboard extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||||
|
@property() public hassInfo!: HassioHomeAssistantInfo;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<div class="content">
|
||||||
|
<hassio-hass-update
|
||||||
|
.hass=${this.hass}
|
||||||
|
.hassInfo=${this.hassInfo}
|
||||||
|
></hassio-hass-update>
|
||||||
|
<hassio-addons
|
||||||
|
.hass=${this.hass}
|
||||||
|
.addons=${this.supervisorInfo.addons}
|
||||||
|
></hassio-addons>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.content {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-dashboard": HassioDashboard;
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,21 @@
|
|||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
import "../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
import "../../src/resources/ha-style";
|
import "../../../../src/resources/ha-style";
|
||||||
|
import "../../../../src/components/dialog/ha-paper-dialog";
|
||||||
|
import { customElement } from "lit-element";
|
||||||
|
import { PaperDialogElement } from "@polymer/paper-dialog";
|
||||||
|
|
||||||
|
@customElement("dialog-hassio-markdown")
|
||||||
class HassioMarkdownDialog extends PolymerElement {
|
class HassioMarkdownDialog extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style include="ha-style-dialog">
|
<style include="ha-style-dialog">
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
@ -31,10 +34,10 @@ class HassioMarkdownDialog extends PolymerElement {
|
|||||||
margin: 4px;
|
margin: 4px;
|
||||||
}
|
}
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
paper-dialog::before {
|
ha-paper-dialog::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
@ -50,7 +53,7 @@ class HassioMarkdownDialog extends PolymerElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<paper-dialog id="dialog" with-backdrop="">
|
<ha-paper-dialog id="dialog" with-backdrop="">
|
||||||
<app-toolbar>
|
<app-toolbar>
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hassio:close"
|
icon="hassio:close"
|
||||||
@ -61,7 +64,7 @@ class HassioMarkdownDialog extends PolymerElement {
|
|||||||
<paper-dialog-scrollable>
|
<paper-dialog-scrollable>
|
||||||
<ha-markdown content="[[content]]"></ha-markdown>
|
<ha-markdown content="[[content]]"></ha-markdown>
|
||||||
</paper-dialog-scrollable>
|
</paper-dialog-scrollable>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,8 +75,14 @@ class HassioMarkdownDialog extends PolymerElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
openDialog() {
|
public showDialog(params) {
|
||||||
this.$.dialog.open();
|
this.setProperties(params);
|
||||||
|
(this.$.dialog as PaperDialogElement).open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-hassio-markdown": HassioMarkdownDialog;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("hassio-markdown-dialog", HassioMarkdownDialog);
|
|
18
hassio/src/dialogs/markdown/show-dialog-hassio-markdown.ts
Normal file
18
hassio/src/dialogs/markdown/show-dialog-hassio-markdown.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface HassioMarkdownDialogParams {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showHassioMarkdownDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: HassioMarkdownDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-hassio-markdown",
|
||||||
|
dialogImport: () =>
|
||||||
|
import(/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"),
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -2,20 +2,65 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-checkbox/paper-checkbox";
|
import "@polymer/paper-checkbox/paper-checkbox";
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
import { getSignedPath } from "../../../src/auth/data";
|
import { getSignedPath } from "../../../../src/auth/data";
|
||||||
|
|
||||||
import "../../../src/resources/ha-style";
|
import "../../../../src/resources/ha-style";
|
||||||
|
import "../../../../src/components/dialog/ha-paper-dialog";
|
||||||
|
import { customElement } from "lit-element";
|
||||||
|
import { PaperDialogElement } from "@polymer/paper-dialog";
|
||||||
|
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
||||||
|
import { fetchHassioSnapshotInfo } from "../../../../src/data/hassio";
|
||||||
|
|
||||||
|
const _computeFolders = (folders) => {
|
||||||
|
const list: Array<{ slug: string; name: string; checked: boolean }> = [];
|
||||||
|
if (folders.includes("homeassistant")) {
|
||||||
|
list.push({
|
||||||
|
slug: "homeassistant",
|
||||||
|
name: "Home Assistant configuration",
|
||||||
|
checked: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (folders.includes("ssl")) {
|
||||||
|
list.push({ slug: "ssl", name: "SSL", checked: true });
|
||||||
|
}
|
||||||
|
if (folders.includes("share")) {
|
||||||
|
list.push({ slug: "share", name: "Share", checked: true });
|
||||||
|
}
|
||||||
|
if (folders.includes("addons/local")) {
|
||||||
|
list.push({ slug: "addons/local", name: "Local add-ons", checked: true });
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _computeAddons = (addons) => {
|
||||||
|
return addons.map((addon) => ({
|
||||||
|
slug: addon.slug,
|
||||||
|
name: addon.name,
|
||||||
|
version: addon.version,
|
||||||
|
checked: true,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("dialog-hassio-snapshot")
|
||||||
|
class HassioSnapshotDialog extends PolymerElement {
|
||||||
|
// Commented out because it breaks Polymer! Kept around for when we migrate
|
||||||
|
// to Lit. Now just putting ts-ignore everywhere because we need this out.
|
||||||
|
// Sorry future developer.
|
||||||
|
// public hass!: HomeAssistant;
|
||||||
|
// protected error?: string;
|
||||||
|
// private snapshot?: any;
|
||||||
|
// private dialogParams?: HassioSnapshotDialogParams;
|
||||||
|
// private restoreHass!: boolean;
|
||||||
|
// private snapshotPassword!: string;
|
||||||
|
|
||||||
class HassioSnapshot extends PolymerElement {
|
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style include="ha-style-dialog">
|
<style include="ha-style-dialog">
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
@ -29,7 +74,7 @@ class HassioSnapshot extends PolymerElement {
|
|||||||
app-toolbar [main-title] {
|
app-toolbar [main-title] {
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
paper-dialog-scrollable {
|
ha-paper-dialog-scrollable {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
paper-checkbox {
|
paper-checkbox {
|
||||||
@ -37,7 +82,7 @@ class HassioSnapshot extends PolymerElement {
|
|||||||
margin: 4px;
|
margin: 4px;
|
||||||
}
|
}
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@ -57,7 +102,7 @@ class HassioSnapshot extends PolymerElement {
|
|||||||
color: var(--google-red-500);
|
color: var(--google-red-500);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<paper-dialog
|
<ha-paper-dialog
|
||||||
id="dialog"
|
id="dialog"
|
||||||
with-backdrop=""
|
with-backdrop=""
|
||||||
on-iron-overlay-closed="_dialogClosed"
|
on-iron-overlay-closed="_dialogClosed"
|
||||||
@ -77,22 +122,18 @@ class HassioSnapshot extends PolymerElement {
|
|||||||
<paper-checkbox checked="{{restoreHass}}">
|
<paper-checkbox checked="{{restoreHass}}">
|
||||||
Home Assistant [[snapshot.homeassistant]]
|
Home Assistant [[snapshot.homeassistant]]
|
||||||
</paper-checkbox>
|
</paper-checkbox>
|
||||||
<template is="dom-if" if="[[snapshot.addons.length]]">
|
<template is="dom-if" if="[[_folders.length]]">
|
||||||
<div>Folders:</div>
|
<div>Folders:</div>
|
||||||
<template is="dom-repeat" items="[[snapshot.folders]]">
|
<template is="dom-repeat" items="[[_folders]]">
|
||||||
<paper-checkbox checked="{{item.checked}}">
|
<paper-checkbox checked="{{item.checked}}">
|
||||||
[[item.name]]
|
[[item.name]]
|
||||||
</paper-checkbox>
|
</paper-checkbox>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template is="dom-if" if="[[snapshot.addons.length]]">
|
<template is="dom-if" if="[[_addons.length]]">
|
||||||
<div>Add-ons:</div>
|
<div>Add-ons:</div>
|
||||||
<paper-dialog-scrollable>
|
<paper-dialog-scrollable>
|
||||||
<template
|
<template is="dom-repeat" items="[[_addons]]" sort="_sortAddons">
|
||||||
is="dom-repeat"
|
|
||||||
items="[[snapshot.addons]]"
|
|
||||||
sort="_sortAddons"
|
|
||||||
>
|
|
||||||
<paper-checkbox checked="{{item.checked}}">
|
<paper-checkbox checked="{{item.checked}}">
|
||||||
[[item.name]] <span class="details">([[item.version]])</span>
|
[[item.name]] <span class="details">([[item.version]])</span>
|
||||||
</paper-checkbox>
|
</paper-checkbox>
|
||||||
@ -132,23 +173,17 @@ class HassioSnapshot extends PolymerElement {
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
hass: Object,
|
hass: Object,
|
||||||
snapshotSlug: {
|
dialogParams: Object,
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
observer: "_snapshotSlugChanged",
|
|
||||||
},
|
|
||||||
snapshotDeleted: {
|
|
||||||
type: Boolean,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
snapshot: Object,
|
snapshot: Object,
|
||||||
|
_folders: Object,
|
||||||
|
_addons: Object,
|
||||||
restoreHass: {
|
restoreHass: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
value: true,
|
value: true,
|
||||||
@ -158,140 +193,136 @@ class HassioSnapshot extends PolymerElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_snapshotSlugChanged(snapshotSlug) {
|
public async showDialog(params: HassioSnapshotDialogParams) {
|
||||||
if (!snapshotSlug || snapshotSlug === "update") return;
|
// @ts-ignore
|
||||||
this.hass.callApi("get", `hassio/snapshots/${snapshotSlug}/info`).then(
|
const snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
|
||||||
(info) => {
|
this.setProperties({
|
||||||
info.data.folders = this._computeFolders(info.data.folders);
|
dialogParams: params,
|
||||||
info.data.addons = this._computeAddons(info.data.addons);
|
snapshot,
|
||||||
this.snapshot = info.data;
|
_folders: _computeFolders(snapshot.folders),
|
||||||
this.$.dialog.open();
|
_addons: _computeAddons(snapshot.addons),
|
||||||
},
|
});
|
||||||
() => {
|
(this.$.dialog as PaperDialogElement).open();
|
||||||
this.snapshot = null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeFolders(folders) {
|
protected _isFullSnapshot(type) {
|
||||||
const list = [];
|
|
||||||
if (folders.includes("homeassistant"))
|
|
||||||
list.push({
|
|
||||||
slug: "homeassistant",
|
|
||||||
name: "Home Assistant configuration",
|
|
||||||
checked: true,
|
|
||||||
});
|
|
||||||
if (folders.includes("ssl"))
|
|
||||||
list.push({ slug: "ssl", name: "SSL", checked: true });
|
|
||||||
if (folders.includes("share"))
|
|
||||||
list.push({ slug: "share", name: "Share", checked: true });
|
|
||||||
if (folders.includes("addons/local"))
|
|
||||||
list.push({ slug: "addons/local", name: "Local add-ons", checked: true });
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeAddons(addons) {
|
|
||||||
return addons.map((addon) => ({
|
|
||||||
slug: addon.slug,
|
|
||||||
name: addon.name,
|
|
||||||
version: addon.version,
|
|
||||||
checked: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_isFullSnapshot(type) {
|
|
||||||
return type === "full";
|
return type === "full";
|
||||||
}
|
}
|
||||||
|
|
||||||
_partialRestoreClicked() {
|
protected _partialRestoreClicked() {
|
||||||
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const addons = this.snapshot.addons
|
// @ts-ignore
|
||||||
|
const addons = this._addons
|
||||||
.filter((addon) => addon.checked)
|
.filter((addon) => addon.checked)
|
||||||
.map((addon) => addon.slug);
|
.map((addon) => addon.slug);
|
||||||
const folders = this.snapshot.folders
|
// @ts-ignore
|
||||||
|
const folders = this._folders
|
||||||
.filter((folder) => folder.checked)
|
.filter((folder) => folder.checked)
|
||||||
.map((folder) => folder.slug);
|
.map((folder) => folder.slug);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
// @ts-ignore
|
||||||
homeassistant: this.restoreHass,
|
homeassistant: this.restoreHass,
|
||||||
addons: addons,
|
addons,
|
||||||
folders: folders,
|
folders,
|
||||||
};
|
};
|
||||||
if (this.snapshot.protected) data.password = this.snapshotPassword;
|
// @ts-ignore
|
||||||
|
if (this.snapshot.protected) {
|
||||||
|
// @ts-ignore
|
||||||
|
data.password = this.snapshotPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
this.hass
|
this.hass
|
||||||
.callApi(
|
.callApi(
|
||||||
"post",
|
"POST",
|
||||||
`hassio/snapshots/${this.snapshotSlug}/restore/partial`,
|
// @ts-ignore
|
||||||
|
`hassio/snapshots/${this.dialogParams!.slug}/restore/partial`,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
alert("Snapshot restored!");
|
alert("Snapshot restored!");
|
||||||
this.$.dialog.close();
|
(this.$.dialog as PaperDialogElement).close();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
// @ts-ignore
|
||||||
this.error = error.body.message;
|
this.error = error.body.message;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_fullRestoreClicked() {
|
protected _fullRestoreClicked() {
|
||||||
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// @ts-ignore
|
||||||
const data = this.snapshot.protected
|
const data = this.snapshot.protected
|
||||||
? { password: this.snapshotPassword }
|
? {
|
||||||
: null;
|
password:
|
||||||
|
// @ts-ignore
|
||||||
|
this.snapshotPassword,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
// @ts-ignore
|
||||||
this.hass
|
this.hass
|
||||||
.callApi(
|
.callApi(
|
||||||
"post",
|
"POST",
|
||||||
`hassio/snapshots/${this.snapshotSlug}/restore/full`,
|
// @ts-ignore
|
||||||
|
`hassio/snapshots/${this.dialogParams!.slug}/restore/full`,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
alert("Snapshot restored!");
|
alert("Snapshot restored!");
|
||||||
this.$.dialog.close();
|
(this.$.dialog as PaperDialogElement).close();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
// @ts-ignore
|
||||||
this.error = error.body.message;
|
this.error = error.body.message;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_deleteClicked() {
|
protected _deleteClicked() {
|
||||||
if (!confirm("Are you sure you want to delete this snapshot?")) {
|
if (!confirm("Are you sure you want to delete this snapshot?")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// @ts-ignore
|
||||||
this.hass
|
this.hass
|
||||||
.callApi("post", `hassio/snapshots/${this.snapshotSlug}/remove`)
|
// @ts-ignore
|
||||||
|
.callApi("POST", `hassio/snapshots/${this.dialogParams!.slug}/remove`)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
this.$.dialog.close();
|
(this.$.dialog as PaperDialogElement).close();
|
||||||
this.snapshotDeleted = true;
|
// @ts-ignore
|
||||||
|
this.dialogParams!.onDelete();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
// @ts-ignore
|
||||||
this.error = error.body.message;
|
this.error = error.body.message;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _downloadClicked() {
|
protected async _downloadClicked() {
|
||||||
let signedPath;
|
let signedPath;
|
||||||
try {
|
try {
|
||||||
signedPath = await getSignedPath(
|
signedPath = await getSignedPath(
|
||||||
|
// @ts-ignore
|
||||||
this.hass,
|
this.hass,
|
||||||
`/api/hassio/snapshots/${this.snapshotSlug}/download`
|
// @ts-ignore
|
||||||
|
`/api/hassio/snapshots/${this.dialogParams!.slug}/download`
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(`Error: ${err.message}`);
|
alert(`Error: ${err.message}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// @ts-ignore
|
||||||
const name = this._computeName(this.snapshot).replace(/[^a-z0-9]+/gi, "_");
|
const name = this._computeName(this.snapshot).replace(/[^a-z0-9]+/gi, "_");
|
||||||
const a = document.createElement("A");
|
const a = document.createElement("a");
|
||||||
a.href = signedPath.path;
|
a.href = signedPath.path;
|
||||||
a.download = `Hass_io_${name}.tar`;
|
a.download = `Hass_io_${name}.tar`;
|
||||||
this.$.dialog.appendChild(a);
|
this.$.dialog.appendChild(a);
|
||||||
@ -299,23 +330,23 @@ class HassioSnapshot extends PolymerElement {
|
|||||||
this.$.dialog.removeChild(a);
|
this.$.dialog.removeChild(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeName(snapshot) {
|
protected _computeName(snapshot) {
|
||||||
return snapshot.name || snapshot.slug;
|
return snapshot ? snapshot.name || snapshot.slug : "Unnamed snapshot";
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeType(type) {
|
protected _computeType(type) {
|
||||||
return type === "full" ? "Full snapshot" : "Partial snapshot";
|
return type === "full" ? "Full snapshot" : "Partial snapshot";
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeSize(size) {
|
protected _computeSize(size) {
|
||||||
return Math.ceil(size * 10) / 10 + " MB";
|
return Math.ceil(size * 10) / 10 + " MB";
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortAddons(a, b) {
|
protected _sortAddons(a, b) {
|
||||||
return a.name < b.name ? -1 : 1;
|
return a.name < b.name ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_formatDatetime(datetime) {
|
protected _formatDatetime(datetime) {
|
||||||
return new Date(datetime).toLocaleDateString(navigator.language, {
|
return new Date(datetime).toLocaleDateString(navigator.language, {
|
||||||
weekday: "long",
|
weekday: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
@ -326,8 +357,18 @@ class HassioSnapshot extends PolymerElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_dialogClosed() {
|
protected _dialogClosed() {
|
||||||
this.snapshotSlug = null;
|
this.setProperties({
|
||||||
|
dialogParams: undefined,
|
||||||
|
snapshot: undefined,
|
||||||
|
_addons: [],
|
||||||
|
_folders: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-hassio-snapshot": HassioSnapshotDialog;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("hassio-snapshot", HassioSnapshot);
|
|
18
hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts
Normal file
18
hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface HassioSnapshotDialogParams {
|
||||||
|
slug: string;
|
||||||
|
onDelete: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showHassioSnapshotDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: HassioSnapshotDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-hassio-snapshot",
|
||||||
|
dialogImport: () =>
|
||||||
|
import(/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"),
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -1,5 +1,17 @@
|
|||||||
window.loadES5Adapter().then(() => {
|
window.loadES5Adapter().then(() => {
|
||||||
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons.js");
|
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons");
|
||||||
import(/* webpackChunkName: "hassio-main" */ "./hassio-main.js");
|
import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
|
||||||
});
|
});
|
||||||
document.body.style.height = "100%";
|
const styleEl = document.createElement("style");
|
||||||
|
styleEl.innerHTML = `
|
||||||
|
body {
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(styleEl);
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
class HassioData extends PolymerElement {
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
|
|
||||||
supervisor: {
|
|
||||||
type: Object,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
host: {
|
|
||||||
type: Object,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
homeassistant: {
|
|
||||||
type: Object,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh() {
|
|
||||||
return Promise.all([
|
|
||||||
this.fetchSupervisorInfo(),
|
|
||||||
this.fetchHostInfo(),
|
|
||||||
this.fetchHassInfo(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchSupervisorInfo() {
|
|
||||||
return this.hass.callApi("get", "hassio/supervisor/info").then((info) => {
|
|
||||||
this.supervisor = info.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchHostInfo() {
|
|
||||||
return this.hass.callApi("get", "hassio/host/info").then((info) => {
|
|
||||||
this.host = info.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchHassInfo() {
|
|
||||||
return this.hass
|
|
||||||
.callApi("get", "hassio/homeassistant/info")
|
|
||||||
.then((info) => {
|
|
||||||
this.homeassistant = info.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hassio-data", HassioData);
|
|
@ -1,142 +0,0 @@
|
|||||||
import "@polymer/app-route/app-route";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../src/layouts/hass-loading-screen";
|
|
||||||
import "./addon-view/hassio-addon-view";
|
|
||||||
import "./hassio-data";
|
|
||||||
import "./hassio-pages-with-tabs";
|
|
||||||
|
|
||||||
import applyThemesOnElement from "../../src/common/dom/apply_themes_on_element";
|
|
||||||
import EventsMixin from "../../src/mixins/events-mixin";
|
|
||||||
import NavigateMixin from "../../src/mixins/navigate-mixin";
|
|
||||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
|
||||||
|
|
||||||
class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<app-route
|
|
||||||
route="[[route]]"
|
|
||||||
pattern="/:page"
|
|
||||||
data="{{routeData}}"
|
|
||||||
></app-route>
|
|
||||||
<hassio-data
|
|
||||||
id="data"
|
|
||||||
hass="[[hass]]"
|
|
||||||
supervisor="{{supervisorInfo}}"
|
|
||||||
homeassistant="{{hassInfo}}"
|
|
||||||
host="{{hostInfo}}"
|
|
||||||
></hassio-data>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[!loaded]]">
|
|
||||||
<hass-loading-screen></hass-loading-screen>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[loaded]]">
|
|
||||||
<template is="dom-if" if="[[!equalsAddon(routeData.page)]]">
|
|
||||||
<hassio-pages-with-tabs
|
|
||||||
hass="[[hass]]"
|
|
||||||
page="[[routeData.page]]"
|
|
||||||
supervisor-info="[[supervisorInfo]]"
|
|
||||||
hass-info="[[hassInfo]]"
|
|
||||||
host-info="[[hostInfo]]"
|
|
||||||
></hassio-pages-with-tabs>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[equalsAddon(routeData.page)]]">
|
|
||||||
<hassio-addon-view
|
|
||||||
hass="[[hass]]"
|
|
||||||
route="[[route]]"
|
|
||||||
></hassio-addon-view>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
route: {
|
|
||||||
type: Object,
|
|
||||||
// Fake route object
|
|
||||||
value: {
|
|
||||||
prefix: "/hassio",
|
|
||||||
path: "/dashboard",
|
|
||||||
__queryParams: {},
|
|
||||||
},
|
|
||||||
observer: "routeChanged",
|
|
||||||
},
|
|
||||||
routeData: Object,
|
|
||||||
supervisorInfo: Object,
|
|
||||||
hostInfo: Object,
|
|
||||||
hassInfo: Object,
|
|
||||||
loaded: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: "computeIsLoaded(supervisorInfo, hostInfo, hassInfo)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
applyThemesOnElement(this, this.hass.themes, this.hass.selectedTheme, true);
|
|
||||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
|
||||||
// Paulus - March 17, 2019
|
|
||||||
// We went to a single hass-toggle-menu event in HA 0.90. However, the
|
|
||||||
// supervisor UI can also run under older versions of Home Assistant.
|
|
||||||
// So here we are going to translate toggle events into the appropriate
|
|
||||||
// open and close events. These events are a no-op in newer versions of
|
|
||||||
// Home Assistant.
|
|
||||||
this.addEventListener("hass-toggle-menu", () => {
|
|
||||||
fireEvent(
|
|
||||||
window.parent.customPanel,
|
|
||||||
this.hass.dockedSidebar ? "hass-close-menu" : "hass-open-menu"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
// Paulus - March 19, 2019
|
|
||||||
// We changed the navigate event to fire directly on the window, as that's
|
|
||||||
// where we are listening for it. However, the older panel_custom will
|
|
||||||
// listen on this element for navigation events, so we need to forward them.
|
|
||||||
window.addEventListener("location-changed", (ev) =>
|
|
||||||
fireEvent(this, ev.type, ev.detail, {
|
|
||||||
bubbles: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
this.routeChanged(this.route);
|
|
||||||
}
|
|
||||||
|
|
||||||
apiCalled(ev) {
|
|
||||||
if (ev.detail.success) {
|
|
||||||
let tries = 1;
|
|
||||||
|
|
||||||
const tryUpdate = () => {
|
|
||||||
this.$.data.refresh().catch(function() {
|
|
||||||
tries += 1;
|
|
||||||
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
tryUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
computeIsLoaded(supervisorInfo, hostInfo, hassInfo) {
|
|
||||||
return supervisorInfo !== null && hostInfo !== null && hassInfo !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
routeChanged(route) {
|
|
||||||
if (route.path === "" && route.prefix === "/hassio") {
|
|
||||||
this.navigate("/hassio/dashboard", true);
|
|
||||||
}
|
|
||||||
fireEvent(this, "iron-resize");
|
|
||||||
}
|
|
||||||
|
|
||||||
equalsAddon(page) {
|
|
||||||
return page && page === "addon";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hassio-main", HassioMain);
|
|
152
hassio/src/hassio-main.ts
Normal file
152
hassio/src/hassio-main.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import { customElement, PropertyValues, property } from "lit-element";
|
||||||
|
import { PolymerElement } from "@polymer/polymer";
|
||||||
|
import "@polymer/paper-icon-button";
|
||||||
|
|
||||||
|
import "../../src/resources/ha-style";
|
||||||
|
import applyThemesOnElement from "../../src/common/dom/apply_themes_on_element";
|
||||||
|
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||||
|
import {
|
||||||
|
HassRouterPage,
|
||||||
|
RouterOptions,
|
||||||
|
} from "../../src/layouts/hass-router-page";
|
||||||
|
import { HomeAssistant } from "../../src/types";
|
||||||
|
import {
|
||||||
|
fetchHassioSupervisorInfo,
|
||||||
|
fetchHassioHostInfo,
|
||||||
|
fetchHassioHomeAssistantInfo,
|
||||||
|
HassioSupervisorInfo,
|
||||||
|
HassioHostInfo,
|
||||||
|
HassioHomeAssistantInfo,
|
||||||
|
} from "../../src/data/hassio";
|
||||||
|
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
||||||
|
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||||
|
// Don't codesplit it, that way the dashboard always loads fast.
|
||||||
|
import "./hassio-pages-with-tabs";
|
||||||
|
|
||||||
|
// The register callback of the IronA11yKeysBehavior inside paper-icon-button
|
||||||
|
// is not called, causing _keyBindings to be uninitiliazed for paper-icon-button,
|
||||||
|
// causing an exception when added to DOM. When transpiled to ES5, this will
|
||||||
|
// break the build.
|
||||||
|
customElements.get("paper-icon-button").prototype._keyBindings = {};
|
||||||
|
|
||||||
|
@customElement("hassio-main")
|
||||||
|
class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
protected routerOptions: RouterOptions = {
|
||||||
|
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
||||||
|
defaultPage: "dashboard",
|
||||||
|
initialLoad: () => this._fetchData(),
|
||||||
|
showLoading: true,
|
||||||
|
routes: {
|
||||||
|
dashboard: {
|
||||||
|
tag: "hassio-pages-with-tabs",
|
||||||
|
cache: true,
|
||||||
|
},
|
||||||
|
snapshots: "dashboard",
|
||||||
|
store: "dashboard",
|
||||||
|
system: "dashboard",
|
||||||
|
addon: {
|
||||||
|
tag: "hassio-addon-view",
|
||||||
|
load: () => import("./addon-view/hassio-addon-view"),
|
||||||
|
},
|
||||||
|
ingress: {
|
||||||
|
tag: "hassio-ingress-view",
|
||||||
|
load: () => import("./ingress-view/hassio-ingress-view"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
@property() private _supervisorInfo: HassioSupervisorInfo;
|
||||||
|
@property() private _hostInfo: HassioHostInfo;
|
||||||
|
@property() private _hassInfo: HassioHomeAssistantInfo;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
|
applyThemesOnElement(this, this.hass.themes, this.hass.selectedTheme, true);
|
||||||
|
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||||
|
// Paulus - March 17, 2019
|
||||||
|
// We went to a single hass-toggle-menu event in HA 0.90. However, the
|
||||||
|
// supervisor UI can also run under older versions of Home Assistant.
|
||||||
|
// So here we are going to translate toggle events into the appropriate
|
||||||
|
// open and close events. These events are a no-op in newer versions of
|
||||||
|
// Home Assistant.
|
||||||
|
this.addEventListener("hass-toggle-menu", () => {
|
||||||
|
fireEvent(
|
||||||
|
(window.parent as any).customPanel,
|
||||||
|
// @ts-ignore
|
||||||
|
this.hass.dockedSidebar ? "hass-close-menu" : "hass-open-menu"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Paulus - March 19, 2019
|
||||||
|
// We changed the navigate event to fire directly on the window, as that's
|
||||||
|
// where we are listening for it. However, the older panel_custom will
|
||||||
|
// listen on this element for navigation events, so we need to forward them.
|
||||||
|
window.addEventListener("location-changed", (ev) =>
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, ev.type, ev.detail, {
|
||||||
|
bubbles: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
makeDialogManager(this, document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updatePageEl(el) {
|
||||||
|
// the tabs page does its own routing so needs full route.
|
||||||
|
const route =
|
||||||
|
el.nodeName === "HASSIO-PAGES-WITH-TABS" ? this.route : this.routeTail;
|
||||||
|
|
||||||
|
if ("setProperties" in el) {
|
||||||
|
// As long as we have Polymer pages
|
||||||
|
(el as PolymerElement).setProperties({
|
||||||
|
hass: this.hass,
|
||||||
|
supervisorInfo: this._supervisorInfo,
|
||||||
|
hostInfo: this._hostInfo,
|
||||||
|
hassInfo: this._hassInfo,
|
||||||
|
route,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
el.hass = this.hass;
|
||||||
|
el.supervisorInfo = this._supervisorInfo;
|
||||||
|
el.hostInfo = this._hostInfo;
|
||||||
|
el.hassInfo = this._hassInfo;
|
||||||
|
el.route = route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchData() {
|
||||||
|
const [supervisorInfo, hostInfo, hassInfo] = await Promise.all([
|
||||||
|
fetchHassioSupervisorInfo(this.hass),
|
||||||
|
fetchHassioHostInfo(this.hass),
|
||||||
|
fetchHassioHomeAssistantInfo(this.hass),
|
||||||
|
]);
|
||||||
|
this._supervisorInfo = supervisorInfo;
|
||||||
|
this._hostInfo = hostInfo;
|
||||||
|
this._hassInfo = hassInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _apiCalled(ev) {
|
||||||
|
if (!ev.detail.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tries = 1;
|
||||||
|
|
||||||
|
const tryUpdate = () => {
|
||||||
|
this._fetchData().catch(() => {
|
||||||
|
tries += 1;
|
||||||
|
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
tryUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-main": HassioMain;
|
||||||
|
}
|
||||||
|
}
|
@ -1,161 +0,0 @@
|
|||||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
|
||||||
import "@polymer/paper-tabs/paper-tab";
|
|
||||||
import "@polymer/paper-tabs/paper-tabs";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../src/components/ha-menu-button";
|
|
||||||
import "../../src/resources/ha-style";
|
|
||||||
import "./addon-store/hassio-addon-store";
|
|
||||||
import "./dashboard/hassio-dashboard";
|
|
||||||
import "./hassio-markdown-dialog";
|
|
||||||
import "./snapshots/hassio-snapshot";
|
|
||||||
import "./snapshots/hassio-snapshots";
|
|
||||||
import "./system/hassio-system";
|
|
||||||
|
|
||||||
import scrollToTarget from "../../src/common/dom/scroll-to-target";
|
|
||||||
|
|
||||||
import NavigateMixin from "../../src/mixins/navigate-mixin";
|
|
||||||
|
|
||||||
class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex iron-positioning ha-style">
|
|
||||||
:host {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
--paper-card-header-color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
paper-tabs {
|
|
||||||
margin-left: 12px;
|
|
||||||
--paper-tabs-selection-bar-color: #fff;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<app-header-layout id="layout" has-scrolling-region>
|
|
||||||
<app-header fixed slot="header">
|
|
||||||
<app-toolbar>
|
|
||||||
<ha-menu-button hassio></ha-menu-button>
|
|
||||||
<div main-title>Hass.io</div>
|
|
||||||
<template is="dom-if" if="[[showRefreshButton(page)]]">
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hassio:refresh"
|
|
||||||
on-click="refreshClicked"
|
|
||||||
></paper-icon-button>
|
|
||||||
</template>
|
|
||||||
</app-toolbar>
|
|
||||||
<paper-tabs
|
|
||||||
scrollable=""
|
|
||||||
selected="[[page]]"
|
|
||||||
attr-for-selected="page-name"
|
|
||||||
on-iron-activate="handlePageSelected"
|
|
||||||
>
|
|
||||||
<paper-tab page-name="dashboard">Dashboard</paper-tab>
|
|
||||||
<paper-tab page-name="snapshots">Snapshots</paper-tab>
|
|
||||||
<paper-tab page-name="store">Add-on store</paper-tab>
|
|
||||||
<paper-tab page-name="system">System</paper-tab>
|
|
||||||
</paper-tabs>
|
|
||||||
</app-header>
|
|
||||||
<template is="dom-if" if='[[equals(page, "dashboard")]]'>
|
|
||||||
<hassio-dashboard
|
|
||||||
hass="[[hass]]"
|
|
||||||
supervisor-info="[[supervisorInfo]]"
|
|
||||||
hass-info="[[hassInfo]]"
|
|
||||||
></hassio-dashboard>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if='[[equals(page, "snapshots")]]'>
|
|
||||||
<hassio-snapshots
|
|
||||||
hass="[[hass]]"
|
|
||||||
installed-addons="[[supervisorInfo.addons]]"
|
|
||||||
snapshot-slug="{{snapshotSlug}}"
|
|
||||||
snapshot-deleted="{{snapshotDeleted}}"
|
|
||||||
></hassio-snapshots>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if='[[equals(page, "store")]]'>
|
|
||||||
<hassio-addon-store hass="[[hass]]"></hassio-addon-store>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if='[[equals(page, "system")]]'>
|
|
||||||
<hassio-system
|
|
||||||
hass="[[hass]]"
|
|
||||||
supervisor-info="[[supervisorInfo]]"
|
|
||||||
host-info="[[hostInfo]]"
|
|
||||||
></hassio-system>
|
|
||||||
</template>
|
|
||||||
</app-header-layout>
|
|
||||||
|
|
||||||
<hassio-markdown-dialog
|
|
||||||
title="[[markdownTitle]]"
|
|
||||||
content="[[markdownContent]]"
|
|
||||||
></hassio-markdown-dialog>
|
|
||||||
|
|
||||||
<template is="dom-if" if='[[equals(page, "snapshots")]]'>
|
|
||||||
<hassio-snapshot
|
|
||||||
hass="[[hass]]"
|
|
||||||
snapshot-slug="{{snapshotSlug}}"
|
|
||||||
snapshot-deleted="{{snapshotDeleted}}"
|
|
||||||
></hassio-snapshot>
|
|
||||||
</template>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
page: String,
|
|
||||||
supervisorInfo: Object,
|
|
||||||
hostInfo: Object,
|
|
||||||
hassInfo: Object,
|
|
||||||
snapshotSlug: String,
|
|
||||||
snapshotDeleted: Boolean,
|
|
||||||
|
|
||||||
markdownTitle: String,
|
|
||||||
markdownContent: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
this.addEventListener("hassio-markdown-dialog", (ev) =>
|
|
||||||
this.openMarkdown(ev)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePageSelected(ev) {
|
|
||||||
const newPage = ev.detail.item.getAttribute("page-name");
|
|
||||||
if (newPage !== this.page) {
|
|
||||||
this.navigate(`/hassio/${newPage}`);
|
|
||||||
}
|
|
||||||
scrollToTarget(this, this.$.layout.header.scrollTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(a, b) {
|
|
||||||
return a === b;
|
|
||||||
}
|
|
||||||
|
|
||||||
showRefreshButton(page) {
|
|
||||||
return page === "store" || page === "snapshots";
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshClicked() {
|
|
||||||
if (this.page === "snapshots") {
|
|
||||||
this.shadowRoot.querySelector("hassio-snapshots").refreshData();
|
|
||||||
} else {
|
|
||||||
this.shadowRoot.querySelector("hassio-addon-store").refreshData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openMarkdown(ev) {
|
|
||||||
this.setProperties({
|
|
||||||
markdownTitle: ev.detail.title,
|
|
||||||
markdownContent: ev.detail.content,
|
|
||||||
});
|
|
||||||
this.shadowRoot.querySelector("hassio-markdown-dialog").openDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hassio-pages-with-tabs", HassioPagesWithTabs);
|
|
131
hassio/src/hassio-pages-with-tabs.ts
Normal file
131
hassio/src/hassio-pages-with-tabs.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
CSSResultArray,
|
||||||
|
css,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||||
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
import "@polymer/paper-tabs/paper-tab";
|
||||||
|
import "@polymer/paper-tabs/paper-tabs";
|
||||||
|
|
||||||
|
import "../../src/components/ha-menu-button";
|
||||||
|
import "../../src/resources/ha-style";
|
||||||
|
import "./hassio-tabs-router";
|
||||||
|
|
||||||
|
import scrollToTarget from "../../src/common/dom/scroll-to-target";
|
||||||
|
|
||||||
|
import { haStyle } from "../../src/resources/styles";
|
||||||
|
import { HomeAssistant, Route } from "../../src/types";
|
||||||
|
import { navigate } from "../../src/common/navigate";
|
||||||
|
import {
|
||||||
|
HassioSupervisorInfo,
|
||||||
|
HassioHostInfo,
|
||||||
|
HassioHomeAssistantInfo,
|
||||||
|
} from "../../src/data/hassio";
|
||||||
|
|
||||||
|
const HAS_REFRESH_BUTTON = ["store", "snapshots"];
|
||||||
|
|
||||||
|
@customElement("hassio-pages-with-tabs")
|
||||||
|
class HassioPagesWithTabs extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public route!: Route;
|
||||||
|
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||||
|
@property() public hostInfo!: HassioHostInfo;
|
||||||
|
@property() public hassInfo!: HassioHomeAssistantInfo;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
const page = this._page;
|
||||||
|
return html`
|
||||||
|
<app-header-layout has-scrolling-region>
|
||||||
|
<app-header fixed slot="header">
|
||||||
|
<app-toolbar>
|
||||||
|
<ha-menu-button hassio></ha-menu-button>
|
||||||
|
<div main-title>Hass.io</div>
|
||||||
|
${HAS_REFRESH_BUTTON.includes(page)
|
||||||
|
? html`
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hassio:refresh"
|
||||||
|
@click=${this.refreshClicked}
|
||||||
|
></paper-icon-button>
|
||||||
|
`
|
||||||
|
: undefined}
|
||||||
|
</app-toolbar>
|
||||||
|
<paper-tabs
|
||||||
|
scrollable
|
||||||
|
attr-for-selected="page-name"
|
||||||
|
.selected=${page}
|
||||||
|
@iron-activate=${this.handlePageSelected}
|
||||||
|
>
|
||||||
|
<paper-tab page-name="dashboard">Dashboard</paper-tab>
|
||||||
|
<paper-tab page-name="snapshots">Snapshots</paper-tab>
|
||||||
|
<paper-tab page-name="store">Add-on store</paper-tab>
|
||||||
|
<paper-tab page-name="system">System</paper-tab>
|
||||||
|
</paper-tabs>
|
||||||
|
</app-header>
|
||||||
|
<hassio-tabs-router
|
||||||
|
.route=${this.route}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.supervisorInfo=${this.supervisorInfo}
|
||||||
|
.hostInfo=${this.hostInfo}
|
||||||
|
.hassInfo=${this.hassInfo}
|
||||||
|
></hassio-tabs-router>
|
||||||
|
</app-header-layout>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handlePageSelected(ev) {
|
||||||
|
const newPage = ev.detail.item.getAttribute("page-name");
|
||||||
|
if (newPage !== this._page) {
|
||||||
|
navigate(this, `/hassio/${newPage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToTarget(
|
||||||
|
this,
|
||||||
|
// @ts-ignore
|
||||||
|
this.shadowRoot!.querySelector("app-header-layout").header.scrollTarget
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshClicked() {
|
||||||
|
if (this._page === "snapshots") {
|
||||||
|
// @ts-ignore
|
||||||
|
this.shadowRoot.querySelector("hassio-snapshots").refreshData();
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
this.shadowRoot.querySelector("hassio-addon-store").refreshData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _page() {
|
||||||
|
return this.route.path.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultArray {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
--paper-card-header-color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
paper-tabs {
|
||||||
|
margin-left: 12px;
|
||||||
|
--paper-tabs-selection-bar-color: #fff;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-pages-with-tabs": HassioPagesWithTabs;
|
||||||
|
}
|
||||||
|
}
|
66
hassio/src/hassio-tabs-router.ts
Normal file
66
hassio/src/hassio-tabs-router.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
HassRouterPage,
|
||||||
|
RouterOptions,
|
||||||
|
} from "../../src/layouts/hass-router-page";
|
||||||
|
import { customElement, property } from "lit-element";
|
||||||
|
import { PolymerElement } from "@polymer/polymer";
|
||||||
|
import { HomeAssistant } from "../../src/types";
|
||||||
|
// Don't codesplit it, that way the dashboard always loads fast.
|
||||||
|
import "./dashboard/hassio-dashboard";
|
||||||
|
// Don't codesplit the others, because it breaks the UI when pushed to a Pi
|
||||||
|
import "./snapshots/hassio-snapshots";
|
||||||
|
import "./addon-store/hassio-addon-store";
|
||||||
|
import "./system/hassio-system";
|
||||||
|
import {
|
||||||
|
HassioSupervisorInfo,
|
||||||
|
HassioHostInfo,
|
||||||
|
HassioHomeAssistantInfo,
|
||||||
|
} from "../../src/data/hassio";
|
||||||
|
|
||||||
|
@customElement("hassio-tabs-router")
|
||||||
|
class HassioTabsRouter extends HassRouterPage {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public supervisorInfo: HassioSupervisorInfo;
|
||||||
|
@property() public hostInfo: HassioHostInfo;
|
||||||
|
@property() public hassInfo: HassioHomeAssistantInfo;
|
||||||
|
|
||||||
|
protected routerOptions: RouterOptions = {
|
||||||
|
routes: {
|
||||||
|
dashboard: {
|
||||||
|
tag: "hassio-dashboard",
|
||||||
|
},
|
||||||
|
snapshots: {
|
||||||
|
tag: "hassio-snapshots",
|
||||||
|
},
|
||||||
|
store: {
|
||||||
|
tag: "hassio-addon-store",
|
||||||
|
},
|
||||||
|
system: {
|
||||||
|
tag: "hassio-system",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
protected updatePageEl(el) {
|
||||||
|
if ("setProperties" in el) {
|
||||||
|
// As long as we have Polymer pages
|
||||||
|
(el as PolymerElement).setProperties({
|
||||||
|
hass: this.hass,
|
||||||
|
supervisorInfo: this.supervisorInfo,
|
||||||
|
hostInfo: this.hostInfo,
|
||||||
|
hassInfo: this.hassInfo,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
el.hass = this.hass;
|
||||||
|
el.supervisorInfo = this.supervisorInfo;
|
||||||
|
el.hostInfo = this.hostInfo;
|
||||||
|
el.hassInfo = this.hassInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-tabs-router": HassioTabsRouter;
|
||||||
|
}
|
||||||
|
}
|
100
hassio/src/ingress-view/hassio-ingress-view.ts
Normal file
100
hassio/src/ingress-view/hassio-ingress-view.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
PropertyValues,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
|
import {
|
||||||
|
createHassioSession,
|
||||||
|
HassioAddonDetails,
|
||||||
|
fetchHassioAddonInfo,
|
||||||
|
} from "../../../src/data/hassio";
|
||||||
|
import "../../../src/layouts/hass-loading-screen";
|
||||||
|
import "../../../src/layouts/hass-subpage";
|
||||||
|
|
||||||
|
@customElement("hassio-ingress-view")
|
||||||
|
class HassioIngressView extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public route!: Route;
|
||||||
|
@property() private _addon?: HassioAddonDetails;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this._addon) {
|
||||||
|
return html`
|
||||||
|
<hass-loading-screen></hass-loading-screen>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hass-subpage .header=${this._addon.name} hassio>
|
||||||
|
<iframe src=${this._addon.ingress_url}></iframe>
|
||||||
|
</hass-subpage>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
|
if (!changedProps.has("route")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addon = this.route.path.substr(1);
|
||||||
|
|
||||||
|
const oldRoute = changedProps.get("route") as this["route"] | undefined;
|
||||||
|
const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;
|
||||||
|
|
||||||
|
if (addon && addon !== oldAddon) {
|
||||||
|
this._fetchData(addon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchData(addonSlug: string) {
|
||||||
|
try {
|
||||||
|
const [addon] = await Promise.all([
|
||||||
|
fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {
|
||||||
|
throw new Error("Failed to fetch add-on info");
|
||||||
|
}),
|
||||||
|
createHassioSession(this.hass).catch(() => {
|
||||||
|
throw new Error("Failed to create an ingress session");
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!addon.ingress) {
|
||||||
|
throw new Error("This add-on does not support ingress");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._addon = addon;
|
||||||
|
} catch (err) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error(err);
|
||||||
|
alert(err.message || "Unknown error starting ingress.");
|
||||||
|
history.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
iframe {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
paper-icon-button {
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-ingress-view": HassioIngressView;
|
||||||
|
}
|
||||||
|
}
|
@ -1,56 +1,64 @@
|
|||||||
|
import { css } from "lit-element";
|
||||||
|
|
||||||
const documentContainer = document.createElement("template");
|
const documentContainer = document.createElement("template");
|
||||||
documentContainer.setAttribute("style", "display: none;");
|
documentContainer.setAttribute("style", "display: none;");
|
||||||
|
|
||||||
|
export const hassioStyle = css`
|
||||||
|
.card-group {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.card-group .title {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
font-size: 2em;
|
||||||
|
padding-left: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.card-group .description {
|
||||||
|
font-size: 0.5em;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.card-group paper-card {
|
||||||
|
--card-group-columns: 4;
|
||||||
|
width: calc(
|
||||||
|
(100% - 12px * var(--card-group-columns)) / var(--card-group-columns)
|
||||||
|
);
|
||||||
|
margin: 4px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 1200px) and (min-width: 901px) {
|
||||||
|
.card-group paper-card {
|
||||||
|
--card-group-columns: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 900px) and (min-width: 601px) {
|
||||||
|
.card-group paper-card {
|
||||||
|
--card-group-columns: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 600px) and (min-width: 0) {
|
||||||
|
.card-group paper-card {
|
||||||
|
width: 100%;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ha-call-api-button {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: var(--google-red-500);
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
documentContainer.innerHTML = `<dom-module id="hassio-style">
|
documentContainer.innerHTML = `<dom-module id="hassio-style">
|
||||||
<template>
|
<template>
|
||||||
<style>
|
<style>
|
||||||
.card-group {
|
${hassioStyle.toString()}
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
.card-group .title {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
font-size: 2em;
|
|
||||||
padding-left: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.card-group .description {
|
|
||||||
font-size: 0.5em;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
.card-group paper-card {
|
|
||||||
--card-group-columns: 4;
|
|
||||||
width: calc((100% - 12px * var(--card-group-columns)) / var(--card-group-columns));
|
|
||||||
margin: 4px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 1200px) and (min-width: 901px) {
|
|
||||||
.card-group paper-card {
|
|
||||||
--card-group-columns: 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 900px) and (min-width: 601px) {
|
|
||||||
.card-group paper-card {
|
|
||||||
--card-group-columns: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 600px) and (min-width: 0) {
|
|
||||||
.card-group paper-card {
|
|
||||||
width: 100%;
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ha-call-api-button {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--google-red-500);
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</template>
|
</template>
|
||||||
</dom-module>`;
|
</dom-module>`;
|
||||||
|
@ -1,311 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-card/paper-card";
|
|
||||||
import "@polymer/paper-checkbox/paper-checkbox";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-radio-button/paper-radio-button";
|
|
||||||
import "@polymer/paper-radio-group/paper-radio-group";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../components/hassio-card-content";
|
|
||||||
import "../resources/hassio-style";
|
|
||||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
|
||||||
|
|
||||||
class HassioSnapshots extends EventsMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style hassio-style">
|
|
||||||
paper-radio-group {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
paper-radio-button {
|
|
||||||
padding: 0 0 2px 2px;
|
|
||||||
}
|
|
||||||
paper-radio-button,
|
|
||||||
paper-checkbox,
|
|
||||||
paper-input[type="password"] {
|
|
||||||
display: block;
|
|
||||||
margin: 4px 0 4px 48px;
|
|
||||||
}
|
|
||||||
.pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="content">
|
|
||||||
<div class="card-group">
|
|
||||||
<div class="title">
|
|
||||||
Create snapshot
|
|
||||||
<div class="description">
|
|
||||||
Snapshots allow you to easily backup and restore all data of your
|
|
||||||
Hass.io instance.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<paper-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<paper-input
|
|
||||||
autofocus=""
|
|
||||||
label="Name"
|
|
||||||
value="{{snapshotName}}"
|
|
||||||
></paper-input>
|
|
||||||
Type:
|
|
||||||
<paper-radio-group selected="{{snapshotType}}">
|
|
||||||
<paper-radio-button name="full">
|
|
||||||
Full snapshot
|
|
||||||
</paper-radio-button>
|
|
||||||
<paper-radio-button name="partial">
|
|
||||||
Partial snapshot
|
|
||||||
</paper-radio-button>
|
|
||||||
</paper-radio-group>
|
|
||||||
<template is="dom-if" if="[[!_fullSelected(snapshotType)]]">
|
|
||||||
Folders:
|
|
||||||
<template is="dom-repeat" items="[[folderList]]">
|
|
||||||
<paper-checkbox checked="{{item.checked}}">
|
|
||||||
[[item.name]]
|
|
||||||
</paper-checkbox>
|
|
||||||
</template>
|
|
||||||
Add-ons:
|
|
||||||
<template
|
|
||||||
is="dom-repeat"
|
|
||||||
items="[[addonList]]"
|
|
||||||
sort="_sortAddons"
|
|
||||||
>
|
|
||||||
<paper-checkbox checked="{{item.checked}}">
|
|
||||||
[[item.name]]
|
|
||||||
</paper-checkbox>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
Security:
|
|
||||||
<paper-checkbox checked="{{snapshotHasPassword}}"
|
|
||||||
>Password protection</paper-checkbox
|
|
||||||
>
|
|
||||||
<template is="dom-if" if="[[snapshotHasPassword]]">
|
|
||||||
<paper-input
|
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
value="{{snapshotPassword}}"
|
|
||||||
></paper-input>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[error]]">
|
|
||||||
<p class="error">[[error]]</p>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<mwc-button
|
|
||||||
disabled="[[creatingSnapshot]]"
|
|
||||||
on-click="_createSnapshot"
|
|
||||||
>Create</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</paper-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-group">
|
|
||||||
<div class="title">Available snapshots</div>
|
|
||||||
<template is="dom-if" if="[[!snapshots.length]]">
|
|
||||||
<paper-card>
|
|
||||||
<div class="card-content">You don't have any snapshots yet.</div>
|
|
||||||
</paper-card>
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
is="dom-repeat"
|
|
||||||
items="[[snapshots]]"
|
|
||||||
as="snapshot"
|
|
||||||
sort="_sortSnapshots"
|
|
||||||
>
|
|
||||||
<paper-card class="pointer" on-click="_snapshotClicked">
|
|
||||||
<div class="card-content">
|
|
||||||
<hassio-card-content
|
|
||||||
hass="[[hass]]"
|
|
||||||
title="[[_computeName(snapshot)]]"
|
|
||||||
description="[[_computeDetails(snapshot)]]"
|
|
||||||
datetime="[[snapshot.date]]"
|
|
||||||
icon="[[_computeIcon(snapshot.type)]]"
|
|
||||||
icon-class="snapshot"
|
|
||||||
></hassio-card-content>
|
|
||||||
</div>
|
|
||||||
</paper-card>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
snapshotName: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
snapshotPassword: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
snapshotHasPassword: Boolean,
|
|
||||||
snapshotType: {
|
|
||||||
type: String,
|
|
||||||
value: "full",
|
|
||||||
},
|
|
||||||
snapshots: {
|
|
||||||
type: Array,
|
|
||||||
value: [],
|
|
||||||
},
|
|
||||||
installedAddons: {
|
|
||||||
type: Array,
|
|
||||||
observer: "_installedAddonsChanged",
|
|
||||||
},
|
|
||||||
addonList: Array,
|
|
||||||
folderList: {
|
|
||||||
type: Array,
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
slug: "homeassistant",
|
|
||||||
name: "Home Assistant configuration",
|
|
||||||
checked: true,
|
|
||||||
},
|
|
||||||
{ slug: "ssl", name: "SSL", checked: true },
|
|
||||||
{ slug: "share", name: "Share", checked: true },
|
|
||||||
{ slug: "addons/local", name: "Local add-ons", checked: true },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
snapshotSlug: {
|
|
||||||
type: String,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
snapshotDeleted: {
|
|
||||||
type: Boolean,
|
|
||||||
notify: true,
|
|
||||||
observer: "_snapshotDeletedChanged",
|
|
||||||
},
|
|
||||||
creatingSnapshot: Boolean,
|
|
||||||
dialogOpened: Boolean,
|
|
||||||
error: String,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ready() {
|
|
||||||
super.ready();
|
|
||||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
|
||||||
this._updateSnapshots();
|
|
||||||
}
|
|
||||||
|
|
||||||
_apiCalled(ev) {
|
|
||||||
if (ev.detail.success) {
|
|
||||||
this._updateSnapshots();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateSnapshots() {
|
|
||||||
this.hass.callApi("get", "hassio/snapshots").then(
|
|
||||||
(result) => {
|
|
||||||
this.snapshots = result.data.snapshots;
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
this.error = error.message;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_createSnapshot() {
|
|
||||||
this.error = "";
|
|
||||||
if (this.snapshotHasPassword && !this.snapshotPassword.length) {
|
|
||||||
this.error = "Please enter a password.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.creatingSnapshot = true;
|
|
||||||
let name = this.snapshotName;
|
|
||||||
if (!name.length) {
|
|
||||||
name = new Date().toLocaleDateString(navigator.language, {
|
|
||||||
weekday: "long",
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let data;
|
|
||||||
let path;
|
|
||||||
if (this.snapshotType === "full") {
|
|
||||||
data = { name: name };
|
|
||||||
path = "hassio/snapshots/new/full";
|
|
||||||
} else {
|
|
||||||
const addons = this.addonList
|
|
||||||
.filter((addon) => addon.checked)
|
|
||||||
.map((addon) => addon.slug);
|
|
||||||
const folders = this.folderList
|
|
||||||
.filter((folder) => folder.checked)
|
|
||||||
.map((folder) => folder.slug);
|
|
||||||
|
|
||||||
data = { name: name, folders: folders, addons: addons };
|
|
||||||
path = "hassio/snapshots/new/partial";
|
|
||||||
}
|
|
||||||
if (this.snapshotHasPassword) {
|
|
||||||
data.password = this.snapshotPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hass.callApi("post", path, data).then(
|
|
||||||
() => {
|
|
||||||
this.creatingSnapshot = false;
|
|
||||||
this.fire("hass-api-called", { success: true });
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
this.creatingSnapshot = false;
|
|
||||||
this.error = error.message;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_installedAddonsChanged(addons) {
|
|
||||||
this.addonList = addons.map((addon) => ({
|
|
||||||
slug: addon.slug,
|
|
||||||
name: addon.name,
|
|
||||||
checked: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_sortAddons(a, b) {
|
|
||||||
return a.name < b.name ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sortSnapshots(a, b) {
|
|
||||||
return a.date < b.date ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeName(snapshot) {
|
|
||||||
return snapshot.name || snapshot.slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeDetails(snapshot) {
|
|
||||||
const type =
|
|
||||||
snapshot.type === "full" ? "Full snapshot" : "Partial snapshot";
|
|
||||||
return snapshot.protected ? `${type}, password protected` : type;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeIcon(type) {
|
|
||||||
return type === "full"
|
|
||||||
? "hassio:package-variant-closed"
|
|
||||||
: "hassio:package-variant";
|
|
||||||
}
|
|
||||||
|
|
||||||
_snapshotClicked(ev) {
|
|
||||||
this.snapshotSlug = ev.model.snapshot.slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
_fullSelected(type) {
|
|
||||||
return type === "full";
|
|
||||||
}
|
|
||||||
|
|
||||||
_snapshotDeletedChanged(snapshotDeleted) {
|
|
||||||
if (snapshotDeleted) {
|
|
||||||
this._updateSnapshots();
|
|
||||||
this.snapshotDeleted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshData() {
|
|
||||||
this.hass.callApi("post", "hassio/snapshots/reload").then(() => {
|
|
||||||
this._updateSnapshots();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hassio-snapshots", HassioSnapshots);
|
|
363
hassio/src/snapshots/hassio-snapshots.ts
Normal file
363
hassio/src/snapshots/hassio-snapshots.ts
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
CSSResultArray,
|
||||||
|
css,
|
||||||
|
property,
|
||||||
|
PropertyValues,
|
||||||
|
customElement,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-card/paper-card";
|
||||||
|
import "@polymer/paper-checkbox/paper-checkbox";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "@polymer/paper-radio-button/paper-radio-button";
|
||||||
|
import "@polymer/paper-radio-group/paper-radio-group";
|
||||||
|
|
||||||
|
import "../components/hassio-card-content";
|
||||||
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
|
||||||
|
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
|
||||||
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
import {
|
||||||
|
HassioSnapshot,
|
||||||
|
HassioSupervisorInfo,
|
||||||
|
fetchHassioSnapshots,
|
||||||
|
reloadHassioSnapshots,
|
||||||
|
HassioFullSnapshotCreateParams,
|
||||||
|
HassioPartialSnapshotCreateParams,
|
||||||
|
createHassioFullSnapshot,
|
||||||
|
createHassioPartialSnapshot,
|
||||||
|
} from "../../../src/data/hassio";
|
||||||
|
import { PolymerChangedEvent } from "../../../src/polymer-types";
|
||||||
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
|
||||||
|
// Not duplicate, used for typing
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
||||||
|
|
||||||
|
interface CheckboxItem {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
checked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("hassio-snapshots")
|
||||||
|
class HassioSnapshots extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||||
|
@property() private _snapshotName = "";
|
||||||
|
@property() private _snapshotPassword = "";
|
||||||
|
@property() private _snapshotHasPassword = false;
|
||||||
|
@property() private _snapshotType: HassioSnapshot["type"] = "full";
|
||||||
|
@property() private _snapshots?: HassioSnapshot[] = [];
|
||||||
|
@property() private _addonList: CheckboxItem[] = [];
|
||||||
|
@property() private _folderList: CheckboxItem[] = [
|
||||||
|
{
|
||||||
|
slug: "homeassistant",
|
||||||
|
name: "Home Assistant configuration",
|
||||||
|
checked: true,
|
||||||
|
},
|
||||||
|
{ slug: "ssl", name: "SSL", checked: true },
|
||||||
|
{ slug: "share", name: "Share", checked: true },
|
||||||
|
{ slug: "addons/local", name: "Local add-ons", checked: true },
|
||||||
|
];
|
||||||
|
@property() private _creatingSnapshot = false;
|
||||||
|
@property() private _error = "";
|
||||||
|
|
||||||
|
public async refreshData() {
|
||||||
|
await reloadHassioSnapshots(this.hass);
|
||||||
|
await this._updateSnapshots();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<div class="content">
|
||||||
|
<div class="card-group">
|
||||||
|
<div class="title">
|
||||||
|
Create snapshot
|
||||||
|
<div class="description">
|
||||||
|
Snapshots allow you to easily backup and restore all data of your
|
||||||
|
Hass.io instance.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<paper-card>
|
||||||
|
<div class="card-content">
|
||||||
|
<paper-input
|
||||||
|
autofocus
|
||||||
|
label="Name"
|
||||||
|
name="snapshotName"
|
||||||
|
.value=${this._snapshotName}
|
||||||
|
@value-changed=${this._handleTextValueChanged}
|
||||||
|
></paper-input>
|
||||||
|
Type:
|
||||||
|
<paper-radio-group
|
||||||
|
name="snapshotType"
|
||||||
|
.selected=${this._snapshotType}
|
||||||
|
@selected-changed=${this._handleRadioValueChanged}
|
||||||
|
>
|
||||||
|
<paper-radio-button name="full">
|
||||||
|
Full snapshot
|
||||||
|
</paper-radio-button>
|
||||||
|
<paper-radio-button name="partial">
|
||||||
|
Partial snapshot
|
||||||
|
</paper-radio-button>
|
||||||
|
</paper-radio-group>
|
||||||
|
${this._snapshotType === "full"
|
||||||
|
? undefined
|
||||||
|
: html`
|
||||||
|
Folders:
|
||||||
|
${this._folderList.map(
|
||||||
|
(folder, idx) => html`
|
||||||
|
<paper-checkbox
|
||||||
|
.idx=${idx}
|
||||||
|
.checked=${folder.checked}
|
||||||
|
@checked-changed=${this._folderChecked}
|
||||||
|
>
|
||||||
|
${folder.name}
|
||||||
|
</paper-checkbox>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
Add-ons:
|
||||||
|
${this._addonList.map(
|
||||||
|
(addon, idx) => html`
|
||||||
|
<paper-checkbox
|
||||||
|
.idx=${idx}
|
||||||
|
.checked="{{item.checked}}"
|
||||||
|
@checked-changed=${this._addonChecked}
|
||||||
|
>
|
||||||
|
${addon.name}
|
||||||
|
</paper-checkbox>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`}
|
||||||
|
Security:
|
||||||
|
<paper-checkbox
|
||||||
|
name="snapshotHasPassword"
|
||||||
|
.checked=${this._snapshotHasPassword}
|
||||||
|
@checked-changed=${this._handleCheckboxValueChanged}
|
||||||
|
>
|
||||||
|
Password protection
|
||||||
|
</paper-checkbox>
|
||||||
|
${this._snapshotHasPassword
|
||||||
|
? html`
|
||||||
|
<paper-input
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
name="snapshotPassword"
|
||||||
|
.value=${this._snapshotPassword}
|
||||||
|
@value-changed=${this._handleTextValueChanged}
|
||||||
|
></paper-input>
|
||||||
|
`
|
||||||
|
: undefined}
|
||||||
|
${this._error !== ""
|
||||||
|
? html`
|
||||||
|
<p class="error">${this._error}</p>
|
||||||
|
`
|
||||||
|
: undefined}
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button
|
||||||
|
.disabled=${this._creatingSnapshot}
|
||||||
|
@click=${this._createSnapshot}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-group">
|
||||||
|
<div class="title">Available snapshots</div>
|
||||||
|
${this._snapshots === undefined
|
||||||
|
? undefined
|
||||||
|
: this._snapshots.length === 0
|
||||||
|
? html`
|
||||||
|
<paper-card>
|
||||||
|
<div class="card-content">
|
||||||
|
You don't have any snapshots yet.
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
`
|
||||||
|
: this._snapshots.map(
|
||||||
|
(snapshot) => html`
|
||||||
|
<paper-card
|
||||||
|
class="pointer"
|
||||||
|
.snapshot=${snapshot}
|
||||||
|
@click=${this._snapshotClicked}
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
<hassio-card-content
|
||||||
|
.hass=${this.hass}
|
||||||
|
.title=${snapshot.name || snapshot.slug}
|
||||||
|
.description=${this._computeDetails(snapshot)}
|
||||||
|
.datetime=${snapshot.date}
|
||||||
|
.icon=${snapshot.type === "full"
|
||||||
|
? "hassio:package-variant-closed"
|
||||||
|
: "hassio:package-variant"}
|
||||||
|
.
|
||||||
|
.icon-class="snapshot"
|
||||||
|
></hassio-card-content>
|
||||||
|
</div>
|
||||||
|
</paper-card>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this._updateSnapshots();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
if (changedProps.has("supervisorInfo")) {
|
||||||
|
this._addonList = this.supervisorInfo.addons
|
||||||
|
.map((addon) => ({
|
||||||
|
slug: addon.slug,
|
||||||
|
name: addon.name,
|
||||||
|
checked: true,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleTextValueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
const input = ev.currentTarget as PaperInputElement;
|
||||||
|
this[`_${input.name}`] = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleCheckboxValueChanged(ev) {
|
||||||
|
const input = ev.currentTarget as PaperCheckboxElement;
|
||||||
|
this[`_${input.name}`] = input.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleRadioValueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
const input = ev.currentTarget as PaperRadioGroupElement;
|
||||||
|
this[`_${input.getAttribute("name")}`] = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _folderChecked(ev) {
|
||||||
|
const { idx, checked } = ev.currentTarget!;
|
||||||
|
this._folderList = this._folderList.map((folder, curIdx) =>
|
||||||
|
curIdx === idx ? { ...folder, checked } : folder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addonChecked(ev) {
|
||||||
|
const { idx, checked } = ev.currentTarget!;
|
||||||
|
this._addonList = this._addonList.map((addon, curIdx) =>
|
||||||
|
curIdx === idx ? { ...addon, checked } : addon
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateSnapshots() {
|
||||||
|
try {
|
||||||
|
this._snapshots = await fetchHassioSnapshots(this.hass);
|
||||||
|
this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1));
|
||||||
|
} catch (err) {
|
||||||
|
this._error = err.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createSnapshot() {
|
||||||
|
this._error = "";
|
||||||
|
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
|
||||||
|
this._error = "Please enter a password.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._creatingSnapshot = true;
|
||||||
|
await this.updateComplete;
|
||||||
|
|
||||||
|
const name =
|
||||||
|
this._snapshotName ||
|
||||||
|
new Date().toLocaleDateString(navigator.language, {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this._snapshotType === "full") {
|
||||||
|
const data: HassioFullSnapshotCreateParams = { name };
|
||||||
|
if (this._snapshotHasPassword) {
|
||||||
|
data.password = this._snapshotPassword;
|
||||||
|
}
|
||||||
|
await createHassioFullSnapshot(this.hass, data);
|
||||||
|
} else {
|
||||||
|
const addons = this._addonList
|
||||||
|
.filter((addon) => addon.checked)
|
||||||
|
.map((addon) => addon.slug);
|
||||||
|
const folders = this._folderList
|
||||||
|
.filter((folder) => folder.checked)
|
||||||
|
.map((folder) => folder.slug);
|
||||||
|
|
||||||
|
const data: HassioPartialSnapshotCreateParams = {
|
||||||
|
name,
|
||||||
|
folders,
|
||||||
|
addons,
|
||||||
|
};
|
||||||
|
if (this._snapshotHasPassword) {
|
||||||
|
data.password = this._snapshotPassword;
|
||||||
|
}
|
||||||
|
await createHassioPartialSnapshot(this.hass, data);
|
||||||
|
}
|
||||||
|
this._updateSnapshots();
|
||||||
|
fireEvent(this, "hass-api-called", { success: true, response: null });
|
||||||
|
} catch (err) {
|
||||||
|
this._error = err.message;
|
||||||
|
} finally {
|
||||||
|
this._creatingSnapshot = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeDetails(snapshot: HassioSnapshot) {
|
||||||
|
const type =
|
||||||
|
snapshot.type === "full" ? "Full snapshot" : "Partial snapshot";
|
||||||
|
return snapshot.protected ? `${type}, password protected` : type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _snapshotClicked(ev) {
|
||||||
|
showHassioSnapshotDialog(this, {
|
||||||
|
slug: ev.currentTarget!.snapshot.slug,
|
||||||
|
onDelete: () => this._updateSnapshots(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultArray {
|
||||||
|
return [
|
||||||
|
hassioStyle,
|
||||||
|
css`
|
||||||
|
paper-radio-group {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
paper-radio-button {
|
||||||
|
padding: 0 0 2px 2px;
|
||||||
|
}
|
||||||
|
paper-radio-button,
|
||||||
|
paper-checkbox,
|
||||||
|
paper-input[type="password"] {
|
||||||
|
display: block;
|
||||||
|
margin: 4px 0 4px 48px;
|
||||||
|
}
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hassio-snapshots": HassioSnapshots;
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,12 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|||||||
import "../../../src/components/buttons/ha-call-api-button";
|
import "../../../src/components/buttons/ha-call-api-button";
|
||||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||||
|
|
||||||
|
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||||
|
|
||||||
class HassioHostInfo extends EventsMixin(PolymerElement) {
|
class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style include="iron-flex ha-style">
|
<style>
|
||||||
paper-card {
|
paper-card {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
@ -173,7 +175,7 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
|||||||
() => "Error getting hardware info"
|
() => "Error getting hardware info"
|
||||||
)
|
)
|
||||||
.then((content) => {
|
.then((content) => {
|
||||||
this.fire("hassio-markdown-dialog", {
|
showHassioMarkdownDialog(this, {
|
||||||
title: "Hardware",
|
title: "Hardware",
|
||||||
content: content,
|
content: content,
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,7 @@ import EventsMixin from "../../../src/mixins/events-mixin";
|
|||||||
class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style include="iron-flex ha-style">
|
<style>
|
||||||
paper-card {
|
paper-card {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ import "./hassio-supervisor-log";
|
|||||||
class HassioSystem extends PolymerElement {
|
class HassioSystem extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style include="iron-flex ha-style">
|
<style>
|
||||||
.content {
|
.content {
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
|
147
package.json
147
package.json
@ -20,7 +20,7 @@
|
|||||||
"@material/mwc-button": "^0.5.0",
|
"@material/mwc-button": "^0.5.0",
|
||||||
"@material/mwc-ripple": "^0.5.0",
|
"@material/mwc-ripple": "^0.5.0",
|
||||||
"@mdi/svg": "3.5.95",
|
"@mdi/svg": "3.5.95",
|
||||||
"@polymer/app-layout": "^3.0.1",
|
"@polymer/app-layout": "^3.0.2",
|
||||||
"@polymer/app-localize-behavior": "^3.0.1",
|
"@polymer/app-localize-behavior": "^3.0.1",
|
||||||
"@polymer/app-route": "^3.0.2",
|
"@polymer/app-route": "^3.0.2",
|
||||||
"@polymer/app-storage": "^3.0.2",
|
"@polymer/app-storage": "^3.0.2",
|
||||||
@ -39,14 +39,14 @@
|
|||||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||||
"@polymer/neon-animation": "^3.0.1",
|
"@polymer/neon-animation": "^3.0.1",
|
||||||
"@polymer/paper-card": "^3.0.1",
|
"@polymer/paper-card": "^3.0.1",
|
||||||
"@polymer/paper-checkbox": "^3.0.1",
|
"@polymer/paper-checkbox": "^3.1.0",
|
||||||
"@polymer/paper-dialog": "^3.0.1",
|
"@polymer/paper-dialog": "^3.0.1",
|
||||||
"@polymer/paper-dialog-behavior": "^3.0.1",
|
"@polymer/paper-dialog-behavior": "^3.0.1",
|
||||||
"@polymer/paper-dialog-scrollable": "^3.0.1",
|
"@polymer/paper-dialog-scrollable": "^3.0.1",
|
||||||
"@polymer/paper-drawer-panel": "^3.0.1",
|
"@polymer/paper-drawer-panel": "^3.0.1",
|
||||||
"@polymer/paper-dropdown-menu": "^3.0.1",
|
"@polymer/paper-dropdown-menu": "^3.0.1",
|
||||||
"@polymer/paper-fab": "^3.0.1",
|
"@polymer/paper-fab": "^3.0.1",
|
||||||
"@polymer/paper-icon-button": "^3.0.1",
|
"@polymer/paper-icon-button": "^3.0.2",
|
||||||
"@polymer/paper-input": "^3.0.1",
|
"@polymer/paper-input": "^3.0.1",
|
||||||
"@polymer/paper-item": "^3.0.1",
|
"@polymer/paper-item": "^3.0.1",
|
||||||
"@polymer/paper-listbox": "^3.0.1",
|
"@polymer/paper-listbox": "^3.0.1",
|
||||||
@ -57,119 +57,114 @@
|
|||||||
"@polymer/paper-ripple": "^3.0.1",
|
"@polymer/paper-ripple": "^3.0.1",
|
||||||
"@polymer/paper-scroll-header-panel": "^3.0.1",
|
"@polymer/paper-scroll-header-panel": "^3.0.1",
|
||||||
"@polymer/paper-slider": "^3.0.1",
|
"@polymer/paper-slider": "^3.0.1",
|
||||||
"@polymer/paper-spinner": "^3.0.1",
|
"@polymer/paper-spinner": "^3.0.2",
|
||||||
"@polymer/paper-styles": "^3.0.1",
|
"@polymer/paper-styles": "^3.0.1",
|
||||||
"@polymer/paper-tabs": "^3.0.1",
|
"@polymer/paper-tabs": "^3.0.1",
|
||||||
"@polymer/paper-toast": "^3.0.1",
|
"@polymer/paper-toast": "^3.0.1",
|
||||||
"@polymer/paper-toggle-button": "^3.0.1",
|
"@polymer/paper-toggle-button": "^3.0.1",
|
||||||
"@polymer/paper-tooltip": "^3.0.1",
|
"@polymer/paper-tooltip": "^3.0.1",
|
||||||
"@polymer/polymer": "^3.0.5",
|
"@polymer/polymer": "^3.2.0",
|
||||||
"@vaadin/vaadin-combo-box": "^4.2.0",
|
"@vaadin/vaadin-combo-box": "^4.2.8",
|
||||||
"@vaadin/vaadin-date-picker": "^3.3.1",
|
"@vaadin/vaadin-date-picker": "^3.3.3",
|
||||||
"@webcomponents/shadycss": "^1.9.0",
|
"@webcomponents/shadycss": "^1.9.0",
|
||||||
"@webcomponents/webcomponentsjs": "^2.2.6",
|
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||||
"chart.js": "~2.7.2",
|
"chart.js": "~2.8.0",
|
||||||
"chartjs-chart-timeline": "^0.2.1",
|
"chartjs-chart-timeline": "^0.3.0",
|
||||||
"codemirror": "^5.43.0",
|
"codemirror": "^5.45.0",
|
||||||
"deep-clone-simple": "^1.1.1",
|
"deep-clone-simple": "^1.1.1",
|
||||||
"es6-object-assign": "^1.1.0",
|
"es6-object-assign": "^1.1.0",
|
||||||
"fecha": "^3.0.0",
|
"fecha": "^3.0.2",
|
||||||
"hls.js": "^0.12.3",
|
"hls.js": "^0.12.4",
|
||||||
"home-assistant-js-websocket": "^3.4.0",
|
"home-assistant-js-websocket": "^3.4.0",
|
||||||
"intl-messageformat": "^2.2.0",
|
"intl-messageformat": "^2.2.0",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"js-yaml": "^3.12.0",
|
"js-yaml": "^3.13.0",
|
||||||
"leaflet": "^1.3.4",
|
"leaflet": "^1.4.0",
|
||||||
"lit-element": "^2.1.0",
|
"lit-element": "^2.1.0",
|
||||||
"lit-html": "^1.0.0",
|
"lit-html": "^1.0.0",
|
||||||
"marked": "^0.6.0",
|
"marked": "^0.6.1",
|
||||||
"mdn-polyfills": "^5.12.0",
|
"mdn-polyfills": "^5.16.0",
|
||||||
"memoize-one": "^5.0.0",
|
"memoize-one": "^5.0.2",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.24.0",
|
||||||
"preact": "^8.3.1",
|
"preact": "^8.4.2",
|
||||||
"preact-compat": "^3.18.4",
|
"preact-compat": "^3.18.4",
|
||||||
"react-big-calendar": "^0.19.2",
|
"react-big-calendar": "^0.20.4",
|
||||||
"regenerator-runtime": "^0.12.1",
|
"regenerator-runtime": "^0.13.2",
|
||||||
"round-slider": "^1.3.2",
|
"round-slider": "^1.3.3",
|
||||||
"superstruct": "^0.6.0",
|
"superstruct": "^0.6.1",
|
||||||
"unfetch": "^4.0.1",
|
"unfetch": "^4.1.0",
|
||||||
"web-animations-js": "^2.3.1",
|
"web-animations-js": "^2.3.1",
|
||||||
"xss": "^1.0.3"
|
"xss": "^1.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.1.2",
|
"@babel/core": "^7.4.0",
|
||||||
"@babel/plugin-external-helpers": "^7.0.0",
|
"@babel/plugin-external-helpers": "^7.2.0",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.3.0",
|
"@babel/plugin-proposal-class-properties": "^7.4.0",
|
||||||
"@babel/plugin-proposal-decorators": "^7.3.0",
|
"@babel/plugin-proposal-decorators": "^7.4.0",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
"@babel/plugin-proposal-object-rest-spread": "^7.4.0",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/plugin-transform-react-jsx": "^7.0.0",
|
"@babel/plugin-transform-react-jsx": "^7.3.0",
|
||||||
"@babel/preset-env": "^7.1.0",
|
"@babel/preset-env": "^7.4.2",
|
||||||
"@babel/preset-typescript": "^7.1.0",
|
"@babel/preset-typescript": "^7.3.3",
|
||||||
"@gfx/zopfli": "^1.0.9",
|
"@gfx/zopfli": "^1.0.11",
|
||||||
"@types/chai": "^4.1.7",
|
"@types/chai": "^4.1.7",
|
||||||
"@types/codemirror": "^0.0.71",
|
"@types/hls.js": "^0.12.3",
|
||||||
"@types/hls.js": "^0.12.2",
|
|
||||||
"@types/leaflet": "^1.4.3",
|
"@types/leaflet": "^1.4.3",
|
||||||
"@types/memoize-one": "^4.1.0",
|
"@types/memoize-one": "4.1.0",
|
||||||
"@types/mocha": "^5.2.5",
|
"@types/mocha": "^5.2.6",
|
||||||
"babel-eslint": "^10",
|
"babel-eslint": "^10",
|
||||||
"babel-loader": "^8.0.4",
|
"babel-loader": "^8.0.5",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"compression-webpack-plugin": "^2.0.0",
|
"compression-webpack-plugin": "^2.0.0",
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^5.0.2",
|
||||||
"del": "^3.0.0",
|
"del": "^4.0.0",
|
||||||
"eslint": "^5.6.0",
|
"eslint": "^5.15.3",
|
||||||
"eslint-config-airbnb-base": "^13.1.0",
|
"eslint-config-airbnb-base": "^13.1.0",
|
||||||
"eslint-config-prettier": "^4.0.0",
|
"eslint-config-prettier": "^4.1.0",
|
||||||
"eslint-import-resolver-webpack": "^0.10.1",
|
"eslint-import-resolver-webpack": "^0.11.0",
|
||||||
"eslint-plugin-import": "^2.14.0",
|
"eslint-plugin-import": "^2.16.0",
|
||||||
"eslint-plugin-prettier": "^3.0.0",
|
"eslint-plugin-prettier": "^3.0.1",
|
||||||
"eslint-plugin-react": "^7.11.1",
|
"eslint-plugin-react": "^7.12.4",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^4.0.0",
|
||||||
"gulp-foreach": "^0.1.0",
|
"gulp-foreach": "^0.1.0",
|
||||||
"gulp-hash": "^4.2.2",
|
"gulp-hash": "^4.2.2",
|
||||||
"gulp-hash-filename": "^2.0.1",
|
"gulp-hash-filename": "^2.0.1",
|
||||||
"gulp-insert": "^0.5.0",
|
"gulp-insert": "^0.5.0",
|
||||||
"gulp-json-transform": "^0.4.5",
|
"gulp-json-transform": "^0.4.6",
|
||||||
"gulp-jsonminify": "^1.1.0",
|
"gulp-jsonminify": "^1.1.0",
|
||||||
"gulp-merge-json": "^1.3.1",
|
"gulp-merge-json": "^1.3.1",
|
||||||
"gulp-rename": "^1.4.0",
|
"gulp-rename": "^1.4.0",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"husky": "^1.1.0",
|
"husky": "^1.3.1",
|
||||||
"lint-staged": "^8.0.2",
|
"lint-staged": "^8.1.5",
|
||||||
"merge-stream": "^1.0.1",
|
"merge-stream": "^1.0.1",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^6.0.2",
|
||||||
"parse5": "^5.1.0",
|
"parse5": "^5.1.0",
|
||||||
"polymer-cli": "^1.8.0",
|
"polymer-cli": "^1.9.7",
|
||||||
"prettier": "^1.14.3",
|
"prettier": "^1.16.4",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^2.0.0",
|
||||||
"reify": "^0.18.1",
|
"reify": "^0.18.1",
|
||||||
"require-dir": "^1.0.0",
|
"require-dir": "^1.2.0",
|
||||||
"sinon": "^7.1.1",
|
"sinon": "^7.3.1",
|
||||||
"terser-webpack-plugin": "^1.2.3",
|
"terser-webpack-plugin": "^1.2.3",
|
||||||
"ts-mocha": "^2.0.0",
|
"ts-mocha": "^6.0.0",
|
||||||
"tslint": "^5.11.0",
|
"tslint": "^5.14.0",
|
||||||
"tslint-config-prettier": "^1.15.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"tslint-eslint-rules": "^5.4.0",
|
"tslint-eslint-rules": "^5.4.0",
|
||||||
"tslint-plugin-prettier": "^2.0.1",
|
"tslint-plugin-prettier": "^2.0.1",
|
||||||
"typescript": "^3.1.4",
|
"typescript": "^3.4.1",
|
||||||
"uglifyjs-webpack-plugin": "^2.1.1",
|
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||||
"wct-browser-legacy": "^1.0.1",
|
"wct-browser-legacy": "^1.0.2",
|
||||||
"web-component-tester": "^6.8.0",
|
"web-component-tester": "^6.9.2",
|
||||||
"webpack": "^4.19.1",
|
"webpack": "^4.29.6",
|
||||||
"webpack-cli": "^3.1.0",
|
"webpack-cli": "^3.3.0",
|
||||||
"webpack-dev-server": "^3.1.8",
|
"webpack-dev-server": "^3.2.1",
|
||||||
"workbox-webpack-plugin": "^3.5.0"
|
"workbox-webpack-plugin": "^4.1.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@polymer/polymer": "3.1.0",
|
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||||
"@webcomponents/webcomponentsjs": "^2.2.6",
|
"@vaadin/vaadin-lumo-styles": "^1.4.2"
|
||||||
"@webcomponents/shadycss": "^1.9.0",
|
|
||||||
"@vaadin/vaadin-overlay": "3.2.2",
|
|
||||||
"@vaadin/vaadin-lumo-styles": "1.3.0",
|
|
||||||
"@polymer/iron-overlay-behavior": "^3.0.2"
|
|
||||||
},
|
},
|
||||||
"main": "src/home-assistant.js",
|
"main": "src/home-assistant.js",
|
||||||
"husky": {
|
"husky": {
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20190331.0",
|
version="20190410.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -8,6 +8,8 @@ const fixedDeviceClassIcons = {
|
|||||||
illuminance: "hass:brightness-5",
|
illuminance: "hass:brightness-5",
|
||||||
temperature: "hass:thermometer",
|
temperature: "hass:thermometer",
|
||||||
pressure: "hass:gauge",
|
pressure: "hass:gauge",
|
||||||
|
power: "hass:flash",
|
||||||
|
signal_strength: "hass:wifi",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function sensorIcon(state: HassEntity) {
|
export default function sensorIcon(state: HassEntity) {
|
||||||
|
@ -10,11 +10,11 @@ export const timeCachePromiseFunc = async <T>(
|
|||||||
func: (
|
func: (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
...args: Array<unknown>
|
...args: unknown[]
|
||||||
) => Promise<T>,
|
) => Promise<T>,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
...args: Array<unknown>
|
...args: unknown[]
|
||||||
): Promise<T> => {
|
): Promise<T> => {
|
||||||
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
|
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import "@polymer/paper-dialog/paper-dialog";
|
import "@polymer/paper-dialog/paper-dialog";
|
||||||
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
||||||
import { HaIronFocusablesHelper } from "./ha-iron-focusables-helper.js";
|
import { HaIronFocusablesHelper } from "./ha-iron-focusables-helper.js";
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
||||||
|
|
||||||
const paperDialogClass = customElements.get("paper-dialog");
|
const paperDialogClass = customElements.get("paper-dialog");
|
||||||
|
|
||||||
@ -13,10 +15,10 @@ const haTabFixBehaviorImpl = {
|
|||||||
|
|
||||||
// paper-dialog that uses the haTabFixBehaviorImpl behvaior
|
// paper-dialog that uses the haTabFixBehaviorImpl behvaior
|
||||||
// export class HaPaperDialog extends paperDialogClass {}
|
// export class HaPaperDialog extends paperDialogClass {}
|
||||||
export class HaPaperDialog extends mixinBehaviors(
|
// @ts-ignore
|
||||||
[haTabFixBehaviorImpl],
|
export class HaPaperDialog
|
||||||
paperDialogClass
|
extends mixinBehaviors([haTabFixBehaviorImpl], paperDialogClass)
|
||||||
) {}
|
implements PaperDialogElement {}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
28
src/components/ha-paper-dropdown-menu.ts
Normal file
28
src/components/ha-paper-dropdown-menu.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
|
import { Constructor } from "lit-element";
|
||||||
|
import { PolymerElement } from "@polymer/polymer";
|
||||||
|
|
||||||
|
const paperDropdownClass = customElements.get(
|
||||||
|
"paper-dropdown-menu"
|
||||||
|
) as Constructor<PolymerElement>;
|
||||||
|
|
||||||
|
// patches paper drop down to properly support RTL - https://github.com/PolymerElements/paper-dropdown-menu/issues/183
|
||||||
|
export class HaPaperDropdownClass extends paperDropdownClass {
|
||||||
|
public ready() {
|
||||||
|
super.ready();
|
||||||
|
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||||
|
setTimeout(() => {
|
||||||
|
if (window.getComputedStyle(this).direction === "rtl") {
|
||||||
|
this.style.textAlign = "right";
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-paper-dropdown-menu": HaPaperDropdownClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("ha-paper-dropdown-menu", HaPaperDropdownClass);
|
@ -9,10 +9,16 @@ const paperIconButtonClass = customElements.get(
|
|||||||
) as Constructor<PaperIconButtonElement>;
|
) as Constructor<PaperIconButtonElement>;
|
||||||
|
|
||||||
export class HaPaperIconButtonArrowPrev extends paperIconButtonClass {
|
export class HaPaperIconButtonArrowPrev extends paperIconButtonClass {
|
||||||
|
public hassio?: boolean;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
this.icon =
|
this.icon =
|
||||||
window.getComputedStyle(this).direction === "ltr"
|
window.getComputedStyle(this).direction === "ltr"
|
||||||
? "hass:arrow-left"
|
? this.hassio
|
||||||
|
? "hassio:arrow-left"
|
||||||
|
: "hass:arrow-left"
|
||||||
|
: this.hassio
|
||||||
|
? "hassio:arrow-right"
|
||||||
: "hass:arrow-right";
|
: "hass:arrow-right";
|
||||||
|
|
||||||
// calling super after setting icon to have it consistently show the icon (otherwise not always shown)
|
// calling super after setting icon to have it consistently show the icon (otherwise not always shown)
|
||||||
|
217
src/data/hassio.ts
Normal file
217
src/data/hassio.ts
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
interface HassioResponse<T> {
|
||||||
|
data: T;
|
||||||
|
result: "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateSessionResponse {
|
||||||
|
session: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HassioAddonInfo {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description: string;
|
||||||
|
repository: "core" | "local" | string;
|
||||||
|
version: string;
|
||||||
|
installed: string | undefined;
|
||||||
|
detached: boolean;
|
||||||
|
available: boolean;
|
||||||
|
build: boolean;
|
||||||
|
url: string | null;
|
||||||
|
icon: boolean;
|
||||||
|
logo: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HassioAddonDetails {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description: string;
|
||||||
|
long_description: null | string;
|
||||||
|
auto_update: boolean;
|
||||||
|
url: null | string;
|
||||||
|
detached: boolean;
|
||||||
|
available: boolean;
|
||||||
|
arch: "armhf" | "aarch64" | "i386" | "amd64";
|
||||||
|
machine: any;
|
||||||
|
homeassistant: string;
|
||||||
|
repository: null | string;
|
||||||
|
version: null | string;
|
||||||
|
last_version: string;
|
||||||
|
state: "none" | "started" | "stopped";
|
||||||
|
boot: "auto" | "manual";
|
||||||
|
build: boolean;
|
||||||
|
options: object;
|
||||||
|
network: null | object;
|
||||||
|
host_network: boolean;
|
||||||
|
host_pid: boolean;
|
||||||
|
host_ipc: boolean;
|
||||||
|
host_dbus: boolean;
|
||||||
|
privileged: any;
|
||||||
|
apparmor: "disable" | "default" | "profile";
|
||||||
|
devices: string[];
|
||||||
|
auto_uart: boolean;
|
||||||
|
icon: boolean;
|
||||||
|
logo: boolean;
|
||||||
|
changelog: boolean;
|
||||||
|
hassio_api: boolean;
|
||||||
|
hassio_role: "default" | "homeassistant" | "manager" | "admin";
|
||||||
|
homeassistant_api: boolean;
|
||||||
|
auth_api: boolean;
|
||||||
|
full_access: boolean;
|
||||||
|
protected: boolean;
|
||||||
|
rating: "1-6";
|
||||||
|
stdin: boolean;
|
||||||
|
webui: null | string;
|
||||||
|
gpio: boolean;
|
||||||
|
kernel_modules: boolean;
|
||||||
|
devicetree: boolean;
|
||||||
|
docker_api: boolean;
|
||||||
|
audio: boolean;
|
||||||
|
audio_input: null | string;
|
||||||
|
audio_output: null | string;
|
||||||
|
services_role: string[];
|
||||||
|
discovery: string[];
|
||||||
|
ip_address: string;
|
||||||
|
ingress: boolean;
|
||||||
|
ingress_entry: null | string;
|
||||||
|
ingress_url: null | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HassioAddonRepository {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
source: string;
|
||||||
|
url: string;
|
||||||
|
maintainer: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HassioAddonsInfo {
|
||||||
|
addons: HassioAddonInfo[];
|
||||||
|
repositories: HassioAddonRepository[];
|
||||||
|
}
|
||||||
|
export interface HassioHassOSInfo {
|
||||||
|
version: string;
|
||||||
|
version_cli: string;
|
||||||
|
version_latest: string;
|
||||||
|
version_cli_latest: string;
|
||||||
|
board: "ova" | "rpi";
|
||||||
|
}
|
||||||
|
export type HassioHomeAssistantInfo = any;
|
||||||
|
export type HassioSupervisorInfo = any;
|
||||||
|
export type HassioHostInfo = any;
|
||||||
|
|
||||||
|
export interface HassioSnapshot {
|
||||||
|
slug: string;
|
||||||
|
date: string;
|
||||||
|
name: string;
|
||||||
|
type: "full" | "partial";
|
||||||
|
protected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HassioSnapshotDetail extends HassioSnapshot {
|
||||||
|
size: string;
|
||||||
|
homeassistant: string;
|
||||||
|
addons: Array<{
|
||||||
|
slug: "ADDON_SLUG";
|
||||||
|
name: "NAME";
|
||||||
|
version: "INSTALLED_VERSION";
|
||||||
|
size: "SIZE_IN_MB";
|
||||||
|
}>;
|
||||||
|
repositories: string[];
|
||||||
|
folders: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HassioFullSnapshotCreateParams {
|
||||||
|
name: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
export interface HassioPartialSnapshotCreateParams {
|
||||||
|
name: string;
|
||||||
|
folders: string[];
|
||||||
|
addons: string[];
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hassioApiResultExtractor = <T>(response: HassioResponse<T>) =>
|
||||||
|
response.data;
|
||||||
|
|
||||||
|
export const createHassioSession = async (hass: HomeAssistant) => {
|
||||||
|
const response = await hass.callApi<HassioResponse<CreateSessionResponse>>(
|
||||||
|
"POST",
|
||||||
|
"hassio/ingress/session"
|
||||||
|
);
|
||||||
|
document.cookie = `ingress_session=${
|
||||||
|
response.data.session
|
||||||
|
};path=/api/hassio_ingress/`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const reloadHassioAddons = (hass: HomeAssistant) =>
|
||||||
|
hass.callApi<unknown>("POST", `hassio/addons/reload`);
|
||||||
|
|
||||||
|
export const fetchHassioAddonsInfo = (hass: HomeAssistant) =>
|
||||||
|
hass
|
||||||
|
.callApi<HassioResponse<HassioAddonsInfo>>("GET", `hassio/addons`)
|
||||||
|
.then(hassioApiResultExtractor);
|
||||||
|
|
||||||
|
export const fetchHassioAddonInfo = (hass: HomeAssistant, addon: string) =>
|
||||||
|
hass
|
||||||
|
.callApi<HassioResponse<HassioAddonDetails>>(
|
||||||
|
"GET",
|
||||||
|
`hassio/addons/${addon}/info`
|
||||||
|
)
|
||||||
|
.then(hassioApiResultExtractor);
|
||||||
|
|
||||||
|
export const fetchHassioSupervisorInfo = (hass: HomeAssistant) =>
|
||||||
|
hass
|
||||||
|
.callApi<HassioResponse<HassioSupervisorInfo>>(
|
||||||
|
"GET",
|
||||||
|
"hassio/supervisor/info"
|
||||||
|
)
|
||||||
|
.then(hassioApiResultExtractor);
|
||||||
|
|
||||||
|
export const fetchHassioHostInfo = (hass: HomeAssistant) =>
|
||||||
|
hass
|
||||||
|
.callApi<HassioResponse<HassioHostInfo>>("GET", "hassio/host/info")
|
||||||
|
.then(hassioApiResultExtractor);
|
||||||
|
|
||||||
|
export const fetchHassioHomeAssistantInfo = (hass: HomeAssistant) =>
|
||||||
|
hass
|
||||||
|
.callApi<HassioResponse<HassioHomeAssistantInfo>>(
|
||||||
|
"GET",
|
||||||
|
"hassio/homeassistant/info"
|
||||||
|
)
|
||||||
|
.then(hassioApiResultExtractor);
|
||||||
|
|
||||||
|
export const fetchHassioSnapshots = (hass: HomeAssistant) =>
|
||||||
|
hass
|
||||||
|
.callApi<HassioResponse<{ snapshots: HassioSnapshot[] }>>(
|
||||||
|
"GET",
|
||||||
|
"hassio/snapshots"
|
||||||
|
)
|
||||||
|
.then((resp) => resp.data.snapshots);
|
||||||
|
|
||||||
|
export const reloadHassioSnapshots = (hass: HomeAssistant) =>
|
||||||
|
hass.callApi<unknown>("POST", `hassio/snapshots/reload`);
|
||||||
|
|
||||||
|
export const createHassioFullSnapshot = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
data: HassioFullSnapshotCreateParams
|
||||||
|
) => hass.callApi<unknown>("POST", "hassio/snapshots/new/full", data);
|
||||||
|
|
||||||
|
export const createHassioPartialSnapshot = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
data: HassioPartialSnapshotCreateParams
|
||||||
|
) => hass.callApi<unknown>("POST", "hassio/snapshots/new/partial", data);
|
||||||
|
|
||||||
|
export const fetchHassioSnapshotInfo = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
snapshot: string
|
||||||
|
) =>
|
||||||
|
hass
|
||||||
|
.callApi<HassioResponse<HassioSnapshotDetail>>(
|
||||||
|
"GET",
|
||||||
|
`hassio/snapshots/${snapshot}/info`
|
||||||
|
)
|
||||||
|
.then(hassioApiResultExtractor);
|
@ -16,6 +16,7 @@ export interface ZHADevice {
|
|||||||
manufacturer_code: number;
|
manufacturer_code: number;
|
||||||
device_reg_id: string;
|
device_reg_id: string;
|
||||||
user_given_name?: string;
|
user_given_name?: string;
|
||||||
|
power_source?: string;
|
||||||
area_id?: string;
|
area_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,14 +12,14 @@ import "@material/mwc-button";
|
|||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import "@polymer/paper-spinner/paper-spinner";
|
import "@polymer/paper-spinner/paper-spinner";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
// Not duplicate, is for typing
|
|
||||||
// tslint:disable-next-line
|
|
||||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
|
||||||
|
|
||||||
import "../../components/ha-form";
|
import "../../components/ha-form";
|
||||||
import "../../components/ha-markdown";
|
import "../../components/ha-markdown";
|
||||||
import "../../resources/ha-style";
|
import "../../resources/ha-style";
|
||||||
|
import "../../components/dialog/ha-paper-dialog";
|
||||||
|
// Not duplicate, is for typing
|
||||||
|
// tslint:disable-next-line
|
||||||
|
import { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
import {
|
import {
|
||||||
fetchConfigFlow,
|
fetchConfigFlow,
|
||||||
@ -108,7 +108,11 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog with-backdrop opened @opened-changed=${this._openedChanged}>
|
<ha-paper-dialog
|
||||||
|
with-backdrop
|
||||||
|
opened
|
||||||
|
@opened-changed=${this._openedChanged}
|
||||||
|
>
|
||||||
${this._loading
|
${this._loading
|
||||||
? html`
|
? html`
|
||||||
<step-flow-loading></step-flow-loading>
|
<step-flow-loading></step-flow-loading>
|
||||||
@ -144,7 +148,7 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
.areas=${this._areas}
|
.areas=${this._areas}
|
||||||
></step-flow-create-entry>
|
></step-flow-create-entry>
|
||||||
`}
|
`}
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,8 +170,8 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _dialog(): PaperDialogElement {
|
private get _dialog(): HaPaperDialog {
|
||||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchDevices(configEntryId) {
|
private async _fetchDevices(configEntryId) {
|
||||||
@ -226,10 +230,10 @@ class ConfigFlowDialog extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
paper-dialog > * {
|
ha-paper-dialog > * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -33,20 +33,17 @@ class StepFlowAbort extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<h2>Aborted</h2>
|
<h2>Aborted</h2>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${
|
${description
|
||||||
description
|
? html`
|
||||||
? html`
|
<ha-markdown .content=${description} allow-svg></ha-markdown>
|
||||||
<ha-markdown .content=${description} allow-svg></ha-markdown>
|
`
|
||||||
`
|
: ""}
|
||||||
: ""
|
</div>
|
||||||
}
|
<div class="buttons">
|
||||||
</div>
|
<mwc-button @click="${this._flowDone}">Close</mwc-button>
|
||||||
<div class="buttons">
|
</div>
|
||||||
<mwc-button @click="${this._flowDone}">Close</mwc-button>
|
|
||||||
</div>
|
|
||||||
</paper-dialog>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,71 +52,61 @@ class StepFlowCreateEntry extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<h2>Success!</h2>
|
<h2>Success!</h2>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${
|
${description
|
||||||
description
|
? html`
|
||||||
? html`
|
<ha-markdown .content=${description} allow-svg></ha-markdown>
|
||||||
<ha-markdown .content=${description} allow-svg></ha-markdown>
|
`
|
||||||
`
|
: ""}
|
||||||
: ""
|
<p>Created config for ${step.title}.</p>
|
||||||
}
|
${this.devices.length === 0
|
||||||
<p>Created config for ${step.title}.</p>
|
? ""
|
||||||
${
|
: html`
|
||||||
this.devices.length === 0
|
<p>We found the following devices:</p>
|
||||||
? ""
|
<div class="devices">
|
||||||
: html`
|
${this.devices.map(
|
||||||
<p>We found the following devices:</p>
|
(device) =>
|
||||||
<div class="devices">
|
html`
|
||||||
${this.devices.map(
|
<div class="device">
|
||||||
(device) =>
|
<b>${device.name}</b><br />
|
||||||
html`
|
${device.model} (${device.manufacturer})
|
||||||
<div class="device">
|
|
||||||
<b>${device.name}</b><br />
|
|
||||||
${device.model} (${device.manufacturer})
|
|
||||||
|
|
||||||
<paper-dropdown-menu-light
|
<paper-dropdown-menu-light
|
||||||
label="Area"
|
label="Area"
|
||||||
.device=${device.id}
|
.device=${device.id}
|
||||||
@selected-item-changed=${this._handleAreaChanged}
|
@selected-item-changed=${this._handleAreaChanged}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
<paper-listbox slot="dropdown-content" selected="0">
|
||||||
slot="dropdown-content"
|
<paper-item>
|
||||||
selected="0"
|
${localize(
|
||||||
>
|
"ui.panel.config.integrations.config_entry.no_area"
|
||||||
<paper-item>
|
)}
|
||||||
${localize(
|
</paper-item>
|
||||||
"ui.panel.config.integrations.config_entry.no_area"
|
${this.areas.map(
|
||||||
)}
|
(area) => html`
|
||||||
|
<paper-item .area=${area.area_id}>
|
||||||
|
${area.name}
|
||||||
</paper-item>
|
</paper-item>
|
||||||
${this.areas.map(
|
`
|
||||||
(area) => html`
|
)}
|
||||||
<paper-item .area=${area.area_id}>
|
</paper-listbox>
|
||||||
${area.name}
|
</paper-dropdown-menu-light>
|
||||||
</paper-item>
|
</div>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</paper-listbox>
|
</div>
|
||||||
</paper-dropdown-menu-light>
|
`}
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="buttons">
|
||||||
)}
|
${this.devices.length > 0
|
||||||
</div>
|
? html`
|
||||||
`
|
<mwc-button @click="${this._addArea}">Add Area</mwc-button>
|
||||||
}
|
`
|
||||||
</div>
|
: ""}
|
||||||
<div class="buttons">
|
|
||||||
${
|
|
||||||
this.devices.length > 0
|
|
||||||
? html`
|
|
||||||
<mwc-button @click="${this._addArea}">Add Area</mwc-button>
|
|
||||||
`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
<mwc-button @click="${this._flowDone}">Finish</mwc-button>
|
<mwc-button @click="${this._flowDone}">Finish</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isComponentLoaded(this.hass, "config.entity_registry") ||
|
!isComponentLoaded(this.hass, "config") ||
|
||||||
(oldVal && oldVal.entity_id === newVal.entity_id)
|
(oldVal && oldVal.entity_id === newVal.entity_id)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
57
src/dialogs/make-dialog-manager.ts
Normal file
57
src/dialogs/make-dialog-manager.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
||||||
|
import { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// for fire event
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"show-dialog": ShowDialogParams<unknown>;
|
||||||
|
}
|
||||||
|
// for add event listener
|
||||||
|
interface HTMLElementEventMap {
|
||||||
|
"show-dialog": HASSDomEvent<ShowDialogParams<unknown>>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]> extends HTMLElement {
|
||||||
|
showDialog(params: T);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShowDialogParams<T> {
|
||||||
|
dialogTag: keyof HTMLElementTagNameMap;
|
||||||
|
dialogImport: () => Promise<unknown>;
|
||||||
|
dialogParams: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LOADED = {};
|
||||||
|
|
||||||
|
export const showDialog = async (
|
||||||
|
element: HTMLElement & ProvideHassElement,
|
||||||
|
root: ShadowRoot | HTMLElement,
|
||||||
|
dialogImport: () => Promise<unknown>,
|
||||||
|
dialogTag: string,
|
||||||
|
dialogParams: unknown
|
||||||
|
) => {
|
||||||
|
if (!(dialogTag in LOADED)) {
|
||||||
|
LOADED[dialogTag] = dialogImport().then(() => {
|
||||||
|
const dialogEl = document.createElement(dialogTag) as HassDialog;
|
||||||
|
element.provideHass(dialogEl);
|
||||||
|
root.appendChild(dialogEl);
|
||||||
|
return dialogEl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const dialogElement = await LOADED[dialogTag];
|
||||||
|
dialogElement.showDialog(dialogParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeDialogManager = (
|
||||||
|
element: HTMLElement & ProvideHassElement,
|
||||||
|
root: ShadowRoot | HTMLElement
|
||||||
|
) => {
|
||||||
|
element.addEventListener(
|
||||||
|
"show-dialog",
|
||||||
|
async (e: HASSDomEvent<ShowDialogParams<unknown>>) => {
|
||||||
|
const { dialogTag, dialogImport, dialogParams } = e.detail;
|
||||||
|
showDialog(element, root, dialogImport, dialogTag, dialogParams);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
@ -1,5 +1,4 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||||
@ -10,6 +9,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|||||||
|
|
||||||
import "../../../components/ha-climate-control";
|
import "../../../components/ha-climate-control";
|
||||||
import "../../../components/ha-paper-slider";
|
import "../../../components/ha-paper-slider";
|
||||||
|
import "../../../components/ha-paper-dropdown-menu";
|
||||||
|
|
||||||
import attributeClassNames from "../../../common/entity/attribute_class_names";
|
import attributeClassNames from "../../../common/entity/attribute_class_names";
|
||||||
import featureClassNames from "../../../common/entity/feature_class_names";
|
import featureClassNames from "../../../common/entity/feature_class_names";
|
||||||
@ -64,7 +64,7 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
margin: 22px 16px 0 0;
|
margin: 22px 16px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-dropdown-menu {
|
ha-paper-dropdown-menu {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<template is="dom-if" if="[[supportsOperationMode(stateObj)]]">
|
<template is="dom-if" if="[[supportsOperationMode(stateObj)]]">
|
||||||
<div class="container-operation_list">
|
<div class="container-operation_list">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
label-float=""
|
label-float=""
|
||||||
dynamic-align=""
|
dynamic-align=""
|
||||||
label="[[localize('ui.card.climate.operation')]]"
|
label="[[localize('ui.card.climate.operation')]]"
|
||||||
@ -212,14 +212,14 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template is="dom-if" if="[[supportsFanMode(stateObj)]]">
|
<template is="dom-if" if="[[supportsFanMode(stateObj)]]">
|
||||||
<div class="container-fan_list">
|
<div class="container-fan_list">
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
label-float=""
|
label-float=""
|
||||||
dynamic-align=""
|
dynamic-align=""
|
||||||
label="[[localize('ui.card.climate.fan_mode')]]"
|
label="[[localize('ui.card.climate.fan_mode')]]"
|
||||||
@ -233,13 +233,13 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<paper-item>[[_localizeFanMode(localize, item)]]</paper-item>
|
<paper-item>[[_localizeFanMode(localize, item)]]</paper-item>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template is="dom-if" if="[[supportsSwingMode(stateObj)]]">
|
<template is="dom-if" if="[[supportsSwingMode(stateObj)]]">
|
||||||
<div class="container-swing_list">
|
<div class="container-swing_list">
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
label-float=""
|
label-float=""
|
||||||
dynamic-align=""
|
dynamic-align=""
|
||||||
label="[[localize('ui.card.climate.swing_mode')]]"
|
label="[[localize('ui.card.climate.swing_mode')]]"
|
||||||
@ -253,7 +253,7 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<paper-item>[[item]]</paper-item>
|
<paper-item>[[item]]</paper-item>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
@ -8,6 +7,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
import "../../../components/ha-attributes";
|
import "../../../components/ha-attributes";
|
||||||
|
import "../../../components/ha-paper-dropdown-menu";
|
||||||
|
|
||||||
import attributeClassNames from "../../../common/entity/attribute_class_names";
|
import attributeClassNames from "../../../common/entity/attribute_class_names";
|
||||||
import EventsMixin from "../../../mixins/events-mixin";
|
import EventsMixin from "../../../mixins/events-mixin";
|
||||||
@ -33,7 +33,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-dropdown-menu {
|
ha-paper-dropdown-menu {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
|
|
||||||
<div class$="[[computeClassNames(stateObj)]]">
|
<div class$="[[computeClassNames(stateObj)]]">
|
||||||
<div class="container-speed_list">
|
<div class="container-speed_list">
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
label-float=""
|
label-float=""
|
||||||
dynamic-align=""
|
dynamic-align=""
|
||||||
label="[[localize('ui.card.fan.speed')]]"
|
label="[[localize('ui.card.fan.speed')]]"
|
||||||
@ -57,7 +57,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<paper-item>[[item]]</paper-item>
|
<paper-item>[[item]]</paper-item>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container-oscillating">
|
<div class="container-oscillating">
|
||||||
|
@ -2,7 +2,6 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
import "@polymer/polymer/polymer-legacy";
|
|
||||||
import "@vaadin/vaadin-date-picker/vaadin-date-picker";
|
import "@vaadin/vaadin-date-picker/vaadin-date-picker";
|
||||||
|
|
||||||
import "../../../components/ha-relative-time";
|
import "../../../components/ha-relative-time";
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
@ -8,6 +7,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|||||||
import "../../../components/ha-attributes";
|
import "../../../components/ha-attributes";
|
||||||
import "../../../components/ha-color-picker";
|
import "../../../components/ha-color-picker";
|
||||||
import "../../../components/ha-labeled-slider";
|
import "../../../components/ha-labeled-slider";
|
||||||
|
import "../../../components/ha-paper-dropdown-menu";
|
||||||
|
|
||||||
import featureClassNames from "../../../common/entity/feature_class_names";
|
import featureClassNames from "../../../common/entity/feature_class_names";
|
||||||
import EventsMixin from "../../../mixins/events-mixin";
|
import EventsMixin from "../../../mixins/events-mixin";
|
||||||
@ -177,7 +177,7 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control effect_list">
|
<div class="control effect_list">
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
label-float=""
|
label-float=""
|
||||||
dynamic-align=""
|
dynamic-align=""
|
||||||
label="[[localize('ui.card.light.effect')]]"
|
label="[[localize('ui.card.light.effect')]]"
|
||||||
@ -190,7 +190,7 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<paper-item>[[item]]</paper-item>
|
<paper-item>[[item]]</paper-item>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ha-attributes
|
<ha-attributes
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
import "@polymer/iron-icon/iron-icon";
|
import "@polymer/iron-icon/iron-icon";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
@ -8,6 +7,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
import "../../../components/ha-paper-slider";
|
import "../../../components/ha-paper-slider";
|
||||||
|
import "../../../components/ha-paper-dropdown-menu";
|
||||||
import HassMediaPlayerEntity from "../../../util/hass-media-player-model";
|
import HassMediaPlayerEntity from "../../../util/hass-media-player-model";
|
||||||
|
|
||||||
import attributeClassNames from "../../../common/entity/attribute_class_names";
|
import attributeClassNames from "../../../common/entity/attribute_class_names";
|
||||||
@ -50,7 +50,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-dropdown-menu.source-input {
|
ha-paper-dropdown-menu.source-input {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
hidden$="[[computeHideSelectSource(playerObj)]]"
|
hidden$="[[computeHideSelectSource(playerObj)]]"
|
||||||
>
|
>
|
||||||
<iron-icon class="source-input" icon="hass:login-variant"></iron-icon>
|
<iron-icon class="source-input" icon="hass:login-variant"></iron-icon>
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
class="flex source-input"
|
class="flex source-input"
|
||||||
dynamic-align=""
|
dynamic-align=""
|
||||||
label-float=""
|
label-float=""
|
||||||
@ -159,13 +159,13 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<paper-item>[[item]]</paper-item>
|
<paper-item>[[item]]</paper-item>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
<!-- SOUND MODE PICKER -->
|
<!-- SOUND MODE PICKER -->
|
||||||
<template is="dom-if" if="[[!computeHideSelectSoundMode(playerObj)]]">
|
<template is="dom-if" if="[[!computeHideSelectSoundMode(playerObj)]]">
|
||||||
<div class="controls layout horizontal justified">
|
<div class="controls layout horizontal justified">
|
||||||
<iron-icon class="source-input" icon="hass:music-note"></iron-icon>
|
<iron-icon class="source-input" icon="hass:music-note"></iron-icon>
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
class="flex source-input"
|
class="flex source-input"
|
||||||
dynamic-align
|
dynamic-align
|
||||||
label-float
|
label-float
|
||||||
@ -180,7 +180,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<paper-item item-name$="[[item]]">[[item]]</paper-item>
|
<paper-item item-name$="[[item]]">[[item]]</paper-item>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- TTS -->
|
<!-- TTS -->
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
import "@polymer/iron-icon/iron-icon";
|
import "@polymer/iron-icon/iron-icon";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
import "@polymer/paper-icon-button/paper-icon-button";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
@ -8,6 +7,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
import "../../../components/ha-attributes";
|
import "../../../components/ha-attributes";
|
||||||
|
import "../../../components/ha-paper-dropdown-menu";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
|
|
||||||
class MoreInfoVacuum extends PolymerElement {
|
class MoreInfoVacuum extends PolymerElement {
|
||||||
@ -104,7 +104,7 @@ class MoreInfoVacuum extends PolymerElement {
|
|||||||
|
|
||||||
<div hidden$="[[!supportsFanSpeed(stateObj)]]">
|
<div hidden$="[[!supportsFanSpeed(stateObj)]]">
|
||||||
<div class="horizontal justified layout">
|
<div class="horizontal justified layout">
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
label-float=""
|
label-float=""
|
||||||
dynamic-align=""
|
dynamic-align=""
|
||||||
label="Fan speed"
|
label="Fan speed"
|
||||||
@ -117,7 +117,7 @@ class MoreInfoVacuum extends PolymerElement {
|
|||||||
<paper-item>[[item]]</paper-item>
|
<paper-item>[[item]]</paper-item>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
<div
|
<div
|
||||||
style="justify-content: center; align-self: center; padding-top: 1.3em"
|
style="justify-content: center; align-self: center; padding-top: 1.3em"
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||||
@ -10,6 +9,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|||||||
|
|
||||||
import "../../../components/ha-water_heater-control";
|
import "../../../components/ha-water_heater-control";
|
||||||
import "../../../components/ha-paper-slider";
|
import "../../../components/ha-paper-slider";
|
||||||
|
import "../../../components/ha-paper-dropdown-menu";
|
||||||
|
|
||||||
import featureClassNames from "../../../common/entity/feature_class_names";
|
import featureClassNames from "../../../common/entity/feature_class_names";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
@ -40,7 +40,7 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
|
|
||||||
.container-operation_list iron-icon,
|
.container-operation_list iron-icon,
|
||||||
|
|
||||||
paper-dropdown-menu {
|
ha-paper-dropdown-menu {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
<template is="dom-if" if="[[supportsOperationMode(stateObj)]]">
|
<template is="dom-if" if="[[supportsOperationMode(stateObj)]]">
|
||||||
<div class="container-operation_list">
|
<div class="container-operation_list">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<paper-dropdown-menu
|
<ha-paper-dropdown-menu
|
||||||
label-float=""
|
label-float=""
|
||||||
dynamic-align=""
|
dynamic-align=""
|
||||||
label="[[localize('ui.card.water_heater.operation')]]"
|
label="[[localize('ui.card.water_heater.operation')]]"
|
||||||
@ -112,7 +112,7 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-dropdown-menu>
|
</ha-paper-dropdown-menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -10,7 +10,6 @@ import LocalizeMixin from "../../mixins/localize-mixin";
|
|||||||
|
|
||||||
import computeStateName from "../../common/entity/compute_state_name";
|
import computeStateName from "../../common/entity/compute_state_name";
|
||||||
import computeDomain from "../../common/entity/compute_domain";
|
import computeDomain from "../../common/entity/compute_domain";
|
||||||
import isComponentLoaded from "../../common/config/is_component_loaded";
|
|
||||||
import { updateEntityRegistryEntry } from "../../data/entity_registry";
|
import { updateEntityRegistryEntry } from "../../data/entity_registry";
|
||||||
|
|
||||||
import "../../components/ha-paper-icon-button-arrow-prev";
|
import "../../components/ha-paper-icon-button-arrow-prev";
|
||||||
@ -74,11 +73,6 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
hass: Object,
|
hass: Object,
|
||||||
stateObj: Object,
|
stateObj: Object,
|
||||||
|
|
||||||
_componentLoaded: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: "_computeComponentLoaded(hass)",
|
|
||||||
},
|
|
||||||
|
|
||||||
registryInfo: {
|
registryInfo: {
|
||||||
type: Object,
|
type: Object,
|
||||||
observer: "_registryInfoChanged",
|
observer: "_registryInfoChanged",
|
||||||
@ -95,10 +89,6 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
return computeStateName(stateObj);
|
return computeStateName(stateObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeComponentLoaded(hass) {
|
|
||||||
return isComponentLoaded(hass, "config.entity_registry");
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeInvalid(entityId) {
|
_computeInvalid(entityId) {
|
||||||
return computeDomain(this.stateObj.entity_id) !== computeDomain(entityId);
|
return computeDomain(this.stateObj.entity_id) !== computeDomain(entityId);
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,12 @@ import objAssign from "es6-object-assign";
|
|||||||
objAssign.polyfill();
|
objAssign.polyfill();
|
||||||
|
|
||||||
if (Object.values === undefined) {
|
if (Object.values === undefined) {
|
||||||
Object.values = function(target) {
|
Object.values = (target) => {
|
||||||
return Object.keys(target).map(function(key) {
|
return Object.keys(target).map((key) => target[key]);
|
||||||
return target[key];
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable */
|
/* tslint:disable */
|
||||||
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
||||||
if (!String.prototype.padStart) {
|
if (!String.prototype.padStart) {
|
||||||
@ -31,4 +29,4 @@ if (!String.prototype.padStart) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/* eslint-enable */
|
/* tslint:enable */
|
@ -6,13 +6,13 @@ function initRouting() {
|
|||||||
// Cache static content (including translations) on first access.
|
// Cache static content (including translations) on first access.
|
||||||
workbox.routing.registerRoute(
|
workbox.routing.registerRoute(
|
||||||
new RegExp(`${location.host}/(static|frontend_latest|frontend_es5)/.+`),
|
new RegExp(`${location.host}/(static|frontend_latest|frontend_es5)/.+`),
|
||||||
workbox.strategies.cacheFirst()
|
new workbox.strategies.CacheFirst()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get api from network.
|
// Get api from network.
|
||||||
workbox.routing.registerRoute(
|
workbox.routing.registerRoute(
|
||||||
new RegExp(`${location.host}/api/.*`),
|
new RegExp(`${location.host}/api/.*`),
|
||||||
workbox.strategies.networkOnly()
|
new workbox.strategies.NetworkOnly()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get manifest and service worker from network.
|
// Get manifest and service worker from network.
|
||||||
@ -20,7 +20,7 @@ function initRouting() {
|
|||||||
new RegExp(
|
new RegExp(
|
||||||
`${location.host}/(service_worker.js|service_worker_es5.js|manifest.json)`
|
`${location.host}/(service_worker.js|service_worker_es5.js|manifest.json)`
|
||||||
),
|
),
|
||||||
workbox.strategies.networkOnly()
|
new workbox.strategies.NetworkOnly()
|
||||||
);
|
);
|
||||||
|
|
||||||
// For rest of the files (on Home Assistant domain only) try both cache and network.
|
// For rest of the files (on Home Assistant domain only) try both cache and network.
|
||||||
@ -29,7 +29,7 @@ function initRouting() {
|
|||||||
// file.
|
// file.
|
||||||
workbox.routing.registerRoute(
|
workbox.routing.registerRoute(
|
||||||
new RegExp(`${location.host}/.*`),
|
new RegExp(`${location.host}/.*`),
|
||||||
workbox.strategies.staleWhileRevalidate()
|
new workbox.strategies.StaleWhileRevalidate()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +147,12 @@ function initPushNotifications() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.addEventListener("install", (event) => {
|
||||||
|
// Delete all runtime caching, so that index.html has to be refetched.
|
||||||
|
const cacheName = workbox.core.cacheNames.runtime;
|
||||||
|
event.waitUntil(caches.delete(cacheName));
|
||||||
|
});
|
||||||
|
|
||||||
self.addEventListener("message", (message) => {
|
self.addEventListener("message", (message) => {
|
||||||
if (message.data.type === "skipWaiting") {
|
if (message.data.type === "skipWaiting") {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Light.ttf' as='font' crossorigin />
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Light.ttf' as='font' crossorigin />
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
||||||
<%= require('raw-loader!./_header.html.template') %>
|
<%= require('raw-loader!./_header.html.template').default %>
|
||||||
<style>
|
<style>
|
||||||
.content {
|
.content {
|
||||||
padding: 20px 16px;
|
padding: 20px 16px;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<link rel='preload' href='<%= coreJS %>' as='script'/>
|
<link rel='preload' href='<%= coreJS %>' as='script'/>
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
|
||||||
<%= require('raw-loader!./_header.html.template') %>
|
<%= require('raw-loader!./_header.html.template').default %>
|
||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
<link rel='apple-touch-icon' sizes='180x180'
|
<link rel='apple-touch-icon' sizes='180x180'
|
||||||
href='/static/icons/favicon-apple-180x180.png'>
|
href='/static/icons/favicon-apple-180x180.png'>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Light.ttf' as='font' crossorigin />
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Light.ttf' as='font' crossorigin />
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
||||||
<%= require('raw-loader!./_header.html.template') %>
|
<%= require('raw-loader!./_header.html.template').default %>
|
||||||
<style>
|
<style>
|
||||||
.content {
|
.content {
|
||||||
padding: 20px 16px;
|
padding: 20px 16px;
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { Constructor, LitElement } from "lit-element";
|
import { Constructor, LitElement } from "lit-element";
|
||||||
import { HASSDomEvent, ValidHassDomEvent } from "../../common/dom/fire_event";
|
import { HASSDomEvent } from "../../common/dom/fire_event";
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
import { HassBaseEl } from "./hass-base-mixin";
|
||||||
|
import {
|
||||||
|
makeDialogManager,
|
||||||
|
showDialog,
|
||||||
|
} from "../../dialogs/make-dialog-manager";
|
||||||
|
|
||||||
interface RegisterDialogParams {
|
interface RegisterDialogParams {
|
||||||
dialogShowEvent: keyof HASSDomEvents;
|
dialogShowEvent: keyof HASSDomEvents;
|
||||||
@ -8,31 +12,17 @@ interface RegisterDialogParams {
|
|||||||
dialogImport: () => Promise<unknown>;
|
dialogImport: () => Promise<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShowDialogParams<T> {
|
|
||||||
dialogTag: keyof HTMLElementTagNameMap;
|
|
||||||
dialogImport: () => Promise<unknown>;
|
|
||||||
dialogParams: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]> extends HTMLElement {
|
|
||||||
showDialog(params: T);
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"register-dialog": RegisterDialogParams;
|
"register-dialog": RegisterDialogParams;
|
||||||
"show-dialog": ShowDialogParams<unknown>;
|
|
||||||
}
|
}
|
||||||
// for add event listener
|
// for add event listener
|
||||||
interface HTMLElementEventMap {
|
interface HTMLElementEventMap {
|
||||||
"register-dialog": HASSDomEvent<RegisterDialogParams>;
|
"register-dialog": HASSDomEvent<RegisterDialogParams>;
|
||||||
"show-dialog": HASSDomEvent<ShowDialogParams<unknown>>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LOADED = {};
|
|
||||||
|
|
||||||
export const dialogManagerMixin = (
|
export const dialogManagerMixin = (
|
||||||
superClass: Constructor<LitElement & HassBaseEl>
|
superClass: Constructor<LitElement & HassBaseEl>
|
||||||
) =>
|
) =>
|
||||||
@ -43,13 +33,7 @@ export const dialogManagerMixin = (
|
|||||||
this.addEventListener("register-dialog", (e) =>
|
this.addEventListener("register-dialog", (e) =>
|
||||||
this.registerDialog(e.detail)
|
this.registerDialog(e.detail)
|
||||||
);
|
);
|
||||||
this.addEventListener(
|
makeDialogManager(this, this.shadowRoot!);
|
||||||
"show-dialog",
|
|
||||||
async (e: HASSDomEvent<ShowDialogParams<unknown>>) => {
|
|
||||||
const { dialogTag, dialogImport, dialogParams } = e.detail;
|
|
||||||
this._showDialog(dialogImport, dialogTag, dialogParams);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerDialog({
|
private registerDialog({
|
||||||
@ -58,28 +42,13 @@ export const dialogManagerMixin = (
|
|||||||
dialogImport,
|
dialogImport,
|
||||||
}: RegisterDialogParams) {
|
}: RegisterDialogParams) {
|
||||||
this.addEventListener(dialogShowEvent, (showEv) => {
|
this.addEventListener(dialogShowEvent, (showEv) => {
|
||||||
this._showDialog(
|
showDialog(
|
||||||
|
this,
|
||||||
|
this.shadowRoot!,
|
||||||
dialogImport,
|
dialogImport,
|
||||||
dialogTag,
|
dialogTag,
|
||||||
(showEv as HASSDomEvent<unknown>).detail
|
(showEv as HASSDomEvent<unknown>).detail
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _showDialog(
|
|
||||||
dialogImport: () => Promise<unknown>,
|
|
||||||
dialogTag: string,
|
|
||||||
dialogParams: unknown
|
|
||||||
) {
|
|
||||||
if (!(dialogTag in LOADED)) {
|
|
||||||
LOADED[dialogTag] = dialogImport().then(() => {
|
|
||||||
const dialogEl = document.createElement(dialogTag) as HassDialog;
|
|
||||||
this.provideHass(dialogEl);
|
|
||||||
this.shadowRoot!.appendChild(dialogEl);
|
|
||||||
return dialogEl;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const element = await LOADED[dialogTag];
|
|
||||||
element.showDialog(dialogParams);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ export class HassBaseEl {
|
|||||||
protected hassDisconnected() {}
|
protected hassDisconnected() {}
|
||||||
protected hassChanged(_hass: HomeAssistant, _oldHass?: HomeAssistant) {}
|
protected hassChanged(_hass: HomeAssistant, _oldHass?: HomeAssistant) {}
|
||||||
protected panelUrlChanged(_newPanelUrl: string) {}
|
protected panelUrlChanged(_newPanelUrl: string) {}
|
||||||
protected provideHass(_el: HTMLElement) {}
|
public provideHass(_el: HTMLElement) {}
|
||||||
protected _updateHass(_obj: Partial<HomeAssistant>) {}
|
protected _updateHass(_obj: Partial<HomeAssistant>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ export default <T>(superClass: Constructor<T>): Constructor<T & HassBaseEl> =>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected provideHass(el) {
|
public provideHass(el) {
|
||||||
this.__provideHass.push(el);
|
this.__provideHass.push(el);
|
||||||
el.hass = this.hass;
|
el.hass = this.hass;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { UpdatingElement, property, PropertyValues } from "lit-element";
|
import { UpdatingElement, property, PropertyValues } from "lit-element";
|
||||||
import "./hass-error-screen";
|
import "./hass-error-screen";
|
||||||
|
import "./hass-loading-screen";
|
||||||
import { Route } from "../types";
|
import { Route } from "../types";
|
||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@ -15,18 +16,27 @@ const extractPage = (path: string, defaultPage: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface RouteOptions {
|
export interface RouteOptions {
|
||||||
|
// HTML tag of the route page.
|
||||||
tag: string;
|
tag: string;
|
||||||
load: () => Promise<unknown>;
|
// Function to load the page.
|
||||||
|
load?: () => Promise<unknown>;
|
||||||
cache?: boolean;
|
cache?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RouterOptions {
|
export interface RouterOptions {
|
||||||
|
// The default route to show if path does not define a page.
|
||||||
defaultPage?: string;
|
defaultPage?: string;
|
||||||
|
// If all routes should be preloaded
|
||||||
preloadAll?: boolean;
|
preloadAll?: boolean;
|
||||||
|
// If a route has been shown, should we keep the element in memory
|
||||||
cacheAll?: boolean;
|
cacheAll?: boolean;
|
||||||
|
// Should we show a loading spinner while we load the element for the route
|
||||||
showLoading?: boolean;
|
showLoading?: boolean;
|
||||||
|
// Promise that resolves when the initial data is loaded which is needed to show any route.
|
||||||
|
initialLoad?: () => Promise<unknown>;
|
||||||
routes: {
|
routes: {
|
||||||
[route: string]: RouteOptions;
|
// If it's a string, it is another route whose options should be adopted.
|
||||||
|
[route: string]: RouteOptions | string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,16 +48,10 @@ export class HassRouterPage extends UpdatingElement {
|
|||||||
|
|
||||||
protected routerOptions!: RouterOptions;
|
protected routerOptions!: RouterOptions;
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional variable to define extra routes dynamically.
|
|
||||||
* It is preferred to use static routes.
|
|
||||||
*/
|
|
||||||
protected extraRoutes?: {
|
|
||||||
[route: string]: RouteOptions;
|
|
||||||
};
|
|
||||||
private _currentPage = "";
|
private _currentPage = "";
|
||||||
private _currentLoadProm?: Promise<void>;
|
private _currentLoadProm?: Promise<void>;
|
||||||
private _cache = {};
|
private _cache = {};
|
||||||
|
private _initialLoadDone = false;
|
||||||
private _computeTail = memoizeOne((route: Route) => {
|
private _computeTail = memoizeOne((route: Route) => {
|
||||||
const dividerPos = route.path.indexOf("/", 1);
|
const dividerPos = route.path.indexOf("/", 1);
|
||||||
return dividerPos === -1
|
return dividerPos === -1
|
||||||
@ -64,6 +68,12 @@ export class HassRouterPage extends UpdatingElement {
|
|||||||
protected update(changedProps: PropertyValues) {
|
protected update(changedProps: PropertyValues) {
|
||||||
super.update(changedProps);
|
super.update(changedProps);
|
||||||
|
|
||||||
|
const routerOptions = this.routerOptions || { routes: {} };
|
||||||
|
|
||||||
|
if (routerOptions && routerOptions.initialLoad && !this._initialLoadDone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!changedProps.has("route")) {
|
if (!changedProps.has("route")) {
|
||||||
// Do not update if we have a currentLoadProm, because that means
|
// Do not update if we have a currentLoadProm, because that means
|
||||||
// that there is still an old panel shown and we're moving to a new one.
|
// that there is still an old panel shown and we're moving to a new one.
|
||||||
@ -74,14 +84,22 @@ export class HassRouterPage extends UpdatingElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const route = this.route;
|
const route = this.route;
|
||||||
const routerOptions = this.routerOptions || { routes: {} };
|
const defaultPage = routerOptions.defaultPage;
|
||||||
const defaultPage = routerOptions.defaultPage || "";
|
|
||||||
|
|
||||||
if (route && route.path === "") {
|
if (route && route.path === "" && defaultPage !== undefined) {
|
||||||
navigate(this, `${route.prefix}/${defaultPage}`, true);
|
navigate(this, `${route.prefix}/${defaultPage}`, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newPage = route ? extractPage(route.path, defaultPage) : "not_found";
|
let newPage = route
|
||||||
|
? extractPage(route.path, defaultPage || "")
|
||||||
|
: "not_found";
|
||||||
|
let routeOptions = routerOptions.routes[newPage];
|
||||||
|
|
||||||
|
// Handle redirects
|
||||||
|
while (typeof routeOptions === "string") {
|
||||||
|
newPage = routeOptions;
|
||||||
|
routeOptions = routerOptions.routes[newPage];
|
||||||
|
}
|
||||||
|
|
||||||
if (this._currentPage === newPage) {
|
if (this._currentPage === newPage) {
|
||||||
if (this.lastChild) {
|
if (this.lastChild) {
|
||||||
@ -90,8 +108,6 @@ export class HassRouterPage extends UpdatingElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeOptions = routerOptions.routes[newPage];
|
|
||||||
|
|
||||||
if (!routeOptions) {
|
if (!routeOptions) {
|
||||||
this._currentPage = "";
|
this._currentPage = "";
|
||||||
if (this.lastChild) {
|
if (this.lastChild) {
|
||||||
@ -101,10 +117,15 @@ export class HassRouterPage extends UpdatingElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._currentPage = newPage;
|
this._currentPage = newPage;
|
||||||
const loadProm = routeOptions.load();
|
const loadProm = routeOptions.load
|
||||||
|
? routeOptions.load()
|
||||||
|
: Promise.resolve();
|
||||||
|
|
||||||
// Check when loading the page source failed.
|
// Check when loading the page source failed.
|
||||||
loadProm.catch(() => {
|
loadProm.catch((err) => {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error("Error loading page", newPage, err);
|
||||||
|
|
||||||
// Verify that we're still trying to show the same page.
|
// Verify that we're still trying to show the same page.
|
||||||
if (this._currentPage !== newPage) {
|
if (this._currentPage !== newPage) {
|
||||||
return;
|
return;
|
||||||
@ -151,7 +172,12 @@ export class HassRouterPage extends UpdatingElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
created = true;
|
created = true;
|
||||||
this._createPanel(routerOptions, newPage, routeOptions);
|
this._createPanel(
|
||||||
|
routerOptions,
|
||||||
|
newPage,
|
||||||
|
// @ts-ignore TS forgot this is not a string.
|
||||||
|
routeOptions
|
||||||
|
);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
this._currentLoadProm = undefined;
|
this._currentLoadProm = undefined;
|
||||||
@ -164,10 +190,28 @@ export class HassRouterPage extends UpdatingElement {
|
|||||||
|
|
||||||
const options = this.routerOptions;
|
const options = this.routerOptions;
|
||||||
|
|
||||||
if (options && options.preloadAll) {
|
if (!options) {
|
||||||
Object.values(options.routes).forEach((route) => route.load());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.preloadAll) {
|
||||||
|
Object.values(options.routes).forEach(
|
||||||
|
(route) => typeof route === "object" && route.load && route.load()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.initialLoad) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this._initialLoadDone) {
|
||||||
|
this.appendChild(this.createLoadingScreen());
|
||||||
|
}
|
||||||
|
}, LOADING_SCREEN_THRESHOLD);
|
||||||
|
|
||||||
|
options.initialLoad().then(() => {
|
||||||
|
this._initialLoadDone = true;
|
||||||
|
this.requestUpdate("route");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createLoadingScreen() {
|
protected createLoadingScreen() {
|
||||||
|
@ -1,37 +1,44 @@
|
|||||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
|
||||||
import {
|
import {
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
html,
|
html,
|
||||||
customElement,
|
customElement,
|
||||||
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { haStyle } from "../resources/styles";
|
import "../components/ha-menu-button";
|
||||||
|
import "../components/ha-paper-icon-button-arrow-prev";
|
||||||
|
|
||||||
@customElement("hass-subpage")
|
@customElement("hass-subpage")
|
||||||
class HassSubpage extends LitElement {
|
class HassSubpage extends LitElement {
|
||||||
@property()
|
@property()
|
||||||
public header?: string;
|
public header?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public root = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public hassio = false;
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
<app-header-layout has-scrolling-region>
|
<div class="toolbar">
|
||||||
<app-header slot="header" fixed>
|
${this.root
|
||||||
<app-toolbar>
|
? html`
|
||||||
<ha-paper-icon-button-arrow-prev
|
<ha-menu-button .hassio=${this.hassio}></ha-menu-button>
|
||||||
@click=${this._backTapped}
|
`
|
||||||
></ha-paper-icon-button-arrow-prev>
|
: html`
|
||||||
<div main-title>${this.header}</div>
|
<ha-paper-icon-button-arrow-prev
|
||||||
<slot name="toolbar-icon"></slot>
|
.hassio=${this.hassio}
|
||||||
</app-toolbar>
|
@click=${this._backTapped}
|
||||||
</app-header>
|
></ha-paper-icon-button-arrow-prev>
|
||||||
|
`}
|
||||||
|
|
||||||
<slot></slot>
|
<div main-title>${this.header}</div>
|
||||||
</app-header-layout>
|
<slot name="toolbar-icon"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="content"><slot></slot></div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +47,46 @@ class HassSubpage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return haStyle;
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 20px;
|
||||||
|
height: 64px;
|
||||||
|
padding: 0 16px;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--text-primary-color, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-menu-button,
|
||||||
|
ha-paper-icon-button-arrow-prev,
|
||||||
|
::slotted([slot="toolbar-icon"]) {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[main-title] {
|
||||||
|
margin: 0 0 0 24px;
|
||||||
|
line-height: 20px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 64px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ import "@polymer/app-layout/app-drawer/app-drawer";
|
|||||||
// Not a duplicate, it's for typing
|
// Not a duplicate, it's for typing
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { AppDrawerElement } from "@polymer/app-layout/app-drawer/app-drawer";
|
import { AppDrawerElement } from "@polymer/app-layout/app-drawer/app-drawer";
|
||||||
import "@polymer/app-route/app-route";
|
|
||||||
import "@polymer/iron-media-query/iron-media-query";
|
import "@polymer/iron-media-query/iron-media-query";
|
||||||
|
|
||||||
import "./partial-panel-resolver";
|
import "./partial-panel-resolver";
|
||||||
|
35
src/layouts/loading-screen.ts
Normal file
35
src/layouts/loading-screen.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
customElement,
|
||||||
|
CSSResult,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
|
@customElement("loading-screen")
|
||||||
|
class LoadingScreen extends LitElement {
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<paper-spinner-lite active></paper-spinner-lite>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"loading-screen": LoadingScreen;
|
||||||
|
}
|
||||||
|
}
|
@ -74,6 +74,8 @@ class PartialPanelResolver extends HassRouterPage {
|
|||||||
@property() public narrow?: boolean;
|
@property() public narrow?: boolean;
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
|
||||||
if (!changedProps.has("hass")) {
|
if (!changedProps.has("hass")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
32
src/mixins/provide-hass-lit-mixin.ts
Normal file
32
src/mixins/provide-hass-lit-mixin.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { UpdatingElement, Constructor, PropertyValues } from "lit-element";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface ProvideHassElement {
|
||||||
|
provideHass(element: HTMLElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
|
||||||
|
export const ProvideHassLitMixin = <T extends UpdatingElement>(
|
||||||
|
superClass: Constructor<T>
|
||||||
|
): Constructor<T & ProvideHassElement> =>
|
||||||
|
// @ts-ignore
|
||||||
|
class extends superClass {
|
||||||
|
protected hass!: HomeAssistant;
|
||||||
|
private __provideHass: HTMLElement[] = [];
|
||||||
|
|
||||||
|
public provideHass(el) {
|
||||||
|
this.__provideHass.push(el);
|
||||||
|
el.hass = this.hass;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
|
||||||
|
if (changedProps.has("hass")) {
|
||||||
|
this.__provideHass.forEach((el) => {
|
||||||
|
(el as any).hass = this.hass;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -6,10 +6,10 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
|
import "../../../components/dialog/ha-paper-dialog";
|
||||||
import { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";
|
import { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";
|
||||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
import { haStyleDialog } from "../../../resources/styles";
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
@ -47,7 +47,7 @@ class DialogAreaDetail extends LitElement {
|
|||||||
const entry = this._params.entry;
|
const entry = this._params.entry;
|
||||||
const nameInvalid = this._name.trim() === "";
|
const nameInvalid = this._name.trim() === "";
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog
|
<ha-paper-dialog
|
||||||
with-backdrop
|
with-backdrop
|
||||||
opened
|
opened
|
||||||
@opened-changed="${this._openedChanged}"
|
@opened-changed="${this._openedChanged}"
|
||||||
@ -108,7 +108,7 @@ class DialogAreaDetail extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ class DialogAreaDetail extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
}
|
}
|
||||||
.form {
|
.form {
|
||||||
|
@ -9,10 +9,10 @@ import {
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
import "../../../components/dialog/ha-paper-dialog";
|
||||||
// This is not a duplicate import, one is for types, one is for element.
|
// This is not a duplicate import, one is for types, one is for element.
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
import { HaPaperDialog } from "../../../components/dialog/ha-paper-dialog";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ export class CloudWebhookManageDialog extends LitElement {
|
|||||||
? "https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger"
|
? "https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger"
|
||||||
: `https://www.home-assistant.io/components/${webhook.domain}/`;
|
: `https://www.home-assistant.io/components/${webhook.domain}/`;
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog with-backdrop>
|
<ha-paper-dialog with-backdrop>
|
||||||
<h2>Webhook for ${webhook.name}</h2>
|
<h2>Webhook for ${webhook.name}</h2>
|
||||||
<div>
|
<div>
|
||||||
<p>The webhook is available at the following url:</p>
|
<p>The webhook is available at the following url:</p>
|
||||||
@ -80,12 +80,12 @@ export class CloudWebhookManageDialog extends LitElement {
|
|||||||
>
|
>
|
||||||
<mwc-button @click="${this._closeDialog}">CLOSE</mwc-button>
|
<mwc-button @click="${this._closeDialog}">CLOSE</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _dialog(): PaperDialogElement {
|
private get _dialog(): HaPaperDialog {
|
||||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _paperInput(): PaperInputElement {
|
private get _paperInput(): PaperInputElement {
|
||||||
@ -127,7 +127,7 @@ export class CloudWebhookManageDialog extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
width: 650px;
|
width: 650px;
|
||||||
}
|
}
|
||||||
paper-input {
|
paper-input {
|
||||||
|
@ -8,10 +8,10 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
import "../../../components/dialog/ha-paper-dialog";
|
||||||
// This is not a duplicate import, one is for types, one is for element.
|
// This is not a duplicate import, one is for types, one is for element.
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
import { HaPaperDialog } from "../../../components/dialog/ha-paper-dialog";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
@ -39,7 +39,7 @@ class DialogCloudCertificate extends LitElement {
|
|||||||
const { certificateInfo } = this._params;
|
const { certificateInfo } = this._params;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog with-backdrop>
|
<ha-paper-dialog with-backdrop>
|
||||||
<h2>Certificate Information</h2>
|
<h2>Certificate Information</h2>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
@ -58,12 +58,12 @@ class DialogCloudCertificate extends LitElement {
|
|||||||
<div class="paper-dialog-buttons">
|
<div class="paper-dialog-buttons">
|
||||||
<mwc-button @click="${this._closeDialog}">CLOSE</mwc-button>
|
<mwc-button @click="${this._closeDialog}">CLOSE</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _dialog(): PaperDialogElement {
|
private get _dialog(): HaPaperDialog {
|
||||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _closeDialog() {
|
private _closeDialog() {
|
||||||
@ -74,7 +74,7 @@ class DialogCloudCertificate extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
width: 535px;
|
width: 535px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
@ -63,7 +63,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
</template>
|
</template>
|
||||||
<template is="dom-if" if="[[!cloudStatus.logged_in]]">
|
<template is="dom-if" if="[[!cloudStatus.logged_in]]">
|
||||||
<div secondary="">
|
<div secondary="">
|
||||||
[[localize('ui.panel.config.cloud.description_not_login')]]
|
[[localize('ui.panel.config.cloud.description_features')]]
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
|
@ -6,10 +6,11 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
|
import "../../../components/dialog/ha-paper-dialog";
|
||||||
|
|
||||||
import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-registry-detail";
|
import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-registry-detail";
|
||||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
import { haStyleDialog } from "../../../resources/styles";
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
@ -56,7 +57,7 @@ class DialogEntityRegistryDetail extends LitElement {
|
|||||||
computeDomain(this._params.entry.entity_id);
|
computeDomain(this._params.entry.entity_id);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog
|
<ha-paper-dialog
|
||||||
with-backdrop
|
with-backdrop
|
||||||
opened
|
opened
|
||||||
@opened-changed="${this._openedChanged}"
|
@opened-changed="${this._openedChanged}"
|
||||||
@ -116,7 +117,7 @@ class DialogEntityRegistryDetail extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +167,7 @@ class DialogEntityRegistryDetail extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
}
|
}
|
||||||
.form {
|
.form {
|
||||||
|
@ -6,11 +6,12 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
property,
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
|
||||||
|
import "../../../components/dialog/ha-paper-dialog";
|
||||||
|
|
||||||
import "../../../components/entity/ha-entities-picker";
|
import "../../../components/entity/ha-entities-picker";
|
||||||
import "../../../components/user/ha-user-picker";
|
import "../../../components/user/ha-user-picker";
|
||||||
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
||||||
@ -49,7 +50,7 @@ class DialogPersonDetail extends LitElement {
|
|||||||
}
|
}
|
||||||
const nameInvalid = this._name.trim() === "";
|
const nameInvalid = this._name.trim() === "";
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog
|
<ha-paper-dialog
|
||||||
with-backdrop
|
with-backdrop
|
||||||
opened
|
opened
|
||||||
@opened-changed="${this._openedChanged}"
|
@opened-changed="${this._openedChanged}"
|
||||||
@ -114,7 +115,7 @@ class DialogPersonDetail extends LitElement {
|
|||||||
${this._params.entry ? "UPDATE" : "CREATE"}
|
${this._params.entry ? "UPDATE" : "CREATE"}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +176,7 @@ class DialogPersonDetail extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
}
|
}
|
||||||
.form {
|
.form {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-spinner/paper-spinner";
|
import "@polymer/paper-spinner/paper-spinner";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
|
import "../../../components/dialog/ha-paper-dialog";
|
||||||
import "../../../resources/ha-style";
|
import "../../../resources/ha-style";
|
||||||
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||||
@ -18,14 +18,14 @@ class HaDialogAddUser extends LocalizeMixin(PolymerElement) {
|
|||||||
.error {
|
.error {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
.username {
|
.username {
|
||||||
margin-top: -8px;
|
margin-top: -8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<paper-dialog
|
<ha-paper-dialog
|
||||||
id="dialog"
|
id="dialog"
|
||||||
with-backdrop
|
with-backdrop
|
||||||
opened="{{_opened}}"
|
opened="{{_opened}}"
|
||||||
@ -76,7 +76,7 @@ class HaDialogAddUser extends LocalizeMixin(PolymerElement) {
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ZHADevice } from "../../../data/zha";
|
||||||
|
|
||||||
export const formatAsPaddedHex = (value: string | number): string => {
|
export const formatAsPaddedHex = (value: string | number): string => {
|
||||||
let hex = value;
|
let hex = value;
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
@ -5,3 +7,9 @@ export const formatAsPaddedHex = (value: string | number): string => {
|
|||||||
}
|
}
|
||||||
return "0x" + hex.toString(16).padStart(4, "0");
|
return "0x" + hex.toString(16).padStart(4, "0");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sortZHADevices = (a: ZHADevice, b: ZHADevice): number => {
|
||||||
|
const nameA = a.user_given_name ? a.user_given_name : a.name;
|
||||||
|
const nameb = b.user_given_name ? b.user_given_name : b.name;
|
||||||
|
return nameA.localeCompare(nameb);
|
||||||
|
};
|
||||||
|
@ -20,6 +20,7 @@ import { HASSDomEvent } from "../../../common/dom/fire_event";
|
|||||||
import { Cluster, fetchBindableDevices, ZHADevice } from "../../../data/zha";
|
import { Cluster, fetchBindableDevices, ZHADevice } from "../../../data/zha";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { sortZHADevices } from "./functions";
|
||||||
import { ZHAClusterSelectedParams, ZHADeviceSelectedParams } from "./types";
|
import { ZHAClusterSelectedParams, ZHADeviceSelectedParams } from "./types";
|
||||||
|
|
||||||
export class HaConfigZha extends LitElement {
|
export class HaConfigZha extends LitElement {
|
||||||
@ -99,9 +100,7 @@ export class HaConfigZha extends LitElement {
|
|||||||
this._bindableDevices = (await fetchBindableDevices(
|
this._bindableDevices = (await fetchBindableDevices(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._selectedDevice!.ieee
|
this._selectedDevice!.ieee
|
||||||
)).sort((a, b) => {
|
)).sort(sortZHADevices);
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,23 +18,28 @@ import {
|
|||||||
|
|
||||||
import { ZHADevice } from "../../../data/zha";
|
import { ZHADevice } from "../../../data/zha";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
|
|
||||||
@customElement("zha-add-devices-page")
|
@customElement("zha-add-devices-page")
|
||||||
class ZHAAddDevicesPage extends LitElement {
|
class ZHAAddDevicesPage extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@property() public isWide?: boolean;
|
@property() public isWide?: boolean;
|
||||||
|
@property() public route?: Route;
|
||||||
@property() private _error?: string;
|
@property() private _error?: string;
|
||||||
@property() private _discoveredDevices: ZHADevice[] = [];
|
@property() private _discoveredDevices: ZHADevice[] = [];
|
||||||
@property() private _formattedEvents: string = "";
|
@property() private _formattedEvents: string = "";
|
||||||
@property() private _active: boolean = false;
|
@property() private _active: boolean = false;
|
||||||
@property() private _showHelp: boolean = false;
|
@property() private _showHelp: boolean = false;
|
||||||
|
private _ieeeAddress?: string;
|
||||||
private _addDevicesTimeoutHandle: any = undefined;
|
private _addDevicesTimeoutHandle: any = undefined;
|
||||||
private _subscribed?: Promise<() => Promise<void>>;
|
private _subscribed?: Promise<() => Promise<void>>;
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._subscribe();
|
if (this.route && this.route.path && this.route.path !== "") {
|
||||||
|
this._ieeeAddress = this.route.path.substring(1);
|
||||||
|
}
|
||||||
|
this._subscribe(this._ieeeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
@ -151,15 +156,19 @@ class ZHAAddDevicesPage extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _subscribe(): void {
|
private _subscribe(ieeeAddress: string | undefined): void {
|
||||||
|
const data: any = { type: "zha/devices/permit" };
|
||||||
|
if (ieeeAddress) {
|
||||||
|
data.ieee = ieeeAddress;
|
||||||
|
}
|
||||||
this._subscribed = this.hass!.connection.subscribeMessage(
|
this._subscribed = this.hass!.connection.subscribeMessage(
|
||||||
(message) => this._handleMessage(message),
|
(message) => this._handleMessage(message),
|
||||||
{ type: "zha/devices/permit" }
|
data
|
||||||
);
|
);
|
||||||
this._active = true;
|
this._active = true;
|
||||||
this._addDevicesTimeoutHandle = setTimeout(
|
this._addDevicesTimeoutHandle = setTimeout(
|
||||||
() => this._unsubscribe(),
|
() => this._unsubscribe(),
|
||||||
60000
|
75000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import { bindDevices, unbindDevices, ZHADevice } from "../../../data/zha";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { ItemSelectedEvent } from "./types";
|
import { ItemSelectedEvent } from "./types";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
|
||||||
@customElement("zha-binding-control")
|
@customElement("zha-binding-control")
|
||||||
export class ZHABindingControl extends LitElement {
|
export class ZHABindingControl extends LitElement {
|
||||||
@ -64,7 +65,11 @@ export class ZHABindingControl extends LitElement {
|
|||||||
>
|
>
|
||||||
${this.bindableDevices.map(
|
${this.bindableDevices.map(
|
||||||
(device) => html`
|
(device) => html`
|
||||||
<paper-item>${device.name}</paper-item>
|
<paper-item
|
||||||
|
>${device.user_given_name
|
||||||
|
? device.user_given_name
|
||||||
|
: device.name}</paper-item
|
||||||
|
>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "../../../components/buttons/ha-call-service-button";
|
import "../../../components/buttons/ha-call-service-button";
|
||||||
|
import "../../../components/ha-service-description";
|
||||||
import "../../../components/entity/state-badge";
|
import "../../../components/entity/state-badge";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-card/paper-card";
|
import "@polymer/paper-card/paper-card";
|
||||||
@ -34,6 +35,7 @@ import { reconfigureNode, ZHADevice } from "../../../data/zha";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { ItemSelectedEvent, NodeServiceData } from "./types";
|
import { ItemSelectedEvent, NodeServiceData } from "./types";
|
||||||
|
import { navigate } from "../../../common/navigate";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -222,6 +224,23 @@ class ZHADeviceCard extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this.device!.power_source === "Mains"
|
||||||
|
? html`
|
||||||
|
<mwc-button @click=${this._onAddDevicesClick}>
|
||||||
|
Add Devices
|
||||||
|
</mwc-button>
|
||||||
|
${this.showHelp
|
||||||
|
? html`
|
||||||
|
<ha-service-description
|
||||||
|
.hass="${this.hass}"
|
||||||
|
domain="zha"
|
||||||
|
service="permit"
|
||||||
|
class="help-text2"
|
||||||
|
/>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""
|
: ""
|
||||||
@ -281,6 +300,10 @@ class ZHADeviceCard extends LitElement {
|
|||||||
this.device!.area_id = newAreaId;
|
this.device!.area_id = newAreaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _onAddDevicesClick() {
|
||||||
|
navigate(this, "add/" + this.device!.ieee);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@ -24,6 +24,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
|||||||
import { fetchDevices, ZHADevice } from "../../../data/zha";
|
import { fetchDevices, ZHADevice } from "../../../data/zha";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { sortZHADevices } from "./functions";
|
||||||
import { ItemSelectedEvent, ZHADeviceRemovedEvent } from "./types";
|
import { ItemSelectedEvent, ZHADeviceRemovedEvent } from "./types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -141,9 +142,7 @@ export class ZHANode extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchDevices() {
|
private async _fetchDevices() {
|
||||||
this._nodes = (await fetchDevices(this.hass!)).sort((a, b) => {
|
this._nodes = (await fetchDevices(this.hass!)).sort(sortZHADevices);
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onDeviceRemoved(event: ZHADeviceRemovedEvent): void {
|
private _onDeviceRemoved(event: ZHADeviceRemovedEvent): void {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
|
||||||
|
import "../../../components/dialog/ha-paper-dialog";
|
||||||
import "../../../resources/ha-style";
|
import "../../../resources/ha-style";
|
||||||
|
|
||||||
import EventsMixin from "../../../mixins/events-mixin";
|
import EventsMixin from "../../../mixins/events-mixin";
|
||||||
@ -12,12 +12,12 @@ class ZwaveLogDialog extends EventsMixin(PolymerElement) {
|
|||||||
return html`
|
return html`
|
||||||
<style include="ha-style-dialog">
|
<style include="ha-style-dialog">
|
||||||
</style>
|
</style>
|
||||||
<paper-dialog id="pwaDialog" with-backdrop="" opened="{{_opened}}">
|
<ha-paper-dialog id="pwaDialog" with-backdrop="" opened="{{_opened}}">
|
||||||
<h2>OpenZwave internal logfile</h2>
|
<h2>OpenZwave internal logfile</h2>
|
||||||
<paper-dialog-scrollable>
|
<paper-dialog-scrollable>
|
||||||
<pre>[[_ozwLog]]</pre>
|
<pre>[[_ozwLog]]</pre>
|
||||||
<paper-dialog-scrollable>
|
<paper-dialog-scrollable>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@ import "@material/mwc-button";
|
|||||||
import "@polymer/paper-card/paper-card";
|
import "@polymer/paper-card/paper-card";
|
||||||
import "@polymer/paper-checkbox/paper-checkbox";
|
import "@polymer/paper-checkbox/paper-checkbox";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
import EventsMixin from "../../../mixins/events-mixin";
|
import EventsMixin from "../../../mixins/events-mixin";
|
||||||
|
@ -6,9 +6,10 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
|
|
||||||
|
import "../../components/dialog/ha-paper-dialog";
|
||||||
|
|
||||||
import { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
import { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
@ -34,7 +35,7 @@ class DialogSystemLogDetail extends LitElement {
|
|||||||
const item = this._params.item;
|
const item = this._params.item;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog
|
<ha-paper-dialog
|
||||||
with-backdrop
|
with-backdrop
|
||||||
opened
|
opened
|
||||||
@opened-changed="${this._openedChanged}"
|
@opened-changed="${this._openedChanged}"
|
||||||
@ -53,7 +54,7 @@ class DialogSystemLogDetail extends LitElement {
|
|||||||
`
|
`
|
||||||
: html``}
|
: html``}
|
||||||
</paper-dialog-scrollable>
|
</paper-dialog-scrollable>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ class DialogSystemLogDetail extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
@ -106,7 +106,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card .header="${title}">
|
<ha-card .header="${title}">
|
||||||
<div class="entities ${classMap({ "no-header": !title })}">
|
<div class="${classMap({ entities: true, "no-header": !title })}">
|
||||||
${this._configEntities!.map((entityConf) =>
|
${this._configEntities!.map((entityConf) =>
|
||||||
this.renderEntity(entityConf)
|
this.renderEntity(entityConf)
|
||||||
)}
|
)}
|
||||||
|
@ -21,7 +21,7 @@ declare global {
|
|||||||
export class HuiYamlEditor extends HTMLElement {
|
export class HuiYamlEditor extends HTMLElement {
|
||||||
public _hass?: HomeAssistant;
|
public _hass?: HomeAssistant;
|
||||||
|
|
||||||
public codemirror: CodeMirror;
|
public codemirror!: any;
|
||||||
|
|
||||||
private _value: string;
|
private _value: string;
|
||||||
|
|
||||||
@ -89,22 +89,25 @@ export class HuiYamlEditor extends HTMLElement {
|
|||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
if (!this.codemirror) {
|
if (!this.codemirror) {
|
||||||
this.codemirror = CodeMirror(this.shadowRoot, {
|
this.codemirror = CodeMirror(
|
||||||
value: this._value,
|
(this.shadowRoot as unknown) as HTMLElement,
|
||||||
lineNumbers: true,
|
{
|
||||||
mode: "yaml",
|
value: this._value,
|
||||||
tabSize: 2,
|
lineNumbers: true,
|
||||||
autofocus: true,
|
mode: "yaml",
|
||||||
viewportMargin: Infinity,
|
tabSize: 2,
|
||||||
extraKeys: {
|
autofocus: true,
|
||||||
Tab: "indentMore",
|
viewportMargin: Infinity,
|
||||||
"Shift-Tab": "indentLess",
|
extraKeys: {
|
||||||
},
|
Tab: "indentMore",
|
||||||
gutters:
|
"Shift-Tab": "indentLess",
|
||||||
this._hass && computeRTL(this._hass!)
|
},
|
||||||
? ["rtl-gutter", "CodeMirror-linenumbers"]
|
gutters:
|
||||||
: [],
|
this._hass && computeRTL(this._hass!)
|
||||||
});
|
? ["rtl-gutter", "CodeMirror-linenumbers"]
|
||||||
|
: [],
|
||||||
|
}
|
||||||
|
);
|
||||||
this.setScrollBarDirection();
|
this.setScrollBarDirection();
|
||||||
this.codemirror.on("changes", () => this._onChange());
|
this.codemirror.on("changes", () => this._onChange());
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,14 +10,14 @@ import "@material/mwc-button";
|
|||||||
import "./hui-notification-item-template";
|
import "./hui-notification-item-template";
|
||||||
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { HassNotification } from "./types";
|
||||||
|
|
||||||
@customElement("hui-configurator-notification-item")
|
@customElement("hui-configurator-notification-item")
|
||||||
export class HuiConfiguratorNotificationItem extends LitElement {
|
export class HuiConfiguratorNotificationItem extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public notification?: HassEntity;
|
@property() public notification?: HassNotification;
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass || !this.notification) {
|
if (!this.hass || !this.notification) {
|
||||||
|
@ -10,14 +10,14 @@ import {
|
|||||||
import "./hui-configurator-notification-item";
|
import "./hui-configurator-notification-item";
|
||||||
import "./hui-persistent-notification-item";
|
import "./hui-persistent-notification-item";
|
||||||
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { HassNotification } from "./types";
|
||||||
|
|
||||||
@customElement("hui-notification-item")
|
@customElement("hui-notification-item")
|
||||||
export class HuiNotificationItem extends LitElement {
|
export class HuiNotificationItem extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public notification?: HassEntity;
|
@property() public notification?: HassNotification;
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
if (!this.hass || !this.notification || changedProps.has("notification")) {
|
if (!this.hass || !this.notification || changedProps.has("notification")) {
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-icon-button/paper-icon-button";
|
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
|
||||||
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../../../components/ha-relative-time";
|
|
||||||
import "../../../../components/ha-markdown";
|
|
||||||
import "./hui-notification-item-template";
|
|
||||||
|
|
||||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
export class HuiPersistentNotificationItem extends LocalizeMixin(
|
|
||||||
PolymerElement
|
|
||||||
) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
.time {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
ha-relative-time {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<hui-notification-item-template>
|
|
||||||
<span slot="header">[[_computeTitle(notification)]]</span>
|
|
||||||
|
|
||||||
<ha-markdown content="[[notification.message]]"></ha-markdown>
|
|
||||||
|
|
||||||
<div class="time">
|
|
||||||
<span>
|
|
||||||
<ha-relative-time
|
|
||||||
hass="[[hass]]"
|
|
||||||
datetime="[[notification.created_at]]"
|
|
||||||
></ha-relative-time>
|
|
||||||
<paper-tooltip
|
|
||||||
>[[_computeTooltip(hass, notification)]]</paper-tooltip
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mwc-button slot="actions" on-click="_handleDismiss"
|
|
||||||
>[[localize('ui.card.persistent_notification.dismiss')]]</mwc-button
|
|
||||||
>
|
|
||||||
</hui-notification-item-template>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
notification: Object,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleDismiss() {
|
|
||||||
this.hass.callService("persistent_notification", "dismiss", {
|
|
||||||
notification_id: this.notification.notification_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeTitle(notification) {
|
|
||||||
return notification.title || notification.notification_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeTooltip(hass, notification) {
|
|
||||||
if (!hass || !notification) return null;
|
|
||||||
|
|
||||||
const d = new Date(notification.created_at);
|
|
||||||
return d.toLocaleDateString(hass.language, {
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
minute: "numeric",
|
|
||||||
hour: "numeric",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define(
|
|
||||||
"hui-persistent-notification-item",
|
|
||||||
HuiPersistentNotificationItem
|
|
||||||
);
|
|
@ -0,0 +1,110 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
|
|
||||||
|
import "../../../../components/ha-relative-time";
|
||||||
|
import "../../../../components/ha-markdown";
|
||||||
|
import "./hui-notification-item-template";
|
||||||
|
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { HassNotification } from "./types";
|
||||||
|
|
||||||
|
@customElement("hui-persistent-notification-item")
|
||||||
|
export class HuiPersistentNotificationItem extends LitElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public notification?: HassNotification;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
if (!this.hass || !this.notification) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hui-notification-item-template>
|
||||||
|
<span slot="header">${this._computeTitle(this.notification)}</span>
|
||||||
|
|
||||||
|
<ha-markdown content="${this.notification.message}"></ha-markdown>
|
||||||
|
|
||||||
|
<div class="time">
|
||||||
|
<span>
|
||||||
|
<ha-relative-time
|
||||||
|
.hass="${this.hass}"
|
||||||
|
.datetime="${this.notification.created_at}"
|
||||||
|
></ha-relative-time>
|
||||||
|
<paper-tooltip
|
||||||
|
>${this._computeTooltip(
|
||||||
|
this.hass,
|
||||||
|
this.notification
|
||||||
|
)}</paper-tooltip
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mwc-button slot="actions" @click="${this._handleDismiss}"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.card.persistent_notification.dismiss"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
</hui-notification-item-template>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.time {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
ha-relative-time {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDismiss(): void {
|
||||||
|
this.hass!.callService("persistent_notification", "dismiss", {
|
||||||
|
notification_id: this.notification!.notification_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeTitle(notification: HassNotification): string | undefined {
|
||||||
|
return notification.title || notification.notification_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeTooltip(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
notification: HassNotification
|
||||||
|
): string | undefined {
|
||||||
|
if (!hass || !notification) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = new Date(notification.created_at!);
|
||||||
|
return d.toLocaleDateString(hass.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-persistent-notification-item": HuiPersistentNotificationItem;
|
||||||
|
}
|
||||||
|
}
|
8
src/panels/lovelace/components/notifications/types.ts
Normal file
8
src/panels/lovelace/components/notifications/types.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
export declare type HassNotification = HassEntity & {
|
||||||
|
notification_id?: string;
|
||||||
|
created_at?: string;
|
||||||
|
title?: string;
|
||||||
|
message?: string;
|
||||||
|
};
|
@ -7,10 +7,10 @@ import {
|
|||||||
css,
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "../../../../components/dialog/ha-paper-dialog";
|
||||||
// tslint:disable-next-line:no-duplicate-imports
|
// tslint:disable-next-line:no-duplicate-imports
|
||||||
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
import { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
|
||||||
|
|
||||||
import { moveCard } from "../config-util";
|
import { moveCard } from "../config-util";
|
||||||
import { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
import { MoveCardViewDialogParams } from "./show-move-card-view-dialog";
|
||||||
@ -30,7 +30,7 @@ export class HuiDialogMoveCardView extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog
|
<ha-paper-dialog
|
||||||
with-backdrop
|
with-backdrop
|
||||||
opened
|
opened
|
||||||
@opened-changed="${this._openedChanged}"
|
@opened-changed="${this._openedChanged}"
|
||||||
@ -46,7 +46,7 @@ export class HuiDialogMoveCardView extends LitElement {
|
|||||||
>
|
>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,8 +76,8 @@ export class HuiDialogMoveCardView extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _dialog(): PaperDialogElement {
|
private get _dialog(): HaPaperDialog {
|
||||||
return this.shadowRoot!.querySelector("paper-dialog")!;
|
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _moveCard(e: Event): void {
|
private _moveCard(e: Event): void {
|
||||||
|
@ -6,9 +6,10 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
customElement,
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
|
|
||||||
|
import "../../../../components/dialog/ha-paper-dialog";
|
||||||
|
|
||||||
import { haStyleDialog } from "../../../../resources/styles";
|
import { haStyleDialog } from "../../../../resources/styles";
|
||||||
|
|
||||||
import "./hui-card-picker";
|
import "./hui-card-picker";
|
||||||
@ -23,7 +24,7 @@ export class HuiDialogPickCard extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
<paper-dialog
|
<ha-paper-dialog
|
||||||
with-backdrop
|
with-backdrop
|
||||||
opened
|
opened
|
||||||
@opened-changed="${this._openedChanged}"
|
@opened-changed="${this._openedChanged}"
|
||||||
@ -40,7 +41,7 @@ export class HuiDialogPickCard extends LitElement {
|
|||||||
<div class="paper-dialog-buttons">
|
<div class="paper-dialog-buttons">
|
||||||
<mwc-button @click="${this._skipPick}">MANUAL CARD</mwc-button>
|
<mwc-button @click="${this._skipPick}">MANUAL CARD</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</ha-paper-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,19 +61,19 @@ export class HuiDialogPickCard extends LitElement {
|
|||||||
css`
|
css`
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
/* overrule the ha-style-dialog max-height on small screens */
|
/* overrule the ha-style-dialog max-height on small screens */
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: 660px) {
|
@media all and (min-width: 660px) {
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
width: 650px;
|
width: 650px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-dialog {
|
ha-paper-dialog {
|
||||||
max-width: 650px;
|
max-width: 650px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user