diff --git a/demo/src/configs/arsaboo/entities.ts b/demo/src/configs/arsaboo/entities.ts index 369d5ecfa7..61935dd381 100644 --- a/demo/src/configs/arsaboo/entities.ts +++ b/demo/src/configs/arsaboo/entities.ts @@ -980,12 +980,12 @@ export const demoEntitiesArsaboo: () => Entity[] = () => entity_id: "climate.upstairs", state: "auto", attributes: { - current_temperature: 66, - min_temp: 45, - max_temp: 95, + current_temperature: 22, + min_temp: 15, + max_temp: 30, temperature: null, - target_temp_high: 79, - target_temp_low: 66, + target_temp_high: 24, + target_temp_low: 20, fan_mode: "auto", fan_list: ["auto", "on"], operation_mode: "auto", diff --git a/demo/webpack.config.js b/demo/webpack.config.js index 63ac853147..35c1ccdc93 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -19,7 +19,7 @@ module.exports = { devtool: isProd ? "cheap-source-map" : "inline-source-map", entry: { main: "./src/entrypoint.ts", - compatibility: "../src/entrypoints/compatibility.js", + compatibility: "../src/entrypoints/compatibility.ts", }, module: { rules: [ diff --git a/gulp/tasks/gen-icons.js b/gulp/tasks/gen-icons.js index 18a5bb42c4..c47b42f360 100644 --- a/gulp/tasks/gen-icons.js +++ b/gulp/tasks/gen-icons.js @@ -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,18 +95,27 @@ 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)); } -gulp.task("gen-icons-mdi", () => genMDIIcons()); -gulp.task("gen-icons-hass", () => genHassIcons()); -gulp.task("gen-icons", ["gen-icons-hass", "gen-icons-mdi"], () => {}); +gulp.task("gen-icons-mdi", (done) => { + genMDIIcons(); + done(); +}); +gulp.task("gen-icons-hass", (done) => { + genHassIcons(); + done(); +}); +gulp.task("gen-icons", gulp.series("gen-icons-hass", "gen-icons-mdi")); module.exports = { findIcons, diff --git a/gulp/tasks/translations.js b/gulp/tasks/translations.js index 898ee60cff..3cf30958b1 100755 --- a/gulp/tasks/translations.js +++ b/gulp/tasks/translations.js @@ -118,198 +118,219 @@ tasks.push(taskName); * the Lokalise update to translations/en.json will not happen immediately. */ taskName = "build-master-translation"; -gulp.task(taskName, ["clean-translations"], function() { - return gulp - .src("src/translations/en.json") - .pipe( - transform(function(data, file) { - return lokalise_transform(data, data); - }) - ) - .pipe(rename("translationMaster.json")) - .pipe(gulp.dest(workDir)); -}); +gulp.task( + taskName, + gulp.series("clean-translations", function() { + return gulp + .src("src/translations/en.json") + .pipe( + transform(function(data, file) { + return lokalise_transform(data, data); + }) + ) + .pipe(rename("translationMaster.json")) + .pipe(gulp.dest(workDir)); + }) +); tasks.push(taskName); taskName = "build-merged-translations"; -gulp.task(taskName, ["build-master-translation"], function() { - return gulp.src(inDir + "/*.json").pipe( - foreach(function(stream, file) { - // For each language generate a merged json file. It begins with the master - // translation as a failsafe for untranslated strings, and merges all parent - // tags into one file for each specific subtag - // - // TODO: This is a naive interpretation of BCP47 that should be improved. - // Will be OK for now as long as we don't have anything more complicated - // than a base translation + region. - const tr = path.basename(file.history[0], ".json"); - const subtags = tr.split("-"); - const src = [workDir + "/translationMaster.json"]; - for (let i = 1; i <= subtags.length; i++) { - const lang = subtags.slice(0, i).join("-"); - src.push(inDir + "/" + lang + ".json"); - } - return gulp - .src(src) - .pipe(transform((data) => emptyFilter(data))) - .pipe( - merge({ - fileName: tr + ".json", - }) - ) - .pipe(gulp.dest(fullDir)); - }) - ); -}); +gulp.task( + taskName, + gulp.series("build-master-translation", function() { + return gulp.src(inDir + "/*.json").pipe( + foreach(function(stream, file) { + // For each language generate a merged json file. It begins with the master + // translation as a failsafe for untranslated strings, and merges all parent + // tags into one file for each specific subtag + // + // TODO: This is a naive interpretation of BCP47 that should be improved. + // Will be OK for now as long as we don't have anything more complicated + // than a base translation + region. + const tr = path.basename(file.history[0], ".json"); + const subtags = tr.split("-"); + const src = [workDir + "/translationMaster.json"]; + for (let i = 1; i <= subtags.length; i++) { + const lang = subtags.slice(0, i).join("-"); + src.push(inDir + "/" + lang + ".json"); + } + return gulp + .src(src, { allowEmpty: true }) + .pipe(transform((data) => emptyFilter(data))) + .pipe( + merge({ + fileName: tr + ".json", + }) + ) + .pipe(gulp.dest(fullDir)); + }) + ); + }) +); tasks.push(taskName); const splitTasks = []; TRANSLATION_FRAGMENTS.forEach((fragment) => { taskName = "build-translation-fragment-" + fragment; - gulp.task(taskName, ["build-merged-translations"], function() { - // Return only the translations for this fragment. - return gulp - .src(fullDir + "/*.json") - .pipe( - transform((data) => ({ - ui: { - panel: { - [fragment]: data.ui.panel[fragment], + gulp.task( + taskName, + gulp.series("build-merged-translations", function() { + // Return only the translations for this fragment. + return gulp + .src(fullDir + "/*.json") + .pipe( + transform((data) => ({ + ui: { + panel: { + [fragment]: data.ui.panel[fragment], + }, }, - }, - })) - ) - .pipe(gulp.dest(workDir + "/" + fragment)); - }); + })) + ) + .pipe(gulp.dest(workDir + "/" + fragment)); + }) + ); tasks.push(taskName); splitTasks.push(taskName); }); taskName = "build-translation-core"; -gulp.task(taskName, ["build-merged-translations"], function() { - // Remove the fragment translations from the core translation. - return gulp - .src(fullDir + "/*.json") - .pipe( - transform((data) => { - TRANSLATION_FRAGMENTS.forEach((fragment) => { - delete data.ui.panel[fragment]; - }); - return data; - }) - ) - .pipe(gulp.dest(coreDir)); -}); +gulp.task( + taskName, + gulp.series("build-merged-translations", function() { + // Remove the fragment translations from the core translation. + return gulp + .src(fullDir + "/*.json") + .pipe( + transform((data) => { + TRANSLATION_FRAGMENTS.forEach((fragment) => { + delete data.ui.panel[fragment]; + }); + return data; + }) + ) + .pipe(gulp.dest(coreDir)); + }) +); tasks.push(taskName); splitTasks.push(taskName); taskName = "build-flattened-translations"; -gulp.task(taskName, splitTasks, function() { - // Flatten the split versions of our translations, and move them into outDir - return gulp - .src( - TRANSLATION_FRAGMENTS.map( - (fragment) => workDir + "/" + fragment + "/*.json" - ).concat(coreDir + "/*.json"), - { base: workDir } - ) - .pipe( - transform(function(data) { - // Polymer.AppLocalizeBehavior requires flattened json - return flatten(data); - }) - ) - .pipe(minify()) - .pipe(hashFilename()) - .pipe( - rename((filePath) => { - if (filePath.dirname === "core") { - filePath.dirname = ""; - } - }) - ) - .pipe(gulp.dest(outDir)); -}); +gulp.task( + taskName, + gulp.series(...splitTasks, function() { + // Flatten the split versions of our translations, and move them into outDir + return gulp + .src( + TRANSLATION_FRAGMENTS.map( + (fragment) => workDir + "/" + fragment + "/*.json" + ).concat(coreDir + "/*.json"), + { base: workDir } + ) + .pipe( + transform(function(data) { + // Polymer.AppLocalizeBehavior requires flattened json + return flatten(data); + }) + ) + .pipe(minify()) + .pipe(hashFilename()) + .pipe( + rename((filePath) => { + if (filePath.dirname === "core") { + filePath.dirname = ""; + } + }) + ) + .pipe(gulp.dest(outDir)); + }) +); tasks.push(taskName); taskName = "build-translation-fingerprints"; -gulp.task(taskName, ["build-flattened-translations"], function() { - return gulp - .src(outDir + "/**/*.json") - .pipe( - rename({ - extname: "", - }) - ) - .pipe( - hash({ - algorithm: "md5", - hashLength: 32, - template: "<%= name %>.json", - }) - ) - .pipe(hash.manifest("translationFingerprints.json")) - .pipe( - transform(function(data) { - // After generating fingerprints of our translation files, consolidate - // all translation fragment fingerprints under the translation name key - const newData = {}; - Object.entries(data).forEach(([key, value]) => { - const [path, _md5] = key.rsplit("-", 1); - // let translation = key; - let translation = path; - const parts = translation.split("/"); - if (parts.length === 2) { - translation = parts[1]; - } - if (!(translation in newData)) { - newData[translation] = { - fingerprints: {}, - }; - } - newData[translation].fingerprints[path] = value; - }); - return newData; - }) - ) - .pipe(gulp.dest(workDir)); -}); +gulp.task( + taskName, + gulp.series("build-flattened-translations", function() { + return gulp + .src(outDir + "/**/*.json") + .pipe( + rename({ + extname: "", + }) + ) + .pipe( + hash({ + algorithm: "md5", + hashLength: 32, + template: "<%= name %>.json", + }) + ) + .pipe(hash.manifest("translationFingerprints.json")) + .pipe( + transform(function(data) { + // After generating fingerprints of our translation files, consolidate + // all translation fragment fingerprints under the translation name key + const newData = {}; + Object.entries(data).forEach(([key, value]) => { + const [path, _md5] = key.rsplit("-", 1); + // let translation = key; + let translation = path; + const parts = translation.split("/"); + if (parts.length === 2) { + translation = parts[1]; + } + if (!(translation in newData)) { + newData[translation] = { + fingerprints: {}, + }; + } + newData[translation].fingerprints[path] = value; + }); + return newData; + }) + ) + .pipe(gulp.dest(workDir)); + }) +); tasks.push(taskName); taskName = "build-translations"; -gulp.task(taskName, ["build-translation-fingerprints"], function() { - return gulp - .src([ - "src/translations/translationMetadata.json", - workDir + "/translationFingerprints.json", - ]) - .pipe(merge({})) - .pipe( - transform(function(data) { - const newData = {}; - Object.entries(data).forEach(([key, value]) => { - // Filter out translations without native name. - if (data[key].nativeName) { - newData[key] = data[key]; - } else { - console.warn( - `Skipping language ${key}. Native name was not translated.` - ); - } - if (data[key]) newData[key] = value; - }); - return newData; - }) - ) - .pipe( - transform((data) => ({ - fragments: TRANSLATION_FRAGMENTS, - translations: data, - })) - ) - .pipe(rename("translationMetadata.json")) - .pipe(gulp.dest(workDir)); -}); +gulp.task( + taskName, + gulp.series("build-translation-fingerprints", function() { + return gulp + .src([ + "src/translations/translationMetadata.json", + workDir + "/translationFingerprints.json", + ]) + .pipe(merge({})) + .pipe( + transform(function(data) { + const newData = {}; + Object.entries(data).forEach(([key, value]) => { + // Filter out translations without native name. + if (data[key].nativeName) { + newData[key] = data[key]; + } else { + console.warn( + `Skipping language ${key}. Native name was not translated.` + ); + } + if (data[key]) newData[key] = value; + }); + return newData; + }) + ) + .pipe( + transform((data) => ({ + fragments: TRANSLATION_FRAGMENTS, + translations: data, + })) + ) + .pipe(rename("translationMetadata.json")) + .pipe(gulp.dest(workDir)); + }) +); tasks.push(taskName); module.exports = tasks; diff --git a/hassio/script/gen-icons.js b/hassio/script/gen-icons.js index 0c952ad04d..61fc39e212 100755 --- a/hassio/script/gen-icons.js +++ b/hassio/script/gen-icons.js @@ -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)); } diff --git a/hassio/src/addon-store/hassio-addon-repository.js b/hassio/src/addon-store/hassio-addon-repository.js deleted file mode 100644 index cc95f068bf..0000000000 --- a/hassio/src/addon-store/hassio-addon-repository.js +++ /dev/null @@ -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` - - - `; - } - - 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); diff --git a/hassio/src/addon-store/hassio-addon-repository.ts b/hassio/src/addon-store/hassio-addon-repository.ts new file mode 100644 index 0000000000..dd5a56ea46 --- /dev/null +++ b/hassio/src/addon-store/hassio-addon-repository.ts @@ -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` +
+
+ ${repo.name} +
+ Maintained by ${repo.maintainer}
+ ${repo.url} +
+
+ + ${this.addons + .sort((a, b) => + a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1 + ) + .map( + (addon) => html` + +
+ +
+
+ ` + )} +
+ `; + } + + 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); diff --git a/hassio/src/addon-store/hassio-addon-store.js b/hassio/src/addon-store/hassio-addon-store.js deleted file mode 100644 index 4e49bc6628..0000000000 --- a/hassio/src/addon-store/hassio-addon-store.js +++ /dev/null @@ -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` - - - - - `; - } - - 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); diff --git a/hassio/src/addon-store/hassio-addon-store.ts b/hassio/src/addon-store/hassio-addon-store.ts new file mode 100644 index 0000000000..35440bb58e --- /dev/null +++ b/hassio/src/addon-store/hassio-addon-store.ts @@ -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` + + `; + } + 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` + + `); + } + + return html` + + + ${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); diff --git a/hassio/src/addon-store/hassio-repositories-editor.js b/hassio/src/addon-store/hassio-repositories-editor.js deleted file mode 100644 index 93455375d8..0000000000 --- a/hassio/src/addon-store/hassio-repositories-editor.js +++ /dev/null @@ -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` - -
-
- Repositories -
- Configure which add-on repositories to fetch data from: -
-
- - -
- - -
-
- Add -
-
-
- `; - } - - 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); diff --git a/hassio/src/addon-store/hassio-repositories-editor.ts b/hassio/src/addon-store/hassio-repositories-editor.ts new file mode 100644 index 0000000000..f901ed5a35 --- /dev/null +++ b/hassio/src/addon-store/hassio-repositories-editor.ts @@ -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` +
+
+ Repositories +
+ Configure which add-on repositories to fetch data from: +
+
+ ${// 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` + +
+ +
+
+ + Remove + +
+
+ ` + )} + + +
+ + +
+
+ + Add + +
+
+
+ `; + } + + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + + if (changedProps.has("repos")) { + this._repoUrl = ""; + } + } + + private _urlChanged(ev: PolymerChangedEvent) { + 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; + } +} diff --git a/hassio/src/addon-view/hassio-addon-info.js b/hassio/src/addon-view/hassio-addon-info.js index 792407116b..10d8de3ad5 100644 --- a/hassio/src/addon-view/hassio-addon-info.js +++ b/hassio/src/addon-view/hassio-addon-info.js @@ -10,7 +10,9 @@ import "../../../src/components/ha-markdown"; import "../../../src/components/buttons/ha-call-api-button"; import "../../../src/resources/ha-style"; import EventsMixin from "../../../src/mixins/events-mixin"; +import { navigate } from "../../../src/common/navigate"; +import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown"; import "../components/hassio-card-content"; const PERMIS_DESC = { @@ -59,6 +61,11 @@ const PERMIS_DESC = { description: "An addon can authenticate users against Home Assistant, allowing add-ons to give users the possibility to log into applications running inside add-ons, using their Home Assistant username/password. This badge indicates if the add-on author requests this capability.", }, + ingress: { + title: "Ingress", + description: + "This add-on is using Ingress to embed its interface securely into Home Assistant.", + }, }; class HassioAddonInfo extends EventsMixin(PolymerElement) { @@ -161,12 +168,18 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) { icon="hassio:arrow-up-bold-circle" icon-class="update" > +
Update + Update + +
+