Merge pull request #5757 from home-assistant/dev
12
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
@ -62,6 +62,18 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
|
||||
- Browser and browser version:
|
||||
- Operating system:
|
||||
|
||||
## State of relevant entities
|
||||
|
||||
<!--
|
||||
If your issue is about how an entity is shown in the UI, please add the state
|
||||
and attributes for all situations with a screenshot of the UI.
|
||||
You can find this information at `/developer-tools/state`
|
||||
-->
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
## Problem-relevant configuration
|
||||
|
||||
<!--
|
||||
|
||||
2
.github/workflows/ci.yaml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
env:
|
||||
CI: true
|
||||
- name: Build icons
|
||||
run: ./node_modules/.bin/gulp gen-icons-hassio gen-icons-mdi gen-icons-app
|
||||
run: ./node_modules/.bin/gulp gen-icons-json
|
||||
- name: Build translations
|
||||
run: ./node_modules/.bin/gulp build-translations
|
||||
- name: Run eslint
|
||||
|
||||
@ -5,7 +5,7 @@ const envVars = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./compress.js");
|
||||
require("./webpack.js");
|
||||
@ -21,7 +21,7 @@ gulp.task(
|
||||
"clean",
|
||||
gulp.parallel(
|
||||
"gen-service-worker-dev",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi"),
|
||||
"gen-icons-json",
|
||||
"gen-pages-dev",
|
||||
"gen-index-app-dev",
|
||||
"build-translations"
|
||||
@ -38,7 +38,7 @@ gulp.task(
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static",
|
||||
"webpack-prod-app",
|
||||
...// Don't compress running tests
|
||||
|
||||
@ -2,7 +2,6 @@ const gulp = require("gulp");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
@ -17,12 +16,7 @@ gulp.task(
|
||||
},
|
||||
"clean-cast",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel(
|
||||
"gen-icons-app",
|
||||
"gen-icons-mdi",
|
||||
"gen-index-cast-dev",
|
||||
"build-translations"
|
||||
),
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-cast",
|
||||
"webpack-dev-server-cast"
|
||||
)
|
||||
@ -36,7 +30,7 @@ gulp.task(
|
||||
},
|
||||
"clean-cast",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-cast",
|
||||
"webpack-prod-cast",
|
||||
"gen-index-cast-prod"
|
||||
|
||||
@ -3,7 +3,7 @@ const gulp = require("gulp");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
@ -17,13 +17,7 @@ gulp.task(
|
||||
},
|
||||
"clean-demo",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel(
|
||||
"gen-icons-app",
|
||||
"gen-icons-mdi",
|
||||
"gen-icons-demo",
|
||||
"gen-index-demo-dev",
|
||||
"build-translations"
|
||||
),
|
||||
gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
|
||||
"copy-static-demo",
|
||||
"webpack-dev-server-demo"
|
||||
)
|
||||
@ -38,12 +32,7 @@ gulp.task(
|
||||
"clean-demo",
|
||||
// Cast needs to be backwards compatible and older HA has no translations
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel(
|
||||
"gen-icons-app",
|
||||
"gen-icons-mdi",
|
||||
"gen-icons-demo",
|
||||
"build-translations"
|
||||
),
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-demo",
|
||||
"webpack-prod-demo",
|
||||
"gen-index-demo-prod"
|
||||
|
||||
@ -47,11 +47,9 @@ gulp.task("gen-pages-dev", (done) => {
|
||||
for (const page of PAGES) {
|
||||
const content = renderTemplate(page, {
|
||||
latestPageJS: `/frontend_latest/${page}.js`,
|
||||
latestHassIconsJS: "/frontend_latest/hass-icons.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5PageJS: `/frontend_es5/${page}.js`,
|
||||
es5HassIconsJS: "/frontend_es5/hass-icons.js",
|
||||
});
|
||||
|
||||
fs.outputFileSync(path.resolve(config.root, `${page}.html`), content);
|
||||
@ -66,11 +64,9 @@ gulp.task("gen-pages-prod", (done) => {
|
||||
for (const page of PAGES) {
|
||||
const content = renderTemplate(page, {
|
||||
latestPageJS: latestManifest[`${page}.js`],
|
||||
latestHassIconsJS: latestManifest["hass-icons.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5PageJS: es5Manifest[`${page}.js`],
|
||||
es5HassIconsJS: es5Manifest["hass-icons.js"],
|
||||
});
|
||||
|
||||
fs.outputFileSync(
|
||||
@ -88,13 +84,11 @@ gulp.task("gen-index-app-dev", (done) => {
|
||||
latestAppJS: "/frontend_latest/app.js",
|
||||
latestCoreJS: "/frontend_latest/core.js",
|
||||
latestCustomPanelJS: "/frontend_latest/custom-panel.js",
|
||||
latestHassIconsJS: "/frontend_latest/hass-icons.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5AppJS: "/frontend_es5/app.js",
|
||||
es5CoreJS: "/frontend_es5/core.js",
|
||||
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
|
||||
es5HassIconsJS: "/frontend_es5/hass-icons.js",
|
||||
}).replace(/#THEMEC/g, "{{ theme_color }}");
|
||||
|
||||
fs.outputFileSync(path.resolve(config.root, "index.html"), content);
|
||||
@ -108,13 +102,11 @@ gulp.task("gen-index-app-prod", (done) => {
|
||||
latestAppJS: latestManifest["app.js"],
|
||||
latestCoreJS: latestManifest["core.js"],
|
||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||
latestHassIconsJS: latestManifest["hass-icons.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5AppJS: es5Manifest["app.js"],
|
||||
es5CoreJS: es5Manifest["core.js"],
|
||||
es5CustomPanelJS: es5Manifest["custom-panel.js"],
|
||||
es5HassIconsJS: es5Manifest["hass-icons.js"],
|
||||
});
|
||||
const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}");
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ const gulp = require("gulp");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
@ -17,7 +17,7 @@ gulp.task(
|
||||
},
|
||||
"clean-gallery",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-gallery",
|
||||
"gen-index-gallery-dev",
|
||||
"webpack-dev-server-gallery"
|
||||
@ -32,7 +32,7 @@ gulp.task(
|
||||
},
|
||||
"clean-gallery",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-gallery",
|
||||
"webpack-prod-gallery",
|
||||
"gen-index-gallery-prod"
|
||||
|
||||
@ -26,6 +26,13 @@ function copyTranslations(staticDir) {
|
||||
);
|
||||
}
|
||||
|
||||
function copyMdiIcons(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
|
||||
// MDI icons output
|
||||
fs.copySync(polyPath("build/mdi"), staticPath("mdi"));
|
||||
}
|
||||
|
||||
function copyPolyfills(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
|
||||
@ -80,6 +87,7 @@ gulp.task("copy-static", (done) => {
|
||||
copyPolyfills(staticDir);
|
||||
copyFonts(staticDir);
|
||||
copyTranslations(staticDir);
|
||||
copyMdiIcons(staticDir);
|
||||
|
||||
// Panel assets
|
||||
copyFileDir(
|
||||
@ -103,6 +111,7 @@ gulp.task("copy-static-demo", (done) => {
|
||||
copyMapPanel(paths.demo_static);
|
||||
copyFonts(paths.demo_static);
|
||||
copyTranslations(paths.demo_static);
|
||||
copyMdiIcons(paths.demo_static);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -115,6 +124,7 @@ gulp.task("copy-static-cast", (done) => {
|
||||
copyMapPanel(paths.cast_static);
|
||||
copyFonts(paths.cast_static);
|
||||
copyTranslations(paths.cast_static);
|
||||
copyMdiIcons(paths.cast_static);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -127,5 +137,6 @@ gulp.task("copy-static-gallery", (done) => {
|
||||
copyMapPanel(paths.gallery_static);
|
||||
copyFonts(paths.gallery_static);
|
||||
copyTranslations(paths.gallery_static);
|
||||
copyMdiIcons(paths.gallery_static);
|
||||
done();
|
||||
});
|
||||
|
||||
109
build-scripts/gulp/gen-icons-json.js
Normal file
@ -0,0 +1,109 @@
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const hash = require("object-hash");
|
||||
|
||||
const ICON_PACKAGE_PATH = path.resolve(
|
||||
__dirname,
|
||||
"../../node_modules/@mdi/svg/"
|
||||
);
|
||||
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
|
||||
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
|
||||
const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi");
|
||||
|
||||
const encoding = "utf8";
|
||||
|
||||
const getMeta = () => {
|
||||
const file = fs.readFileSync(META_PATH, { encoding });
|
||||
const meta = JSON.parse(file);
|
||||
return meta.map((icon) => {
|
||||
const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, {
|
||||
encoding,
|
||||
});
|
||||
return { path: svg.match(/ d="([^"]+)"/)[1], name: icon.name };
|
||||
});
|
||||
};
|
||||
|
||||
const splitBySize = (meta) => {
|
||||
const chunks = [];
|
||||
const CHUNK_SIZE = 100000;
|
||||
|
||||
let curSize = 0;
|
||||
let startKey;
|
||||
let icons = [];
|
||||
|
||||
Object.values(meta).forEach((icon) => {
|
||||
if (startKey === undefined) {
|
||||
startKey = icon.name;
|
||||
}
|
||||
curSize += icon.path.length;
|
||||
icons.push(icon);
|
||||
if (curSize > CHUNK_SIZE) {
|
||||
chunks.push({
|
||||
startKey,
|
||||
endKey: icon.name,
|
||||
icons,
|
||||
});
|
||||
curSize = 0;
|
||||
startKey = undefined;
|
||||
icons = [];
|
||||
}
|
||||
});
|
||||
|
||||
chunks.push({
|
||||
startKey,
|
||||
icons,
|
||||
});
|
||||
|
||||
return chunks;
|
||||
};
|
||||
|
||||
const findDifferentiator = (curString, prevString) => {
|
||||
for (let i = 0; i < curString.length; i++) {
|
||||
if (curString[i] !== prevString[i]) {
|
||||
return curString.substring(0, i + 1);
|
||||
}
|
||||
}
|
||||
console.error("Cannot find differentiator", curString, prevString);
|
||||
return undefined;
|
||||
};
|
||||
|
||||
gulp.task("gen-icons-json", (done) => {
|
||||
const meta = getMeta();
|
||||
const split = splitBySize(meta);
|
||||
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
}
|
||||
const manifest = [];
|
||||
|
||||
let lastEnd;
|
||||
split.forEach((chunk) => {
|
||||
let startKey;
|
||||
if (lastEnd === undefined) {
|
||||
chunk.startKey = undefined;
|
||||
startKey = undefined;
|
||||
} else {
|
||||
startKey = findDifferentiator(chunk.startKey, lastEnd);
|
||||
}
|
||||
lastEnd = chunk.endKey;
|
||||
|
||||
const output = {};
|
||||
chunk.icons.forEach((icon) => {
|
||||
output[icon.name] = icon.path;
|
||||
});
|
||||
const filename = hash(output);
|
||||
manifest.push({ start: startKey, file: filename });
|
||||
fs.writeFileSync(
|
||||
path.resolve(OUTPUT_DIR, `${filename}.json`),
|
||||
JSON.stringify(output)
|
||||
);
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
path.resolve(OUTPUT_DIR, "iconMetadata.json"),
|
||||
JSON.stringify(manifest)
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
||||
@ -1,127 +0,0 @@
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const paths = require("../paths");
|
||||
const { mapFiles } = require("../util");
|
||||
|
||||
const ICON_PACKAGE_PATH = path.resolve(
|
||||
__dirname,
|
||||
"../../node_modules/@mdi/svg/"
|
||||
);
|
||||
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
|
||||
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
|
||||
const OUTPUT_DIR = path.resolve(__dirname, "../../build");
|
||||
const MDI_OUTPUT_PATH = path.resolve(OUTPUT_DIR, "mdi.html");
|
||||
const HASS_OUTPUT_PATH = path.resolve(OUTPUT_DIR, "hass-icons.html");
|
||||
|
||||
const BUILT_IN_PANEL_ICONS = [
|
||||
"calendar", // Calendar
|
||||
"settings", // Config
|
||||
"home-assistant", // Hass.io
|
||||
"poll-box", // History panel
|
||||
"format-list-bulleted-type", // Logbook
|
||||
"mailbox", // Mailbox
|
||||
"tooltip-account", // Map
|
||||
"cart", // Shopping List
|
||||
"hammer", // developer-tools
|
||||
];
|
||||
|
||||
// Given an icon name, load the SVG file
|
||||
function loadIcon(name) {
|
||||
const iconPath = path.resolve(ICON_PATH, `${name}.svg`);
|
||||
try {
|
||||
return fs.readFileSync(iconPath, "utf-8");
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Given an SVG file, convert it to an iron-iconset-svg definition
|
||||
function transformXMLtoPolymer(name, xml) {
|
||||
const start = xml.indexOf("><path") + 1;
|
||||
const end = xml.length - start - 6;
|
||||
const pth = xml.substr(start, end);
|
||||
return `<g id="${name}">${pth}</g>`;
|
||||
}
|
||||
|
||||
// Given an iconset name and icon names, generate a polymer iconset
|
||||
function generateIconset(iconsetName, iconNames) {
|
||||
const iconDefs = Array.from(iconNames)
|
||||
.map((name) => {
|
||||
const iconDef = loadIcon(name);
|
||||
if (!iconDef) {
|
||||
throw new Error(`Unknown icon referenced: ${name}`);
|
||||
}
|
||||
return transformXMLtoPolymer(name, iconDef);
|
||||
})
|
||||
.join("");
|
||||
return `<ha-iconset-svg name="${iconsetName}" size="24"><svg><defs>${iconDefs}</defs></svg></ha-iconset-svg>`;
|
||||
}
|
||||
|
||||
// Find all icons used by the project.
|
||||
function findIcons(searchPath, iconsetName) {
|
||||
const iconRegex = new RegExp(`${iconsetName}:[\\w-]+`, "g");
|
||||
const icons = new Set();
|
||||
function processFile(filename) {
|
||||
const content = fs.readFileSync(filename);
|
||||
let match;
|
||||
// eslint-disable-next-line
|
||||
while ((match = iconRegex.exec(content))) {
|
||||
// strip off "hass:" and add to set
|
||||
icons.add(match[0].substr(iconsetName.length + 1));
|
||||
}
|
||||
}
|
||||
mapFiles(searchPath, ".js", processFile);
|
||||
mapFiles(searchPath, ".ts", processFile);
|
||||
return icons;
|
||||
}
|
||||
|
||||
gulp.task("gen-icons-mdi", (done) => {
|
||||
const meta = JSON.parse(
|
||||
fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8")
|
||||
);
|
||||
const iconNames = meta.map((iconInfo) => iconInfo.name);
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR);
|
||||
}
|
||||
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames));
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-icons-app", (done) => {
|
||||
const iconNames = findIcons("./src", "hass");
|
||||
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));
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-icons-demo", (done) => {
|
||||
const iconNames = findIcons(path.resolve(paths.demo_dir, "./src"), "hademo");
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.demo_dir, "hademo-icons.html"),
|
||||
generateIconset("hademo", iconNames)
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-icons-hassio", (done) => {
|
||||
const iconNames = findIcons(
|
||||
path.resolve(paths.hassio_dir, "./src"),
|
||||
"hassio"
|
||||
);
|
||||
// Find hassio icons inside HA main repo.
|
||||
for (const item of findIcons(
|
||||
path.resolve(paths.polymer_dir, "./src"),
|
||||
"hassio"
|
||||
)) {
|
||||
iconNames.add(item);
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.hassio_dir, "hassio-icons.html"),
|
||||
generateIconset("hassio", iconNames)
|
||||
);
|
||||
done();
|
||||
});
|
||||
@ -3,7 +3,7 @@ const gulp = require("gulp");
|
||||
const envVars = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./webpack.js");
|
||||
require("./compress.js");
|
||||
|
||||
@ -14,7 +14,6 @@ gulp.task(
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean-hassio",
|
||||
gulp.parallel("gen-icons-hassio", "gen-icons-mdi"),
|
||||
"webpack-watch-hassio"
|
||||
)
|
||||
);
|
||||
@ -26,7 +25,6 @@ gulp.task(
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean-hassio",
|
||||
gulp.parallel("gen-icons-hassio", "gen-icons-mdi"),
|
||||
"webpack-prod-hassio",
|
||||
...// Don't compress running tests
|
||||
(envVars.isTravis() ? [] : ["compress-hassio"])
|
||||
|
||||
@ -28,7 +28,7 @@ const runDevServer = ({
|
||||
open: true,
|
||||
watchContentBase: true,
|
||||
contentBase,
|
||||
}).listen(port, listenHost, function(err) {
|
||||
}).listen(port, listenHost, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
@ -49,6 +49,9 @@ const createWebpackConfig = ({
|
||||
},
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
esprima: "esprima",
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
@ -137,7 +140,6 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
core: "./src/entrypoints/core.ts",
|
||||
compatibility: "./src/entrypoints/compatibility.ts",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.ts",
|
||||
"hass-icons": "./src/entrypoints/hass-icons.ts",
|
||||
},
|
||||
outputRoot: paths.root,
|
||||
isProdBuild,
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import "../../../src/components/ha-iconset-svg";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "../../../src/resources/hass-icons";
|
||||
import "../../../src/resources/roboto";
|
||||
import "./layout/hc-connect";
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/iron-icon";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||
@ -83,7 +82,7 @@ class HcCast extends LitElement {
|
||||
? html`
|
||||
<p class="center-item">
|
||||
<mwc-button raised @click=${this._handleLaunch}>
|
||||
<iron-icon icon="hass:cast"></iron-icon>
|
||||
<ha-icon icon="hass:cast"></ha-icon>
|
||||
Start Casting
|
||||
</mwc-button>
|
||||
</p>
|
||||
@ -121,7 +120,7 @@ class HcCast extends LitElement {
|
||||
${this.castManager.status
|
||||
? html`
|
||||
<mwc-button @click=${this._handleLaunch}>
|
||||
<iron-icon icon="hass:cast-connected"></iron-icon>
|
||||
<ha-icon icon="hass:cast-connected"></ha-icon>
|
||||
Manage
|
||||
</mwc-button>
|
||||
`
|
||||
@ -243,7 +242,7 @@ class HcCast extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
mwc-button iron-icon {
|
||||
mwc-button ha-icon {
|
||||
margin-right: 8px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/iron-icon";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
Auth,
|
||||
@ -27,6 +26,7 @@ import {
|
||||
loadTokens,
|
||||
saveTokens,
|
||||
} from "../../../../src/common/auth/token_storage";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
|
||||
import "./hc-layout";
|
||||
@ -136,11 +136,11 @@ export class HcConnect extends LitElement {
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._handleDemo}>
|
||||
Show Demo
|
||||
<iron-icon
|
||||
<ha-icon
|
||||
.icon=${this.castManager.castState === "CONNECTED"
|
||||
? "hass:cast-connected"
|
||||
: "hass:cast"}
|
||||
></iron-icon>
|
||||
></ha-icon>
|
||||
</mwc-button>
|
||||
<div class="spacer"></div>
|
||||
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
|
||||
@ -316,7 +316,7 @@ export class HcConnect extends LitElement {
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
mwc-button iron-icon {
|
||||
mwc-button ha-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../src/components/ha-iconset-svg";
|
||||
import "../../../src/resources/hass-icons";
|
||||
import "../../../src/resources/roboto";
|
||||
import "./layout/hc-lovelace";
|
||||
|
||||
@ -63,8 +63,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
elements: [
|
||||
{
|
||||
style: {
|
||||
"--iron-icon-width": "100px",
|
||||
"--iron-icon-height": "100px",
|
||||
"--mdc-icon-size": "100px",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
},
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import "@polymer/paper-styles/typography";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
import "../../src/components/ha-iconset-svg";
|
||||
import "../../src/resources/ha-style";
|
||||
import "../../src/resources/hass-icons";
|
||||
import "../../src/resources/roboto";
|
||||
import "./ha-demo";
|
||||
import "./resources/hademo-icons";
|
||||
|
||||
/* polyfill for paper-dropdown */
|
||||
setTimeout(() => {
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import "../../../src/components/ha-iconset-svg";
|
||||
import iconSetContent from "../../hademo-icons.html";
|
||||
|
||||
const documentContainer = document.createElement("template");
|
||||
documentContainer.setAttribute("style", "display: none;");
|
||||
documentContainer.innerHTML = iconSetContent;
|
||||
document.head.appendChild(documentContainer.content);
|
||||
@ -50,10 +50,9 @@ const CONFIGS = [
|
||||
top: 12%
|
||||
left: 6%
|
||||
transform: rotate(-60deg) scaleX(-1)
|
||||
--iron-icon-height: 30px
|
||||
--iron-icon-width: 30px
|
||||
--iron-icon-stroke-color: black
|
||||
--iron-icon-fill-color: rgba(50, 50, 50, .75)
|
||||
--mdc-icon-size: 30px
|
||||
--mdc-icon-stroke-color: black
|
||||
--mdc-icon-fill-color: rgba(50, 50, 50, .75)
|
||||
- type: image
|
||||
entity: light.bed_light
|
||||
tap_action:
|
||||
@ -99,10 +98,9 @@ const CONFIGS = [
|
||||
top: 12%
|
||||
left: 6%
|
||||
transform: rotate(-60deg) scaleX(-1)
|
||||
--iron-icon-height: 30px
|
||||
--iron-icon-width: 30px
|
||||
--iron-icon-stroke-color: black
|
||||
--iron-icon-fill-color: rgba(50, 50, 50, .75)
|
||||
--mdc-icon-size: 30px
|
||||
--mdc-icon-stroke-color: black
|
||||
--mdc-icon-fill-color: rgba(50, 50, 50, .75)
|
||||
- type: image
|
||||
entity: light.bed_light
|
||||
tap_action:
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import "@polymer/paper-styles/typography";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
import "../../src/components/ha-iconset-svg";
|
||||
import "../../src/resources/ha-style";
|
||||
import "../../src/resources/hass-icons";
|
||||
import "../../src/resources/roboto";
|
||||
import "./ha-gallery";
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
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/iron-icon/iron-icon";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/components/ha-icon";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
@ -28,7 +28,7 @@ class HaGallery extends PolymerElement {
|
||||
app-header-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
paper-icon-button.invisible {
|
||||
ha-icon-button.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@ -67,11 +67,11 @@ class HaGallery extends PolymerElement {
|
||||
<app-header-layout>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:arrow-left"
|
||||
on-click="_backTapped"
|
||||
class$='[[_computeHeaderButtonClass(_demo)]]'
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<div main-title>[[_withDefault(_demo, "Home Assistant Gallery")]]</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -98,7 +98,7 @@ class HaGallery extends PolymerElement {
|
||||
<a href='#[[item]]'>
|
||||
<paper-item>
|
||||
<paper-item-body>{{ item }}</paper-item-body>
|
||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||
<ha-icon icon="hass:chevron-right"></ha-icon>
|
||||
</paper-item>
|
||||
</a>
|
||||
</template>
|
||||
@ -114,7 +114,7 @@ class HaGallery extends PolymerElement {
|
||||
<a href='#[[item]]'>
|
||||
<paper-item>
|
||||
<paper-item-body>{{ item }}</paper-item-body>
|
||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||
<ha-icon icon="hass:chevron-right"></ha-icon>
|
||||
</paper-item>
|
||||
</a>
|
||||
</template>
|
||||
@ -130,7 +130,7 @@ class HaGallery extends PolymerElement {
|
||||
<a href='#[[item]]'>
|
||||
<paper-item>
|
||||
<paper-item-body>{{ item }}</paper-item-body>
|
||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||
<ha-icon icon="hass:chevron-right"></ha-icon>
|
||||
</paper-item>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
1
hassio/.gitignore
vendored
@ -1 +0,0 @@
|
||||
hassio-icons.html
|
||||
@ -13,11 +13,14 @@ import {
|
||||
reloadHassioAddons,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import "../../../src/layouts/loading-screen";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import "../components/hassio-search-input";
|
||||
import "./hassio-addon-repository";
|
||||
import "./hassio-repositories-editor";
|
||||
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
|
||||
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||
if (a.slug === "local") {
|
||||
return -1;
|
||||
@ -35,11 +38,15 @@ const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||
};
|
||||
|
||||
class HassioAddonStore extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() private _addons?: HassioAddonInfo[];
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property() private _repos?: HassioAddonRepository[];
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) private _addons?: HassioAddonInfo[];
|
||||
|
||||
@property({ attribute: false }) private _repos?: HassioAddonRepository[];
|
||||
|
||||
@property() private _filter?: string;
|
||||
|
||||
@ -52,42 +59,62 @@ class HassioAddonStore extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
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 (this._repos) {
|
||||
for (const repo of this._repos) {
|
||||
const addons = this._addons!.filter(
|
||||
(addon) => addon.repository === repo.slug
|
||||
);
|
||||
|
||||
if (addons.length === 0) {
|
||||
continue;
|
||||
if (addons.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
repos.push(html`
|
||||
<hassio-addon-repository
|
||||
.hass=${this.hass}
|
||||
.repo=${repo}
|
||||
.addons=${addons}
|
||||
.filter=${this._filter}
|
||||
></hassio-addon-repository>
|
||||
`);
|
||||
}
|
||||
|
||||
repos.push(html`
|
||||
<hassio-addon-repository
|
||||
.hass=${this.hass}
|
||||
.repo=${repo}
|
||||
.addons=${addons}
|
||||
.filter=${this._filter}
|
||||
></hassio-addon-repository>
|
||||
`);
|
||||
}
|
||||
|
||||
return html`
|
||||
<hassio-repositories-editor
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.repos=${this._repos}
|
||||
></hassio-repositories-editor>
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
hassio
|
||||
main-page
|
||||
.tabs=${supervisorTabs}
|
||||
>
|
||||
<span slot="header">Add-on store</span>
|
||||
<ha-icon-button
|
||||
icon="hassio:reload"
|
||||
slot="toolbar-icon"
|
||||
aria-label="Reload add-ons"
|
||||
@click=${this.refreshData}
|
||||
></ha-icon-button>
|
||||
|
||||
<hassio-search-input
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._filterChanged}
|
||||
></hassio-search-input>
|
||||
${repos.length === 0
|
||||
? html`<loading-screen></loading-screen>`
|
||||
: html`
|
||||
<hassio-repositories-editor
|
||||
.hass=${this.hass}
|
||||
.repos=${this._repos!}
|
||||
></hassio-repositories-editor>
|
||||
|
||||
${repos}
|
||||
<hassio-search-input
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._filterChanged}
|
||||
></hassio-search-input>
|
||||
|
||||
${repos}
|
||||
`}
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
@ -19,6 +18,7 @@ import { PolymerChangedEvent } from "../../../src/polymer-types";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../components/hassio-card-content";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "../../../src/components/ha-icon";
|
||||
|
||||
@customElement("hassio-repositories-editor")
|
||||
class HassioRepositoriesEditor extends LitElement {
|
||||
@ -76,7 +76,7 @@ class HassioRepositoriesEditor extends LitElement {
|
||||
|
||||
<paper-card>
|
||||
<div class="card-content add">
|
||||
<iron-icon icon="hassio:github-circle"></iron-icon>
|
||||
<ha-icon icon="hassio:github-circle"></ha-icon>
|
||||
<paper-input
|
||||
label="Add new repository by URL"
|
||||
.value=${this._repoUrl}
|
||||
@ -130,7 +130,7 @@ class HassioRepositoriesEditor extends LitElement {
|
||||
.add {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
iron-icon {
|
||||
ha-icon {
|
||||
color: var(--secondary-text-color);
|
||||
margin-right: 16px;
|
||||
display: inline-block;
|
||||
|
||||
@ -18,20 +18,21 @@ import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import {
|
||||
fetchHassioHardwareAudio,
|
||||
HassioHardwareAudioDevice,
|
||||
} from "../../../src/data/hassio/hardware";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
} from "../../../../src/data/hassio/hardware";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-addon-audio")
|
||||
class HassioAddonAudio extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public addon!: HassioAddonDetails;
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@ -56,7 +57,7 @@ class HassioAddonAudio extends LitElement {
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
.selected=${this._selectedInput}
|
||||
.selected=${this._selectedInput!}
|
||||
>
|
||||
${this._inputDevices &&
|
||||
this._inputDevices.map((item) => {
|
||||
@ -75,7 +76,7 @@ class HassioAddonAudio extends LitElement {
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
.selected=${this._selectedOutput}
|
||||
.selected=${this._selectedOutput!}
|
||||
>
|
||||
${this._outputDevices &&
|
||||
this._outputDevices.map((item) => {
|
||||
@ -183,6 +184,9 @@ class HassioAddonAudio extends LitElement {
|
||||
} catch {
|
||||
this._error = "Failed to set addon audio device";
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
hassio/src/addon-view/config/hassio-addon-config-tab.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
||||
import "./hassio-addon-audio";
|
||||
import "./hassio-addon-config";
|
||||
import "./hassio-addon-network";
|
||||
|
||||
@customElement("hassio-addon-config-tab")
|
||||
class HassioAddonConfigDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
<hassio-addon-config
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-config>
|
||||
${this.addon.network
|
||||
? html`
|
||||
<hassio-addon-network
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-network>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.audio
|
||||
? html`
|
||||
<hassio-addon-audio
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-audio>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 8px;
|
||||
max-width: 1024px;
|
||||
}
|
||||
hassio-addon-network,
|
||||
hassio-addon-audio,
|
||||
hassio-addon-config {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-addon-config-tab": HassioAddonConfigDashboard;
|
||||
}
|
||||
}
|
||||
@ -12,24 +12,26 @@ import {
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../src/components/ha-yaml-editor";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
|
||||
@customElement("hassio-addon-config")
|
||||
class HassioAddonConfig extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public addon!: HassioAddonDetails;
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@ -43,7 +45,8 @@ class HassioAddonConfig extends LitElement {
|
||||
const valid = editor ? editor.isValid : true;
|
||||
|
||||
return html`
|
||||
<paper-card heading="Config">
|
||||
<h1>${this.addon.name}</h1>
|
||||
<paper-card heading="Configuration">
|
||||
<div class="card-content">
|
||||
<ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
@ -113,6 +116,7 @@ class HassioAddonConfig extends LitElement {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to reset all your options?",
|
||||
confirmText: "reset options",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
@ -164,6 +168,9 @@ class HassioAddonConfig extends LitElement {
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,15 +10,17 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
interface NetworkItem {
|
||||
description: string;
|
||||
@ -32,9 +34,9 @@ interface NetworkItemInput extends PaperInputElement {
|
||||
|
||||
@customElement("hassio-addon-network")
|
||||
class HassioAddonNetwork extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public addon!: HassioAddonDetails;
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@ -70,7 +72,7 @@ class HassioAddonNetwork extends LitElement {
|
||||
<paper-input
|
||||
@value-changed=${this._configChanged}
|
||||
placeholder="disabled"
|
||||
.value=${item.host}
|
||||
.value=${String(item.host)}
|
||||
.container=${item.container}
|
||||
no-label-float
|
||||
></paper-input>
|
||||
@ -165,6 +167,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
}
|
||||
|
||||
private async _saveTapped(): Promise<void> {
|
||||
@ -191,6 +196,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
fetchHassioAddonDocumentation,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
||||
@customElement("hassio-addon-documentation-tab")
|
||||
class HassioAddonDocumentationDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@property() private _content?: string;
|
||||
|
||||
public async connectedCallback(): Promise<void> {
|
||||
super.connectedCallback();
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
<paper-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<ha-markdown .content=${this._content}></ha-markdown>`
|
||||
: html`<loading-screen></loading-screen>`}
|
||||
</div>
|
||||
</paper-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 8px;
|
||||
max-width: 1024px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._error = undefined;
|
||||
try {
|
||||
this._content = await fetchHassioAddonDocumentation(
|
||||
this.hass,
|
||||
this.addon!.slug
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon documentation, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-addon-documentation-tab": HassioAddonDocumentationDashboard;
|
||||
}
|
||||
}
|
||||
185
hassio/src/addon-view/hassio-addon-dashboard.ts
Normal file
@ -0,0 +1,185 @@
|
||||
import "../../../src/components/ha-icon-button";
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./config/hassio-addon-audio";
|
||||
import "./config/hassio-addon-config";
|
||||
import "./info/hassio-addon-info";
|
||||
import "./log/hassio-addon-logs";
|
||||
import "./config/hassio-addon-network";
|
||||
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
|
||||
import "./hassio-addon-router";
|
||||
|
||||
@customElement("hassio-addon-dashboard")
|
||||
class HassioAddonDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
private _computeTail = memoizeOne((route: Route) => {
|
||||
const dividerPos = route.path.indexOf("/", 1);
|
||||
return dividerPos === -1
|
||||
? {
|
||||
prefix: route.prefix + route.path,
|
||||
path: "",
|
||||
}
|
||||
: {
|
||||
prefix: route.prefix + route.path.substr(0, dividerPos),
|
||||
path: route.path.substr(dividerPos),
|
||||
};
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
|
||||
const addonTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Info",
|
||||
path: `/hassio/addon/${this.addon.slug}/info`,
|
||||
icon: "hassio:information-variant",
|
||||
},
|
||||
];
|
||||
|
||||
if (this.addon.documentation) {
|
||||
addonTabs.push({
|
||||
name: "Documentation",
|
||||
path: `/hassio/addon/${this.addon.slug}/documentation`,
|
||||
icon: "hassio:file-document",
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.version) {
|
||||
addonTabs.push(
|
||||
{
|
||||
name: "Configuration",
|
||||
path: `/hassio/addon/${this.addon.slug}/config`,
|
||||
icon: "hassio:cogs",
|
||||
},
|
||||
{
|
||||
name: "Log",
|
||||
path: `/hassio/addon/${this.addon.slug}/logs`,
|
||||
icon: "hassio:math-log",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const route = this._computeTail(this.route);
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.backPath=${this.addon.version ? "/hassio/dashboard" : "/hassio/store"}
|
||||
.route=${route}
|
||||
hassio
|
||||
.tabs=${addonTabs}
|
||||
>
|
||||
<span slot="header">${this.addon.name}</span>
|
||||
<hassio-addon-router
|
||||
.route=${route}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-router>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
color: var(--primary-text-color);
|
||||
--paper-card-header-color: var(--primary-text-color);
|
||||
}
|
||||
.content {
|
||||
padding: 24px 0 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
hassio-addon-info,
|
||||
hassio-addon-network,
|
||||
hassio-addon-audio,
|
||||
hassio-addon-config {
|
||||
margin-bottom: 24px;
|
||||
width: 600px;
|
||||
}
|
||||
hassio-addon-logs {
|
||||
max-width: calc(100% - 8px);
|
||||
min-width: 600px;
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
hassio-addon-info,
|
||||
hassio-addon-network,
|
||||
hassio-addon-audio,
|
||||
hassio-addon-config,
|
||||
hassio-addon-logs {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected async firstUpdated(): Promise<void> {
|
||||
await this._routeDataChanged(this.route);
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
}
|
||||
|
||||
private async _apiCalled(ev): Promise<void> {
|
||||
const path: string = ev.detail.path;
|
||||
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "uninstall") {
|
||||
history.back();
|
||||
} else {
|
||||
await this._routeDataChanged(this.route);
|
||||
}
|
||||
}
|
||||
|
||||
private async _routeDataChanged(routeData: Route): Promise<void> {
|
||||
const addon = routeData.path.split("/")[1];
|
||||
try {
|
||||
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
|
||||
this.addon = addoninfo;
|
||||
} catch {
|
||||
this.addon = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-addon-dashboard": HassioAddonDashboard;
|
||||
}
|
||||
}
|
||||
53
hassio/src/addon-view/hassio-addon-router.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../../src/layouts/hass-router-page";
|
||||
import { customElement, property } from "lit-element";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
// Don't codesplit the others, because it breaks the UI when pushed to a Pi
|
||||
import "./info/hassio-addon-info-tab";
|
||||
import "./config/hassio-addon-config-tab";
|
||||
import "./log/hassio-addon-log-tab";
|
||||
import "./documentation/hassio-addon-documentation-tab";
|
||||
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
|
||||
|
||||
@customElement("hassio-addon-router")
|
||||
class HassioAddonRouter extends HassRouterPage {
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "info",
|
||||
showLoading: true,
|
||||
routes: {
|
||||
info: {
|
||||
tag: "hassio-addon-info-tab",
|
||||
},
|
||||
documentation: {
|
||||
tag: "hassio-addon-documentation-tab",
|
||||
},
|
||||
config: {
|
||||
tag: "hassio-addon-config-tab",
|
||||
},
|
||||
logs: {
|
||||
tag: "hassio-addon-log-tab",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
protected updatePageEl(el) {
|
||||
el.route = this.routeTail;
|
||||
el.hass = this.hass;
|
||||
el.addon = this.addon;
|
||||
el.narrow = this.narrow;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-addon-router": HassioAddonRouter;
|
||||
}
|
||||
}
|
||||
@ -1,158 +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-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import {
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./hassio-addon-audio";
|
||||
import "./hassio-addon-config";
|
||||
import "./hassio-addon-info";
|
||||
import "./hassio-addon-logs";
|
||||
import "./hassio-addon-network";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
|
||||
@customElement("hassio-addon-view")
|
||||
class HassioAddonView extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() public addon?: HassioAddonDetails;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
return html`
|
||||
<hass-subpage header="Hass.io: add-on details" hassio>
|
||||
<div class="content">
|
||||
<hassio-addon-info
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-info>
|
||||
|
||||
${this.addon && this.addon.version
|
||||
? html`
|
||||
<hassio-addon-config
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-config>
|
||||
|
||||
${this.addon.audio
|
||||
? html`
|
||||
<hassio-addon-audio
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-audio>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.network
|
||||
? html`
|
||||
<hassio-addon-network
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-network>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<hassio-addon-logs
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-logs>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
color: var(--primary-text-color);
|
||||
--paper-card-header-color: var(--primary-text-color);
|
||||
}
|
||||
.content {
|
||||
padding: 24px 0 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
hassio-addon-info,
|
||||
hassio-addon-network,
|
||||
hassio-addon-audio,
|
||||
hassio-addon-config {
|
||||
margin-bottom: 24px;
|
||||
width: 600px;
|
||||
}
|
||||
hassio-addon-logs {
|
||||
max-width: calc(100% - 8px);
|
||||
min-width: 600px;
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
hassio-addon-info,
|
||||
hassio-addon-network,
|
||||
hassio-addon-audio,
|
||||
hassio-addon-config,
|
||||
hassio-addon-logs {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected async firstUpdated(): Promise<void> {
|
||||
await this._routeDataChanged(this.route);
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
}
|
||||
|
||||
private async _apiCalled(ev): Promise<void> {
|
||||
const path: string = ev.detail.path;
|
||||
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === "uninstall") {
|
||||
history.back();
|
||||
} else {
|
||||
await this._routeDataChanged(this.route);
|
||||
}
|
||||
}
|
||||
|
||||
private async _routeDataChanged(routeData: Route): Promise<void> {
|
||||
const addon = routeData.path.substr(1);
|
||||
try {
|
||||
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
|
||||
this.addon = addoninfo;
|
||||
} catch {
|
||||
this.addon = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-addon-view": HassioAddonView;
|
||||
}
|
||||
}
|
||||
62
hassio/src/addon-view/info/hassio-addon-info-tab.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
||||
import "./hassio-addon-info";
|
||||
|
||||
@customElement("hassio-addon-info-tab")
|
||||
class HassioAddonInfoDashboard extends LitElement {
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<hassio-addon-info
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-info>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 8px;
|
||||
max-width: 1024px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-addon-info-tab": HassioAddonInfoDashboard;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
@ -12,14 +11,15 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-label-badge";
|
||||
import "../../../src/components/ha-markdown";
|
||||
import "../../../src/components/ha-switch";
|
||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../../src/common/navigate";
|
||||
import "../../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-label-badge";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import {
|
||||
fetchHassioAddonChangelog,
|
||||
HassioAddonDetails,
|
||||
@ -29,18 +29,29 @@ import {
|
||||
setHassioAddonOption,
|
||||
setHassioAddonSecurity,
|
||||
uninstallHassioAddon,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../components/hassio-card-content";
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-card-content";
|
||||
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
|
||||
const STAGE_ICON = {
|
||||
stable: "mdi:check-circle",
|
||||
experimental: "mdi:flask",
|
||||
deprecated: "mdi:exclamation-thick",
|
||||
};
|
||||
|
||||
const PERMIS_DESC = {
|
||||
stage: {
|
||||
title: "Add-on Stage",
|
||||
description: `Add-ons can have one of three stages:\n\n<ha-icon icon='${STAGE_ICON.stable}'></ha-icon>**Stable**: These are add-ons ready to be used in production.\n<ha-icon icon='${STAGE_ICON.experimental}'></ha-icon>**Experimental**: These may contain bugs, and may be unfinished.\n<ha-icon icon='${STAGE_ICON.deprecated}'></ha-icon>**Deprecated**: These add-ons will no longer receive any updates.`,
|
||||
},
|
||||
rating: {
|
||||
title: "Add-on Security Rating",
|
||||
description:
|
||||
"Hass.io provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk).",
|
||||
"Home Assistant provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk).",
|
||||
},
|
||||
host_network: {
|
||||
title: "Host Network",
|
||||
@ -58,19 +69,19 @@ const PERMIS_DESC = {
|
||||
"This add-on is given full access to the hardware of your system, by request of the add-on author. Access is comparable to the privileged mode in Docker. Since this opens up possible security risks, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
|
||||
},
|
||||
hassio_api: {
|
||||
title: "Hass.io API Access",
|
||||
title: "Supervisor API Access",
|
||||
description:
|
||||
"The add-on was given access to the Hass.io API, by request of the add-on author. By default, the add-on can access general version information of your system. When the add-on requests 'manager' or 'admin' level access to the API, it will gain access to control multiple parts of your Hass.io system. This permission is indicated by this badge and will impact the security score of the addon negatively.",
|
||||
"The add-on was given access to the Supervisor API, by request of the add-on author. By default, the add-on can access general version information of your system. When the add-on requests 'manager' or 'admin' level access to the API, it will gain access to control multiple parts of your Home Assistant system. This permission is indicated by this badge and will impact the security score of the addon negatively.",
|
||||
},
|
||||
docker_api: {
|
||||
title: "Full Docker Access",
|
||||
description:
|
||||
"The add-on author has requested the add-on to have management access to the Docker instance running on your system. This mode gives the add-on full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
|
||||
"The add-on author has requested the add-on to have management access to the Docker instance running on your system. This mode gives the add-on full access and control to your entire Home Assistant system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
|
||||
},
|
||||
host_pid: {
|
||||
title: "Host Processes Namespace",
|
||||
description:
|
||||
"Usually, the processes the add-on runs, are isolated from all other system processes. The add-on author has requested the add-on to have access to the system processes running on the host system instance, and allow the add-on to spawn processes on the host system as well. This mode gives the add-on full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
|
||||
"Usually, the processes the add-on runs, are isolated from all other system processes. The add-on author has requested the add-on to have access to the system processes running on the host system instance, and allow the add-on to spawn processes on the host system as well. This mode gives the add-on full access and control to your entire Home Assistant system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
|
||||
},
|
||||
apparmor: {
|
||||
title: "AppArmor",
|
||||
@ -91,9 +102,11 @@ const PERMIS_DESC = {
|
||||
|
||||
@customElement("hassio-addon-info")
|
||||
class HassioAddonInfo extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property() public addon!: HassioAddonDetails;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@ -158,25 +171,25 @@ class HassioAddonInfo extends LitElement {
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<div class="addon-header">
|
||||
${this.addon.name}
|
||||
${!this.narrow ? this.addon.name : ""}
|
||||
<div class="addon-version light-color">
|
||||
${this.addon.version
|
||||
? html`
|
||||
${this.addon.version}
|
||||
${this._computeIsRunning
|
||||
? html`
|
||||
<iron-icon
|
||||
<ha-icon
|
||||
title="Add-on is running"
|
||||
class="running"
|
||||
icon="hassio:circle"
|
||||
></iron-icon>
|
||||
></ha-icon>
|
||||
`
|
||||
: html`
|
||||
<iron-icon
|
||||
<ha-icon
|
||||
title="Add-on is stopped"
|
||||
class="stopped"
|
||||
icon="hassio:circle"
|
||||
></iron-icon>
|
||||
></ha-icon>
|
||||
`}
|
||||
`
|
||||
: html` ${this.addon.version_latest} `}
|
||||
@ -185,7 +198,7 @@ class HassioAddonInfo extends LitElement {
|
||||
<div class="description light-color">
|
||||
${this.addon.description}.<br />
|
||||
Visit
|
||||
<a href="${this.addon.url}" target="_blank" rel="noreferrer">
|
||||
<a href="${this.addon.url!}" target="_blank" rel="noreferrer">
|
||||
${this.addon.name} page</a
|
||||
>
|
||||
for details.
|
||||
@ -193,7 +206,7 @@ class HassioAddonInfo extends LitElement {
|
||||
${this.addon.logo
|
||||
? html`
|
||||
<a
|
||||
href="${this.addon.url}"
|
||||
href="${this.addon.url!}"
|
||||
target="_blank"
|
||||
class="logo"
|
||||
rel="noreferrer"
|
||||
@ -203,6 +216,18 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<div class="security">
|
||||
<ha-label-badge
|
||||
class=${classMap({
|
||||
green: this.addon.stage === "stable",
|
||||
yellow: this.addon.stage === "experimental",
|
||||
red: this.addon.stage === "deprecated",
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="stage"
|
||||
.icon=${STAGE_ICON[this.addon.stage]}
|
||||
label="stage"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
<ha-label-badge
|
||||
class=${classMap({
|
||||
green: [5, 6].includes(Number(this.addon.rating)),
|
||||
@ -362,7 +387,7 @@ class HassioAddonInfo extends LitElement {
|
||||
<div>
|
||||
Protection mode
|
||||
<span>
|
||||
<iron-icon icon="hassio:information"></iron-icon>
|
||||
<ha-icon icon="hassio:information"></ha-icon>
|
||||
<paper-tooltip>
|
||||
Grant the add-on elevated system access.
|
||||
</paper-tooltip>
|
||||
@ -383,29 +408,8 @@ class HassioAddonInfo extends LitElement {
|
||||
<div class="card-actions">
|
||||
${this.addon.version
|
||||
? html`
|
||||
<mwc-button class="warning" @click=${this._uninstallClicked}>
|
||||
Uninstall
|
||||
</mwc-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
||||
>
|
||||
Rebuild
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: ""}
|
||||
${this._computeIsRunning
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/restart"
|
||||
>
|
||||
Restart
|
||||
</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
@ -413,6 +417,13 @@ class HassioAddonInfo extends LitElement {
|
||||
>
|
||||
Stop
|
||||
</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/restart"
|
||||
>
|
||||
Restart
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: html`
|
||||
<ha-call-api-button
|
||||
@ -425,7 +436,7 @@ class HassioAddonInfo extends LitElement {
|
||||
${this._computeShowWebUI
|
||||
? html`
|
||||
<a
|
||||
.href=${this._pathWebui}
|
||||
href=${this._pathWebui!}
|
||||
tabindex="-1"
|
||||
target="_blank"
|
||||
class="right"
|
||||
@ -444,6 +455,23 @@ class HassioAddonInfo extends LitElement {
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<mwc-button
|
||||
class=" right warning"
|
||||
@click=${this._uninstallClicked}
|
||||
>
|
||||
Uninstall
|
||||
</mwc-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning right"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
||||
>
|
||||
Rebuild
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: html`
|
||||
${!this.addon.available
|
||||
@ -534,7 +562,7 @@ class HassioAddonInfo extends LitElement {
|
||||
width: 180px;
|
||||
display: inline-block;
|
||||
}
|
||||
.state iron-icon {
|
||||
.state ha-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
@ -542,10 +570,10 @@ class HassioAddonInfo extends LitElement {
|
||||
ha-switch {
|
||||
display: flex;
|
||||
}
|
||||
iron-icon.running {
|
||||
ha-icon.running {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
iron-icon.stopped {
|
||||
ha-icon.stopped {
|
||||
color: var(--google-red-300);
|
||||
}
|
||||
ha-call-api-button {
|
||||
@ -590,7 +618,8 @@ class HassioAddonInfo extends LitElement {
|
||||
.security ha-label-badge {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
--iron-icon-height: 45px;
|
||||
--mdc-icon-size: 45px;
|
||||
--ha-label-badge-padding: 8px 0 0 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
@ -776,9 +805,17 @@ class HassioAddonInfo extends LitElement {
|
||||
}
|
||||
|
||||
private async _uninstallClicked(): Promise<void> {
|
||||
if (!confirm("Are you sure you want to uninstall this add-on?")) {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to uninstall this add-on?",
|
||||
confirmText: "uninstall add-on",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._error = undefined;
|
||||
try {
|
||||
await uninstallHassioAddon(this.hass, this.addon.slug);
|
||||
59
hassio/src/addon-view/log/hassio-addon-log-tab.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
||||
import "./hassio-addon-logs";
|
||||
|
||||
@customElement("hassio-addon-log-tab")
|
||||
class HassioAddonLogDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
<hassio-addon-logs
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-logs>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 8px;
|
||||
max-width: 1024px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-addon-log-tab": HassioAddonLogDashboard;
|
||||
}
|
||||
}
|
||||
@ -7,27 +7,26 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import {
|
||||
fetchHassioAddonLogs,
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-addon-logs")
|
||||
class HassioAddonLogs extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public addon!: HassioAddonDetails;
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@query("#content") private _logContent!: any;
|
||||
@property() private _content?: string;
|
||||
|
||||
public async connectedCallback(): Promise<void> {
|
||||
super.connectedCallback();
|
||||
@ -36,9 +35,16 @@ class HassioAddonLogs extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-card heading="Log">
|
||||
<h1>${this.addon.name}</h1>
|
||||
<paper-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
<div class="card-content" id="content"></div>
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<hassio-ansi-to-html
|
||||
.content=${this._content}
|
||||
></hassio-ansi-to-html>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
|
||||
</div>
|
||||
@ -50,17 +56,11 @@ class HassioAddonLogs extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
ANSI_HTML_STYLE,
|
||||
css`
|
||||
:host,
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-bottom: 16px;
|
||||
@ -72,11 +72,7 @@ class HassioAddonLogs extends LitElement {
|
||||
private async _loadData(): Promise<void> {
|
||||
this._error = undefined;
|
||||
try {
|
||||
const content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
|
||||
while (this._logContent.lastChild) {
|
||||
this._logContent.removeChild(this._logContent.lastChild as Node);
|
||||
}
|
||||
this._logContent.appendChild(parseTextToColoredPre(content));
|
||||
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon logs, ${err.body?.message || err}`;
|
||||
}
|
||||
@ -1,223 +0,0 @@
|
||||
import { css } from "lit-element";
|
||||
|
||||
interface State {
|
||||
bold: boolean;
|
||||
italic: boolean;
|
||||
underline: boolean;
|
||||
strikethrough: boolean;
|
||||
foregroundColor: null | string;
|
||||
backgroundColor: null | string;
|
||||
}
|
||||
|
||||
export const ANSI_HTML_STYLE = css`
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.underline.strikethrough {
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
.fg-red {
|
||||
color: rgb(222, 56, 43);
|
||||
}
|
||||
.fg-green {
|
||||
color: rgb(57, 181, 74);
|
||||
}
|
||||
.fg-yellow {
|
||||
color: rgb(255, 199, 6);
|
||||
}
|
||||
.fg-blue {
|
||||
color: rgb(0, 111, 184);
|
||||
}
|
||||
.fg-magenta {
|
||||
color: rgb(118, 38, 113);
|
||||
}
|
||||
.fg-cyan {
|
||||
color: rgb(44, 181, 233);
|
||||
}
|
||||
.fg-white {
|
||||
color: rgb(204, 204, 204);
|
||||
}
|
||||
.bg-black {
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
.bg-red {
|
||||
background-color: rgb(222, 56, 43);
|
||||
}
|
||||
.bg-green {
|
||||
background-color: rgb(57, 181, 74);
|
||||
}
|
||||
.bg-yellow {
|
||||
background-color: rgb(255, 199, 6);
|
||||
}
|
||||
.bg-blue {
|
||||
background-color: rgb(0, 111, 184);
|
||||
}
|
||||
.bg-magenta {
|
||||
background-color: rgb(118, 38, 113);
|
||||
}
|
||||
.bg-cyan {
|
||||
background-color: rgb(44, 181, 233);
|
||||
}
|
||||
.bg-white {
|
||||
background-color: rgb(204, 204, 204);
|
||||
}
|
||||
`;
|
||||
|
||||
export function parseTextToColoredPre(text) {
|
||||
const pre = document.createElement("pre");
|
||||
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
|
||||
let i = 0;
|
||||
|
||||
const state: State = {
|
||||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
strikethrough: false,
|
||||
foregroundColor: null,
|
||||
backgroundColor: null,
|
||||
};
|
||||
|
||||
const addSpan = (content) => {
|
||||
const span = document.createElement("span");
|
||||
if (state.bold) {
|
||||
span.classList.add("bold");
|
||||
}
|
||||
if (state.italic) {
|
||||
span.classList.add("italic");
|
||||
}
|
||||
if (state.underline) {
|
||||
span.classList.add("underline");
|
||||
}
|
||||
if (state.strikethrough) {
|
||||
span.classList.add("strikethrough");
|
||||
}
|
||||
if (state.foregroundColor !== null) {
|
||||
span.classList.add(`fg-${state.foregroundColor}`);
|
||||
}
|
||||
if (state.backgroundColor !== null) {
|
||||
span.classList.add(`bg-${state.backgroundColor}`);
|
||||
}
|
||||
span.appendChild(document.createTextNode(content));
|
||||
pre.appendChild(span);
|
||||
};
|
||||
|
||||
/* eslint-disable no-cond-assign */
|
||||
let match;
|
||||
// eslint-disable-next-line
|
||||
while ((match = re.exec(text)) !== null) {
|
||||
const j = match!.index;
|
||||
addSpan(text.substring(i, j));
|
||||
i = j + match[0].length;
|
||||
|
||||
if (match[1] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match[1].split(";").forEach((colorCode: string) => {
|
||||
switch (parseInt(colorCode, 10)) {
|
||||
case 0:
|
||||
// reset
|
||||
state.bold = false;
|
||||
state.italic = false;
|
||||
state.underline = false;
|
||||
state.strikethrough = false;
|
||||
state.foregroundColor = null;
|
||||
state.backgroundColor = null;
|
||||
break;
|
||||
case 1:
|
||||
state.bold = true;
|
||||
break;
|
||||
case 3:
|
||||
state.italic = true;
|
||||
break;
|
||||
case 4:
|
||||
state.underline = true;
|
||||
break;
|
||||
case 9:
|
||||
state.strikethrough = true;
|
||||
break;
|
||||
case 22:
|
||||
state.bold = false;
|
||||
break;
|
||||
case 23:
|
||||
state.italic = false;
|
||||
break;
|
||||
case 24:
|
||||
state.underline = false;
|
||||
break;
|
||||
case 29:
|
||||
state.strikethrough = false;
|
||||
break;
|
||||
case 30:
|
||||
// foreground black
|
||||
state.foregroundColor = null;
|
||||
break;
|
||||
case 31:
|
||||
state.foregroundColor = "red";
|
||||
break;
|
||||
case 32:
|
||||
state.foregroundColor = "green";
|
||||
break;
|
||||
case 33:
|
||||
state.foregroundColor = "yellow";
|
||||
break;
|
||||
case 34:
|
||||
state.foregroundColor = "blue";
|
||||
break;
|
||||
case 35:
|
||||
state.foregroundColor = "magenta";
|
||||
break;
|
||||
case 36:
|
||||
state.foregroundColor = "cyan";
|
||||
break;
|
||||
case 37:
|
||||
state.foregroundColor = "white";
|
||||
break;
|
||||
case 39:
|
||||
// foreground reset
|
||||
state.foregroundColor = null;
|
||||
break;
|
||||
case 40:
|
||||
state.backgroundColor = "black";
|
||||
break;
|
||||
case 41:
|
||||
state.backgroundColor = "red";
|
||||
break;
|
||||
case 42:
|
||||
state.backgroundColor = "green";
|
||||
break;
|
||||
case 43:
|
||||
state.backgroundColor = "yellow";
|
||||
break;
|
||||
case 44:
|
||||
state.backgroundColor = "blue";
|
||||
break;
|
||||
case 45:
|
||||
state.backgroundColor = "magenta";
|
||||
break;
|
||||
case 46:
|
||||
state.backgroundColor = "cyan";
|
||||
break;
|
||||
case 47:
|
||||
state.backgroundColor = "white";
|
||||
break;
|
||||
case 49:
|
||||
// background reset
|
||||
state.backgroundColor = null;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
addSpan(text.substring(i));
|
||||
|
||||
return pre;
|
||||
}
|
||||
253
hassio/src/components/hassio-ansi-to-html.ts
Normal file
@ -0,0 +1,253 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
interface State {
|
||||
bold: boolean;
|
||||
italic: boolean;
|
||||
underline: boolean;
|
||||
strikethrough: boolean;
|
||||
foregroundColor: null | string;
|
||||
backgroundColor: null | string;
|
||||
}
|
||||
|
||||
@customElement("hassio-ansi-to-html")
|
||||
class HassioAnsiToHtml extends LitElement {
|
||||
@property() public content!: string;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
return html`${this._parseTextToColoredPre(this.content)}`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.underline.strikethrough {
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
.fg-red {
|
||||
color: rgb(222, 56, 43);
|
||||
}
|
||||
.fg-green {
|
||||
color: rgb(57, 181, 74);
|
||||
}
|
||||
.fg-yellow {
|
||||
color: rgb(255, 199, 6);
|
||||
}
|
||||
.fg-blue {
|
||||
color: rgb(0, 111, 184);
|
||||
}
|
||||
.fg-magenta {
|
||||
color: rgb(118, 38, 113);
|
||||
}
|
||||
.fg-cyan {
|
||||
color: rgb(44, 181, 233);
|
||||
}
|
||||
.fg-white {
|
||||
color: rgb(204, 204, 204);
|
||||
}
|
||||
.bg-black {
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
.bg-red {
|
||||
background-color: rgb(222, 56, 43);
|
||||
}
|
||||
.bg-green {
|
||||
background-color: rgb(57, 181, 74);
|
||||
}
|
||||
.bg-yellow {
|
||||
background-color: rgb(255, 199, 6);
|
||||
}
|
||||
.bg-blue {
|
||||
background-color: rgb(0, 111, 184);
|
||||
}
|
||||
.bg-magenta {
|
||||
background-color: rgb(118, 38, 113);
|
||||
}
|
||||
.bg-cyan {
|
||||
background-color: rgb(44, 181, 233);
|
||||
}
|
||||
.bg-white {
|
||||
background-color: rgb(204, 204, 204);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _parseTextToColoredPre(text) {
|
||||
const pre = document.createElement("pre");
|
||||
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
|
||||
let i = 0;
|
||||
|
||||
const state: State = {
|
||||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
strikethrough: false,
|
||||
foregroundColor: null,
|
||||
backgroundColor: null,
|
||||
};
|
||||
|
||||
const addSpan = (content) => {
|
||||
const span = document.createElement("span");
|
||||
if (state.bold) {
|
||||
span.classList.add("bold");
|
||||
}
|
||||
if (state.italic) {
|
||||
span.classList.add("italic");
|
||||
}
|
||||
if (state.underline) {
|
||||
span.classList.add("underline");
|
||||
}
|
||||
if (state.strikethrough) {
|
||||
span.classList.add("strikethrough");
|
||||
}
|
||||
if (state.foregroundColor !== null) {
|
||||
span.classList.add(`fg-${state.foregroundColor}`);
|
||||
}
|
||||
if (state.backgroundColor !== null) {
|
||||
span.classList.add(`bg-${state.backgroundColor}`);
|
||||
}
|
||||
span.appendChild(document.createTextNode(content));
|
||||
pre.appendChild(span);
|
||||
};
|
||||
|
||||
/* eslint-disable no-cond-assign */
|
||||
let match;
|
||||
// eslint-disable-next-line
|
||||
while ((match = re.exec(text)) !== null) {
|
||||
const j = match!.index;
|
||||
addSpan(text.substring(i, j));
|
||||
i = j + match[0].length;
|
||||
|
||||
if (match[1] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match[1].split(";").forEach((colorCode: string) => {
|
||||
switch (parseInt(colorCode, 10)) {
|
||||
case 0:
|
||||
// reset
|
||||
state.bold = false;
|
||||
state.italic = false;
|
||||
state.underline = false;
|
||||
state.strikethrough = false;
|
||||
state.foregroundColor = null;
|
||||
state.backgroundColor = null;
|
||||
break;
|
||||
case 1:
|
||||
state.bold = true;
|
||||
break;
|
||||
case 3:
|
||||
state.italic = true;
|
||||
break;
|
||||
case 4:
|
||||
state.underline = true;
|
||||
break;
|
||||
case 9:
|
||||
state.strikethrough = true;
|
||||
break;
|
||||
case 22:
|
||||
state.bold = false;
|
||||
break;
|
||||
case 23:
|
||||
state.italic = false;
|
||||
break;
|
||||
case 24:
|
||||
state.underline = false;
|
||||
break;
|
||||
case 29:
|
||||
state.strikethrough = false;
|
||||
break;
|
||||
case 30:
|
||||
// foreground black
|
||||
state.foregroundColor = null;
|
||||
break;
|
||||
case 31:
|
||||
state.foregroundColor = "red";
|
||||
break;
|
||||
case 32:
|
||||
state.foregroundColor = "green";
|
||||
break;
|
||||
case 33:
|
||||
state.foregroundColor = "yellow";
|
||||
break;
|
||||
case 34:
|
||||
state.foregroundColor = "blue";
|
||||
break;
|
||||
case 35:
|
||||
state.foregroundColor = "magenta";
|
||||
break;
|
||||
case 36:
|
||||
state.foregroundColor = "cyan";
|
||||
break;
|
||||
case 37:
|
||||
state.foregroundColor = "white";
|
||||
break;
|
||||
case 39:
|
||||
// foreground reset
|
||||
state.foregroundColor = null;
|
||||
break;
|
||||
case 40:
|
||||
state.backgroundColor = "black";
|
||||
break;
|
||||
case 41:
|
||||
state.backgroundColor = "red";
|
||||
break;
|
||||
case 42:
|
||||
state.backgroundColor = "green";
|
||||
break;
|
||||
case 43:
|
||||
state.backgroundColor = "yellow";
|
||||
break;
|
||||
case 44:
|
||||
state.backgroundColor = "blue";
|
||||
break;
|
||||
case 45:
|
||||
state.backgroundColor = "magenta";
|
||||
break;
|
||||
case 46:
|
||||
state.backgroundColor = "cyan";
|
||||
break;
|
||||
case 47:
|
||||
state.backgroundColor = "white";
|
||||
break;
|
||||
case 49:
|
||||
// background reset
|
||||
state.backgroundColor = null;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
addSpan(text.substring(i));
|
||||
|
||||
return pre;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-ansi-to-html": HassioAnsiToHtml;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -9,6 +8,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-relative-time";
|
||||
import "../../../src/components/ha-icon";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
@customElement("hassio-card-content")
|
||||
@ -48,11 +48,11 @@ class HassioCardContent extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<iron-icon
|
||||
<ha-icon
|
||||
class=${this.iconClass}
|
||||
.icon=${this.icon}
|
||||
.title=${this.iconTitle}
|
||||
></iron-icon>
|
||||
></ha-icon>
|
||||
`}
|
||||
<div>
|
||||
<div class="title">
|
||||
@ -78,25 +78,25 @@ class HassioCardContent extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
iron-icon {
|
||||
ha-icon {
|
||||
margin-right: 24px;
|
||||
margin-left: 8px;
|
||||
margin-top: 12px;
|
||||
float: left;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
iron-icon.update {
|
||||
ha-icon.update {
|
||||
color: var(--paper-orange-400);
|
||||
}
|
||||
iron-icon.running,
|
||||
iron-icon.installed {
|
||||
ha-icon.running,
|
||||
ha-icon.installed {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
iron-icon.hassupdate,
|
||||
iron-icon.snapshot {
|
||||
ha-icon.hassupdate,
|
||||
ha-icon.snapshot {
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
iron-icon.not_available {
|
||||
ha-icon.not_available {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.title {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../src/components/ha-icon-button";
|
||||
import "../../../src/components/ha-icon";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
@ -24,21 +24,17 @@ class HassioSearchInput extends LitElement {
|
||||
.value=${this.filter}
|
||||
@value-changed=${this._filterInputChanged}
|
||||
>
|
||||
<iron-icon
|
||||
icon="hassio:magnify"
|
||||
slot="prefix"
|
||||
class="prefix"
|
||||
></iron-icon>
|
||||
<ha-icon icon="hassio:magnify" slot="prefix" class="prefix"></ha-icon>
|
||||
${this.filter &&
|
||||
html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
class="suffix"
|
||||
@click=${this._clearSearch}
|
||||
icon="hassio:close"
|
||||
alt="Clear"
|
||||
title="Clear"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`}
|
||||
</paper-input>
|
||||
</div>
|
||||
@ -71,6 +67,9 @@ class HassioSearchInput extends LitElement {
|
||||
.prefix {
|
||||
margin: 8px;
|
||||
}
|
||||
ha-icon {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ class HassioAddons extends LitElement {
|
||||
}
|
||||
|
||||
private _addonTapped(ev: any): void {
|
||||
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}`);
|
||||
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}/info`);
|
||||
}
|
||||
|
||||
private _openStore(): void {
|
||||
|
||||
@ -13,34 +13,51 @@ import {
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "./hassio-addons";
|
||||
import "./hassio-update";
|
||||
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
|
||||
@customElement("hassio-dashboard")
|
||||
class HassioDashboard extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property() public hassInfo!: HassioHomeAssistantInfo;
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
|
||||
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="content">
|
||||
<hassio-update
|
||||
.hass=${this.hass}
|
||||
.hassInfo=${this.hassInfo}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-update>
|
||||
<hassio-addons
|
||||
.hass=${this.hass}
|
||||
.addons=${this.supervisorInfo.addons}
|
||||
></hassio-addons>
|
||||
</div>
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
hassio
|
||||
main-page
|
||||
.route=${this.route}
|
||||
.tabs=${supervisorTabs}
|
||||
>
|
||||
<span slot="header">Dashboard</span>
|
||||
<div class="content">
|
||||
<hassio-update
|
||||
.hass=${this.hass}
|
||||
.hassInfo=${this.hassInfo}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-update>
|
||||
<hassio-addons
|
||||
.hass=${this.hass}
|
||||
.addons=${this.supervisorInfo.addons}
|
||||
></hassio-addons>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
@ -17,6 +16,7 @@ import {
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import "../../../src/components/ha-icon";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../components/hassio-card-content";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
@ -112,7 +112,7 @@ export class HassioUpdate extends LitElement {
|
||||
${icon
|
||||
? html`
|
||||
<div class="icon">
|
||||
<iron-icon .icon=${icon}></iron-icon>
|
||||
<ha-icon .icon=${icon}></ha-icon>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@ -158,15 +158,16 @@ export class HassioUpdate extends LitElement {
|
||||
hassioStyle,
|
||||
css`
|
||||
.icon {
|
||||
--iron-icon-height: 48px;
|
||||
--iron-icon-width: 48px;
|
||||
--mdc-icon-size: 48px;
|
||||
float: right;
|
||||
margin: 0 0 2px 10px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.update-heading {
|
||||
font-size: var(--paper-font-subhead_-_font-size);
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5em;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.warning {
|
||||
color: var(--secondary-text-color);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { PaperDialogElement } from "@polymer/paper-dialog";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -36,10 +36,10 @@ class HassioMarkdownDialog extends LitElement {
|
||||
return html`
|
||||
<ha-paper-dialog id="dialog" with-backdrop="">
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hassio:close"
|
||||
dialog-dismiss=""
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<div main-title="">${this.title}</div>
|
||||
</app-toolbar>
|
||||
<paper-dialog-scrollable>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
||||
import { PaperDialogElement } from "@polymer/paper-dialog";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
@ -119,10 +119,10 @@ class HassioSnapshotDialog extends LitElement {
|
||||
.on-iron-overlay-closed=${this._dialogClosed}
|
||||
>
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hassio:close"
|
||||
dialog-dismiss=""
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<div main-title="">${this._computeName}</div>
|
||||
</app-toolbar>
|
||||
<div class="details">
|
||||
@ -200,13 +200,13 @@ class HassioSnapshotDialog extends LitElement {
|
||||
<ul class="buttons">
|
||||
<li>
|
||||
<mwc-button @click=${this._downloadClicked}>
|
||||
<iron-icon icon="hassio:download" class="icon"></iron-icon>
|
||||
<ha-icon icon="hassio:download" class="icon"></ha-icon>
|
||||
Download Snapshot
|
||||
</mwc-button>
|
||||
</li>
|
||||
<li>
|
||||
<mwc-button @click=${this._partialRestoreClicked}>
|
||||
<iron-icon icon="hassio:history" class="icon"> </iron-icon>
|
||||
<ha-icon icon="hassio:history" class="icon"> </ha-icon>
|
||||
Restore Selected
|
||||
</mwc-button>
|
||||
</li>
|
||||
@ -214,7 +214,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
? html`
|
||||
<li>
|
||||
<mwc-button @click=${this._fullRestoreClicked}>
|
||||
<iron-icon icon="hassio:history" class="icon"> </iron-icon>
|
||||
<ha-icon icon="hassio:history" class="icon"> </ha-icon>
|
||||
Wipe & restore
|
||||
</mwc-button>
|
||||
</li>
|
||||
@ -222,7 +222,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
: ""}
|
||||
<li>
|
||||
<mwc-button @click=${this._deleteClicked}>
|
||||
<iron-icon icon="hassio:delete" class="icon warning"> </iron-icon>
|
||||
<ha-icon icon="hassio:delete" class="icon warning"> </ha-icon>
|
||||
<span class="warning">Delete Snapshot</span>
|
||||
</mwc-button>
|
||||
</li>
|
||||
|
||||
33
hassio/src/dialogs/suggestAddonRestart.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import type { LitElement } from "lit-element";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
restartHassioAddon,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
|
||||
export const suggestAddonRestart = async (
|
||||
element: LitElement,
|
||||
hass: HomeAssistant,
|
||||
addon: HassioAddonDetails
|
||||
): Promise<void> => {
|
||||
const confirmed = await showConfirmationDialog(element, {
|
||||
title: addon.name,
|
||||
text: "Do you want to restart the add-on with your changes?",
|
||||
confirmText: "restart add-on",
|
||||
dismissText: "no",
|
||||
});
|
||||
if (confirmed) {
|
||||
try {
|
||||
await restartHassioAddon(hass, addon.slug);
|
||||
} catch (err) {
|
||||
showAlertDialog(element, {
|
||||
title: "Failed to restart",
|
||||
text: err.body.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -2,8 +2,6 @@ window.loadES5Adapter().then(() => {
|
||||
// eslint-disable-next-line
|
||||
import(/* webpackChunkName: "roboto" */ "../../src/resources/roboto");
|
||||
// eslint-disable-next-line
|
||||
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons");
|
||||
// eslint-disable-next-line
|
||||
import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { customElement, property, PropertyValues } from "lit-element";
|
||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||
@ -32,13 +32,13 @@ import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||
import "../../src/resources/ha-style";
|
||||
import { HomeAssistant } from "../../src/types";
|
||||
// Don't codesplit it, that way the dashboard always loads fast.
|
||||
import "./hassio-pages-with-tabs";
|
||||
import "./hassio-panel";
|
||||
|
||||
// The register callback of the IronA11yKeysBehavior inside paper-icon-button
|
||||
// is not called, causing _keyBindings to be uninitiliazed for paper-icon-button,
|
||||
// The register callback of the IronA11yKeysBehavior inside ha-icon-button
|
||||
// is not called, causing _keyBindings to be uninitiliazed for ha-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 = {};
|
||||
customElements.get("ha-icon-button").prototype._keyBindings = {};
|
||||
|
||||
@customElement("hassio-main")
|
||||
class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
@ -55,17 +55,17 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
showLoading: true,
|
||||
routes: {
|
||||
dashboard: {
|
||||
tag: "hassio-pages-with-tabs",
|
||||
tag: "hassio-panel",
|
||||
cache: true,
|
||||
},
|
||||
snapshots: "dashboard",
|
||||
store: "dashboard",
|
||||
system: "dashboard",
|
||||
addon: {
|
||||
tag: "hassio-addon-view",
|
||||
tag: "hassio-addon-dashboard",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"
|
||||
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
|
||||
),
|
||||
},
|
||||
ingress: {
|
||||
@ -132,8 +132,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
|
||||
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;
|
||||
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
||||
|
||||
if ("setProperties" in el) {
|
||||
// As long as we have Polymer pages
|
||||
@ -205,7 +204,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
await awaitAlert(
|
||||
{
|
||||
text: "Unable to fetch add-on info to start Ingress",
|
||||
title: "Hass.io",
|
||||
title: "Supervisor",
|
||||
},
|
||||
() => history.back()
|
||||
);
|
||||
@ -231,7 +230,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
text: "Add-on is not running. Please start it first",
|
||||
title: addon.name,
|
||||
},
|
||||
() => navigate(this, `/hassio/addon/${addon.slug}`, true)
|
||||
() => navigate(this, `/hassio/addon/${addon.slug}/info`, true)
|
||||
);
|
||||
|
||||
return;
|
||||
|
||||
@ -1,141 +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 {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import scrollToTarget from "../../src/common/dom/scroll-to-target";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import "../../src/components/ha-menu-button";
|
||||
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import "../../src/resources/ha-style";
|
||||
import { haStyle } from "../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-tabs-router";
|
||||
|
||||
const HAS_REFRESH_BUTTON = ["store", "snapshots"];
|
||||
|
||||
@customElement("hassio-pages-with-tabs")
|
||||
class HassioPagesWithTabs extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property() public hostInfo!: HassioHostInfo;
|
||||
|
||||
@property() public hassInfo!: HassioHomeAssistantInfo;
|
||||
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const page = this._page;
|
||||
return html`
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header fixed slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
hassio
|
||||
></ha-menu-button>
|
||||
<div main-title>Supervisor</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}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></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: var(--text-primary-color, #fff);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-pages-with-tabs": HassioPagesWithTabs;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { customElement, property } from "lit-element";
|
||||
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
@ -9,7 +8,7 @@ import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../src/layouts/hass-router-page";
|
||||
import { HomeAssistant } from "../../src/types";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./addon-store/hassio-addon-store";
|
||||
// Don't codesplit it, that way the dashboard always loads fast.
|
||||
import "./dashboard/hassio-dashboard";
|
||||
@ -17,29 +16,33 @@ import "./dashboard/hassio-dashboard";
|
||||
import "./snapshots/hassio-snapshots";
|
||||
import "./system/hassio-system";
|
||||
|
||||
@customElement("hassio-tabs-router")
|
||||
class HassioTabsRouter extends HassRouterPage {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@customElement("hassio-panel-router")
|
||||
class HassioPanelRouter extends HassRouterPage {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public supervisorInfo: HassioSupervisorInfo;
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property() public hostInfo: HassioHostInfo;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property() public hassInfo: HassioHomeAssistantInfo;
|
||||
@property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo;
|
||||
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
@property({ attribute: false }) public hostInfo: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
|
||||
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
routes: {
|
||||
dashboard: {
|
||||
tag: "hassio-dashboard",
|
||||
},
|
||||
snapshots: {
|
||||
tag: "hassio-snapshots",
|
||||
},
|
||||
store: {
|
||||
tag: "hassio-addon-store",
|
||||
},
|
||||
snapshots: {
|
||||
tag: "hassio-snapshots",
|
||||
},
|
||||
system: {
|
||||
tag: "hassio-system",
|
||||
},
|
||||
@ -47,27 +50,18 @@ class HassioTabsRouter extends HassRouterPage {
|
||||
};
|
||||
|
||||
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,
|
||||
hassOsInfo: this.hassOsInfo,
|
||||
});
|
||||
} else {
|
||||
el.hass = this.hass;
|
||||
el.supervisorInfo = this.supervisorInfo;
|
||||
el.hostInfo = this.hostInfo;
|
||||
el.hassInfo = this.hassInfo;
|
||||
el.hassOsInfo = this.hassOsInfo;
|
||||
}
|
||||
el.hass = this.hass;
|
||||
el.route = this.route;
|
||||
el.narrow = this.narrow;
|
||||
el.supervisorInfo = this.supervisorInfo;
|
||||
el.hostInfo = this.hostInfo;
|
||||
el.hassInfo = this.hassInfo;
|
||||
el.hassOsInfo = this.hassOsInfo;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-tabs-router": HassioTabsRouter;
|
||||
"hassio-panel-router": HassioPanelRouter;
|
||||
}
|
||||
}
|
||||
77
hassio/src/hassio-panel.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import "../../src/resources/ha-style";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-panel-router";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
|
||||
export const supervisorTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
icon: "hassio:view-dashboard",
|
||||
},
|
||||
{
|
||||
name: "Add-on store",
|
||||
path: `/hassio/store`,
|
||||
icon: "hassio:store",
|
||||
},
|
||||
{
|
||||
name: "Snapshots",
|
||||
path: `/hassio/snapshots`,
|
||||
icon: "hassio:backup-restore",
|
||||
},
|
||||
{
|
||||
name: "System",
|
||||
path: `/hassio/system`,
|
||||
icon: "hassio:cogs",
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("hassio-panel")
|
||||
class HassioPanel extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hostInfo!: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
|
||||
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hassio-panel-router
|
||||
.route=${this.route}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassInfo=${this.hassInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-panel-router>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-panel": HassioPanel;
|
||||
}
|
||||
}
|
||||
@ -86,7 +86,7 @@ class HassioIngressView extends LitElement {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
paper-icon-button {
|
||||
ha-icon-button {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
`;
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import "../../../src/components/ha-iconset-svg";
|
||||
import iconSetContent from "../../hassio-icons.html";
|
||||
|
||||
const documentContainer = document.createElement("template");
|
||||
documentContainer.setAttribute("style", "display: none;");
|
||||
documentContainer.innerHTML = iconSetContent;
|
||||
document.head.appendChild(documentContainer.content);
|
||||
@ -4,8 +4,12 @@ export const hassioStyle = css`
|
||||
.content {
|
||||
margin: 8px;
|
||||
}
|
||||
h1 {
|
||||
h1,
|
||||
.description,
|
||||
.card-content {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin-bottom: 8px;
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
|
||||
@ -30,11 +30,14 @@ import {
|
||||
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||
import { PolymerChangedEvent } from "../../../src/polymer-types";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../components/hassio-card-content";
|
||||
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
|
||||
interface CheckboxItem {
|
||||
slug: string;
|
||||
name: string;
|
||||
@ -43,9 +46,13 @@ interface CheckboxItem {
|
||||
|
||||
@customElement("hassio-snapshots")
|
||||
class HassioSnapshots extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property() private _snapshotName = "";
|
||||
|
||||
@ -81,135 +88,153 @@ class HassioSnapshots extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="content">
|
||||
<h1>
|
||||
Create snapshot
|
||||
</h1>
|
||||
<p class="description">
|
||||
Snapshots allow you to easily backup and restore all data of your Home
|
||||
Assistant instance.
|
||||
</p>
|
||||
<div class="card-group">
|
||||
<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=${addon.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>
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
hassio
|
||||
main-page
|
||||
.route=${this.route}
|
||||
.tabs=${supervisorTabs}
|
||||
>
|
||||
<span slot="header">Snapshots</span>
|
||||
|
||||
<h1>Available snapshots</h1>
|
||||
<div class="card-group">
|
||||
${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}
|
||||
>
|
||||
<ha-icon-button
|
||||
icon="hassio:reload"
|
||||
slot="toolbar-icon"
|
||||
aria-label="Reload snapshots"
|
||||
@click=${this.refreshData}
|
||||
></ha-icon-button>
|
||||
|
||||
<div class="content">
|
||||
<h1>
|
||||
Create snapshot
|
||||
</h1>
|
||||
<p class="description">
|
||||
Snapshots allow you to easily backup and restore all data of your
|
||||
Home Assistant instance.
|
||||
</p>
|
||||
<div class="card-group">
|
||||
<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=${addon.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>
|
||||
|
||||
<h1>Available snapshots</h1>
|
||||
<div class="card-group">
|
||||
${this._snapshots === undefined
|
||||
? undefined
|
||||
: this._snapshots.length === 0
|
||||
? html`
|
||||
<paper-card>
|
||||
<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>
|
||||
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>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@ -12,13 +12,23 @@ import {
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
fetchHassioHostInfo,
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo as HassioHostInfoType,
|
||||
rebootHost,
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
changeHostOptions,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
|
||||
@customElement("hassio-host-info")
|
||||
class HassioHostInfo extends LitElement {
|
||||
@ -76,21 +86,15 @@ class HassioHostInfo extends LitElement {
|
||||
<div class="card-actions">
|
||||
${this.hostInfo.features.includes("reboot")
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
path="hassio/host/reboot"
|
||||
>Reboot</ha-call-api-button
|
||||
<mwc-button class="warning" @click=${this._rebootHost}
|
||||
>Reboot</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.features.includes("shutdown")
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
path="hassio/host/shutdown"
|
||||
>Shutdown</ha-call-api-button
|
||||
<mwc-button class="warning" @click=${this._shutdownHost}
|
||||
>Shutdown</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
@ -106,11 +110,7 @@ class HassioHostInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.version !== this.hostInfo.version_latest
|
||||
? html`
|
||||
<ha-call-api-button .hass=${this.hass} path="hassio/os/update"
|
||||
>Update</ha-call-api-button
|
||||
>
|
||||
`
|
||||
? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> `
|
||||
: ""}
|
||||
</div>
|
||||
</paper-card>
|
||||
@ -189,6 +189,72 @@ class HassioHostInfo extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _rebootHost(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Reboot",
|
||||
text: "Are you sure you want to reboot the host?",
|
||||
confirmText: "reboot host",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text: err.body.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _shutdownHost(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Shutdown",
|
||||
text: "Are you sure you want to shutdown the host?",
|
||||
confirmText: "shutdown host",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text: err.body.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateOS(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update",
|
||||
text: "Are you sure you want to update the OS?",
|
||||
confirmText: "update os",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateOS(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
text: err.body.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _objectToMarkdown(obj, indent = ""): string {
|
||||
let data = "";
|
||||
Object.keys(obj).forEach((key) => {
|
||||
@ -210,11 +276,25 @@ class HassioHostInfo extends LitElement {
|
||||
return data;
|
||||
}
|
||||
|
||||
private _changeHostnameClicked(): void {
|
||||
const curHostname = this.hostInfo.hostname;
|
||||
const hostname = prompt("Please enter a new hostname:", curHostname);
|
||||
private async _changeHostnameClicked(): Promise<void> {
|
||||
const curHostname: string = this.hostInfo.hostname;
|
||||
const hostname = await showPromptDialog(this, {
|
||||
title: "Change hostname",
|
||||
inputLabel: "Please enter a new hostname:",
|
||||
inputType: "string",
|
||||
defaultValue: curHostname,
|
||||
});
|
||||
|
||||
if (hostname && hostname !== curHostname) {
|
||||
this.hass.callApi("POST", "hassio/host/options", { hostname });
|
||||
try {
|
||||
await changeHostOptions(this.hass, { hostname });
|
||||
this.hostInfo = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
text: err.body.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
|
||||
@customElement("hassio-supervisor-info")
|
||||
class HassioSupervisorInfo extends LitElement {
|
||||
@ -142,17 +143,30 @@ class HassioSupervisorInfo extends LitElement {
|
||||
}
|
||||
|
||||
private async _joinBeta() {
|
||||
if (
|
||||
!confirm(`WARNING:
|
||||
Beta releases are for testers and early adopters and can contain unstable code changes. Make sure you have backups of your data before you activate this feature.
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "WARNING",
|
||||
text: html` Beta releases are for testers and early adopters and can
|
||||
contain unstable code changes.
|
||||
<br />
|
||||
<b>
|
||||
Make sure you have backups of your data before you activate this
|
||||
feature.
|
||||
</b>
|
||||
<br /><br />
|
||||
This includes beta releases for:
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
<br />
|
||||
Do you want to join the beta channel?`,
|
||||
confirmText: "join beta",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
This includes beta releases for:
|
||||
- Home Assistant (Release Candidates)
|
||||
- Hass.io supervisor
|
||||
- Host system`)
|
||||
) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data: SupervisorOptions = { channel: "beta" };
|
||||
await setSupervisorOption(this.hass, data);
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
@ -7,22 +10,58 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fetchSupervisorLogs } from "../../../src/data/hassio/supervisor";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html";
|
||||
|
||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||
|
||||
import "../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "../../../src/layouts/loading-screen";
|
||||
|
||||
interface LogProvider {
|
||||
key: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const logProviders: LogProvider[] = [
|
||||
{
|
||||
key: "supervisor",
|
||||
name: "Supervisor",
|
||||
},
|
||||
{
|
||||
key: "core",
|
||||
name: "Core",
|
||||
},
|
||||
{
|
||||
key: "host",
|
||||
name: "Host",
|
||||
},
|
||||
{
|
||||
key: "dns",
|
||||
name: "DNS",
|
||||
},
|
||||
{
|
||||
key: "audio",
|
||||
name: "Audio",
|
||||
},
|
||||
{
|
||||
key: "multicast",
|
||||
name: "Multicast",
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("hassio-supervisor-log")
|
||||
class HassioSupervisorLog extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@query("#content") private _logContent!: HTMLDivElement;
|
||||
@property() private _selectedLogProvider = "supervisor";
|
||||
|
||||
@property() private _content?: string;
|
||||
|
||||
public async connectedCallback(): Promise<void> {
|
||||
super.connectedCallback();
|
||||
@ -33,7 +72,36 @@ class HassioSupervisorLog extends LitElement {
|
||||
return html`
|
||||
<paper-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
<div class="card-content" id="content"></div>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-dropdown-menu
|
||||
label="Log provider"
|
||||
@iron-select=${this._setLogProvider}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="provider"
|
||||
.selected=${this._selectedLogProvider}
|
||||
>
|
||||
${logProviders.map((provider) => {
|
||||
return html`
|
||||
<paper-item provider=${provider.key}
|
||||
>${provider.name}</paper-item
|
||||
>
|
||||
`;
|
||||
})}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<div class="card-content" id="content">
|
||||
${this._content
|
||||
? html`<hassio-ansi-to-html
|
||||
.content=${this._content}
|
||||
></hassio-ansi-to-html>`
|
||||
: html`<loading-screen></loading-screen>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
|
||||
</div>
|
||||
@ -45,7 +113,6 @@ class HassioSupervisorLog extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
ANSI_HTML_STYLE,
|
||||
css`
|
||||
paper-card {
|
||||
width: 100%;
|
||||
@ -53,22 +120,36 @@ class HassioSupervisorLog extends LitElement {
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
paper-dropdown-menu {
|
||||
padding: 0 2%;
|
||||
width: 96%;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-content {
|
||||
padding-top: 0px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private async _setLogProvider(ev): Promise<void> {
|
||||
const provider = ev.detail.item.getAttribute("provider");
|
||||
this._selectedLogProvider = provider;
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._error = undefined;
|
||||
this._content = undefined;
|
||||
|
||||
try {
|
||||
const content = await fetchSupervisorLogs(this.hass);
|
||||
while (this._logContent.lastChild) {
|
||||
this._logContent.removeChild(this._logContent.lastChild as Node);
|
||||
}
|
||||
this._logContent.appendChild(parseTextToColoredPre(content));
|
||||
this._content = await fetchHassioLogs(
|
||||
this.hass,
|
||||
this._selectedLogProvider
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get supervisor logs, ${
|
||||
err.body?.message || err
|
||||
|
||||
@ -14,15 +14,22 @@ import {
|
||||
} from "../../../src/data/hassio/host";
|
||||
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./hassio-host-info";
|
||||
import "./hassio-supervisor-info";
|
||||
import "./hassio-supervisor-log";
|
||||
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
|
||||
@customElement("hassio-system")
|
||||
class HassioSystem extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@ -32,22 +39,32 @@ class HassioSystem extends LitElement {
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
return html`
|
||||
<div class="content">
|
||||
<h1>Information</h1>
|
||||
<div class="card-group">
|
||||
<hassio-supervisor-info
|
||||
.hass=${this.hass}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
></hassio-supervisor-info>
|
||||
<hassio-host-info
|
||||
.hass=${this.hass}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-host-info>
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
hassio
|
||||
main-page
|
||||
.route=${this.route}
|
||||
.tabs=${supervisorTabs}
|
||||
>
|
||||
<span slot="header">System</span>
|
||||
<div class="content">
|
||||
<h1>Information</h1>
|
||||
<div class="card-group">
|
||||
<hassio-supervisor-info
|
||||
.hass=${this.hass}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
></hassio-supervisor-info>
|
||||
<hassio-host-info
|
||||
.hass=${this.hass}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-host-info>
|
||||
</div>
|
||||
<h1>System log</h1>
|
||||
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
|
||||
</div>
|
||||
<h1>System log</h1>
|
||||
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
16
package.json
@ -29,18 +29,18 @@
|
||||
"@material/mwc-checkbox": "^0.13.0",
|
||||
"@material/mwc-dialog": "^0.13.0",
|
||||
"@material/mwc-fab": "^0.13.0",
|
||||
"@material/mwc-icon-button": "^0.13.0",
|
||||
"@material/mwc-ripple": "^0.13.0",
|
||||
"@material/mwc-switch": "^0.13.0",
|
||||
"@mdi/js": "4.9.95",
|
||||
"@mdi/svg": "4.9.95",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
"@polymer/app-localize-behavior": "^3.0.1",
|
||||
"@polymer/app-route": "^3.0.2",
|
||||
"@polymer/app-storage": "^3.0.2",
|
||||
"@polymer/font-roboto": "^3.0.2",
|
||||
"@polymer/iron-autogrow-textarea": "^3.0.1",
|
||||
"@polymer/iron-flex-layout": "^3.0.1",
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
"@polymer/iron-iconset-svg": "^3.0.1",
|
||||
"@polymer/iron-image": "^3.0.1",
|
||||
"@polymer/iron-input": "^3.0.1",
|
||||
"@polymer/iron-label": "^3.0.1",
|
||||
@ -56,7 +56,6 @@
|
||||
"@polymer/paper-dialog-scrollable": "^3.0.1",
|
||||
"@polymer/paper-drawer-panel": "^3.0.1",
|
||||
"@polymer/paper-dropdown-menu": "^3.0.1",
|
||||
"@polymer/paper-icon-button": "^3.0.2",
|
||||
"@polymer/paper-input": "^3.0.1",
|
||||
"@polymer/paper-item": "^3.0.1",
|
||||
"@polymer/paper-listbox": "^3.0.1",
|
||||
@ -74,7 +73,6 @@
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"@thomasloven/round-slider": "0.3.7",
|
||||
"@types/resize-observer-browser": "^0.1.3",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||
"@webcomponents/shadycss": "^1.9.0",
|
||||
@ -91,7 +89,8 @@
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "5.0.0",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
"leaflet": "^1.4.0",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
@ -136,6 +135,7 @@
|
||||
"@types/leaflet-draw": "^1.0.1",
|
||||
"@types/memoize-one": "4.1.0",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/resize-observer-browser": "^0.1.3",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
||||
"@typescript-eslint/parser": "^2.28.0",
|
||||
@ -171,6 +171,7 @@
|
||||
"map-stream": "^0.0.7",
|
||||
"merge-stream": "^1.0.1",
|
||||
"mocha": "^6.0.2",
|
||||
"object-hash": "^2.0.3",
|
||||
"parse5": "^5.1.0",
|
||||
"prettier": "^2.0.4",
|
||||
"raw-loader": "^2.0.0",
|
||||
@ -199,12 +200,15 @@
|
||||
"lit-html": "^1.1.2",
|
||||
"@material/button": "^5.0.0",
|
||||
"@material/checkbox": "^5.0.0",
|
||||
"@material/density": "^5.0.0",
|
||||
"@material/dialog": "^5.0.0",
|
||||
"@material/fab": "^5.0.0",
|
||||
"@material/feature-targeting": "^5.0.0",
|
||||
"@material/switch": "^5.0.0",
|
||||
"@material/ripple": "^5.0.0",
|
||||
"@material/dom": "^5.0.0",
|
||||
"@material/touch-target": "^5.0.0"
|
||||
"@material/touch-target": "^5.0.0",
|
||||
"@material/theme": "^5.0.0"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 655 B |
|
Before Width: | Height: | Size: 910 B After Width: | Height: | Size: 888 B |
|
Before Width: | Height: | Size: 822 B After Width: | Height: | Size: 807 B |
|
Before Width: | Height: | Size: 648 B After Width: | Height: | Size: 639 B |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 840 B |
|
Before Width: | Height: | Size: 818 B After Width: | Height: | Size: 798 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 492 B After Width: | Height: | Size: 487 B |
|
Before Width: | Height: | Size: 817 B After Width: | Height: | Size: 774 B |
@ -6,6 +6,6 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
STATS=1 NODE_ENV=production webpack --profile --json > compilation-stats.json
|
||||
STATS=1 NODE_ENV=production ./node_modules/.bin/webpack --profile --json > compilation-stats.json
|
||||
npx webpack-bundle-analyzer compilation-stats.json hass_frontend/frontend_latest
|
||||
rm compilation-stats.json
|
||||
|
||||
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200427.2",
|
||||
version="20200505.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
||||
@ -103,6 +103,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
${this.localize("ui.panel.page-authorize.abort_intro")}:
|
||||
<ha-markdown
|
||||
allowsvg
|
||||
breaks
|
||||
.content=${this.localize(
|
||||
`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`
|
||||
)}
|
||||
@ -113,6 +114,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
${this._computeStepDescription(step)
|
||||
? html`
|
||||
<ha-markdown
|
||||
breaks
|
||||
.content=${this._computeStepDescription(step)}
|
||||
></ha-markdown>
|
||||
`
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
@ -50,14 +50,14 @@ class SearchInput extends LitElement {
|
||||
<ha-icon icon="hass:magnify" slot="prefix" class="prefix"></ha-icon>
|
||||
${this.filter &&
|
||||
html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
class="suffix"
|
||||
@click=${this._clearSearch}
|
||||
icon="hass:close"
|
||||
alt="Clear"
|
||||
title="Clear"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`}
|
||||
</paper-input>
|
||||
`;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import IntlMessageFormat from "intl-messageformat/src/main";
|
||||
import IntlMessageFormat from "intl-messageformat";
|
||||
import { Resources } from "../../types";
|
||||
|
||||
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
||||
@ -59,7 +59,7 @@ export const computeLocalize = (
|
||||
let translatedMessage = cache._localizationCache[messageKey];
|
||||
|
||||
if (!translatedMessage) {
|
||||
translatedMessage = new (IntlMessageFormat as any)(
|
||||
translatedMessage = new IntlMessageFormat(
|
||||
translatedValue,
|
||||
language,
|
||||
formats
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -61,7 +61,7 @@ const rowRenderer = (
|
||||
margin: -10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
paper-icon-button {
|
||||
ha-icon-button {
|
||||
float: right;
|
||||
}
|
||||
.devices {
|
||||
@ -325,7 +325,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.device-picker.clear"
|
||||
)}
|
||||
@ -336,12 +336,12 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
no-ripple
|
||||
>
|
||||
Clear
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${areas.length > 0
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.device-picker.show_devices"
|
||||
)}
|
||||
@ -350,7 +350,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
>
|
||||
Toggle
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
@ -408,7 +408,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > paper-icon-button {
|
||||
paper-input > ha-icon-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 2px;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -247,7 +247,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.device-picker.clear"
|
||||
)}
|
||||
@ -258,12 +258,12 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
no-ripple
|
||||
>
|
||||
Clear
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${devices.length > 0
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.device-picker.show_devices"
|
||||
)}
|
||||
@ -272,7 +272,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
>
|
||||
Toggle
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
@ -311,9 +311,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > paper-icon-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
paper-input > ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button-light";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* eslint-plugin-disable lit */
|
||||
import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../ha-icon-button";
|
||||
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
||||
import { timeOut } from "@polymer/polymer/lib/utils/async";
|
||||
import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
|
||||
@ -107,7 +107,7 @@ class HaChartBase extends mixinBehaviors(
|
||||
margin-right: inherit;
|
||||
margin-left: 4px;
|
||||
}
|
||||
paper-icon-button {
|
||||
ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button-light";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
customElement,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -192,7 +192,7 @@ class HaEntityPicker extends LitElement {
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.clear"
|
||||
)}
|
||||
@ -203,11 +203,11 @@ class HaEntityPicker extends LitElement {
|
||||
no-ripple
|
||||
>
|
||||
Clear
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.show_entities"
|
||||
)}
|
||||
@ -216,7 +216,7 @@ class HaEntityPicker extends LitElement {
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
>
|
||||
Toggle
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
</paper-input>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
@ -252,9 +252,8 @@ class HaEntityPicker extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > paper-icon-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
paper-input > ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 0px 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../ha-icon-button";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@ -37,20 +37,20 @@ class HaEntityToggle extends LitElement {
|
||||
|
||||
if (this.stateObj.attributes.assumed_state) {
|
||||
return html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${`Turn ${computeStateName(this.stateObj)} off`}
|
||||
icon="hass:flash-off"
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@click=${this._turnOff}
|
||||
?state-active=${!this._isOn}
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
aria-label=${`Turn ${computeStateName(this.stateObj)} on`}
|
||||
icon="hass:flash"
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@click=${this._turnOn}
|
||||
?state-active=${this._isOn}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -144,15 +144,12 @@ class HaEntityToggle extends LitElement {
|
||||
white-space: nowrap;
|
||||
min-width: 38px;
|
||||
}
|
||||
paper-icon-button {
|
||||
color: var(
|
||||
--paper-icon-button-inactive-color,
|
||||
var(--primary-text-color)
|
||||
);
|
||||
ha-icon-button {
|
||||
color: var(--ha-icon-button-inactive-color, var(--primary-text-color));
|
||||
transition: color 0.5s;
|
||||
}
|
||||
paper-icon-button[state-active] {
|
||||
color: var(--paper-icon-button-active-color, var(--primary-color));
|
||||
ha-icon-button[state-active] {
|
||||
color: var(--ha-icon-button-active-color, var(--primary-color));
|
||||
}
|
||||
ha-switch {
|
||||
padding: 13px 5px;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "./ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -117,7 +117,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.area-picker.clear"
|
||||
)}
|
||||
@ -128,12 +128,12 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
no-ripple
|
||||
>
|
||||
${this.hass.localize("ui.components.area-picker.clear")}
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${this._areas.length > 0
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.area-picker.show_areas"
|
||||
)}
|
||||
@ -142,7 +142,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
>
|
||||
${this.hass.localize("ui.components.area-picker.toggle")}
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
@ -214,9 +214,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > paper-icon-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
paper-input > ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "./ha-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@ -31,9 +31,8 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
|
||||
font-size: 200%;
|
||||
text-align: right;
|
||||
}
|
||||
paper-icon-button {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
ha-icon-button {
|
||||
--mdc-icon-size: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -41,16 +40,16 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
|
||||
<div id="target_temperature">[[value]] [[units]]</div>
|
||||
<div class="control-buttons">
|
||||
<div>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:chevron-up"
|
||||
on-click="incrementValue"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<div>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:chevron-down"
|
||||
on-click="decrementValue"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "./ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
@ -11,7 +11,7 @@ class HaComboBox extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
paper-input > paper-icon-button {
|
||||
paper-input > ha-icon-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 2px;
|
||||
@ -36,19 +36,19 @@ class HaComboBox extends EventsMixin(PolymerElement) {
|
||||
class="input"
|
||||
value="[[value]]"
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
icon="hass:close"
|
||||
hidden$="[[!value]]"
|
||||
>Clear</paper-icon-button
|
||||
>Clear</ha-icon-button
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
icon="[[_computeToggleIcon(opened)]]"
|
||||
hidden$="[[!items.length]]"
|
||||
>Toggle</paper-icon-button
|
||||
>Toggle</ha-icon-button
|
||||
>
|
||||
</paper-input>
|
||||
<template>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "./ha-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@ -18,27 +18,27 @@ class HaCoverControls extends PolymerElement {
|
||||
</style>
|
||||
|
||||
<div class="state">
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label="Open cover"
|
||||
icon="[[computeOpenIcon(stateObj)]]"
|
||||
on-click="onOpenTap"
|
||||
invisible$="[[!entityObj.supportsOpen]]"
|
||||
disabled="[[computeOpenDisabled(stateObj, entityObj)]]"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
aria-label="Stop the cover from moving"
|
||||
icon="hass:stop"
|
||||
on-click="onStopTap"
|
||||
invisible$="[[!entityObj.supportsStop]]"
|
||||
disabled="[[computStopDisabled(stateObj)]]"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
aria-label="Close cover"
|
||||
icon="[[computeCloseIcon(stateObj)]]"
|
||||
on-click="onCloseTap"
|
||||
invisible$="[[!entityObj.supportsClose]]"
|
||||
disabled="[[computeClosedDisabled(stateObj, entityObj)]]"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "./ha-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@ -18,30 +18,30 @@ class HaCoverTiltControls extends PolymerElement {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
</style>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label="Open cover tilt"
|
||||
icon="hass:arrow-top-right"
|
||||
on-click="onOpenTiltTap"
|
||||
title="Open tilt"
|
||||
invisible$="[[!entityObj.supportsOpenTilt]]"
|
||||
disabled="[[computeOpenDisabled(stateObj, entityObj)]]"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
aria-label="Stop cover from moving"
|
||||
icon="hass:stop"
|
||||
on-click="onStopTiltTap"
|
||||
invisible$="[[!entityObj.supportsStopTilt]]"
|
||||
disabled="[[computStopDisabled(stateObj)]]"
|
||||
title="Stop tilt"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
aria-label="Close cover tilt"
|
||||
icon="hass:arrow-bottom-left"
|
||||
on-click="onCloseTiltTap"
|
||||
title="Close tilt"
|
||||
invisible$="[[!entityObj.supportsCloseTilt]]"
|
||||
disabled="[[computeClosedDisabled(stateObj, entityObj)]]"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "@material/mwc-dialog";
|
||||
import type { Dialog } from "@material/mwc-dialog";
|
||||
import { style } from "@material/mwc-dialog/mwc-dialog-css";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "./ha-icon-button";
|
||||
import { css, CSSResult, customElement, html } from "lit-element";
|
||||
import type { Constructor, HomeAssistant } from "../types";
|
||||
|
||||
@ -9,12 +9,12 @@ const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
|
||||
|
||||
export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
${title}
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
||||
icon="hass:close"
|
||||
dialogAction="close"
|
||||
class="close_button"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`;
|
||||
|
||||
@customElement("ha-dialog")
|
||||
|
||||
@ -77,7 +77,12 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.data || this.schema.default || 0;
|
||||
return (
|
||||
this.data ||
|
||||
this.schema.description?.suggested_value ||
|
||||
this.schema.default ||
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
private _handleCheckboxChange(ev: Event) {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
@ -21,6 +20,7 @@ import {
|
||||
HaFormMultiSelectData,
|
||||
HaFormMultiSelectSchema,
|
||||
} from "./ha-form";
|
||||
import "../ha-icon";
|
||||
|
||||
@customElement("ha-form-multi_select")
|
||||
export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||
@ -64,11 +64,11 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||
input-aria-haspopup="listbox"
|
||||
autocomplete="off"
|
||||
>
|
||||
<iron-icon
|
||||
<ha-icon
|
||||
icon="paper-dropdown-menu:arrow-drop-down"
|
||||
suffix
|
||||
slot="suffix"
|
||||
></iron-icon>
|
||||
></ha-icon>
|
||||
</paper-input>
|
||||
</div>
|
||||
<paper-listbox
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
@ -47,7 +47,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
.autoValidate=${this.schema.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
toggles
|
||||
.active=${this._unmaskedPassword}
|
||||
slot="suffix"
|
||||
@ -56,7 +56,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
title="Click to toggle between masked and clear password"
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
>
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
</paper-input>
|
||||
`
|
||||
: html`
|
||||
|
||||
@ -30,7 +30,7 @@ export interface HaFormBaseSchema {
|
||||
default?: HaFormData;
|
||||
required?: boolean;
|
||||
optional?: boolean;
|
||||
description?: { suffix?: string };
|
||||
description?: { suffix?: string; suggested_value?: HaFormData };
|
||||
}
|
||||
|
||||
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
||||
|
||||
23
src/components/ha-icon-button-arrow-next.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
export class HaIconButtonArrowNext extends HaIconButton {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:arrow-right"
|
||||
: "hass:arrow-left";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-icon-button-arrow-next": HaIconButtonArrowNext;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon-button-arrow-next", HaIconButtonArrowNext);
|
||||
23
src/components/ha-icon-button-arrow-prev.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
export class HaIconButtonArrowPrev extends HaIconButton {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:arrow-left"
|
||||
: "hass:arrow-right";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-icon-button-arrow-prev": HaIconButtonArrowPrev;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon-button-arrow-prev", HaIconButtonArrowPrev);
|
||||
23
src/components/ha-icon-button-next.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
export class HaIconButtonNext extends HaIconButton {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:chevron-right"
|
||||
: "hass:chevron-left";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-icon-button-next": HaIconButtonNext;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon-button-next", HaIconButtonNext);
|
||||
23
src/components/ha-icon-button-prev.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
export class HaIconButtonPrev extends HaIconButton {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:chevron-left"
|
||||
: "hass:chevron-right";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-icon-button-prev": HaIconButtonPrev;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon-button-prev", HaIconButtonPrev);
|
||||
56
src/components/ha-icon-button.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
TemplateResult,
|
||||
property,
|
||||
LitElement,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "./ha-icon";
|
||||
|
||||
@customElement("ha-icon-button")
|
||||
export class HaIconButton extends LitElement {
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
@property({ type: String }) icon = "";
|
||||
|
||||
@property({ type: String }) label = "";
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-icon-button
|
||||
.label=${this.label || this.icon}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
<ha-icon .icon=${this.icon}></ha-icon>
|
||||
</mwc-icon-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(ev) {
|
||||
if (this.disabled) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
}
|
||||
ha-icon {
|
||||
display: inline-flex;
|
||||
vertical-align: initial;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-icon-button": HaIconButton;
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,16 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
// Not duplicate, this is for typing.
|
||||
// eslint-disable-next-line
|
||||
import { HaIcon } from "./ha-icon";
|
||||
import { HaSvgIcon } from "./ha-svg-icon";
|
||||
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
|
||||
|
||||
export class HaIconNext extends HaIcon {
|
||||
export class HaIconNext extends HaSvgIcon {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
this.path =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:chevron-right"
|
||||
: "hass:chevron-left";
|
||||
? mdiChevronRight
|
||||
: mdiChevronLeft;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
// Not duplicate, this is for typing.
|
||||
// eslint-disable-next-line
|
||||
import { HaIcon } from "./ha-icon";
|
||||
import { HaSvgIcon } from "./ha-svg-icon";
|
||||
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
|
||||
|
||||
export class HaIconPrev extends HaIcon {
|
||||
export class HaIconPrev extends HaSvgIcon {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
this.path =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:chevron-left"
|
||||
: "hass:chevron-right";
|
||||
? mdiChevronLeft
|
||||
: mdiChevronRight;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,34 +1,133 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import type { IronIconElement } from "@polymer/iron-icon/iron-icon";
|
||||
import { Constructor } from "../types";
|
||||
import { get, Store } from "idb-keyval";
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
html,
|
||||
TemplateResult,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import "./ha-svg-icon";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { iconMetadata } from "../resources/icon-metadata";
|
||||
import { IconMetadata } from "../types";
|
||||
|
||||
const ironIconClass = customElements.get("iron-icon") as Constructor<
|
||||
IronIconElement
|
||||
>;
|
||||
|
||||
let loaded = false;
|
||||
|
||||
export class HaIcon extends ironIconClass {
|
||||
private _iconsetName?: string;
|
||||
|
||||
public listen(
|
||||
node: EventTarget | null,
|
||||
eventName: string,
|
||||
methodName: string
|
||||
): void {
|
||||
super.listen(node, eventName, methodName);
|
||||
|
||||
if (!loaded && this._iconsetName === "mdi") {
|
||||
loaded = true;
|
||||
import(/* webpackChunkName: "mdi-icons" */ "../resources/mdi-icons");
|
||||
}
|
||||
}
|
||||
interface Icons {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface Chunks {
|
||||
[key: string]: Promise<Icons>;
|
||||
}
|
||||
|
||||
const iconStore = new Store("hass-icon-db", "mdi-icon-store");
|
||||
const chunks: Chunks = {};
|
||||
const MDI_PREFIXES = ["mdi", "hass", "hassio"];
|
||||
|
||||
const findIconChunk = (icon): string => {
|
||||
let lastChunk: IconMetadata;
|
||||
for (const chunk of iconMetadata) {
|
||||
if (chunk.start !== undefined && icon < chunk.start) {
|
||||
break;
|
||||
}
|
||||
lastChunk = chunk;
|
||||
}
|
||||
return lastChunk!.file;
|
||||
};
|
||||
|
||||
const debouncedWriteCache = debounce(async () => {
|
||||
const keys = Object.keys(chunks);
|
||||
const iconsSets: Icons[] = await Promise.all(Object.values(chunks));
|
||||
// We do a batch opening the store just once, for (considerable) performance
|
||||
iconStore._withIDBStore("readwrite", (store) => {
|
||||
iconsSets.forEach((icons, idx) => {
|
||||
Object.entries(icons).forEach(([name, path]) => {
|
||||
store.put(path, name);
|
||||
});
|
||||
delete chunks[keys[idx]];
|
||||
});
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
@customElement("ha-icon")
|
||||
export class HaIcon extends LitElement {
|
||||
@property() public icon?: string;
|
||||
|
||||
@property() private _path?: string;
|
||||
|
||||
@property() private _noMdi = false;
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("icon")) {
|
||||
this._loadIcon();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.icon) {
|
||||
return html``;
|
||||
}
|
||||
if (this._noMdi) {
|
||||
return html`<iron-icon .icon=${this.icon}></iron-icon>`;
|
||||
}
|
||||
return html`<ha-svg-icon .path=${this._path}></ha-svg-icon>`;
|
||||
}
|
||||
|
||||
private async _loadIcon() {
|
||||
if (!this.icon) {
|
||||
return;
|
||||
}
|
||||
const icon = this.icon.split(":", 2);
|
||||
if (!MDI_PREFIXES.includes(icon[0])) {
|
||||
this._noMdi = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this._noMdi = false;
|
||||
|
||||
const iconName = icon[1];
|
||||
const cachedPath: string = await get(iconName, iconStore);
|
||||
if (cachedPath) {
|
||||
this._path = cachedPath;
|
||||
return;
|
||||
}
|
||||
const chunk = findIconChunk(iconName);
|
||||
|
||||
if (chunk in chunks) {
|
||||
this._setPath(chunks[chunk], iconName);
|
||||
return;
|
||||
}
|
||||
const iconPromise = fetch(`/static/mdi/${chunk}.json`).then((response) =>
|
||||
response.json()
|
||||
);
|
||||
chunks[chunk] = iconPromise;
|
||||
this._setPath(iconPromise, iconName);
|
||||
debouncedWriteCache();
|
||||
}
|
||||
|
||||
private async _setPath(promise: Promise<Icons>, iconName: string) {
|
||||
const iconPack = await promise;
|
||||
this._path = iconPack[iconName];
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
fill: currentcolor;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-icon": HaIcon;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon", HaIcon);
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
import "@polymer/iron-iconset-svg/iron-iconset-svg";
|
||||
|
||||
const IronIconsetClass = customElements.get("iron-iconset-svg");
|
||||
|
||||
class HaIconset extends IronIconsetClass {
|
||||
/**
|
||||
* Fire 'iron-iconset-added' event at next microtask.
|
||||
*/
|
||||
_fireIronIconsetAdded() {
|
||||
this.async(() => this.fire("iron-iconset-added", this, { node: window }));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* When name is changed, register iconset metadata
|
||||
*
|
||||
*/
|
||||
_nameChanged() {
|
||||
this._meta.value = null;
|
||||
this._meta.key = this.name;
|
||||
this._meta.value = this;
|
||||
if (this.ownerDocument && this.ownerDocument.readyState === "loading") {
|
||||
// Document still loading. It could be that not all icons in the iconset are parsed yet.
|
||||
this.ownerDocument.addEventListener("DOMContentLoaded", () => {
|
||||
this._fireIronIconsetAdded();
|
||||
});
|
||||
} else {
|
||||
this._fireIronIconsetAdded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-iconset-svg", HaIconset);
|
||||
@ -65,6 +65,7 @@ class HaLabelBadge extends LitElement {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
padding: var(--ha-label-badge-padding, 0 0 0 0);
|
||||
}
|
||||
.label-badge {
|
||||
position: relative;
|
||||
|
||||
@ -12,6 +12,8 @@ class HaMarkdown extends UpdatingElement {
|
||||
|
||||
@property({ type: Boolean }) public allowSvg = false;
|
||||
|
||||
@property({ type: Boolean }) public breaks = false;
|
||||
|
||||
protected update(changedProps) {
|
||||
super.update(changedProps);
|
||||
|
||||
@ -26,7 +28,7 @@ class HaMarkdown extends UpdatingElement {
|
||||
this.innerHTML = await worker.renderMarkdown(
|
||||
this.content,
|
||||
{
|
||||
breaks: true,
|
||||
breaks: this.breaks,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
},
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@ -13,6 +12,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { subscribeNotifications } from "../data/persistent_notification";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
|
||||
@customElement("ha-menu-button")
|
||||
class HaMenuButton extends LitElement {
|
||||
@ -55,11 +55,11 @@ class HaMenuButton extends LitElement {
|
||||
(entityId) => computeDomain(entityId) === "configurator"
|
||||
));
|
||||
return html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
.icon=${this.hassio ? "hassio:menu" : "hass:menu"}
|
||||
icon="hass:menu"
|
||||
@click=${this._toggleMenu}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
${hasNotifications ? html` <div class="dot"></div> ` : ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import type { PaperIconButtonElement } from "@polymer/paper-icon-button/paper-icon-button";
|
||||
import type { Constructor } from "../types";
|
||||
|
||||
const paperIconButtonClass = customElements.get(
|
||||
"paper-icon-button"
|
||||
) as Constructor<PaperIconButtonElement>;
|
||||
|
||||
export class HaPaperIconButtonArrowNext extends paperIconButtonClass {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:arrow-right"
|
||||
: "hass:arrow-left";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-paper-icon-button-arrow-next": HaPaperIconButtonArrowNext;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
"ha-paper-icon-button-arrow-next",
|
||||
HaPaperIconButtonArrowNext
|
||||
);
|
||||
@ -1,38 +0,0 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import type { PaperIconButtonElement } from "@polymer/paper-icon-button/paper-icon-button";
|
||||
import type { Constructor } from "../types";
|
||||
|
||||
const paperIconButtonClass = customElements.get(
|
||||
"paper-icon-button"
|
||||
) as Constructor<PaperIconButtonElement>;
|
||||
|
||||
export class HaPaperIconButtonArrowPrev extends paperIconButtonClass {
|
||||
public hassio?: boolean;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? this.hassio
|
||||
? "hassio:arrow-left"
|
||||
: "hass:arrow-left"
|
||||
: this.hassio
|
||||
? "hassio:arrow-right"
|
||||
: "hass:arrow-right";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-paper-icon-button-arrow-prev": HaPaperIconButtonArrowPrev;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
"ha-paper-icon-button-arrow-prev",
|
||||
HaPaperIconButtonArrowPrev
|
||||
);
|
||||
@ -1,29 +0,0 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import type { PaperIconButtonElement } from "@polymer/paper-icon-button/paper-icon-button";
|
||||
import type { Constructor } from "../types";
|
||||
|
||||
const paperIconButtonClass = customElements.get(
|
||||
"paper-icon-button"
|
||||
) as Constructor<PaperIconButtonElement>;
|
||||
|
||||
export class HaPaperIconButtonNext extends paperIconButtonClass {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:chevron-right"
|
||||
: "hass:chevron-left";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-paper-icon-button-next": HaPaperIconButtonNext;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-paper-icon-button-next", HaPaperIconButtonNext);
|
||||
@ -1,29 +0,0 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import type { PaperIconButtonElement } from "@polymer/paper-icon-button/paper-icon-button";
|
||||
import type { Constructor } from "../types";
|
||||
|
||||
const paperIconButtonClass = customElements.get(
|
||||
"paper-icon-button"
|
||||
) as Constructor<PaperIconButtonElement>;
|
||||
|
||||
export class HaPaperIconButtonPrev extends paperIconButtonClass {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:chevron-left"
|
||||
: "hass:chevron-right";
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-paper-icon-button-prev": HaPaperIconButtonPrev;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-paper-icon-button-prev", HaPaperIconButtonPrev);
|
||||
@ -1,5 +1,6 @@
|
||||
import { mdiBell, mdiCellphoneSettingsVariant } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "./ha-icon-button";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import type { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
@ -28,6 +29,7 @@ import {
|
||||
getExternalConfig,
|
||||
} from "../external_app/external_config";
|
||||
import type { HomeAssistant, PanelInfo } from "../types";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-icon";
|
||||
import "./ha-menu-button";
|
||||
import "./user/ha-user-badge";
|
||||
@ -151,13 +153,13 @@ class HaSidebar extends LitElement {
|
||||
<div class="menu">
|
||||
${!this.narrow
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
.icon=${hass.dockedSidebar === "docked"
|
||||
? "hass:menu-open"
|
||||
: "hass:menu"}
|
||||
@click=${this._toggleSidebar}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
<span class="title">Home Assistant</span>
|
||||
@ -205,10 +207,10 @@ class HaSidebar extends LitElement {
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
<paper-icon-item>
|
||||
<ha-icon
|
||||
<ha-svg-icon
|
||||
slot="item-icon"
|
||||
icon="hass:cellphone-settings-variant"
|
||||
></ha-icon>
|
||||
.path=${mdiCellphoneSettingsVariant}
|
||||
></ha-svg-icon>
|
||||
<span class="item-text">
|
||||
${hass.localize("ui.sidebar.external_app_configuration")}
|
||||
</span>
|
||||
@ -230,7 +232,7 @@ class HaSidebar extends LitElement {
|
||||
aria-role="option"
|
||||
@click=${this._handleShowNotificationDrawer}
|
||||
>
|
||||
<ha-icon slot="item-icon" icon="hass:bell"></ha-icon>
|
||||
<ha-svg-icon slot="item-icon" .path=${mdiBell}></ha-svg-icon>
|
||||
${!this.expanded && notificationCount > 0
|
||||
? html`
|
||||
<span class="notification-badge" slot="item-icon">
|
||||
@ -494,13 +496,13 @@ class HaSidebar extends LitElement {
|
||||
width: 256px;
|
||||
}
|
||||
|
||||
.menu paper-icon-button {
|
||||
.menu ha-icon-button {
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
:host([expanded]) .menu paper-icon-button {
|
||||
:host([expanded]) .menu ha-icon-button {
|
||||
margin-right: 23px;
|
||||
}
|
||||
:host([expanded][_rtl]) .menu paper-icon-button {
|
||||
:host([expanded][_rtl]) .menu ha-icon-button {
|
||||
margin-right: 0px;
|
||||
margin-left: 23px;
|
||||
}
|
||||
@ -562,7 +564,8 @@ class HaSidebar extends LitElement {
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
ha-icon[slot="item-icon"] {
|
||||
ha-icon[slot="item-icon"],
|
||||
ha-svg-icon[slot="item-icon"] {
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
|
||||
@ -603,7 +606,8 @@ class HaSidebar extends LitElement {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a.iron-selected paper-icon-item ha-icon {
|
||||
a.iron-selected paper-icon-item ha-icon,
|
||||
a.iron-selected paper-icon-item ha-svg-icon {
|
||||
color: var(--sidebar-selected-icon-color);
|
||||
}
|
||||
|
||||
@ -665,7 +669,7 @@ class HaSidebar extends LitElement {
|
||||
padding: 0px 6px;
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
ha-icon + .notification-badge {
|
||||
ha-svg-icon + .notification-badge {
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 26px;
|
||||
@ -710,7 +714,7 @@ class HaSidebar extends LitElement {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:host([_rtl]) .menu paper-icon-button {
|
||||
:host([_rtl]) .menu ha-icon-button {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
52
src/components/ha-svg-icon.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
svg,
|
||||
SVGTemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
@customElement("ha-svg-icon")
|
||||
export class HaSvgIcon extends LitElement {
|
||||
@property() public path?: string;
|
||||
|
||||
protected render(): SVGTemplateResult {
|
||||
return svg`
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false">
|
||||
<g>
|
||||
${this.path ? svg`<path d=${this.path}></path>` : ""}
|
||||
</g>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
fill: currentcolor;
|
||||
width: var(--mdc-icon-size, 24px);
|
||||
height: var(--mdc-icon-size, 24px);
|
||||
}
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-svg-icon": HaSvgIcon;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "./ha-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@ -30,7 +30,7 @@ class HaWaterHeaterControl extends EventsMixin(PolymerElement) {
|
||||
font-size: 200%;
|
||||
text-align: right;
|
||||
}
|
||||
paper-icon-button {
|
||||
ha-icon-button {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
@ -40,16 +40,16 @@ class HaWaterHeaterControl extends EventsMixin(PolymerElement) {
|
||||
<div id="target_temperature">[[value]] [[units]]</div>
|
||||
<div class="control-buttons">
|
||||
<div>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:chevron-up"
|
||||
on-click="incrementValue"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<div>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:chevron-down"
|
||||
on-click="decrementValue"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../ha-icon-button";
|
||||
import { Circle, Layer, Map, Marker } from "leaflet";
|
||||
import {
|
||||
css,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
@ -120,7 +120,7 @@ export type Trigger =
|
||||
| DeviceTrigger;
|
||||
|
||||
export interface LogicalCondition {
|
||||
condition: "and" | "or";
|
||||
condition: "and" | "not" | "or";
|
||||
conditions: Condition[];
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf"];
|
||||
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
|
||||
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
|
||||
handler,
|
||||
show_advanced_options: Boolean(hass.userData?.showAdvanced),
|
||||
});
|
||||
|
||||
export const fetchConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||
|
||||
@ -15,6 +15,7 @@ export interface DeviceRegistryEntry {
|
||||
via_device_id?: string;
|
||||
area_id?: string;
|
||||
name_by_user?: string;
|
||||
entry_type: "service" | null;
|
||||
}
|
||||
|
||||
export interface DeviceEntityLookup {
|
||||
|
||||
@ -26,6 +26,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
auto_update: boolean;
|
||||
url: null | string;
|
||||
detached: boolean;
|
||||
documentation: boolean;
|
||||
available: boolean;
|
||||
arch: "armhf" | "aarch64" | "i386" | "amd64";
|
||||
machine: any;
|
||||
@ -46,6 +47,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
auto_uart: boolean;
|
||||
icon: boolean;
|
||||
logo: boolean;
|
||||
stage: "stable" | "experimental" | "deprecated";
|
||||
changelog: boolean;
|
||||
hassio_api: boolean;
|
||||
hassio_role: "default" | "homeassistant" | "manager" | "admin";
|
||||
@ -135,6 +137,13 @@ export const fetchHassioAddonLogs = async (
|
||||
return hass.callApi<string>("GET", `hassio/addons/${slug}/logs`);
|
||||
};
|
||||
|
||||
export const fetchHassioAddonDocumentation = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
) => {
|
||||
return hass.callApi<string>("GET", `hassio/addons/${slug}/documentation`);
|
||||
};
|
||||
|
||||
export const setHassioAddonOption = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string,
|
||||
@ -166,6 +175,13 @@ export const installHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const restartHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
return hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/restart`
|
||||
);
|
||||
};
|
||||
|
||||
export const uninstallHassioAddon = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
|
||||
@ -27,3 +27,23 @@ export const fetchHassioHassOsInfo = async (hass: HomeAssistant) => {
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const rebootHost = async (hass: HomeAssistant) => {
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/host/reboot");
|
||||
};
|
||||
|
||||
export const shutdownHost = async (hass: HomeAssistant) => {
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/host/shutdown");
|
||||
};
|
||||
|
||||
export const updateOS = async (hass: HomeAssistant) => {
|
||||
return hass.callApi<HassioResponse<void>>("POST", "hassio/os/update");
|
||||
};
|
||||
|
||||
export const changeHostOptions = async (hass: HomeAssistant, options: any) => {
|
||||
return hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
"hassio/host/options",
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
@ -37,8 +37,11 @@ export const fetchHassioSupervisorInfo = async (hass: HomeAssistant) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchSupervisorLogs = async (hass: HomeAssistant) => {
|
||||
return hass.callApi<string>("GET", "hassio/supervisor/logs");
|
||||
export const fetchHassioLogs = async (
|
||||
hass: HomeAssistant,
|
||||
provider: string
|
||||
) => {
|
||||
return hass.callApi<string>("GET", `hassio/${provider}/logs`);
|
||||
};
|
||||
|
||||
export const createHassioSession = async (hass: HomeAssistant) => {
|
||||
|
||||
@ -7,6 +7,7 @@ export const createOptionsFlow = (hass: HomeAssistant, handler: string) =>
|
||||
"config/config_entries/options/flow",
|
||||
{
|
||||
handler,
|
||||
show_advanced_options: Boolean(hass.userData?.showAdvanced),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -3,22 +3,22 @@ import { HomeAssistant, WeatherEntity } from "../types";
|
||||
export const weatherImages = {
|
||||
"clear-night": "/static/images/weather/night.png",
|
||||
cloudy: "/static/images/weather/cloudy.png",
|
||||
fog: "/static/images/weather/cloudy.png",
|
||||
lightning: "/static/images/weather/lightning.png",
|
||||
"lightning-rainy": "/static/images/weather/lightning-rainy.png",
|
||||
partlycloudy: "/static/images/weather/partly-cloudy.png",
|
||||
pouring: "/static/images/weather/pouring.png",
|
||||
rainy: "/static/images/weather/rainy.png",
|
||||
hail: "/static/images/weather/rainy.png",
|
||||
snowy: "/static/images/weather/snowy.png",
|
||||
"snowy-rainy": "/static/images/weather/snowy.png",
|
||||
sunny: "/static/images/weather/sunny.png",
|
||||
windy: "/static/images/weather/windy.png",
|
||||
"windy-variant": "/static/images/weather/windy.png",
|
||||
};
|
||||
|
||||
export const weatherIcons = {
|
||||
exceptional: "hass:alert-circle-outline",
|
||||
fog: "hass:weather-fog",
|
||||
hail: "hass:weather-hail",
|
||||
"snowy-rainy": "hass:weather-snowy-rainy",
|
||||
"windy-variant": "hass:weather-windy-variant",
|
||||
};
|
||||
|
||||
export const cardinalDirections = [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../components/ha-icon-button";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
@ -146,13 +146,13 @@ class DataEntryFlowDialog extends LitElement {
|
||||
// to reset the element.
|
||||
""
|
||||
: html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.dismiss"
|
||||
)}
|
||||
icon="hass:close"
|
||||
dialog-dismiss
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
${this._step === null
|
||||
? // Show handler picker
|
||||
html`
|
||||
@ -336,7 +336,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
paper-icon-button {
|
||||
ha-icon-button {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
margin: 16px 16px 0 0;
|
||||
|
||||
@ -59,7 +59,9 @@ export const showConfigFlowDialog = (
|
||||
);
|
||||
|
||||
return description
|
||||
? html` <ha-markdown allowsvg .content=${description}></ha-markdown> `
|
||||
? html`
|
||||
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
||||
`
|
||||
: "";
|
||||
},
|
||||
|
||||
@ -78,7 +80,9 @@ export const showConfigFlowDialog = (
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
? html` <ha-markdown allowsvg .content=${description}></ha-markdown> `
|
||||
? html`
|
||||
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
||||
`
|
||||
: "";
|
||||
},
|
||||
|
||||
@ -112,7 +116,13 @@ export const showConfigFlowDialog = (
|
||||
)}
|
||||
</p>
|
||||
${description
|
||||
? html` <ha-markdown allowsvg .content=${description}></ha-markdown> `
|
||||
? html`
|
||||
<ha-markdown
|
||||
allowsvg
|
||||
breaks
|
||||
.content=${description}
|
||||
></ha-markdown>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
},
|
||||
@ -128,7 +138,13 @@ export const showConfigFlowDialog = (
|
||||
|
||||
return html`
|
||||
${description
|
||||
? html` <ha-markdown allowsvg .content=${description}></ha-markdown> `
|
||||
? html`
|
||||
<ha-markdown
|
||||
allowsvg
|
||||
breaks
|
||||
.content=${description}
|
||||
></ha-markdown>
|
||||
`
|
||||
: ""}
|
||||
<p>
|
||||
${hass.localize(
|
||||
|
||||
@ -50,7 +50,13 @@ export const showOptionsFlowDialog = (
|
||||
);
|
||||
|
||||
return description
|
||||
? html` <ha-markdown allowsvg .content=${description}></ha-markdown> `
|
||||
? html`
|
||||
<ha-markdown
|
||||
breaks
|
||||
allowsvg
|
||||
.content=${description}
|
||||
></ha-markdown>
|
||||
`
|
||||
: "";
|
||||
},
|
||||
|
||||
@ -69,7 +75,13 @@ export const showOptionsFlowDialog = (
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
? html` <ha-markdown allowsvg .content=${description}></ha-markdown> `
|
||||
? html`
|
||||
<ha-markdown
|
||||
allowsvg
|
||||
breaks
|
||||
.content=${description}
|
||||
></ha-markdown>
|
||||
`
|
||||
: "";
|
||||
},
|
||||
|
||||
|
||||
@ -122,10 +122,14 @@ class StepFlowForm extends LitElement {
|
||||
|
||||
const data = {};
|
||||
this.step.data_schema.forEach((field) => {
|
||||
if ("default" in field) {
|
||||
if (field.description?.suggested_value) {
|
||||
data[field.name] = field.description.suggested_value;
|
||||
} else if ("default" in field) {
|
||||
data[field.name] = field.default;
|
||||
}
|
||||
});
|
||||
|
||||
this._stepData = data;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@ -41,8 +41,8 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
/* overrule the ha-style-dialog max-height on small screens */
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
more-info-controls {
|
||||
--more-info-header-background: var(--primary-color);
|
||||
--more-info-header-color: var(--text-primary-color);
|
||||
--more-info-header-background: var(--app-header-background-color);
|
||||
--more-info-header-color: var(--app-header-text-color, white);
|
||||
}
|
||||
:host {
|
||||
width: 100% !important;
|
||||
|
||||
@ -443,12 +443,6 @@ class MoreInfoClimate extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.container-hvac_modes iron-icon,
|
||||
.container-fan_list iron-icon,
|
||||
.container-swing_list iron-icon {
|
||||
margin: 22px 16px 0 0;
|
||||
}
|
||||
|
||||
ha-paper-dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ class MoreInfoConfigurator extends PolymerElement {
|
||||
<div class="layout vertical">
|
||||
<template is="dom-if" if="[[isConfigurable]]">
|
||||
<ha-markdown
|
||||
breaks
|
||||
content="[[stateObj.attributes.description]]"
|
||||
></ha-markdown>
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
@ -78,18 +78,18 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div class="container-direction">
|
||||
<div class="direction">
|
||||
<div>[[localize('ui.card.fan.direction')]]</div>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:rotate-left"
|
||||
on-click="onDirectionReverse"
|
||||
title="[[localize('ui.card.fan.reverse')]]"
|
||||
disabled="[[computeIsRotatingReverse(stateObj)]]"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
icon="hass:rotate-right"
|
||||
on-click="onDirectionForward"
|
||||
title="[[localize('ui.card.fan.forward')]]"
|
||||
disabled="[[computeIsRotatingForward(stateObj)]]"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -11,6 +11,7 @@ import "../../../components/ha-labeled-slider";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../components/ha-icon-button";
|
||||
|
||||
const FEATURE_CLASS_NAMES = {
|
||||
1: "has-brightness",
|
||||
@ -168,11 +169,11 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
saturation-segments="{{saturationSegments}}"
|
||||
>
|
||||
</ha-color-picker>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="mdi:palette"
|
||||
on-click="segmentClick"
|
||||
class="control segmentationButton"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
|
||||
<div class="control effect_list">
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
@ -11,6 +10,7 @@ import { attributeClassNames } from "../../../common/entity/attribute_class_name
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import "../../../components/ha-paper-slider";
|
||||
import "../../../components/ha-icon";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import HassMediaPlayerEntity from "../../../util/hass-media-player-model";
|
||||
@ -28,7 +28,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
paper-icon-button[highlight] {
|
||||
ha-icon-button[highlight] {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
max-height: 40px;
|
||||
}
|
||||
|
||||
iron-icon.source-input {
|
||||
ha-icon.source-input {
|
||||
padding: 7px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
@ -65,34 +65,34 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div class$="[[computeClassNames(stateObj)]]">
|
||||
<div class="layout horizontal">
|
||||
<div class="flex">
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:power"
|
||||
highlight$="[[playerObj.isOff]]"
|
||||
on-click="handleTogglePower"
|
||||
hidden$="[[computeHidePowerButton(playerObj)]]"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<div>
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[computeShowPlaybackControls(playerObj)]]"
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:skip-previous"
|
||||
on-click="handlePrevious"
|
||||
hidden$="[[!playerObj.supportsPreviousTrack]]"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
icon="[[computePlaybackControlIcon(playerObj)]]"
|
||||
on-click="handlePlaybackControl"
|
||||
hidden$="[[!computePlaybackControlIcon(playerObj)]]"
|
||||
highlight=""
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
icon="hass:skip-next"
|
||||
on-click="handleNext"
|
||||
hidden$="[[!playerObj.supportsNextTrack]]"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,36 +101,36 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
class="volume_buttons center horizontal layout"
|
||||
hidden$="[[computeHideVolumeButtons(playerObj)]]"
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
on-click="handleVolumeTap"
|
||||
icon="hass:volume-off"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
id="volumeDown"
|
||||
disabled$="[[playerObj.isMuted]]"
|
||||
on-mousedown="handleVolumeDown"
|
||||
on-touchstart="handleVolumeDown"
|
||||
on-touchend="handleVolumeTouchEnd"
|
||||
icon="hass:volume-medium"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
id="volumeUp"
|
||||
disabled$="[[playerObj.isMuted]]"
|
||||
on-mousedown="handleVolumeUp"
|
||||
on-touchstart="handleVolumeUp"
|
||||
on-touchend="handleVolumeTouchEnd"
|
||||
icon="hass:volume-high"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<div
|
||||
class="volume center horizontal layout"
|
||||
hidden$="[[!playerObj.supportsVolumeSet]]"
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
on-click="handleVolumeTap"
|
||||
hidden$="[[playerObj.supportsVolumeButtons]]"
|
||||
icon="[[computeMuteVolumeIcon(playerObj)]]"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<ha-paper-slider
|
||||
disabled$="[[playerObj.isMuted]]"
|
||||
min="0"
|
||||
@ -148,7 +148,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
class="controls layout horizontal justified"
|
||||
hidden$="[[computeHideSelectSource(playerObj)]]"
|
||||
>
|
||||
<iron-icon class="source-input" icon="hass:login-variant"></iron-icon>
|
||||
<ha-icon class="source-input" icon="hass:login-variant"></ha-icon>
|
||||
<ha-paper-dropdown-menu
|
||||
class="flex source-input"
|
||||
dynamic-align=""
|
||||
@ -170,7 +170,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<!-- SOUND MODE PICKER -->
|
||||
<template is="dom-if" if="[[!computeHideSelectSoundMode(playerObj)]]">
|
||||
<div class="controls layout horizontal justified">
|
||||
<iron-icon class="source-input" icon="hass:music-note"></iron-icon>
|
||||
<ha-icon class="source-input" icon="hass:music-note"></ha-icon>
|
||||
<ha-paper-dropdown-menu
|
||||
class="flex source-input"
|
||||
dynamic-align
|
||||
@ -202,10 +202,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
value="{{ttsMessage}}"
|
||||
on-keydown="ttsCheckForEnter"
|
||||
></paper-input>
|
||||
<paper-icon-button
|
||||
icon="hass:send"
|
||||
on-click="sendTTS"
|
||||
></paper-icon-button>
|
||||
<ha-icon-button icon="hass:send" on-click="sendTTS"></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@ -122,9 +122,7 @@ class MoreInfoVacuum extends LitElement {
|
||||
? html`
|
||||
<div>
|
||||
<span>
|
||||
<iron-icon
|
||||
.icon=${stateObj.attributes.battery_icon}
|
||||
></iron-icon>
|
||||
<ha-icon .icon=${stateObj.attributes.battery_icon}></ha-icon>
|
||||
${stateObj.attributes.battery_level}%
|
||||
</span>
|
||||
</div>
|
||||
@ -147,14 +145,14 @@ class MoreInfoVacuum extends LitElement {
|
||||
).map(
|
||||
(item) => html`
|
||||
<div>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.icon=${item.icon}
|
||||
.entry=${item}
|
||||
@click=${this.callService}
|
||||
.title=${this.hass!.localize(
|
||||
`ui.dialogs.more_info_control.vacuum.${item.translationKey}`
|
||||
)}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
@ -190,7 +188,7 @@ class MoreInfoVacuum extends LitElement {
|
||||
style="justify-content: center; align-self: center; padding-top: 1.3em"
|
||||
>
|
||||
<span>
|
||||
<iron-icon icon="hass:fan"></iron-icon>
|
||||
<ha-icon icon="hass:fan"></ha-icon>
|
||||
${stateObj.attributes.fan_speed}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -28,16 +28,6 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.container-away_mode,
|
||||
.container-temperature,
|
||||
.container-operation_list,
|
||||
|
||||
.has-away_mode .container-away_mode,
|
||||
.has-target_temperature .container-temperature,
|
||||
.has-operation_mode .container-operation_list,
|
||||
|
||||
.container-operation_list iron-icon,
|
||||
|
||||
ha-paper-dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@ -10,6 +9,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "../../../components/ha-icon";
|
||||
|
||||
const cardinalDirections = [
|
||||
"N",
|
||||
@ -79,7 +79,7 @@ class MoreInfoWeather extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="flex">
|
||||
<iron-icon icon="hass:thermometer"></iron-icon>
|
||||
<ha-icon icon="hass:thermometer"></ha-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.temperature")}
|
||||
</div>
|
||||
@ -90,7 +90,7 @@ class MoreInfoWeather extends LitElement {
|
||||
${this._showValue(this.stateObj.attributes.pressure)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<iron-icon icon="hass:gauge"></iron-icon>
|
||||
<ha-icon icon="hass:gauge"></ha-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.air_pressure")}
|
||||
</div>
|
||||
@ -104,7 +104,7 @@ class MoreInfoWeather extends LitElement {
|
||||
${this._showValue(this.stateObj.attributes.humidity)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<iron-icon icon="hass:water-percent"></iron-icon>
|
||||
<ha-icon icon="hass:water-percent"></ha-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.humidity")}
|
||||
</div>
|
||||
@ -115,7 +115,7 @@ class MoreInfoWeather extends LitElement {
|
||||
${this._showValue(this.stateObj.attributes.wind_speed)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<iron-icon icon="hass:weather-windy"></iron-icon>
|
||||
<ha-icon icon="hass:weather-windy"></ha-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.wind_speed")}
|
||||
</div>
|
||||
@ -131,7 +131,7 @@ class MoreInfoWeather extends LitElement {
|
||||
${this._showValue(this.stateObj.attributes.visibility)
|
||||
? html`
|
||||
<div class="flex">
|
||||
<iron-icon icon="hass:eye"></iron-icon>
|
||||
<ha-icon icon="hass:eye"></ha-icon>
|
||||
<div class="main">
|
||||
${this.hass.localize("ui.card.weather.attributes.visibility")}
|
||||
</div>
|
||||
@ -151,9 +151,9 @@ class MoreInfoWeather extends LitElement {
|
||||
<div class="flex">
|
||||
${item.condition
|
||||
? html`
|
||||
<iron-icon
|
||||
<ha-icon
|
||||
.icon="${weatherIcons[item.condition]}"
|
||||
></iron-icon>
|
||||
></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
${!this._showValue(item.templow)
|
||||
@ -193,7 +193,7 @@ class MoreInfoWeather extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
iron-icon {
|
||||
ha-icon {
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
.section {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../components/ha-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@ -78,27 +78,27 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
</style>
|
||||
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.dismiss')]]"
|
||||
icon="hass:close"
|
||||
dialog-dismiss
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<div class="main-title" main-title="" on-click="enlarge">
|
||||
[[_computeStateName(stateObj)]]
|
||||
</div>
|
||||
<template is="dom-if" if="[[_computeConfig(hass)]]">
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
|
||||
icon="hass:settings"
|
||||
on-click="_gotoSettings"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_computeEdit(hass, stateObj)]]">
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.edit')]]"
|
||||
icon="hass:pencil"
|
||||
on-click="_gotoEdit"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</template>
|
||||
</app-toolbar>
|
||||
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/app-layout/app-drawer/app-drawer";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import "../../components/ha-paper-icon-button-prev";
|
||||
import "../../components/ha-icon-button-prev";
|
||||
import { subscribeNotifications } from "../../data/persistent_notification";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
@ -51,7 +50,7 @@ export class HuiNotificationDrawer extends EventsMixin(
|
||||
<app-drawer id="drawer" opened="{{open}}" disable-swipe align="start">
|
||||
<app-toolbar>
|
||||
<div main-title>[[localize('ui.notification_drawer.title')]]</div>
|
||||
<ha-paper-icon-button-prev on-click="_closeDrawer" aria-label$="[[localize('ui.notification_drawer.close')]]"></paper-icon-button>
|
||||
<ha-icon-button-prev on-click="_closeDrawer" aria-label$="[[localize('ui.notification_drawer.close')]]"></ha-icon-button-prev>
|
||||
</app-toolbar>
|
||||
<div class="notifications">
|
||||
<template is="dom-if" if="[[!_empty(notifications)]]">
|
||||
|
||||
@ -32,7 +32,10 @@ export class HuiPersistentNotificationItem extends LitElement {
|
||||
${this.notification.title}
|
||||
</span>
|
||||
|
||||
<ha-markdown content="${this.notification.message}"></ha-markdown>
|
||||
<ha-markdown
|
||||
breaks
|
||||
content="${this.notification.message}"
|
||||
></ha-markdown>
|
||||
|
||||
<div class="time">
|
||||
<span>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import type { PaperDialogScrollableElement } from "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
@ -176,12 +175,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.active=${Boolean(this.results)}
|
||||
icon="hass:microphone"
|
||||
@click=${this._toggleListening}
|
||||
>
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
@ -369,11 +368,11 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
z-index: 103;
|
||||
}
|
||||
|
||||
paper-icon-button {
|
||||
ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
paper-icon-button[active] {
|
||||
ha-icon-button[active] {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
|
||||
@ -2,9 +2,6 @@
|
||||
/* eslint-disable import/first */
|
||||
import "@polymer/paper-styles/typography";
|
||||
import { setPassiveTouchGestures } from "@polymer/polymer/lib/utils/settings";
|
||||
// For MDI icons. Needs to be part of main bundle or else it won't hook
|
||||
// properly into iron-meta, which is used to transfer iconsets to iron-icon.
|
||||
import "../components/ha-iconset-svg";
|
||||
import "../layouts/home-assistant";
|
||||
import "../resources/html-import/polyfill";
|
||||
import "../resources/roboto";
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
import "../auth/ha-authorize";
|
||||
import "../components/ha-iconset-svg";
|
||||
import "../resources/ha-style";
|
||||
import "../resources/roboto";
|
||||
|
||||
|
||||
@ -1 +0,0 @@
|
||||
import "../resources/hass-icons";
|
||||
@ -1,4 +1,3 @@
|
||||
import "../components/ha-iconset-svg";
|
||||
import "../onboarding/ha-onboarding";
|
||||
import "../resources/ha-style";
|
||||
import "../resources/roboto";
|
||||
|
||||
@ -49,7 +49,6 @@
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestPageJS %>";
|
||||
import "<%= latestHassIconsJS %>";
|
||||
window.providersPromise = fetch("/auth/providers", {
|
||||
credentials: "same-origin",
|
||||
});
|
||||
@ -62,7 +61,6 @@
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("<%= es5Compatibility %>");
|
||||
_ls("<%= es5PageJS %>");
|
||||
_ls("<%= es5HassIconsJS %>");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@ -51,6 +51,9 @@
|
||||
height: 112px;
|
||||
background-color: #THEMEC;
|
||||
}
|
||||
html {
|
||||
background-color: var(--primary-background-color, #fafafa);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -62,7 +65,6 @@
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestCoreJS %>";
|
||||
import "<%= latestAppJS %>";
|
||||
import "<%= latestHassIconsJS %>";
|
||||
window.customPanelJS = "<%= latestCustomPanelJS %>";
|
||||
</script>
|
||||
{% for extra_module in extra_modules -%}
|
||||
@ -79,7 +81,6 @@
|
||||
_ls("<%= es5Compatibility %>");
|
||||
_ls("<%= es5CoreJS %>");
|
||||
_ls("<%= es5AppJS %>");
|
||||
_ls("<%= es5HassIconsJS %>");
|
||||
{% for extra_script in extra_js_es5 -%}
|
||||
_ls("{{ extra_script }}");
|
||||
{% endfor -%}
|
||||
|
||||
@ -51,7 +51,6 @@
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestPageJS %>";
|
||||
import "<%= latestHassIconsJS %>";
|
||||
window.stepsPromise = fetch("/api/onboarding", {
|
||||
credentials: "same-origin",
|
||||
});
|
||||
@ -64,7 +63,6 @@
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("<%= es5Compatibility %>");
|
||||
_ls("<%= es5PageJS %>");
|
||||
_ls("<%= es5HassIconsJS %>");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-paper-icon-button-arrow-prev";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
@ -33,9 +33,9 @@ class HassLoadingScreen extends LitElement {
|
||||
></ha-menu-button>
|
||||
`
|
||||
: html`
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
<ha-icon-button-arrow-prev
|
||||
@click=${this._handleBack}
|
||||
></ha-paper-icon-button-arrow-prev>
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
</app-toolbar>
|
||||
<div class="content">
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-paper-icon-button-arrow-prev";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
|
||||
@customElement("hass-subpage")
|
||||
class HassSubpage extends LitElement {
|
||||
@ -25,12 +25,11 @@ class HassSubpage extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
<ha-icon-button-arrow-prev
|
||||
aria-label="Back"
|
||||
.hassio=${this.hassio}
|
||||
@click=${this._backTapped}
|
||||
class=${classMap({ hidden: !this.showBackButton })}
|
||||
></ha-paper-icon-button-arrow-prev>
|
||||
></ha-icon-button-arrow-prev>
|
||||
|
||||
<div main-title>${this.header}</div>
|
||||
<slot name="toolbar-icon"></slot>
|
||||
@ -66,12 +65,12 @@ class HassSubpage extends LitElement {
|
||||
}
|
||||
|
||||
ha-menu-button,
|
||||
ha-paper-icon-button-arrow-prev,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
ha-paper-icon-button-arrow-prev.hidden {
|
||||
ha-icon-button-arrow-prev.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { navigate } from "../common/navigate";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-paper-icon-button-arrow-prev";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import { HomeAssistant, Route } from "../types";
|
||||
import "../components/ha-icon";
|
||||
|
||||
@ -39,6 +39,8 @@ class HassTabsSubpage extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public hassio = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "main-page" }) public mainPage = false;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() public tabs!: PageNavigation[];
|
||||
@ -82,7 +84,7 @@ class HassTabsSubpage extends LitElement {
|
||||
<span class="name"
|
||||
>${page.translationKey
|
||||
? this.hass.localize(page.translationKey)
|
||||
: name}</span
|
||||
: page.name}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
@ -97,7 +99,7 @@ class HassTabsSubpage extends LitElement {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("route")) {
|
||||
this._activeTab = this.tabs.find((tab) =>
|
||||
this.route.prefix.includes(tab.path)
|
||||
`${this.route.prefix}${this.route.path}`.includes(tab.path)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -114,11 +116,20 @@ class HassTabsSubpage extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
aria-label="Back"
|
||||
.hassio=${this.hassio}
|
||||
@click=${this._backTapped}
|
||||
></ha-paper-icon-button-arrow-prev>
|
||||
${this.mainPage
|
||||
? html`
|
||||
<ha-menu-button
|
||||
.hass=${this.hass}
|
||||
.hassio=${this.hassio}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
aria-label="Back"
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
${this.narrow
|
||||
? html` <div class="main-title"><slot name="header"></slot></div> `
|
||||
: ""}
|
||||
@ -234,7 +245,7 @@ class HassTabsSubpage extends LitElement {
|
||||
}
|
||||
|
||||
ha-menu-button,
|
||||
ha-paper-icon-button-arrow-prev,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
flex-shrink: 0;
|
||||
pointer-events: auto;
|
||||
|
||||
@ -22,7 +22,7 @@ class ActionBadge extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="icon">
|
||||
<iron-icon .icon=${this.icon}></iron-icon>
|
||||
<ha-icon .icon=${this.icon}></ha-icon>
|
||||
${this.badgeIcon
|
||||
? html` <ha-icon class="badge" .icon=${this.badgeIcon}></ha-icon> `
|
||||
: ""}
|
||||
|
||||
@ -104,12 +104,12 @@ class HaConfigAreaPage extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:settings"
|
||||
.entry=${area}
|
||||
@click=${this._showSettings}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
|
||||
<div class="container">
|
||||
${!this.narrow
|
||||
|
||||
@ -114,11 +114,11 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
id="area_id"
|
||||
hasFab
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:help-circle"
|
||||
@click=${this._showHelp}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
?is-wide=${this.isWide}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
|
||||
@ -96,18 +96,18 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<div class="card-menu">
|
||||
${this.index !== 0
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:arrow-up"
|
||||
@click=${this._moveUp}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
${this.index !== this.totalActions - 1
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:arrow-down"
|
||||
@click=${this._moveDown}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
<paper-menu-button
|
||||
@ -117,10 +117,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
vertical-offset="-5"
|
||||
close-on-activate
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:dots-vertical"
|
||||
slot="dropdown-trigger"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
<paper-item
|
||||
@tap=${this._switchYamlMode}
|
||||
|
||||
@ -11,6 +11,7 @@ import type { HomeAssistant } from "../../../../types";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import "./types/ha-automation-condition-and";
|
||||
import "./types/ha-automation-condition-device";
|
||||
import "./types/ha-automation-condition-not";
|
||||
import "./types/ha-automation-condition-numeric_state";
|
||||
import "./types/ha-automation-condition-or";
|
||||
import "./types/ha-automation-condition-state";
|
||||
@ -23,6 +24,7 @@ const OPTIONS = [
|
||||
"device",
|
||||
"and",
|
||||
"or",
|
||||
"not",
|
||||
"state",
|
||||
"numeric_state",
|
||||
"sun",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-menu-button/paper-menu-button";
|
||||
import {
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { customElement } from "lit-element";
|
||||
import { HaLogicalCondition } from "./ha-automation-condition-logical";
|
||||
|
||||
@customElement("ha-automation-condition-not")
|
||||
export class HaNotCondition extends HaLogicalCondition {}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-condition-not": HaNotCondition;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
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 "../../../components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -15,7 +15,6 @@ import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@ -78,14 +77,14 @@ export class HaAutomationEditor extends LitElement {
|
||||
${!this.automationId
|
||||
? ""
|
||||
: html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.delete_automation"
|
||||
)}"
|
||||
icon="hass:delete"
|
||||
@click=${this._deleteConfirm}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`}
|
||||
${this._config
|
||||
? html`
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
@ -105,14 +105,14 @@ class HaAutomationPicker extends LitElement {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_info, automation) => html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.automation=${automation}
|
||||
@click=${this._showInfo}
|
||||
icon="hass:information-outline"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.show_info_automation"
|
||||
)}"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`,
|
||||
};
|
||||
columns.edit = {
|
||||
@ -126,7 +126,7 @@ class HaAutomationPicker extends LitElement {
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.icon=${automation.attributes.id
|
||||
? "hass:pencil"
|
||||
: "hass:pencil-off"}
|
||||
@ -134,7 +134,7 @@ class HaAutomationPicker extends LitElement {
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.show_info_automation"
|
||||
)}"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
${!automation.attributes.id
|
||||
? html`
|
||||
|
||||
@ -12,6 +12,13 @@ import "./ha-automation-editor";
|
||||
import "./ha-automation-picker";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
|
||||
const equal = (a: AutomationEntity[], b: AutomationEntity[]): boolean => {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return a.every((automation, index) => automation === b[index]);
|
||||
};
|
||||
|
||||
@customElement("ha-config-automation")
|
||||
class HaConfigAutomation extends HassRouterPage {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@ -26,7 +33,7 @@ class HaConfigAutomation extends HassRouterPage {
|
||||
|
||||
private _debouncedUpdateAutomations = debounce((pageEl) => {
|
||||
const newAutomations = this._getAutomations(this.hass.states);
|
||||
if (!this._equal(newAutomations, pageEl.automations)) {
|
||||
if (!equal(newAutomations, pageEl.automations)) {
|
||||
pageEl.automations = newAutomations;
|
||||
}
|
||||
}, 10);
|
||||
@ -82,13 +89,6 @@ class HaConfigAutomation extends HassRouterPage {
|
||||
pageEl.automationId = automationId === "new" ? null : automationId;
|
||||
}
|
||||
}
|
||||
|
||||
private _equal(a: AutomationEntity[], b: AutomationEntity[]): boolean {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return a.every((automation, index) => automation === b[index]);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
|
||||
@ -97,10 +97,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
vertical-offset="-5"
|
||||
close-on-activate
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:dots-vertical"
|
||||
slot="dropdown-trigger"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
<paper-item
|
||||
@tap=${this._switchYamlMode}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -154,11 +154,11 @@ class CloudAlexa extends LitElement {
|
||||
${
|
||||
emptyFilter
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:tune"
|
||||
@click=${this._openDomainToggler}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -167,11 +167,11 @@ class CloudGoogleAssistant extends LitElement {
|
||||
${
|
||||
emptyFilter
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:tune"
|
||||
@click=${this._openDomainToggler}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -65,7 +65,7 @@ class CloudLogin extends LocalizeMixin(
|
||||
.flash-msg {
|
||||
padding-right: 44px;
|
||||
}
|
||||
.flash-msg paper-icon-button {
|
||||
.flash-msg ha-icon-button {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
@ -110,8 +110,8 @@ class CloudLogin extends LocalizeMixin(
|
||||
<ha-card hidden$="[[!flashMessage]]">
|
||||
<div class="card-content flash-msg">
|
||||
[[flashMessage]]
|
||||
<paper-icon-button icon="hass:close" on-click="_dismissFlash"
|
||||
>[[localize('ui.panel.config.cloud.login.dismiss')]]</paper-icon-button
|
||||
<ha-icon-button icon="hass:close" on-click="_dismissFlash"
|
||||
>[[localize('ui.panel.config.cloud.login.dismiss')]]</ha-icon-button
|
||||
>
|
||||
<paper-ripple id="flashRipple" noink=""></paper-ripple>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
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 "../../../components/ha-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { sortStatesByName } from "../../../common/entity/states_sort_by_name";
|
||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../resources/ha-style";
|
||||
@ -20,12 +18,7 @@ import "./ha-form-customize";
|
||||
class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
ha-paper-icon-button-arrow-prev[hide] {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style include="ha-style"></style>
|
||||
<hass-tabs-subpage
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@ -28,11 +28,11 @@ class HaCustomizeAttribute extends PolymerElement {
|
||||
}
|
||||
</style>
|
||||
<div id="wrapper" class="form-group"></div>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="button"
|
||||
icon="[[getIcon(item.secondary)]]"
|
||||
on-click="tapButton"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../../components/ha-icon";
|
||||
|
||||
class HaCustomizeIcon extends PolymerElement {
|
||||
static get template() {
|
||||
@ -18,7 +18,7 @@ class HaCustomizeIcon extends PolymerElement {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
<iron-icon class="icon-image" icon="[[item.value]]"></iron-icon>
|
||||
<ha-icon class="icon-image" icon="[[item.value]]"></ha-icon>
|
||||
<paper-input
|
||||
disabled="[[item.secondary]]"
|
||||
label="icon"
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
|
||||
@ -28,24 +28,20 @@ import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
||||
export class HaDeviceEntitiesCard extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public deviceId!: string;
|
||||
|
||||
@property() public entities!: EntityRegistryStateEntry[];
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property() private _showDisabled = false;
|
||||
|
||||
@queryAll("#entities > *") private _entityRows?: LovelaceRow[];
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!changedProps.has("hass")) {
|
||||
return;
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
if (changedProps.has("hass")) {
|
||||
this._entityRows?.forEach((element) => {
|
||||
element.hass = this.hass;
|
||||
});
|
||||
return changedProps.size > 1;
|
||||
}
|
||||
this._entityRows?.forEach((element) => {
|
||||
element.hass = this.hass;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@ -159,7 +155,9 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
addEntitiesToLovelaceView(
|
||||
this,
|
||||
this.hass,
|
||||
this.entities.map((entity) => entity.entity_id)
|
||||
this.entities
|
||||
.filter((entity) => !entity.disabled_by)
|
||||
.map((entity) => entity.entity_id)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -167,11 +167,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: ""
|
||||
}
|
||||
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:settings"
|
||||
@click=${this._showSettings}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
|
||||
<div class="container">
|
||||
<div class="header fullwidth">
|
||||
@ -259,13 +259,13 @@ export class HaConfigDevicePage extends LitElement {
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.automations"
|
||||
)}
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
@click=${this._showAutomationDialog}
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.create"
|
||||
)}
|
||||
icon="hass:plus-circle"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
${this._related?.automation?.length
|
||||
? this._related.automation.map((automation) => {
|
||||
@ -317,26 +317,21 @@ export class HaConfigDevicePage extends LitElement {
|
||||
</div>
|
||||
<div class="column">
|
||||
${
|
||||
isComponentLoaded(this.hass, "scene")
|
||||
isComponentLoaded(this.hass, "scene") && entities.length
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.scenes"
|
||||
)}
|
||||
${
|
||||
entities.length
|
||||
? html`
|
||||
<paper-icon-button
|
||||
|
||||
<ha-icon-button
|
||||
@click=${this._createScene}
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.create"
|
||||
)}
|
||||
icon="hass:plus-circle"
|
||||
></paper-icon-button>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
|
||||
${
|
||||
@ -397,13 +392,13 @@ export class HaConfigDevicePage extends LitElement {
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.script.scripts"
|
||||
)}
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
@click=${this._showScriptDialog}
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.devices.script.create"
|
||||
)}
|
||||
icon="hass:plus-circle"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
${this._related?.script?.length
|
||||
? this._related.script.map((script) => {
|
||||
@ -562,7 +557,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-header paper-icon-button {
|
||||
.card-header ha-icon-button {
|
||||
margin-right: -8px;
|
||||
color: var(--primary-color);
|
||||
height: auto;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-tabs/paper-tab";
|
||||
import "@polymer/paper-tabs/paper-tabs";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
@ -94,25 +94,25 @@ export class DialogEntityEditor extends LitElement {
|
||||
@close-dialog=${this.closeDialog}
|
||||
>
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.dismiss"
|
||||
)}
|
||||
icon="hass:close"
|
||||
dialog-dismiss
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<div class="main-title" main-title>
|
||||
${stateObj ? computeStateName(stateObj) : entry?.name || entityId}
|
||||
</div>
|
||||
${stateObj
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.control"
|
||||
)}
|
||||
icon="hass:tune"
|
||||
@click=${this._openMoreInfo}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</app-toolbar>
|
||||
@ -264,8 +264,8 @@ export class DialogEntityEditor extends LitElement {
|
||||
/* overrule the ha-style-dialog max-height on small screens */
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
app-toolbar {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
background-color: var(--app-header-background-color);
|
||||
color: var(--app-header-text-color, white);
|
||||
}
|
||||
ha-paper-dialog {
|
||||
height: 100%;
|
||||
|
||||
@ -380,31 +380,31 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
`
|
||||
: html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
id="enable-btn"
|
||||
icon="hass:undo"
|
||||
@click=${this._enableSelected}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<paper-tooltip for="enable-btn">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.enable_selected.button"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
id="disable-btn"
|
||||
icon="hass:cancel"
|
||||
@click=${this._disableSelected}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<paper-tooltip for="disable-btn">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.disable_selected.button"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
id="remove-btn"
|
||||
icon="hass:delete"
|
||||
@click=${this._removeSelected}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<paper-tooltip for="remove-btn">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.picker.remove_selected.button"
|
||||
@ -443,7 +443,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
</div>`
|
||||
: ""}
|
||||
<paper-menu-button no-animations horizontal-align="right">
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass!.localize(
|
||||
"ui.panel.config.entities.picker.filter.filter"
|
||||
)}
|
||||
@ -452,7 +452,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
)}"
|
||||
icon="hass:filter-variant"
|
||||
slot="dropdown-trigger"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
<paper-icon-item @tap="${this._showDisabledChanged}">
|
||||
<paper-checkbox
|
||||
@ -745,6 +745,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.search-toolbar search-input {
|
||||
margin-left: 8px;
|
||||
top: 1px;
|
||||
}
|
||||
.search-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -764,7 +768,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
font-size: 16px;
|
||||
}
|
||||
.header-btns > mwc-button,
|
||||
.header-btns > paper-icon-button {
|
||||
.header-btns > ha-icon-button {
|
||||
margin: 8px;
|
||||
}
|
||||
.active-filters {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
@ -96,14 +96,14 @@ class HaInputSelectForm extends LitElement {
|
||||
return html`
|
||||
<paper-item class="option">
|
||||
<paper-item-body> ${option} </paper-item-body>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.index=${index}
|
||||
.title=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_select.remove_option"
|
||||
)}
|
||||
@click=${this._removeOption}
|
||||
icon="hass:delete"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</paper-item>
|
||||
`;
|
||||
})
|
||||
|
||||
@ -10,9 +10,14 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import memoizeOne from "memoize-one";
|
||||
import * as Fuse from "fuse.js";
|
||||
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import {
|
||||
afterNextRender,
|
||||
nextRender,
|
||||
} from "../../../common/util/render-status";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
@ -20,7 +25,6 @@ import {
|
||||
ConfigEntry,
|
||||
deleteConfigEntry,
|
||||
getConfigEntries,
|
||||
updateConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
import {
|
||||
DISCOVERY_SOURCES,
|
||||
@ -29,7 +33,7 @@ import {
|
||||
localizeConfigFlowTitle,
|
||||
subscribeConfigFlowInProgress,
|
||||
} from "../../../data/config_flow";
|
||||
import { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
||||
import type { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
@ -39,19 +43,43 @@ import {
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../../../common/search/search-input";
|
||||
import "./ha-integration-card";
|
||||
import type {
|
||||
ConfigEntryRemovedEvent,
|
||||
ConfigEntryUpdatedEvent,
|
||||
HaIntegrationCard,
|
||||
} from "./ha-integration-card";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
|
||||
localized_title?: string;
|
||||
}
|
||||
|
||||
export interface ConfigEntryExtended extends ConfigEntry {
|
||||
localized_domain_name?: string;
|
||||
}
|
||||
|
||||
const groupByIntegration = (
|
||||
entries: ConfigEntryExtended[]
|
||||
): Map<string, ConfigEntryExtended[]> => {
|
||||
const result = new Map();
|
||||
entries.forEach((entry) => {
|
||||
if (result.has(entry.domain)) {
|
||||
result.get(entry.domain).push(entry);
|
||||
} else {
|
||||
result.set(entry.domain, [entry]);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
@customElement("ha-config-integrations")
|
||||
class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
@ -65,9 +93,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() private _configEntries: ConfigEntry[] = [];
|
||||
@property() private _configEntries: ConfigEntryExtended[] = [];
|
||||
|
||||
@property() private _configEntriesInProgress: DataEntryFlowProgress[] = [];
|
||||
@property()
|
||||
private _configEntriesInProgress: DataEntryFlowProgressExtended[] = [];
|
||||
|
||||
@property() private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
||||
|
||||
@ -79,6 +108,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
window.location.hash.substring(1)
|
||||
);
|
||||
|
||||
@property() private _filter?: string;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||
@ -87,18 +118,91 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
||||
this._deviceRegistryEntries = entries;
|
||||
}),
|
||||
subscribeConfigFlowInProgress(this.hass, (flowsInProgress) => {
|
||||
this._configEntriesInProgress = flowsInProgress;
|
||||
for (const flow of flowsInProgress) {
|
||||
subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => {
|
||||
const translationsPromisses: Promise<void>[] = [];
|
||||
flowsInProgress.forEach((flow) => {
|
||||
// To render title placeholders
|
||||
if (flow.context.title_placeholders) {
|
||||
this.hass.loadBackendTranslation("config", flow.handler);
|
||||
translationsPromisses.push(
|
||||
this.hass.loadBackendTranslation("config", flow.handler)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
await Promise.all(translationsPromisses);
|
||||
await nextRender();
|
||||
this._configEntriesInProgress = flowsInProgress.map((flow) => {
|
||||
return {
|
||||
...flow,
|
||||
localized_title: localizeConfigFlowTitle(this.hass.localize, flow),
|
||||
};
|
||||
});
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private _filterConfigEntries = memoizeOne(
|
||||
(
|
||||
configEntries: ConfigEntryExtended[],
|
||||
filter?: string
|
||||
): ConfigEntryExtended[] => {
|
||||
if (!filter) {
|
||||
return configEntries;
|
||||
}
|
||||
const options: Fuse.FuseOptions<ConfigEntryExtended> = {
|
||||
keys: ["domain", "localized_domain_name", "title"],
|
||||
caseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(configEntries, options);
|
||||
return fuse.search(filter);
|
||||
}
|
||||
);
|
||||
|
||||
private _filterGroupConfigEntries = memoizeOne(
|
||||
(
|
||||
configEntries: ConfigEntryExtended[],
|
||||
filter?: string
|
||||
): [Map<string, ConfigEntryExtended[]>, ConfigEntryExtended[]] => {
|
||||
const filteredConfigEnties = this._filterConfigEntries(
|
||||
configEntries,
|
||||
filter
|
||||
);
|
||||
const ignored: ConfigEntryExtended[] = [];
|
||||
filteredConfigEnties.forEach((item, index) => {
|
||||
if (item.source === "ignore") {
|
||||
ignored.push(filteredConfigEnties.splice(index, 1)[0]);
|
||||
}
|
||||
});
|
||||
return [groupByIntegration(filteredConfigEnties), ignored];
|
||||
}
|
||||
);
|
||||
|
||||
private _filterConfigEntriesInProgress = memoizeOne(
|
||||
(
|
||||
configEntriesInProgress: DataEntryFlowProgressExtended[],
|
||||
filter?: string
|
||||
): DataEntryFlowProgressExtended[] => {
|
||||
configEntriesInProgress = configEntriesInProgress.map(
|
||||
(flow: DataEntryFlowProgressExtended) => ({
|
||||
...flow,
|
||||
title: localizeConfigFlowTitle(this.hass.localize, flow),
|
||||
})
|
||||
);
|
||||
if (!filter) {
|
||||
return configEntriesInProgress;
|
||||
}
|
||||
const options: Fuse.FuseOptions<DataEntryFlowProgressExtended> = {
|
||||
keys: ["handler", "localized_title"],
|
||||
caseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(configEntriesInProgress, options);
|
||||
return fuse.search(filter);
|
||||
}
|
||||
);
|
||||
|
||||
protected firstUpdated(changed: PropertyValues) {
|
||||
super.firstUpdated(changed);
|
||||
this._loadConfigEntries();
|
||||
@ -114,18 +218,37 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
this._configEntries.length
|
||||
) {
|
||||
afterNextRender(() => {
|
||||
const card = this.shadowRoot!.getElementById(
|
||||
this._searchParms.get("config_entry")!
|
||||
const entryId = this._searchParms.get("config_entry")!;
|
||||
const configEntry = this._configEntries.find(
|
||||
(entry) => entry.entry_id === entryId
|
||||
);
|
||||
if (!configEntry) {
|
||||
return;
|
||||
}
|
||||
const card: HaIntegrationCard = this.shadowRoot!.querySelector(
|
||||
`[data-domain=${configEntry?.domain}]`
|
||||
) as HaIntegrationCard;
|
||||
if (card) {
|
||||
card.scrollIntoView();
|
||||
card.scrollIntoView({
|
||||
block: "center",
|
||||
});
|
||||
card.classList.add("highlight");
|
||||
card.selectedConfigEntryId = entryId;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const [
|
||||
groupedConfigEntries,
|
||||
ignoredConfigEntries,
|
||||
] = this._filterGroupConfigEntries(this._configEntries, this._filter);
|
||||
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
||||
this._configEntriesInProgress,
|
||||
this._filter
|
||||
);
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@ -134,6 +257,21 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.integrations}
|
||||
>
|
||||
${this.narrow
|
||||
? html`
|
||||
<div slot="header">
|
||||
<slot name="header">
|
||||
<search-input
|
||||
.filter=${this._filter}
|
||||
class="header"
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._handleSearchChange}
|
||||
></search-input>
|
||||
</slot>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<paper-menu-button
|
||||
close-on-activate
|
||||
no-animations
|
||||
@ -141,16 +279,12 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
horizontal-offset="-5"
|
||||
slot="toolbar-icon"
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:dots-vertical"
|
||||
slot="dropdown-trigger"
|
||||
alt="menu"
|
||||
></paper-icon-button>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
role="listbox"
|
||||
selected="{{selectedItem}}"
|
||||
>
|
||||
></ha-icon-button>
|
||||
<paper-listbox slot="dropdown-content" role="listbox">
|
||||
<paper-item @tap=${this._toggleShowIgnored}>
|
||||
${this.hass.localize(
|
||||
this._showIgnored
|
||||
@ -161,48 +295,63 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
</paper-listbox>
|
||||
</paper-menu-button>
|
||||
|
||||
<div class="container">
|
||||
${!this.narrow
|
||||
? html`
|
||||
<div class="search">
|
||||
<search-input
|
||||
no-label-float
|
||||
no-underline
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
></search-input>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<div
|
||||
class="container"
|
||||
@entry-removed=${this._handleRemoved}
|
||||
@entry-updated=${this._handleUpdated}
|
||||
>
|
||||
${this._showIgnored
|
||||
? this._configEntries
|
||||
.filter((item) => item.source === "ignore")
|
||||
.map(
|
||||
(item: ConfigEntry) => html`
|
||||
<ha-card class="ignored">
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignored"
|
||||
? ignoredConfigEntries.map(
|
||||
(item: ConfigEntryExtended) => html`
|
||||
<ha-card class="ignored">
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignored"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
${item.localized_domain_name}
|
||||
</h2>
|
||||
<mwc-button
|
||||
@click=${this._removeIgnoredIntegration}
|
||||
.entry=${item}
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
${domainToName(this.hass.localize, item.domain)}
|
||||
</h2>
|
||||
<mwc-button
|
||||
@click=${this._removeIgnoredIntegration}
|
||||
.entry=${item}
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
)}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)
|
||||
: ""}
|
||||
${this._configEntriesInProgress.length
|
||||
? this._configEntriesInProgress.map(
|
||||
(flow) => html`
|
||||
${configEntriesInProgress.length
|
||||
? configEntriesInProgress.map(
|
||||
(flow: DataEntryFlowProgressExtended) => html`
|
||||
<ha-card class="discovered">
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
@ -219,153 +368,51 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
/>
|
||||
</div>
|
||||
<h2>
|
||||
${localizeConfigFlowTitle(this.hass.localize, flow)}
|
||||
${flow.localized_title}
|
||||
</h2>
|
||||
<mwc-button
|
||||
unelevated
|
||||
@click=${this._continueFlow}
|
||||
.flowId=${flow.flow_id}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.configure"
|
||||
)}
|
||||
</mwc-button>
|
||||
${DISCOVERY_SOURCES.includes(flow.context.source) &&
|
||||
flow.context.unique_id
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._ignoreFlow}
|
||||
.flow=${flow}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignore"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
<mwc-button
|
||||
unelevated
|
||||
@click=${this._continueFlow}
|
||||
.flowId=${flow.flow_id}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.configure"
|
||||
)}
|
||||
</mwc-button>
|
||||
${DISCOVERY_SOURCES.includes(flow.context.source) &&
|
||||
flow.context.unique_id
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._ignoreFlow}
|
||||
.flow=${flow}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.ignore.ignore"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)
|
||||
: ""}
|
||||
${this._configEntries.length
|
||||
? this._configEntries.map((item: any) => {
|
||||
const devices = this._getDevices(item);
|
||||
const entities = this._getEntities(item);
|
||||
const integrationName = domainToName(
|
||||
this.hass.localize,
|
||||
item.domain
|
||||
);
|
||||
return item.source === "ignore"
|
||||
? ""
|
||||
: html`
|
||||
<ha-card
|
||||
class="integration"
|
||||
.configEntry=${item}
|
||||
.id=${item.entry_id}
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</div>
|
||||
<h1>
|
||||
${integrationName}
|
||||
</h1>
|
||||
<h2>
|
||||
${integrationName === item.title
|
||||
? html` `
|
||||
: item.title}
|
||||
</h2>
|
||||
${devices.length || entities.length
|
||||
? html`
|
||||
<div>
|
||||
${devices.length
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.devices",
|
||||
"count",
|
||||
devices.length
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${devices.length && entities.length
|
||||
? "and"
|
||||
: ""}
|
||||
${entities.length
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
"count",
|
||||
entities.length
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<div>
|
||||
<mwc-button @click=${this._editEntryName}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}</mwc-button
|
||||
>
|
||||
${item.supports_options
|
||||
? html`
|
||||
<mwc-button @click=${this._showOptions}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.options"
|
||||
)}</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<paper-menu-button
|
||||
horizontal-align="right"
|
||||
vertical-align="top"
|
||||
vertical-offset="40"
|
||||
close-on-activate
|
||||
>
|
||||
<paper-icon-button
|
||||
icon="hass:dots-vertical"
|
||||
slot="dropdown-trigger"
|
||||
aria-label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.options"
|
||||
)}
|
||||
></paper-icon-button>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
<paper-item @tap=${this._showSystemOptions}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.system_options"
|
||||
)}</paper-item
|
||||
>
|
||||
<paper-item
|
||||
class="warning"
|
||||
@tap=${this._removeIntegration}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}</paper-item
|
||||
>
|
||||
</paper-listbox>
|
||||
</paper-menu-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
})
|
||||
: html`
|
||||
${groupedConfigEntries.size
|
||||
? Array.from(groupedConfigEntries.entries()).map(
|
||||
([domain, items]) =>
|
||||
html`<ha-integration-card
|
||||
data-domain=${domain}
|
||||
.hass=${this.hass}
|
||||
.domain=${domain}
|
||||
.items=${items}
|
||||
.entityRegistryEntries=${this._entityRegistryEntries}
|
||||
.deviceRegistryEntries=${this._deviceRegistryEntries}
|
||||
></ha-integration-card>`
|
||||
)
|
||||
: !this._configEntries.length
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<h1>
|
||||
@ -383,7 +430,27 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
`}
|
||||
`
|
||||
: ""}
|
||||
${this._filter &&
|
||||
!configEntriesInProgress.length &&
|
||||
!groupedConfigEntries.size &&
|
||||
this._configEntries.length
|
||||
? html`
|
||||
<div class="none-found">
|
||||
<h1>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.none_found"
|
||||
)}
|
||||
</h1>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.none_found_detail"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-fab
|
||||
icon="hass:plus"
|
||||
@ -400,12 +467,40 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _loadConfigEntries() {
|
||||
getConfigEntries(this.hass).then((configEntries) => {
|
||||
this._configEntries = configEntries.sort((conf1, conf2) =>
|
||||
compare(conf1.domain + conf1.title, conf2.domain + conf2.title)
|
||||
);
|
||||
this._configEntries = configEntries
|
||||
.map(
|
||||
(entry: ConfigEntry): ConfigEntryExtended => ({
|
||||
...entry,
|
||||
localized_domain_name: domainToName(
|
||||
this.hass.localize,
|
||||
entry.domain
|
||||
),
|
||||
})
|
||||
)
|
||||
.sort((conf1, conf2) =>
|
||||
caseInsensitiveCompare(
|
||||
conf1.localized_domain_name + conf1.title,
|
||||
conf2.localized_domain_name + conf2.title
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private _handleRemoved(ev: HASSDomEvent<ConfigEntryRemovedEvent>) {
|
||||
this._configEntries = this._configEntries.filter(
|
||||
(entry) => entry.entry_id !== ev.detail.entryId
|
||||
);
|
||||
}
|
||||
|
||||
private _handleUpdated(ev: HASSDomEvent<ConfigEntryUpdatedEvent>) {
|
||||
const newEntry = ev.detail.entry;
|
||||
this._configEntries = this._configEntries!.map((entry) =>
|
||||
entry.entry_id === newEntry.entry_id
|
||||
? { ...newEntry, localized_domain_name: entry.localized_domain_name }
|
||||
: entry
|
||||
);
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => {
|
||||
@ -428,9 +523,9 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _ignoreFlow(ev: Event) {
|
||||
private async _ignoreFlow(ev: Event) {
|
||||
const flow = (ev.target! as any).flow;
|
||||
showConfirmationDialog(this, {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_ignore_title",
|
||||
"name",
|
||||
@ -442,11 +537,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
confirmText: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.ignore"
|
||||
),
|
||||
confirm: () => {
|
||||
ignoreConfigFlow(this.hass, flow.flow_id);
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
},
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
await ignoreConfigFlow(this.hass, flow.flow_id);
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass.connection).refresh();
|
||||
}
|
||||
|
||||
private _toggleShowIgnored() {
|
||||
@ -459,7 +556,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_delete_ignore_title",
|
||||
"name",
|
||||
this.hass.localize(`component.${entry.domain}.config.title`)
|
||||
this.hass.localize(`component.${entry.domain}.title`)
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.integrations.ignore.confirm_delete_ignore"
|
||||
@ -481,22 +578,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _getEntities(configEntry: ConfigEntry): EntityRegistryEntry[] {
|
||||
if (!this._entityRegistryEntries) {
|
||||
return [];
|
||||
}
|
||||
return this._entityRegistryEntries.filter(
|
||||
(entity) => entity.config_entry_id === configEntry.entry_id
|
||||
);
|
||||
}
|
||||
|
||||
private _getDevices(configEntry: ConfigEntry): DeviceRegistryEntry[] {
|
||||
if (!this._deviceRegistryEntries) {
|
||||
return [];
|
||||
}
|
||||
return this._deviceRegistryEntries.filter((device) =>
|
||||
device.config_entries.includes(configEntry.entry_id)
|
||||
);
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
private _onImageLoad(ev) {
|
||||
@ -507,64 +590,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
ev.target.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
private _showOptions(ev) {
|
||||
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
|
||||
}
|
||||
|
||||
private _showSystemOptions(ev) {
|
||||
showConfigEntrySystemOptionsDialog(this, {
|
||||
entry: ev.target.closest("ha-card").configEntry,
|
||||
});
|
||||
}
|
||||
|
||||
private async _editEntryName(ev) {
|
||||
const configEntry = ev.target.closest("ha-card").configEntry;
|
||||
const newName = await showPromptDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"),
|
||||
defaultValue: configEntry.title,
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.integrations.rename_input_label"
|
||||
),
|
||||
});
|
||||
if (newName === null) {
|
||||
return;
|
||||
}
|
||||
const newEntry = await updateConfigEntry(this.hass, configEntry.entry_id, {
|
||||
title: newName,
|
||||
});
|
||||
this._configEntries = this._configEntries!.map((entry) =>
|
||||
entry.entry_id === newEntry.entry_id ? newEntry : entry
|
||||
);
|
||||
}
|
||||
|
||||
private async _removeIntegration(ev) {
|
||||
const entryId = ev.target.closest("ha-card").configEntry.entry_id;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteConfigEntry(this.hass, entryId).then((result) => {
|
||||
this._configEntries = this._configEntries.filter(
|
||||
(entry) => entry.entry_id !== entryId
|
||||
);
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
@ -573,7 +598,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
grid-gap: 16px 16px;
|
||||
padding: 16px;
|
||||
padding: 8px 16px 16px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
ha-card {
|
||||
@ -582,9 +607,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-card.highlight {
|
||||
border: 1px solid var(--accent-color);
|
||||
}
|
||||
.discovered {
|
||||
border: 1px solid var(--primary-color);
|
||||
}
|
||||
@ -597,6 +619,9 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
.ignored {
|
||||
border: 1px solid var(--light-theme-disabled-color);
|
||||
}
|
||||
.ignored img {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.ignored .header {
|
||||
background: var(--light-theme-disabled-color);
|
||||
color: var(--text-primary-color);
|
||||
@ -604,23 +629,13 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
text-align: center;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin-top: 0;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-card.integration .card-content {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
.card-actions {
|
||||
border-top: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.helper {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.image {
|
||||
display: flex;
|
||||
@ -630,12 +645,34 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
margin-bottom: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.none-found {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
search-input.header {
|
||||
display: block;
|
||||
position: relative;
|
||||
left: -8px;
|
||||
top: -7px;
|
||||
color: var(--secondary-text-color);
|
||||
margin-left: 16px;
|
||||
}
|
||||
.search {
|
||||
padding: 0 16px;
|
||||
background: var(--sidebar-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
.search search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
img {
|
||||
max-height: 60px;
|
||||
max-height: 100%;
|
||||
max-width: 90%;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
.none-found {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
margin-bottom: 0;
|
||||
@ -665,10 +702,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
left: 24px;
|
||||
right: auto;
|
||||
}
|
||||
paper-menu-button {
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
404
src/panels/config/integrations/ha-integration-card.ts
Normal file
@ -0,0 +1,404 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
html,
|
||||
CSSResult,
|
||||
css,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { ConfigEntryExtended } from "./ha-config-integrations";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import {
|
||||
ConfigEntry,
|
||||
updateConfigEntry,
|
||||
deleteConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
||||
import {
|
||||
showPromptDialog,
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import "../../../components/ha-icon-next";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
export interface ConfigEntryUpdatedEvent {
|
||||
entry: ConfigEntry;
|
||||
}
|
||||
|
||||
export interface ConfigEntryRemovedEvent {
|
||||
entryId: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"entry-updated": ConfigEntryUpdatedEvent;
|
||||
"entry-removed": ConfigEntryRemovedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-integration-card")
|
||||
export class HaIntegrationCard extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public domain!: string;
|
||||
|
||||
@property() public items!: ConfigEntryExtended[];
|
||||
|
||||
@property() public entityRegistryEntries!: EntityRegistryEntry[];
|
||||
|
||||
@property() public deviceRegistryEntries!: DeviceRegistryEntry[];
|
||||
|
||||
@property() public selectedConfigEntryId?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.items.length === 1) {
|
||||
return this._renderSingleEntry(this.items[0]);
|
||||
}
|
||||
if (this.selectedConfigEntryId) {
|
||||
const configEntry = this.items.find(
|
||||
(entry) => entry.entry_id === this.selectedConfigEntryId
|
||||
);
|
||||
if (configEntry) {
|
||||
return this._renderSingleEntry(configEntry);
|
||||
}
|
||||
}
|
||||
return this._renderGroupedIntegration();
|
||||
}
|
||||
|
||||
private _renderGroupedIntegration(): TemplateResult {
|
||||
return html`
|
||||
<ha-card class="group">
|
||||
<div class="group-header">
|
||||
<img
|
||||
src="https://brands.home-assistant.io/${this.domain}/icon.png"
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
<h1>
|
||||
${domainToName(this.hass.localize, this.domain)}
|
||||
</h1>
|
||||
</div>
|
||||
<paper-listbox>
|
||||
${this.items.map(
|
||||
(item) =>
|
||||
html`<paper-item
|
||||
.entryId=${item.entry_id}
|
||||
@click=${this._selectConfigEntry}
|
||||
><paper-item-body>${item.title}</paper-item-body
|
||||
><ha-icon-next></ha-icon-next
|
||||
></paper-item>`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderSingleEntry(item: ConfigEntryExtended): TemplateResult {
|
||||
const devices = this._getDevices(item);
|
||||
const entities = this._getEntities(item);
|
||||
return html`
|
||||
<ha-card
|
||||
class="single integration"
|
||||
.configEntry=${item}
|
||||
.id=${item.entry_id}
|
||||
>
|
||||
${this.items.length > 1
|
||||
? html`<ha-icon-button
|
||||
class="back-btn"
|
||||
icon="hass:chevron-left"
|
||||
@click=${this._back}
|
||||
></ha-icon-button>`
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
<div class="image">
|
||||
<img
|
||||
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</div>
|
||||
<h1>
|
||||
${item.localized_domain_name}
|
||||
</h1>
|
||||
<h2>
|
||||
${item.localized_domain_name === item.title ? "" : item.title}
|
||||
</h2>
|
||||
${devices.length || entities.length
|
||||
? html`
|
||||
<div>
|
||||
${devices.length
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.devices",
|
||||
"count",
|
||||
devices.length
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${devices.length && entities.length ? "and" : ""}
|
||||
${entities.length
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.entities",
|
||||
"count",
|
||||
entities.length
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<div>
|
||||
<mwc-button @click=${this._editEntryName}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}</mwc-button
|
||||
>
|
||||
${item.supports_options
|
||||
? html`
|
||||
<mwc-button @click=${this._showOptions}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.options"
|
||||
)}</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<paper-menu-button
|
||||
horizontal-align="right"
|
||||
vertical-align="top"
|
||||
vertical-offset="40"
|
||||
close-on-activate
|
||||
>
|
||||
<ha-icon-button
|
||||
icon="hass:dots-vertical"
|
||||
slot="dropdown-trigger"
|
||||
aria-label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_card.options"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
<paper-item @tap=${this._showSystemOptions}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.system_options"
|
||||
)}</paper-item
|
||||
>
|
||||
<paper-item class="warning" @tap=${this._removeIntegration}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete"
|
||||
)}</paper-item
|
||||
>
|
||||
</paper-listbox>
|
||||
</paper-menu-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _selectConfigEntry(ev: Event) {
|
||||
this.selectedConfigEntryId = (ev.currentTarget as any).entryId;
|
||||
}
|
||||
|
||||
private _back() {
|
||||
this.selectedConfigEntryId = undefined;
|
||||
this.classList.remove("highlight");
|
||||
}
|
||||
|
||||
private _getEntities(configEntry: ConfigEntry): EntityRegistryEntry[] {
|
||||
if (!this.entityRegistryEntries) {
|
||||
return [];
|
||||
}
|
||||
return this.entityRegistryEntries.filter(
|
||||
(entity) => entity.config_entry_id === configEntry.entry_id
|
||||
);
|
||||
}
|
||||
|
||||
private _getDevices(configEntry: ConfigEntry): DeviceRegistryEntry[] {
|
||||
if (!this.deviceRegistryEntries) {
|
||||
return [];
|
||||
}
|
||||
return this.deviceRegistryEntries.filter((device) =>
|
||||
device.config_entries.includes(configEntry.entry_id)
|
||||
);
|
||||
}
|
||||
|
||||
private _onImageLoad(ev) {
|
||||
ev.target.style.visibility = "initial";
|
||||
}
|
||||
|
||||
private _onImageError(ev) {
|
||||
ev.target.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
private _showOptions(ev) {
|
||||
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
|
||||
}
|
||||
|
||||
private _showSystemOptions(ev) {
|
||||
showConfigEntrySystemOptionsDialog(this, {
|
||||
entry: ev.target.closest("ha-card").configEntry,
|
||||
});
|
||||
}
|
||||
|
||||
private async _editEntryName(ev) {
|
||||
const configEntry = ev.target.closest("ha-card").configEntry;
|
||||
const newName = await showPromptDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"),
|
||||
defaultValue: configEntry.title,
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.integrations.rename_input_label"
|
||||
),
|
||||
});
|
||||
if (newName === null) {
|
||||
return;
|
||||
}
|
||||
const newEntry = await updateConfigEntry(this.hass, configEntry.entry_id, {
|
||||
title: newName,
|
||||
});
|
||||
fireEvent(this, "entry-updated", { entry: newEntry });
|
||||
}
|
||||
|
||||
private async _removeIntegration(ev) {
|
||||
const entryId = ev.target.closest("ha-card").configEntry.entry_id;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm"
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
deleteConfigEntry(this.hass, entryId).then((result) => {
|
||||
fireEvent(this, "entry-removed", { entryId });
|
||||
|
||||
if (result.require_restart) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
max-width: 500px;
|
||||
}
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
ha-card.single {
|
||||
justify-content: space-between;
|
||||
}
|
||||
:host(.highlight) ha-card {
|
||||
border: 1px solid var(--accent-color);
|
||||
}
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-card.integration .card-content {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
.card-actions {
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
padding: 16px 16px 8px 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.group-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
.group-header img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 60px;
|
||||
margin-bottom: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.none-found {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
h1 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
min-height: 24px;
|
||||
}
|
||||
paper-menu-button {
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0;
|
||||
}
|
||||
@media (min-width: 563px) {
|
||||
paper-listbox {
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-height: 35px;
|
||||
}
|
||||
.back-btn {
|
||||
position: absolute;
|
||||
background: #ffffffe0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-integration-card": HaIntegrationCard;
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@ import {
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
import "../../../../components/ha-fab";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import {
|
||||
createDashboard,
|
||||
deleteDashboard,
|
||||
@ -153,11 +154,11 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
template: (urlPath) =>
|
||||
narrow
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:open-in-new"
|
||||
.urlPath=${urlPath}
|
||||
@click=${this._navigate}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: html`
|
||||
<mwc-button .urlPath=${urlPath} @click=${this._navigate}
|
||||
|
||||
@ -79,6 +79,7 @@ class DialogPersonDetail extends LitElement {
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<div class="form">
|
||||
<paper-input
|
||||
dialogInitialFocus
|
||||
.value=${this._name}
|
||||
@value-changed=${this._nameChanged}
|
||||
label="${this.hass!.localize(
|
||||
@ -87,7 +88,8 @@ class DialogPersonDetail extends LitElement {
|
||||
error-message="${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.name_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
<ha-user-picker
|
||||
label="${this.hass!.localize(
|
||||
|
||||
@ -10,6 +10,14 @@ import {
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./ha-scene-dashboard";
|
||||
import "./ha-scene-editor";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
|
||||
const equal = (a: SceneEntity[], b: SceneEntity[]): boolean => {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return a.every((scene, index) => scene === b[index]);
|
||||
};
|
||||
|
||||
@customElement("ha-config-scene")
|
||||
class HaConfigScene extends HassRouterPage {
|
||||
@ -36,15 +44,18 @@ class HaConfigScene extends HassRouterPage {
|
||||
},
|
||||
};
|
||||
|
||||
private _computeScenes = memoizeOne((states: HassEntities) => {
|
||||
const scenes: SceneEntity[] = [];
|
||||
Object.values(states).forEach((state) => {
|
||||
if (computeStateDomain(state) === "scene" && !state.attributes.hidden) {
|
||||
scenes.push(state as SceneEntity);
|
||||
}
|
||||
});
|
||||
private _debouncedUpdateScenes = debounce((pageEl) => {
|
||||
const newScenes = this._getScenes(this.hass.states);
|
||||
if (!equal(newScenes, pageEl.scenes)) {
|
||||
pageEl.scenes = newScenes;
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return scenes;
|
||||
private _getScenes = memoizeOne((states: HassEntities): SceneEntity[] => {
|
||||
return Object.values(states).filter(
|
||||
(entity) =>
|
||||
computeStateDomain(entity) === "scene" && !entity.attributes.hidden
|
||||
) as SceneEntity[];
|
||||
});
|
||||
|
||||
protected updatePageEl(pageEl, changedProps: PropertyValues) {
|
||||
@ -55,7 +66,11 @@ class HaConfigScene extends HassRouterPage {
|
||||
pageEl.showAdvanced = this.showAdvanced;
|
||||
|
||||
if (this.hass) {
|
||||
pageEl.scenes = this._computeScenes(this.hass.states);
|
||||
if (!pageEl.scenes || !changedProps) {
|
||||
pageEl.scenes = this._getScenes(this.hass.states);
|
||||
} else if (changedProps && changedProps.has("hass")) {
|
||||
this._debouncedUpdateScenes(pageEl);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@ -64,13 +79,7 @@ class HaConfigScene extends HassRouterPage {
|
||||
) {
|
||||
pageEl.creatingNew = undefined;
|
||||
const sceneId = this.routeTail.path.substr(1);
|
||||
pageEl.creatingNew = sceneId === "new";
|
||||
pageEl.scene =
|
||||
sceneId === "new"
|
||||
? undefined
|
||||
: pageEl.scenes.find(
|
||||
(entity: SceneEntity) => entity.attributes.id === sceneId
|
||||
);
|
||||
pageEl.sceneId = sceneId === "new" ? null : sceneId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
@ -54,14 +54,14 @@ class HaSceneDashboard extends LitElement {
|
||||
type: "icon-button",
|
||||
template: (_toggle, scene) =>
|
||||
html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.scene=${scene}
|
||||
icon="hass:play"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.scene.picker.activate_scene"
|
||||
)}"
|
||||
@click=${(ev: Event) => this._activateScene(ev)}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`,
|
||||
},
|
||||
name: {
|
||||
@ -77,14 +77,14 @@ class HaSceneDashboard extends LitElement {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_info, scene) => html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.scene=${scene}
|
||||
@click=${this._showInfo}
|
||||
icon="hass:information-outline"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.scene.picker.show_info_scene"
|
||||
)}"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`,
|
||||
},
|
||||
edit: {
|
||||
@ -98,13 +98,13 @@ class HaSceneDashboard extends LitElement {
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.icon=${scene.attributes.id ? "hass:pencil" : "hass:pencil-off"}
|
||||
.disabled=${!scene.attributes.id}
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.scene.picker.edit_scene"
|
||||
)}"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
${!scene.attributes.id
|
||||
? html`
|
||||
@ -137,11 +137,11 @@ class HaSceneDashboard extends LitElement {
|
||||
)}
|
||||
hasFab
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:help-circle"
|
||||
@click=${this._showHelp}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<a href="/config/scene/edit/new">
|
||||
<ha-fab
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -24,7 +24,6 @@ import "../../../components/device/ha-device-picker";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
@ -46,7 +45,10 @@ import {
|
||||
SceneEntity,
|
||||
SCENE_IGNORED_DOMAINS,
|
||||
} from "../../../data/scene";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@ -73,13 +75,13 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() public scene?: SceneEntity;
|
||||
@property() public sceneId?: string;
|
||||
|
||||
@property() public creatingNew?: boolean;
|
||||
@property() public scenes!: SceneEntity[];
|
||||
|
||||
@property() public showAdvanced!: boolean;
|
||||
|
||||
@property() private _dirty?: boolean;
|
||||
@property() private _dirty = false;
|
||||
|
||||
@property() private _errors?: string;
|
||||
|
||||
@ -93,6 +95,8 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
||||
|
||||
@property() private _scene?: SceneEntity;
|
||||
|
||||
private _storedStates: SceneEntities = {};
|
||||
|
||||
private _unsubscribeEvents?: () => void;
|
||||
@ -172,8 +176,8 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
this._deviceEntityLookup,
|
||||
this._deviceRegistryEntries
|
||||
);
|
||||
const name = this.scene
|
||||
? computeStateName(this.scene)
|
||||
const name = this._scene
|
||||
? computeStateName(this._scene)
|
||||
: this.hass.localize("ui.panel.config.scene.editor.default_name");
|
||||
|
||||
return html`
|
||||
@ -184,17 +188,17 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
.backCallback=${() => this._backTapped()}
|
||||
.tabs=${configSections.automation}
|
||||
>
|
||||
${this.creatingNew
|
||||
${!this.sceneId
|
||||
? ""
|
||||
: html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.scene.picker.delete_scene"
|
||||
)}"
|
||||
icon="hass:delete"
|
||||
@click=${this._deleteTapped}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`}
|
||||
${this._errors ? html` <div class="errors">${this._errors}</div> ` : ""}
|
||||
${this.narrow ? html` <span slot="header">${name}</span> ` : ""}
|
||||
@ -212,7 +216,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.value=${this.scene ? computeStateName(this.scene) : ""}
|
||||
.value=${this._scene ? computeStateName(this._scene) : ""}
|
||||
@value-changed=${this._nameChanged}
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.config.scene.editor.name"
|
||||
@ -240,18 +244,18 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${device.name}
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:delete"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.scene.editor.devices.delete"
|
||||
)}"
|
||||
.device=${device.id}
|
||||
@click=${this._deleteDevice}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
${device.entities.map((entityId) => {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
if (!stateObj) {
|
||||
const entityStateObj = this.hass.states[entityId];
|
||||
if (!entityStateObj) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
@ -261,11 +265,11 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
class="device-entity"
|
||||
>
|
||||
<state-badge
|
||||
.stateObj=${stateObj}
|
||||
.stateObj=${entityStateObj}
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
<paper-item-body>
|
||||
${computeStateName(stateObj)}
|
||||
${computeStateName(entityStateObj)}
|
||||
</paper-item-body>
|
||||
</paper-icon-item>
|
||||
`;
|
||||
@ -313,8 +317,8 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
>
|
||||
${entities.map((entityId) => {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
if (!stateObj) {
|
||||
const entityStateObj = this.hass.states[entityId];
|
||||
if (!entityStateObj) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
@ -324,20 +328,20 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
class="device-entity"
|
||||
>
|
||||
<state-badge
|
||||
.stateObj=${stateObj}
|
||||
.stateObj=${entityStateObj}
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
<paper-item-body>
|
||||
${computeStateName(stateObj)}
|
||||
${computeStateName(entityStateObj)}
|
||||
</paper-item-body>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:delete"
|
||||
.entityId=${entityId}
|
||||
.title="${this.hass.localize(
|
||||
"ui.panel.config.scene.editor.entities.delete"
|
||||
)}"
|
||||
@click=${this._deleteEntity}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</paper-icon-item>
|
||||
`;
|
||||
})}
|
||||
@ -386,19 +390,19 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
const oldscene = changedProps.get("scene") as SceneEntity;
|
||||
const oldscene = changedProps.get("sceneId");
|
||||
|
||||
if (
|
||||
changedProps.has("scene") &&
|
||||
this.scene &&
|
||||
changedProps.has("sceneId") &&
|
||||
this.sceneId &&
|
||||
this.hass &&
|
||||
// Only refresh config if we picked a new scene. If same ID, don't fetch it.
|
||||
(!oldscene || oldscene.attributes.id !== this.scene.attributes.id)
|
||||
(!oldscene || oldscene !== this.sceneId)
|
||||
) {
|
||||
this._loadConfig();
|
||||
}
|
||||
|
||||
if (changedProps.has("creatingNew") && this.creatingNew && this.hass) {
|
||||
if (changedProps.has("sceneId") && !this.sceneId && this.hass) {
|
||||
this._dirty = false;
|
||||
const initData = getSceneEditorInitData();
|
||||
this._config = {
|
||||
@ -436,6 +440,29 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
changedProps.has("scenes") &&
|
||||
this.sceneId &&
|
||||
this._config &&
|
||||
!this._scene
|
||||
) {
|
||||
this._setScene();
|
||||
}
|
||||
}
|
||||
|
||||
private async _setScene() {
|
||||
const scene = this.scenes.find(
|
||||
(entity: SceneEntity) => entity.attributes.id === this.sceneId
|
||||
);
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
this._scene = scene;
|
||||
const { context } = await activateScene(this.hass, this._scene.entity_id);
|
||||
this._activateContextId = context.id;
|
||||
this._unsubscribeEvents = await this.hass!.connection.subscribeEvents<
|
||||
HassEvent
|
||||
>((event) => this._stateChanged(event), "state_changed");
|
||||
}
|
||||
|
||||
private _showMoreInfo(ev: Event) {
|
||||
@ -446,20 +473,20 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
private async _loadConfig() {
|
||||
let config: SceneConfig;
|
||||
try {
|
||||
config = await getSceneConfig(this.hass, this.scene!.attributes.id!);
|
||||
config = await getSceneConfig(this.hass, this.sceneId!);
|
||||
} catch (err) {
|
||||
alert(
|
||||
err.status_code === 404
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.scene.editor.load_error_not_editable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.scene.editor.load_error_unknown",
|
||||
"err_no",
|
||||
err.status_code
|
||||
)
|
||||
);
|
||||
history.back();
|
||||
showAlertDialog(this, {
|
||||
text:
|
||||
err.status_code === 404
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.scene.editor.load_error_not_editable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.scene.editor.load_error_unknown",
|
||||
"err_no",
|
||||
err.status_code
|
||||
),
|
||||
}).then(() => history.back());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -469,13 +496,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
|
||||
this._initEntities(config);
|
||||
|
||||
const { context } = await activateScene(this.hass, this.scene!.entity_id);
|
||||
|
||||
this._activateContextId = context.id;
|
||||
|
||||
this._unsubscribeEvents = await this.hass!.connection.subscribeEvents<
|
||||
HassEvent
|
||||
>((event) => this._stateChanged(event), "state_changed");
|
||||
this._setScene();
|
||||
|
||||
this._dirty = false;
|
||||
this._config = config;
|
||||
@ -598,7 +619,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _delete(): Promise<void> {
|
||||
await deleteScene(this.hass, this.scene!.attributes.id!);
|
||||
await deleteScene(this.hass, this.sceneId!);
|
||||
applyScene(this.hass, this._storedStates);
|
||||
history.back();
|
||||
}
|
||||
@ -634,13 +655,13 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _saveScene(): Promise<void> {
|
||||
const id = this.creatingNew ? "" + Date.now() : this.scene!.attributes.id!;
|
||||
const id = !this.sceneId ? "" + Date.now() : this.sceneId!;
|
||||
this._config = { ...this._config, entities: this._calculateStates() };
|
||||
try {
|
||||
await saveScene(this.hass, id, this._config);
|
||||
this._dirty = false;
|
||||
|
||||
if (this.creatingNew) {
|
||||
if (!this.sceneId) {
|
||||
navigate(this, `/config/scene/edit/${id}`, true);
|
||||
}
|
||||
} catch (err) {
|
||||
@ -693,10 +714,10 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
paper-icon-item {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
ha-card paper-icon-button {
|
||||
ha-card ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.card-header > paper-icon-button {
|
||||
.card-header > ha-icon-button {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: -8px;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { HassEntities, HassEntity } from "home-assistant-js-websocket";
|
||||
import { HassEntities } from "home-assistant-js-websocket";
|
||||
import { customElement, property, PropertyValues } from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
@ -9,6 +9,15 @@ import {
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./ha-script-editor";
|
||||
import "./ha-script-picker";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import { ScriptEntity } from "../../../data/script";
|
||||
|
||||
const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return a.every((enityA, index) => enityA === b[index]);
|
||||
};
|
||||
|
||||
@customElement("ha-config-script")
|
||||
class HaConfigScript extends HassRouterPage {
|
||||
@ -20,7 +29,7 @@ class HaConfigScript extends HassRouterPage {
|
||||
|
||||
@property() public showAdvanced!: boolean;
|
||||
|
||||
@property() public scripts: HassEntity[] = [];
|
||||
@property() public scripts: ScriptEntity[] = [];
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "dashboard",
|
||||
@ -35,15 +44,18 @@ class HaConfigScript extends HassRouterPage {
|
||||
},
|
||||
};
|
||||
|
||||
private _computeScripts = memoizeOne((states: HassEntities) => {
|
||||
const scripts: HassEntity[] = [];
|
||||
Object.values(states).forEach((state) => {
|
||||
if (computeStateDomain(state) === "script" && !state.attributes.hidden) {
|
||||
scripts.push(state);
|
||||
}
|
||||
});
|
||||
private _debouncedUpdateScripts = debounce((pageEl) => {
|
||||
const newScript = this._getScripts(this.hass.states);
|
||||
if (!equal(newScript, pageEl.scripts)) {
|
||||
pageEl.scripts = newScript;
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return scripts;
|
||||
private _getScripts = memoizeOne((states: HassEntities): ScriptEntity[] => {
|
||||
return Object.values(states).filter(
|
||||
(entity) =>
|
||||
computeStateDomain(entity) === "script" && !entity.attributes.hidden
|
||||
) as ScriptEntity[];
|
||||
});
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
@ -59,7 +71,11 @@ class HaConfigScript extends HassRouterPage {
|
||||
pageEl.showAdvanced = this.showAdvanced;
|
||||
|
||||
if (this.hass) {
|
||||
pageEl.scripts = this._computeScripts(this.hass.states);
|
||||
if (!pageEl.scripts || !changedProps) {
|
||||
pageEl.scripts = this._getScripts(this.hass.states);
|
||||
} else if (changedProps && changedProps.has("hass")) {
|
||||
this._debouncedUpdateScripts(pageEl);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@ -68,13 +84,7 @@ class HaConfigScript extends HassRouterPage {
|
||||
) {
|
||||
pageEl.creatingNew = undefined;
|
||||
const scriptEntityId = this.routeTail.path.substr(1);
|
||||
pageEl.creatingNew = scriptEntityId === "new";
|
||||
pageEl.script =
|
||||
scriptEntityId === "new"
|
||||
? undefined
|
||||
: pageEl.scripts.find(
|
||||
(entity: HassEntity) => entity.entity_id === scriptEntityId
|
||||
);
|
||||
pageEl.scriptEntityId = scriptEntityId === "new" ? null : scriptEntityId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
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 "../../../components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -16,13 +16,11 @@ import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import {
|
||||
Action,
|
||||
deleteScript,
|
||||
getScriptEditorInitData,
|
||||
ScriptConfig,
|
||||
ScriptEntity,
|
||||
} from "../../../data/script";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/ha-app-layout";
|
||||
@ -36,16 +34,14 @@ import { configSections } from "../ha-panel-config";
|
||||
export class HaScriptEditor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public script!: ScriptEntity;
|
||||
@property() public scriptEntityId!: string;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() public isWide?: boolean;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() public creatingNew?: boolean;
|
||||
|
||||
@property() private _config?: ScriptConfig;
|
||||
|
||||
@property() private _dirty?: boolean;
|
||||
@ -61,17 +57,17 @@ export class HaScriptEditor extends LitElement {
|
||||
.backCallback=${() => this._backTapped()}
|
||||
.tabs=${configSections.automation}
|
||||
>
|
||||
${this.creatingNew
|
||||
${!this.scriptEntityId
|
||||
? ""
|
||||
: html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.editor.delete_script"
|
||||
)}"
|
||||
icon="hass:delete"
|
||||
@click=${this._deleteConfirm}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`}
|
||||
${this.narrow
|
||||
? html` <span slot="header">${this._config?.alias}</span> `
|
||||
@ -161,18 +157,18 @@ export class HaScriptEditor extends LitElement {
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
const oldScript = changedProps.get("script") as ScriptEntity;
|
||||
const oldScript = changedProps.get("scriptEntityId");
|
||||
if (
|
||||
changedProps.has("script") &&
|
||||
this.script &&
|
||||
changedProps.has("scriptEntityId") &&
|
||||
this.scriptEntityId &&
|
||||
this.hass &&
|
||||
// Only refresh config if we picked a new script. If same ID, don't fetch it.
|
||||
(!oldScript || oldScript.entity_id !== this.script.entity_id)
|
||||
(!oldScript || oldScript !== this.scriptEntityId)
|
||||
) {
|
||||
this.hass
|
||||
.callApi<ScriptConfig>(
|
||||
"GET",
|
||||
`config/script/config/${computeObjectId(this.script.entity_id)}`
|
||||
`config/script/config/${computeObjectId(this.scriptEntityId)}`
|
||||
)
|
||||
.then(
|
||||
(config) => {
|
||||
@ -202,7 +198,11 @@ export class HaScriptEditor extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (changedProps.has("creatingNew") && this.creatingNew && this.hass) {
|
||||
if (
|
||||
changedProps.has("scriptEntityId") &&
|
||||
!this.scriptEntityId &&
|
||||
this.hass
|
||||
) {
|
||||
const initData = getScriptEditorInitData();
|
||||
this._dirty = !!initData;
|
||||
this._config = {
|
||||
@ -259,19 +259,19 @@ export class HaScriptEditor extends LitElement {
|
||||
}
|
||||
|
||||
private async _delete() {
|
||||
await deleteScript(this.hass, computeObjectId(this.script.entity_id));
|
||||
await deleteScript(this.hass, computeObjectId(this.scriptEntityId));
|
||||
history.back();
|
||||
}
|
||||
|
||||
private _saveScript(): void {
|
||||
const id = this.creatingNew
|
||||
? "" + Date.now()
|
||||
: computeObjectId(this.script.entity_id);
|
||||
const id = this.scriptEntityId
|
||||
? computeObjectId(this.scriptEntityId)
|
||||
: Date.now();
|
||||
this.hass!.callApi("POST", "config/script/config/" + id, this._config).then(
|
||||
() => {
|
||||
this._dirty = false;
|
||||
|
||||
if (this.creatingNew) {
|
||||
if (!this.scriptEntityId) {
|
||||
navigate(this, `/config/script/edit/${id}`, true);
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@ -53,14 +53,14 @@ class HaScriptPicker extends LitElement {
|
||||
type: "icon-button",
|
||||
template: (_toggle, script) =>
|
||||
html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.script=${script}
|
||||
icon="hass:play"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.activate_script"
|
||||
)}"
|
||||
@click=${(ev: Event) => this._runScript(ev)}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`,
|
||||
},
|
||||
name: {
|
||||
@ -88,14 +88,14 @@ class HaScriptPicker extends LitElement {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
template: (_info, script) => html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.script=${script}
|
||||
@click=${this._showInfo}
|
||||
icon="hass:information-outline"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.show_info"
|
||||
)}"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`,
|
||||
},
|
||||
edit: {
|
||||
@ -103,12 +103,12 @@ class HaScriptPicker extends LitElement {
|
||||
type: "icon-button",
|
||||
template: (_info, script: any) => html`
|
||||
<a href="/config/script/edit/${script.entity_id}">
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:pencil"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.edit_script"
|
||||
)}"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
`,
|
||||
},
|
||||
@ -132,11 +132,11 @@ class HaScriptPicker extends LitElement {
|
||||
)}
|
||||
hasFab
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:help-circle"
|
||||
@click=${this._showHelp}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<a href="/config/script/edit/new">
|
||||
<ha-fab
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
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 { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
@ -86,11 +86,11 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
"ui.panel.config.zha.add_device_page.search_again"
|
||||
)}
|
||||
</mwc-button>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
${this._showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@ -74,12 +74,12 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
"ui.panel.config.zha.cluster_attributes.header"
|
||||
)}
|
||||
</span>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
>
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
${this.hass!.localize(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@ -68,12 +68,12 @@ export class ZHAClusterCommands extends LitElement {
|
||||
"ui.panel.config.zha.cluster_commands.header"
|
||||
)}
|
||||
</span>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
>
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
${this.hass!.localize(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@ -63,12 +63,12 @@ export class ZHAClusters extends LitElement {
|
||||
<span>
|
||||
${this.hass!.localize("ui.panel.config.zha.clusters.header")}
|
||||
</span>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
>
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
${this.hass!.localize("ui.panel.config.zha.clusters.introduction")}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@ -50,12 +50,12 @@ export class ZHADeviceBindingControl extends LitElement {
|
||||
<ha-config-section .isWide="${this.isWide}">
|
||||
<div class="header" slot="header">
|
||||
<span>Device Binding</span>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
>
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">Bind and unbind devices.</span>
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -10,7 +9,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import {
|
||||
Cluster,
|
||||
fetchBindableDevices,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@ -80,12 +80,12 @@ export class ZHAGroupBindingControl extends LitElement {
|
||||
"ui.panel.config.zha.group_binding.header"
|
||||
)}</span
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
>
|
||||
</paper-icon-button>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction"
|
||||
>${this.hass!.localize(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
@ -101,11 +101,11 @@ export class ZHAGroupPage extends LitElement {
|
||||
|
||||
return html`
|
||||
<hass-subpage .header=${this.group.name}>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:delete"
|
||||
@click=${this._deleteGroup}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<div class="header">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.group_info")}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
@ -56,11 +56,11 @@ export class ZHAGroupsDashboard extends LitElement {
|
||||
"ui.panel.config.zha.groups.groups-header"
|
||||
)}
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
icon="hass:plus"
|
||||
@click=${this._addGroup}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
|
||||
<div class="content">
|
||||
${this._groups
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@ -37,11 +37,11 @@ export class ZHANode extends LitElement {
|
||||
"ui.panel.config.zha.node_management.header"
|
||||
)}</span
|
||||
>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
@click="${this._onHelpTap}"
|
||||
icon="hass:help-circle"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
${this.hass!.localize(
|
||||
|
||||
@ -105,6 +105,7 @@ class DialogZoneDetail extends LitElement {
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<div class="form">
|
||||
<paper-input
|
||||
dialogInitialFocus
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@ -114,7 +115,8 @@ class DialogZoneDetail extends LitElement {
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameValid}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${this._icon}
|
||||
|
||||
@ -152,11 +152,11 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
</paper-item-body>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
icon="hass:pencil"
|
||||
.entry=${entry}
|
||||
@click=${this._openEditEntry}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</paper-icon-item>
|
||||
@ -174,7 +174,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
${state.attributes.friendly_name || state.entity_id}
|
||||
</paper-item-body>
|
||||
<div style="display:inline-block">
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
.entityId=${state.entity_id}
|
||||
icon="hass:pencil"
|
||||
@click=${this._openCoreConfig}
|
||||
@ -185,7 +185,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
? undefined
|
||||
: true
|
||||
)}
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
<paper-tooltip position="left">
|
||||
${state.entity_id === "zone.home"
|
||||
? this.hass.localize(
|
||||
@ -474,7 +474,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-icon,
|
||||
paper-icon-button:not([disabled]) {
|
||||
ha-icon-button:not([disabled]) {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.empty {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@ -14,7 +14,7 @@ import { sortStatesByName } from "../../../common/entity/states_sort_by_name";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-menu-button";
|
||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import "../../../components/ha-icon-button-arrow-prev";
|
||||
import "../../../components/ha-service-description";
|
||||
import "../../../layouts/ha-app-layout";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
@ -88,9 +88,9 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<ha-app-layout has-scrolling-region="">
|
||||
<app-header slot="header" fixed="">
|
||||
<app-toolbar>
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
<ha-icon-button-arrow-prev
|
||||
on-click="_backTapped"
|
||||
></ha-paper-icon-button-arrow-prev>
|
||||
></ha-icon-button-arrow-prev>
|
||||
<div main-title="">
|
||||
[[localize('component.zwave.title')]]
|
||||
</div>
|
||||
@ -107,11 +107,11 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<div class="sectionHeader" slot="header">
|
||||
<span>Z-Wave Node Management</span>
|
||||
<paper-icon-button
|
||||
<ha-icon-button
|
||||
class="toggle-help-icon"
|
||||
on-click="toggleHelp"
|
||||
icon="hass:help-circle"
|
||||
></paper-icon-button>
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<span slot="introduction">
|
||||
Run Z-Wave commands that affect a single node. Pick a node to see a
|
||||
|
||||