1
mirror of https://github.com/home-assistant/frontend synced 2024-09-30 15:52:53 +02:00

Clean up even more (#3074)

This commit is contained in:
Paulus Schoutsen 2019-04-08 15:15:46 -07:00 committed by Pascal Vizeli
parent 8df9ac9dfa
commit 1c17210948
25 changed files with 761 additions and 565 deletions

View File

@ -44,7 +44,7 @@ function transformXMLtoPolymer(name, xml) {
// Given an iconset name and icon names, generate a polymer iconset
function generateIconset(name, iconNames) {
const iconDefs = iconNames
const iconDefs = Array.from(iconNames)
.map((name) => {
const iconDef = loadIcon(name);
if (!iconDef) {
@ -95,12 +95,15 @@ function findIcons(path, iconsetName) {
}
mapFiles(path, ".js", processFile);
mapFiles(path, ".ts", processFile);
return Array.from(icons);
return icons;
}
function genHassIcons() {
const iconNames = findIcons("./src", "hass").concat(BUILT_IN_PANEL_ICONS);
fs.existsSync(OUTPUT_DIR) || fs.mkdirSync(OUTPUT_DIR);
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));
}

View File

@ -6,10 +6,13 @@ const {
genMDIIcons,
} = require("../../gulp/tasks/gen-icons.js");
const MENU_BUTTON_ICON = "menu";
function genHassioIcons() {
const iconNames = findIcons("./src", "hassio").concat(MENU_BUTTON_ICON);
const iconNames = findIcons("./src", "hassio");
for (const item of findIcons("../src", "hassio")) {
iconNames.add(item);
}
fs.writeFileSync("./hassio-icons.html", generateIconset("hassio", iconNames));
}

View File

@ -1,103 +0,0 @@
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/hassio-card-content";
import "../resources/hassio-style";
import NavigateMixin from "../../../src/mixins/navigate-mixin";
class HassioAddonRepository extends NavigateMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style hassio-style">
paper-card {
cursor: pointer;
}
.not_available {
opacity: 0.6;
}
a.repo {
display: block;
color: var(--primary-text-color);
}
</style>
<template is="dom-if" if="[[addons.length]]">
<div class="card-group">
<div class="title">
[[repo.name]]
<div class="description">
Maintained by [[repo.maintainer]]
<a class="repo" href="[[repo.url]]" target="_blank"
>[[repo.url]]</a
>
</div>
</div>
<template
is="dom-repeat"
items="[[addons]]"
as="addon"
sort="sortAddons"
>
<paper-card class$="[[computeClass(addon)]]" on-click="addonTapped">
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[addon.name]]"
description="[[addon.description]]"
available="[[addon.available]]"
icon="[[computeIcon(addon)]]"
icon-title="[[computeIconTitle(addon)]]"
icon-class="[[computeIconClass(addon)]]"
></hassio-card-content>
</div>
</paper-card>
</template>
</div>
</template>
`;
}
static get properties() {
return {
hass: Object,
repo: Object,
addons: Array,
};
}
sortAddons(a, b) {
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
}
computeIcon(addon) {
return addon.installed && addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle";
}
computeIconTitle(addon) {
if (addon.installed)
return addon.installed !== addon.version
? "New version available"
: "Add-on is installed";
return addon.available
? "Add-on is not installed"
: "Add-on is not available on your system";
}
computeIconClass(addon) {
if (addon.installed)
return addon.installed !== addon.version ? "update" : "installed";
return !addon.available ? "not_available" : "";
}
computeClass(addon) {
return !addon.available ? "not_available" : "";
}
addonTapped(ev) {
this.navigate(`/hassio/addon/${ev.model.addon.slug}`);
}
}
customElements.define("hassio-addon-repository", HassioAddonRepository);

View File

@ -0,0 +1,112 @@
import {
css,
TemplateResult,
html,
LitElement,
property,
CSSResultArray,
} from "lit-element";
import "@polymer/paper-card/paper-card";
import "../components/hassio-card-content";
import { hassioStyle } from "../resources/hassio-style";
import { HomeAssistant } from "../../../src/types";
import {
HassioAddonInfo,
HassioAddonRepository,
} from "../../../src/data/hassio";
import { navigate } from "../../../src/common/navigate";
class HassioAddonRepositoryEl extends LitElement {
@property() public hass!: HomeAssistant;
@property() public repo!: HassioAddonRepository;
@property() public addons!: HassioAddonInfo[];
protected render(): TemplateResult | void {
const repo = this.repo;
return html`
<div class="card-group">
<div class="title">
${repo.name}
<div class="description">
Maintained by ${repo.maintainer}<br />
<a class="repo" href=${repo.url} target="_blank">${repo.url}</a>
</div>
</div>
${this.addons
.sort((a, b) =>
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
)
.map(
(addon) => html`
<paper-card
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this.addonTapped}
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${addon.name}
.description=${addon.description}
.available=${addon.available}
.icon=${this.computeIcon(addon)}
.iconTitle=${this.computeIconTitle(addon)}
.iconClass=${this.computeIconClass(addon)}
></hassio-card-content>
</div>
</paper-card>
`
)}
</div>
`;
}
private computeIcon(addon) {
return addon.installed && addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle";
}
private computeIconTitle(addon) {
if (addon.installed) {
return addon.installed !== addon.version
? "New version available"
: "Add-on is installed";
}
return addon.available
? "Add-on is not installed"
: "Add-on is not available on your system";
}
private computeIconClass(addon) {
if (addon.installed) {
return addon.installed !== addon.version ? "update" : "installed";
}
return !addon.available ? "not_available" : "";
}
private addonTapped(ev) {
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}`);
}
static get styles(): CSSResultArray {
return [
hassioStyle,
css`
paper-card {
cursor: pointer;
}
.not_available {
opacity: 0.6;
}
a.repo {
color: var(--primary-text-color);
}
`,
];
}
}
customElements.define("hassio-addon-repository", HassioAddonRepositoryEl);

View File

@ -1,92 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "./hassio-addon-repository";
import "./hassio-repositories-editor";
class HassioAddonStore extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
hassio-addon-repository {
margin-top: 24px;
}
</style>
<hassio-repositories-editor
hass="[[hass]]"
repos="[[repos]]"
></hassio-repositories-editor>
<template is="dom-repeat" items="[[repos]]" as="repo" sort="sortRepos">
<hassio-addon-repository
hass="[[hass]]"
repo="[[repo]]"
addons="[[computeAddons(repo.slug)]]"
></hassio-addon-repository>
</template>
`;
}
static get properties() {
return {
hass: Object,
addons: Array,
repos: Array,
};
}
ready() {
super.ready();
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
this.loadData();
}
apiCalled(ev) {
if (ev.detail.success) {
this.loadData();
}
}
sortRepos(a, b) {
if (a.slug === "local") {
return -1;
}
if (b.slug === "local") {
return 1;
}
if (a.slug === "core") {
return -1;
}
if (b.slug === "core") {
return 1;
}
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
}
computeAddons(repo) {
return this.addons.filter(function(addon) {
return addon.repository === repo;
});
}
loadData() {
this.hass.callApi("get", "hassio/addons").then(
(info) => {
this.addons = info.data.addons;
this.repos = info.data.repositories;
},
() => {
this.addons = [];
this.repos = [];
}
);
}
refreshData() {
this.hass.callApi("post", "hassio/addons/reload").then(() => {
this.loadData();
});
}
}
customElements.define("hassio-addon-store", HassioAddonStore);

View File

@ -0,0 +1,116 @@
import "./hassio-addon-repository";
import "./hassio-repositories-editor";
import { TemplateResult, html } from "lit-html";
import {
LitElement,
CSSResult,
css,
property,
PropertyValues,
} from "lit-element";
import { HomeAssistant } from "../../../src/types";
import {
HassioAddonRepository,
HassioAddonInfo,
fetchHassioAddonsInfo,
reloadHassioAddons,
} from "../../../src/data/hassio";
import "../../../src/layouts/loading-screen";
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
if (a.slug === "local") {
return -1;
}
if (b.slug === "local") {
return 1;
}
if (a.slug === "core") {
return -1;
}
if (b.slug === "core") {
return 1;
}
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
};
class HassioAddonStore extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _addons?: HassioAddonInfo[];
@property() private _repos?: HassioAddonRepository[];
public async refreshData() {
this._repos = undefined;
this._addons = undefined;
await reloadHassioAddons(this.hass);
await this._loadData();
}
protected render(): TemplateResult | void {
if (!this._addons || !this._repos) {
return html`
<loading-screen></loading-screen>
`;
}
const repos: TemplateResult[] = [];
for (const repo of this._repos) {
const addons = this._addons!.filter(
(addon) => addon.repository === repo.slug
);
if (addons.length === 0) {
continue;
}
repos.push(html`
<hassio-addon-repository
.hass=${this.hass}
.repo=${repo}
.addons=${addons}
></hassio-addon-repository>
`);
}
return html`
<hassio-repositories-editor
.hass=${this.hass}
.repos=${this._repos}
></hassio-repositories-editor>
${repos}
`;
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
this._loadData();
}
private apiCalled(ev) {
if (ev.detail.success) {
this._loadData();
}
}
private async _loadData() {
try {
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
this._repos = addonsInfo.repositories;
this._repos.sort(sortRepos);
this._addons = addonsInfo.addons;
} catch (err) {
alert("Failed to fetch add-on info");
}
}
static get styles(): CSSResult {
return css`
hassio-addon-repository {
margin-top: 24px;
}
`;
}
}
customElements.define("hassio-addon-store", HassioAddonStore);

View File

@ -1,120 +0,0 @@
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/buttons/ha-call-api-button";
import "../components/hassio-card-content";
import "../resources/hassio-style";
class HassioRepositoriesEditor extends PolymerElement {
static get template() {
return html`
<style include="ha-style hassio-style">
.add {
padding: 12px 16px;
}
iron-icon {
color: var(--secondary-text-color);
margin-right: 16px;
display: inline-block;
}
paper-input {
width: calc(100% - 49px);
display: inline-block;
}
</style>
<div class="card-group">
<div class="title">
Repositories
<div class="description">
Configure which add-on repositories to fetch data from:
</div>
</div>
<template
id="list"
is="dom-repeat"
items="[[repoList]]"
as="repo"
sort="sortRepos"
>
<paper-card>
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[repo.name]]"
description="[[repo.url]]"
icon="hassio:github-circle"
></hassio-card-content>
</div>
<div class="card-actions">
<ha-call-api-button
hass="[[hass]]"
path="hassio/supervisor/options"
data="[[computeRemoveRepoData(repoList, repo.url)]]"
class="warning"
>Remove</ha-call-api-button
>
</div>
</paper-card>
</template>
<paper-card>
<div class="card-content add">
<iron-icon icon="hassio:github-circle"></iron-icon>
<paper-input
label="Add new repository by URL"
value="{{repoUrl}}"
></paper-input>
</div>
<div class="card-actions">
<ha-call-api-button
hass="[[hass]]"
path="hassio/supervisor/options"
data="[[computeAddRepoData(repoList, repoUrl)]]"
>Add</ha-call-api-button
>
</div>
</paper-card>
</div>
`;
}
static get properties() {
return {
hass: Object,
repos: {
type: Array,
observer: "reposChanged",
},
repoList: Array,
repoUrl: String,
};
}
reposChanged(repos) {
this.repoList = repos.filter(
(repo) => repo.slug !== "core" && repo.slug !== "local"
);
this.repoUrl = "";
}
sortRepos(a, b) {
return a.name < b.name ? -1 : 1;
}
computeRemoveRepoData(repoList, url) {
const list = repoList
.filter((repo) => repo.url !== url)
.map((repo) => repo.source);
return { addons_repositories: list };
}
computeAddRepoData(repoList, url) {
const list = repoList ? repoList.map((repo) => repo.source) : [];
list.push(url);
return { addons_repositories: list };
}
}
customElements.define("hassio-repositories-editor", HassioRepositoriesEditor);

View File

@ -0,0 +1,148 @@
import {
LitElement,
html,
CSSResultArray,
css,
property,
TemplateResult,
customElement,
PropertyValues,
} from "lit-element";
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-input/paper-input";
import memoizeOne from "memoize-one";
import "../../../src/components/buttons/ha-call-api-button";
import "../components/hassio-card-content";
import { hassioStyle } from "../resources/hassio-style";
import { HomeAssistant } from "../../../src/types";
import { HassioAddonRepository } from "../../../src/data/hassio";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { repeat } from "lit-html/directives/repeat";
@customElement("hassio-repositories-editor")
class HassioRepositoriesEditor extends LitElement {
@property() public hass!: HomeAssistant;
@property() public repos!: HassioAddonRepository[];
@property() private _repoUrl = "";
private _sortedRepos = memoizeOne((repos: HassioAddonRepository[]) =>
repos
.filter((repo) => repo.slug !== "core" && repo.slug !== "local")
.sort((a, b) => (a.name < b.name ? -1 : 1))
);
protected render(): TemplateResult | void {
const repos = this._sortedRepos(this.repos);
return html`
<div class="card-group">
<div class="title">
Repositories
<div class="description">
Configure which add-on repositories to fetch data from:
</div>
</div>
${// Use repeat so that the fade-out from call-service-api-button
// stays with the correct repo after we add/delete one.
repeat(
repos,
(repo) => repo.slug,
(repo) => html`
<paper-card>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${repo.name}
.description=${repo.url}
icon="hassio:github-circle"
></hassio-card-content>
</div>
<div class="card-actions">
<ha-call-api-button
path="hassio/supervisor/options"
.hass=${this.hass}
.data=${this.computeRemoveRepoData(repos, repo.url)}
class="warning"
>
Remove
</ha-call-api-button>
</div>
</paper-card>
`
)}
<paper-card>
<div class="card-content add">
<iron-icon icon="hassio:github-circle"></iron-icon>
<paper-input
label="Add new repository by URL"
.value=${this._repoUrl}
@value-changed=${this._urlChanged}
></paper-input>
</div>
<div class="card-actions">
<ha-call-api-button
path="hassio/supervisor/options"
.hass=${this.hass}
.data=${this.computeAddRepoData(repos, this._repoUrl)}
>
Add
</ha-call-api-button>
</div>
</paper-card>
</div>
`;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("repos")) {
this._repoUrl = "";
}
}
private _urlChanged(ev: PolymerChangedEvent<string>) {
this._repoUrl = ev.detail.value;
}
private computeRemoveRepoData(repoList, url) {
const list = repoList
.filter((repo) => repo.url !== url)
.map((repo) => repo.source);
return { addons_repositories: list };
}
private computeAddRepoData(repoList, url) {
const list = repoList ? repoList.map((repo) => repo.source) : [];
list.push(url);
return { addons_repositories: list };
}
static get styles(): CSSResultArray {
return [
hassioStyle,
css`
.add {
padding: 12px 16px;
}
iron-icon {
color: var(--secondary-text-color);
margin-right: 16px;
display: inline-block;
}
paper-input {
width: calc(100% - 49px);
display: inline-block;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-repositories-editor": HassioRepositoriesEditor;
}
}

View File

@ -168,12 +168,18 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
icon="hassio:arrow-up-bold-circle"
icon-class="update"
></hassio-card-content>
<template is="dom-if" if="[[!addon.available]]">
<p>This update is no longer compatible with your system.</p>
</template>
</div>
<div class="card-actions">
<ha-call-api-button
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/update"
>Update</ha-call-api-button
disabled="[[!addon.available]]"
>
Update
</ha-call-api-button
>
<template is="dom-if" if="[[addon.changelog]]">
<mwc-button on-click="openChangelog">Changelog</mwc-button>

View File

@ -5,7 +5,6 @@ import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/resources/ha-style";
import "./hassio-addon-audio";
import "./hassio-addon-config";
import "./hassio-addon-info";
@ -15,7 +14,7 @@ import "./hassio-addon-network";
class HassioAddonView extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
<style>
:host {
color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
@ -48,16 +47,7 @@ class HassioAddonView extends PolymerElement {
}
}
</style>
<app-header-layout has-scrolling-region="">
<app-header fixed="" slot="header">
<app-toolbar>
<paper-icon-button
icon="hassio:arrow-left"
on-click="backTapped"
></paper-icon-button>
<div main-title="">Hass.io: add-on details</div>
</app-toolbar>
</app-header>
<hass-subpage header="Hass.io: add-on details" hassio>
<div class="content">
<hassio-addon-info
hass="[[hass]]"
@ -93,7 +83,7 @@ class HassioAddonView extends PolymerElement {
></hassio-addon-logs>
</template>
</div>
</app-header-layout>
</hass-subpage>
`;
}
@ -141,10 +131,6 @@ class HassioAddonView extends PolymerElement {
);
}
backTapped() {
history.back();
}
_computeSlug(route) {
return route.path.substr(1);
}

View File

@ -1,90 +0,0 @@
import "@polymer/iron-icon/iron-icon";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-relative-time";
class HassioCardContent extends PolymerElement {
static get template() {
return html`
<style>
iron-icon {
margin-right: 16px;
margin-top: 16px;
float: left;
color: var(--secondary-text-color);
}
iron-icon.update {
color: var(--paper-orange-400);
}
iron-icon.running,
iron-icon.installed {
color: var(--paper-green-400);
}
iron-icon.hassupdate,
iron-icon.snapshot {
color: var(--paper-item-icon-color);
}
iron-icon.not_available {
color: var(--google-red-500);
}
.title {
color: var(--primary-text-color);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.addition {
color: var(--secondary-text-color);
overflow: hidden;
position: relative;
height: 2.4em;
line-height: 1.2em;
}
ha-relative-time {
display: block;
}
</style>
<iron-icon
icon="[[icon]]"
class\$="[[iconClass]]"
title="[[iconTitle]]"
></iron-icon>
<div>
<div class="title">[[title]]</div>
<div class="addition">
<template is="dom-if" if="[[description]]">
[[description]]
</template>
<template is="dom-if" if="[[!available]]">
(Not available)
</template>
<template is="dom-if" if="[[datetime]]">
<ha-relative-time
hass="[[hass]]"
class="addition"
datetime="[[datetime]]"
></ha-relative-time>
</template>
</div>
</div>
`;
}
static get properties() {
return {
hass: Object,
title: String,
description: String,
available: Boolean,
datetime: String,
icon: {
type: String,
value: "hass:help-circle",
},
iconTitle: String,
iconClass: String,
};
}
}
customElements.define("hassio-card-content", HassioCardContent);

View File

@ -0,0 +1,97 @@
import {
LitElement,
TemplateResult,
html,
CSSResult,
css,
property,
customElement,
} from "lit-element";
import "@polymer/iron-icon/iron-icon";
import "../../../src/components/ha-relative-time";
import { HomeAssistant } from "../../../src/types";
@customElement("hassio-card-content")
class HassioCardContent extends LitElement {
@property() public hass!: HomeAssistant;
@property() public title!: string;
@property() public description?: string;
@property() public available: boolean = true;
@property() public datetime?: string;
@property() public iconTitle?: string;
@property() public iconClass?: string;
@property() public icon = "hass:help-circle";
protected render(): TemplateResult | void {
return html`
<iron-icon
class=${this.iconClass}
.icon=${this.icon}
.title=${this.iconTitle}
></iron-icon>
<div>
<div class="title">${this.title}</div>
<div class="addition">
${this.description} ${this.available ? undefined : " (Not available"}
${this.datetime
? html`
<ha-relative-time
.hass=${this.hass}
class="addition"
.datetime=${this.datetime}
></ha-relative-time>
`
: undefined}
</div>
</div>
`;
}
static get styles(): CSSResult {
return css`
iron-icon {
margin-right: 16px;
margin-top: 16px;
float: left;
color: var(--secondary-text-color);
}
iron-icon.update {
color: var(--paper-orange-400);
}
iron-icon.running,
iron-icon.installed {
color: var(--paper-green-400);
}
iron-icon.hassupdate,
iron-icon.snapshot {
color: var(--paper-item-icon-color);
}
iron-icon.not_available {
color: var(--google-red-500);
}
.title {
color: var(--primary-text-color);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.addition {
color: var(--secondary-text-color);
overflow: hidden;
position: relative;
height: 2.4em;
line-height: 1.2em;
}
ha-relative-time {
display: block;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-card-content": HassioCardContent;
}
}

View File

@ -1,38 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "./hassio-addons";
import "./hassio-hass-update";
import EventsMixin from "../../../src/mixins/events-mixin";
class HassioDashboard extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
margin: 0 auto;
}
</style>
<div class="content">
<hassio-hass-update
hass="[[hass]]"
hass-info="[[hassInfo]]"
></hassio-hass-update>
<hassio-addons
hass="[[hass]]"
addons="[[supervisorInfo.addons]]"
></hassio-addons>
</div>
`;
}
static get properties() {
return {
hass: Object,
supervisorInfo: Object,
hassInfo: Object,
};
}
}
customElements.define("hassio-dashboard", HassioDashboard);

View File

@ -0,0 +1,52 @@
import {
LitElement,
TemplateResult,
html,
CSSResult,
css,
property,
customElement,
} from "lit-element";
import "./hassio-addons";
import "./hassio-hass-update";
import { HomeAssistant } from "../../../src/types";
import {
HassioSupervisorInfo,
HassioHomeAssistantInfo,
} from "../../../src/data/hassio";
@customElement("hassio-dashboard")
class HassioDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public supervisorInfo!: HassioSupervisorInfo;
@property() public hassInfo!: HassioHomeAssistantInfo;
protected render(): TemplateResult | void {
return html`
<div class="content">
<hassio-hass-update
.hass=${this.hass}
.hassInfo=${this.hassInfo}
></hassio-hass-update>
<hassio-addons
.hass=${this.hass}
.addons=${this.supervisorInfo.addons}
></hassio-addons>
</div>
`;
}
static get styles(): CSSResult {
return css`
.content {
margin: 0 auto;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-dashboard": HassioDashboard;
}
}

View File

@ -14,6 +14,9 @@ import {
fetchHassioSupervisorInfo,
fetchHassioHostInfo,
fetchHassioHomeAssistantInfo,
HassioSupervisorInfo,
HassioHostInfo,
HassioHomeAssistantInfo,
} from "../../src/data/hassio";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
@ -54,9 +57,9 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
},
};
@property() private _supervisorInfo: any;
@property() private _hostInfo: any;
@property() private _hassInfo: any;
@property() private _supervisorInfo: HassioSupervisorInfo;
@property() private _hostInfo: HassioHostInfo;
@property() private _hassInfo: HassioHomeAssistantInfo;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);

View File

@ -23,6 +23,11 @@ import scrollToTarget from "../../src/common/dom/scroll-to-target";
import { haStyle } from "../../src/resources/styles";
import { HomeAssistant, Route } from "../../src/types";
import { navigate } from "../../src/common/navigate";
import {
HassioSupervisorInfo,
HassioHostInfo,
HassioHomeAssistantInfo,
} from "../../src/data/hassio";
const HAS_REFRESH_BUTTON = ["store", "snapshots"];
@ -30,9 +35,9 @@ const HAS_REFRESH_BUTTON = ["store", "snapshots"];
class HassioPagesWithTabs extends LitElement {
@property() public hass!: HomeAssistant;
@property() public route!: Route;
@property() public supervisorInfo!: any;
@property() public hostInfo!: any;
@property() public hassInfo!: any;
@property() public supervisorInfo!: HassioSupervisorInfo;
@property() public hostInfo!: HassioHostInfo;
@property() public hassInfo!: HassioHomeAssistantInfo;
protected render(): TemplateResult | void {
const page = this._page;

View File

@ -7,13 +7,18 @@ import { PolymerElement } from "@polymer/polymer";
import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard";
import {
HassioSupervisorInfo,
HassioHostInfo,
HassioHomeAssistantInfo,
} from "../../src/data/hassio";
@customElement("hassio-tabs-router")
class HassioTabsRouter extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public supervisorInfo: any;
@property() public hostInfo: any;
@property() public hassInfo: any;
@property() public supervisorInfo: HassioSupervisorInfo;
@property() public hostInfo: HassioHostInfo;
@property() public hassInfo: HassioHomeAssistantInfo;
protected routerOptions: RouterOptions = {
routes: {

View File

@ -11,7 +11,7 @@ import {
import { HomeAssistant, Route } from "../../../src/types";
import {
createHassioSession,
HassioAddon,
HassioAddonDetails,
fetchHassioAddonInfo,
} from "../../../src/data/hassio";
import "../../../src/layouts/hass-loading-screen";
@ -20,24 +20,27 @@ import "../../../src/layouts/hass-subpage";
@customElement("hassio-ingress-view")
class HassioIngressView extends LitElement {
@property() public hass!: HomeAssistant;
@property() public route!: Route & { slug: string };
@property() private _hasSession = false;
@property() private _addon?: HassioAddon;
@property() public route!: Route;
@property() private _addon?: HassioAddonDetails;
protected render(): TemplateResult | void {
if (!this._hasSession || !this._addon) {
if (!this._addon) {
return html`
<hass-loading-screen rootnav></hass-loading-screen>
`;
}
return html`
<hass-subpage .header=${this._addon.name} hassio root>
<a .href=${this._addon.ingress_url} slot="toolbar-icon" target="_blank">
<paper-icon-button icon="hassio:open-in-new"></paper-icon-button>
</a>
<iframe src=${this._addon.ingress_url}></iframe>
</hass-subpage>
const iframe = html`
<iframe src=${this._addon.ingress_url}></iframe>
`;
return location.search === "?kiosk"
? iframe
: html`
<hass-subpage .header=${this._addon.name} hassio root>
${iframe}
</hass-subpage>
`;
}
protected updated(changedProps: PropertyValues) {
@ -53,32 +56,30 @@ class HassioIngressView extends LitElement {
const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;
if (addon && addon !== oldAddon) {
this._createSession();
this._fetchAddonInfo(addon);
this._fetchData(addon);
}
}
private async _fetchAddonInfo(addonSlug: string) {
private async _fetchData(addonSlug: string) {
try {
const addon = await fetchHassioAddonInfo(this.hass, addonSlug);
if (addon.ingress) {
this._addon = addon;
} else {
alert("This add-on does not support ingress.");
history.back();
const [addon] = await Promise.all([
fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {
throw new Error("Failed to fetch add-on info");
}),
createHassioSession(this.hass).catch(() => {
throw new Error("Failed to create an ingress session");
}),
]);
if (!addon.ingress) {
throw new Error("This add-on does not support ingress");
}
} catch (err) {
alert("Failed to fetch add-on info");
history.back();
}
}
private async _createSession() {
try {
await createHassioSession(this.hass);
this._hasSession = true;
this._addon = addon;
} catch (err) {
alert("Failed to generate a session");
// tslint:disable-next-line
console.error(err);
alert(err.message || "Unknown error starting ingress.");
history.back();
}
}

View File

@ -1,56 +1,64 @@
import { css } from "lit-element";
const documentContainer = document.createElement("template");
documentContainer.setAttribute("style", "display: none;");
export const hassioStyle = css`
.card-group {
margin-top: 24px;
}
.card-group .title {
color: var(--primary-text-color);
font-size: 2em;
padding-left: 8px;
margin-bottom: 8px;
}
.card-group .description {
font-size: 0.5em;
font-weight: 500;
margin-top: 4px;
}
.card-group paper-card {
--card-group-columns: 4;
width: calc(
(100% - 12px * var(--card-group-columns)) / var(--card-group-columns)
);
margin: 4px;
vertical-align: top;
}
@media screen and (max-width: 1200px) and (min-width: 901px) {
.card-group paper-card {
--card-group-columns: 3;
}
}
@media screen and (max-width: 900px) and (min-width: 601px) {
.card-group paper-card {
--card-group-columns: 2;
}
}
@media screen and (max-width: 600px) and (min-width: 0) {
.card-group paper-card {
width: 100%;
margin: 4px 0;
}
.content {
padding: 0;
}
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.error {
color: var(--google-red-500);
margin-top: 16px;
}
`;
documentContainer.innerHTML = `<dom-module id="hassio-style">
<template>
<style>
.card-group {
margin-top: 24px;
}
.card-group .title {
color: var(--primary-text-color);
font-size: 2em;
padding-left: 8px;
margin-bottom: 8px;
}
.card-group .description {
font-size: 0.5em;
font-weight: 500;
margin-top: 4px;
}
.card-group paper-card {
--card-group-columns: 4;
width: calc((100% - 12px * var(--card-group-columns)) / var(--card-group-columns));
margin: 4px;
vertical-align: top;
}
@media screen and (max-width: 1200px) and (min-width: 901px) {
.card-group paper-card {
--card-group-columns: 3;
}
}
@media screen and (max-width: 900px) and (min-width: 601px) {
.card-group paper-card {
--card-group-columns: 2;
}
}
@media screen and (max-width: 600px) and (min-width: 0) {
.card-group paper-card {
width: 100%;
margin: 4px 0;
}
.content {
padding: 0;
}
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.error {
color: var(--google-red-500);
margin-top: 16px;
}
${hassioStyle.toString()}
</style>
</template>
</dom-module>`;

View File

@ -11,7 +11,7 @@ import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio
class HassioHostInfo extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
<style>
paper-card {
display: inline-block;
width: 400px;

View File

@ -9,7 +9,7 @@ import EventsMixin from "../../../src/mixins/events-mixin";
class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
<style>
paper-card {
display: inline-block;
width: 400px;

View File

@ -1,4 +1,3 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -9,7 +8,7 @@ import "./hassio-supervisor-log";
class HassioSystem extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
<style>
.content {
margin: 4px;
color: var(--primary-text-color);

View File

@ -9,7 +9,22 @@ interface CreateSessionResponse {
session: string;
}
export interface HassioAddon {
export interface HassioAddonInfo {
name: string;
slug: string;
description: string;
repository: "core" | "local" | string;
version: string;
installed: string | undefined;
detached: boolean;
available: boolean;
build: boolean;
url: string | null;
icon: boolean;
logo: boolean;
}
export interface HassioAddonDetails {
name: string;
slug: string;
description: string;
@ -64,6 +79,29 @@ export interface HassioAddon {
ingress_url: null | string;
}
export interface HassioAddonRepository {
slug: string;
name: string;
source: string;
url: string;
maintainer: string;
}
export interface HassioAddonsInfo {
addons: HassioAddonInfo[];
repositories: HassioAddonRepository[];
}
export interface HassioHassOSInfo {
version: string;
version_cli: string;
version_latest: string;
version_cli_latest: string;
board: "ova" | "rpi";
}
export type HassioHomeAssistantInfo = any;
export type HassioSupervisorInfo = any;
export type HassioHostInfo = any;
const hassioApiResultExtractor = <T>(response: HassioResponse<T>) =>
response.data;
@ -77,22 +115,41 @@ export const createHassioSession = async (hass: HomeAssistant) => {
};path=/api/hassio_ingress/`;
};
export const reloadHassioAddons = (hass: HomeAssistant) =>
hass
.callApi<HassioResponse<unknown>>("POST", `hassio/addons/reload`)
.then(hassioApiResultExtractor);
export const fetchHassioAddonsInfo = (hass: HomeAssistant) =>
hass
.callApi<HassioResponse<HassioAddonsInfo>>("GET", `hassio/addons`)
.then(hassioApiResultExtractor);
export const fetchHassioAddonInfo = (hass: HomeAssistant, addon: string) =>
hass
.callApi<HassioResponse<HassioAddon>>("GET", `hassio/addons/${addon}/info`)
.callApi<HassioResponse<HassioAddonDetails>>(
"GET",
`hassio/addons/${addon}/info`
)
.then(hassioApiResultExtractor);
export const fetchHassioSupervisorInfo = (hass: HomeAssistant) =>
hass
.callApi<HassioResponse<any>>("GET", "hassio/supervisor/info")
.callApi<HassioResponse<HassioSupervisorInfo>>(
"GET",
"hassio/supervisor/info"
)
.then(hassioApiResultExtractor);
export const fetchHassioHostInfo = (hass: HomeAssistant) =>
hass
.callApi<HassioResponse<any>>("GET", "hassio/host/info")
.callApi<HassioResponse<HassioHostInfo>>("GET", "hassio/host/info")
.then(hassioApiResultExtractor);
export const fetchHassioHomeAssistantInfo = (hass: HomeAssistant) =>
hass
.callApi<HassioResponse<any>>("GET", "hassio/homeassistant/info")
.callApi<HassioResponse<HassioHomeAssistantInfo>>(
"GET",
"hassio/homeassistant/info"
)
.then(hassioApiResultExtractor);

View File

@ -122,7 +122,10 @@ export class HassRouterPage extends UpdatingElement {
: Promise.resolve();
// Check when loading the page source failed.
loadProm.catch(() => {
loadProm.catch((err) => {
// tslint:disable-next-line
console.error("Error loading page", newPage, err);
// Verify that we're still trying to show the same page.
if (this._currentPage !== newPage) {
return;

View File

@ -0,0 +1,35 @@
import "@polymer/paper-spinner/paper-spinner-lite";
import {
LitElement,
TemplateResult,
html,
css,
customElement,
CSSResult,
} from "lit-element";
@customElement("loading-screen")
class LoadingScreen extends LitElement {
protected render(): TemplateResult | void {
return html`
<paper-spinner-lite active></paper-spinner-lite>
`;
}
static get styles(): CSSResult {
return css`
:host {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"loading-screen": LoadingScreen;
}
}