mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 01:36:49 +00:00
Add advanced mode (#3298)
* Add advanced mode * Move advanced mode to profile * Add promo for advanced mode
This commit is contained in:
parent
2c3cc1fbc7
commit
e804e62e66
74
src/data/collection.ts
Normal file
74
src/data/collection.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
Collection,
|
||||||
|
Connection,
|
||||||
|
getCollection,
|
||||||
|
UnsubscribeFunc,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||||
|
|
||||||
|
interface OptimisticCollection<T> extends Collection<T> {
|
||||||
|
save(data: T): Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an optimistic collection that includes a save function.
|
||||||
|
* When the collection is saved, the collection is optimistically updated.
|
||||||
|
* The update is reversed when the update failed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getOptimisticCollection = <StateType>(
|
||||||
|
saveCollection: (conn: Connection, data: StateType) => Promise<unknown>,
|
||||||
|
conn: Connection,
|
||||||
|
key: string,
|
||||||
|
fetchCollection: (conn: Connection) => Promise<StateType>,
|
||||||
|
subscribeUpdates?: (
|
||||||
|
conn: Connection,
|
||||||
|
store: Store<StateType>
|
||||||
|
) => Promise<UnsubscribeFunc>
|
||||||
|
): OptimisticCollection<StateType> => {
|
||||||
|
const updateKey = `${key}-optimistic`;
|
||||||
|
|
||||||
|
const collection = getCollection<StateType>(
|
||||||
|
conn,
|
||||||
|
key,
|
||||||
|
fetchCollection,
|
||||||
|
async (_conn, store) => {
|
||||||
|
// Subscribe to original updates
|
||||||
|
const subUpResult = subscribeUpdates
|
||||||
|
? subscribeUpdates(conn, store)
|
||||||
|
: undefined;
|
||||||
|
// Store the store
|
||||||
|
conn[updateKey] = store;
|
||||||
|
|
||||||
|
// Unsub function to undo both
|
||||||
|
return () => {
|
||||||
|
if (subUpResult) {
|
||||||
|
subUpResult.then((unsub) => unsub());
|
||||||
|
}
|
||||||
|
conn[updateKey] = undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...collection,
|
||||||
|
async save(data: StateType) {
|
||||||
|
const store: Store<StateType> | undefined = conn[updateKey];
|
||||||
|
let current;
|
||||||
|
|
||||||
|
// Can be undefined if currently no subscribers
|
||||||
|
if (store) {
|
||||||
|
current = store.state;
|
||||||
|
store.setState(data, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await saveCollection(conn, data);
|
||||||
|
} catch (err) {
|
||||||
|
if (store) {
|
||||||
|
store.setState(current as any, true);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -1,8 +1,14 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { Connection } from "home-assistant-js-websocket";
|
||||||
|
import { getOptimisticCollection } from "./collection";
|
||||||
|
|
||||||
|
export interface CoreFrontendUserData {
|
||||||
|
showAdvanced?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// tslint:disable-next-line
|
interface FrontendUserData {
|
||||||
interface FrontendUserData {}
|
core: CoreFrontendUserData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ValidUserDataKey = keyof FrontendUserData;
|
export type ValidUserDataKey = keyof FrontendUserData;
|
||||||
@ -10,10 +16,10 @@ export type ValidUserDataKey = keyof FrontendUserData;
|
|||||||
export const fetchFrontendUserData = async <
|
export const fetchFrontendUserData = async <
|
||||||
UserDataKey extends ValidUserDataKey
|
UserDataKey extends ValidUserDataKey
|
||||||
>(
|
>(
|
||||||
hass: HomeAssistant,
|
conn: Connection,
|
||||||
key: UserDataKey
|
key: UserDataKey
|
||||||
): Promise<FrontendUserData[UserDataKey] | null> => {
|
): Promise<FrontendUserData[UserDataKey] | null> => {
|
||||||
const result = await hass.callWS<{
|
const result = await conn.sendMessagePromise<{
|
||||||
value: FrontendUserData[UserDataKey] | null;
|
value: FrontendUserData[UserDataKey] | null;
|
||||||
}>({
|
}>({
|
||||||
type: "frontend/get_user_data",
|
type: "frontend/get_user_data",
|
||||||
@ -25,12 +31,31 @@ export const fetchFrontendUserData = async <
|
|||||||
export const saveFrontendUserData = async <
|
export const saveFrontendUserData = async <
|
||||||
UserDataKey extends ValidUserDataKey
|
UserDataKey extends ValidUserDataKey
|
||||||
>(
|
>(
|
||||||
hass: HomeAssistant,
|
conn: Connection,
|
||||||
key: UserDataKey,
|
key: UserDataKey,
|
||||||
value: FrontendUserData[UserDataKey]
|
value: FrontendUserData[UserDataKey]
|
||||||
): Promise<void> =>
|
): Promise<void> =>
|
||||||
hass.callWS<void>({
|
conn.sendMessagePromise<void>({
|
||||||
type: "frontend/set_user_data",
|
type: "frontend/set_user_data",
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getOptimisticFrontendUserDataCollection = <
|
||||||
|
UserDataKey extends ValidUserDataKey
|
||||||
|
>(
|
||||||
|
conn: Connection,
|
||||||
|
userDataKey: UserDataKey
|
||||||
|
) =>
|
||||||
|
getOptimisticCollection(
|
||||||
|
(_conn, data) =>
|
||||||
|
saveFrontendUserData(
|
||||||
|
conn,
|
||||||
|
userDataKey,
|
||||||
|
// @ts-ignore
|
||||||
|
data
|
||||||
|
),
|
||||||
|
conn,
|
||||||
|
`_frontendUserData-${userDataKey}`,
|
||||||
|
() => fetchFrontendUserData(conn, userDataKey)
|
||||||
|
);
|
||||||
|
@ -12,12 +12,12 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
||||||
fetchFrontendUserData(hass, "language");
|
fetchFrontendUserData(hass.connection, "language");
|
||||||
|
|
||||||
export const saveTranslationPreferences = (
|
export const saveTranslationPreferences = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
data: FrontendTranslationData
|
data: FrontendTranslationData
|
||||||
) => saveFrontendUserData(hass, "language", data);
|
) => saveFrontendUserData(hass.connection, "language", data);
|
||||||
|
|
||||||
export const getHassTranslations = async (
|
export const getHassTranslations = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -37,6 +37,7 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
|
|||||||
<div class$="[[computeClasses(isWide)]]">
|
<div class$="[[computeClasses(isWide)]]">
|
||||||
<ha-config-section-core
|
<ha-config-section-core
|
||||||
is-wide="[[isWide]]"
|
is-wide="[[isWide]]"
|
||||||
|
show-advanced="[[showAdvanced]]"
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
></ha-config-section-core>
|
></ha-config-section-core>
|
||||||
</div>
|
</div>
|
||||||
@ -48,6 +49,7 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
|
|||||||
return {
|
return {
|
||||||
hass: Object,
|
hass: Object,
|
||||||
isWide: Boolean,
|
isWide: Boolean,
|
||||||
|
showAdvanced: Boolean,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,79 +63,80 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
|||||||
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
|
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
|
||||||
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
|
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
|
||||||
|
|
||||||
<ha-card
|
<template is="dom-if" if="[[showAdvanced]]">
|
||||||
header="[[localize('ui.panel.config.core.section.core.validation.heading')]]"
|
<ha-card
|
||||||
>
|
header="[[localize('ui.panel.config.core.section.core.validation.heading')]]"
|
||||||
<div class="card-content">
|
>
|
||||||
[[localize('ui.panel.config.core.section.core.validation.introduction')]]
|
<div class="card-content">
|
||||||
<template is="dom-if" if="[[!validateLog]]">
|
[[localize('ui.panel.config.core.section.core.validation.introduction')]]
|
||||||
<div class="validate-container">
|
<template is="dom-if" if="[[!validateLog]]">
|
||||||
<template is="dom-if" if="[[!validating]]">
|
<div class="validate-container">
|
||||||
<template is="dom-if" if="[[isValid]]">
|
<template is="dom-if" if="[[!validating]]">
|
||||||
<div class="validate-result" id="result">
|
<template is="dom-if" if="[[isValid]]">
|
||||||
[[localize('ui.panel.config.core.section.core.validation.valid')]]
|
<div class="validate-result" id="result">
|
||||||
</div>
|
[[localize('ui.panel.config.core.section.core.validation.valid')]]
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<mwc-button raised="" on-click="validateConfig">
|
||||||
|
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
|
||||||
|
</mwc-button>
|
||||||
</template>
|
</template>
|
||||||
|
<template is="dom-if" if="[[validating]]">
|
||||||
|
<paper-spinner active=""></paper-spinner>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="[[validateLog]]">
|
||||||
|
<div class="config-invalid">
|
||||||
|
<span class="text">
|
||||||
|
[[localize('ui.panel.config.core.section.core.validation.invalid')]]
|
||||||
|
</span>
|
||||||
<mwc-button raised="" on-click="validateConfig">
|
<mwc-button raised="" on-click="validateConfig">
|
||||||
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
|
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</template>
|
</div>
|
||||||
<template is="dom-if" if="[[validating]]">
|
<div id="configLog" class="validate-log">[[validateLog]]</div>
|
||||||
<paper-spinner active=""></paper-spinner>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</ha-card>
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[validateLog]]">
|
|
||||||
<div class="config-invalid">
|
|
||||||
<span class="text">
|
|
||||||
[[localize('ui.panel.config.core.section.core.validation.invalid')]]
|
|
||||||
</span>
|
|
||||||
<mwc-button raised="" on-click="validateConfig">
|
|
||||||
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
<div id="configLog" class="validate-log">[[validateLog]]</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
|
|
||||||
<ha-card
|
|
||||||
header="[[localize('ui.panel.config.core.section.core.reloading.heading')]]"
|
|
||||||
>
|
|
||||||
<div class="card-content">
|
|
||||||
[[localize('ui.panel.config.core.section.core.reloading.introduction')]]
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<ha-call-service-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
domain="homeassistant"
|
|
||||||
service="reload_core_config"
|
|
||||||
>[[localize('ui.panel.config.core.section.core.reloading.core')]]
|
|
||||||
</ha-call-service-button>
|
|
||||||
<ha-call-service-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
domain="group"
|
|
||||||
service="reload"
|
|
||||||
hidden$="[[!groupLoaded(hass)]]"
|
|
||||||
>[[localize('ui.panel.config.core.section.core.reloading.group')]]
|
|
||||||
</ha-call-service-button>
|
|
||||||
<ha-call-service-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
domain="automation"
|
|
||||||
service="reload"
|
|
||||||
hidden$="[[!automationLoaded(hass)]]"
|
|
||||||
>[[localize('ui.panel.config.core.section.core.reloading.automation')]]
|
|
||||||
</ha-call-service-button>
|
|
||||||
<ha-call-service-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
domain="script"
|
|
||||||
service="reload"
|
|
||||||
hidden$="[[!scriptLoaded(hass)]]"
|
|
||||||
>[[localize('ui.panel.config.core.section.core.reloading.script')]]
|
|
||||||
</ha-call-service-button>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
|
|
||||||
|
<ha-card
|
||||||
|
header="[[localize('ui.panel.config.core.section.core.reloading.heading')]]"
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
[[localize('ui.panel.config.core.section.core.reloading.introduction')]]
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-call-service-button
|
||||||
|
hass="[[hass]]"
|
||||||
|
domain="homeassistant"
|
||||||
|
service="reload_core_config"
|
||||||
|
>[[localize('ui.panel.config.core.section.core.reloading.core')]]
|
||||||
|
</ha-call-service-button>
|
||||||
|
<ha-call-service-button
|
||||||
|
hass="[[hass]]"
|
||||||
|
domain="group"
|
||||||
|
service="reload"
|
||||||
|
hidden$="[[!groupLoaded(hass)]]"
|
||||||
|
>[[localize('ui.panel.config.core.section.core.reloading.group')]]
|
||||||
|
</ha-call-service-button>
|
||||||
|
<ha-call-service-button
|
||||||
|
hass="[[hass]]"
|
||||||
|
domain="automation"
|
||||||
|
service="reload"
|
||||||
|
hidden$="[[!automationLoaded(hass)]]"
|
||||||
|
>[[localize('ui.panel.config.core.section.core.reloading.automation')]]
|
||||||
|
</ha-call-service-button>
|
||||||
|
<ha-call-service-button
|
||||||
|
hass="[[hass]]"
|
||||||
|
domain="script"
|
||||||
|
service="reload"
|
||||||
|
hidden$="[[!scriptLoaded(hass)]]"
|
||||||
|
>[[localize('ui.panel.config.core.section.core.reloading.script')]]
|
||||||
|
</ha-call-service-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</template>
|
||||||
<ha-card
|
<ha-card
|
||||||
header="[[localize('ui.panel.config.core.section.core.server_management.heading')]]"
|
header="[[localize('ui.panel.config.core.section.core.server_management.heading')]]"
|
||||||
>
|
>
|
||||||
@ -188,6 +189,8 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
|||||||
type: String,
|
type: String,
|
||||||
value: "",
|
value: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showAdvanced: Boolean,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,10 +31,17 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
.content {
|
.content {
|
||||||
padding-bottom: 32px;
|
padding-bottom: 32px;
|
||||||
}
|
}
|
||||||
a {
|
ha-card a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
.promo-advanced {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.promo-advanced a {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<app-header-layout has-scrolling-region="">
|
<app-header-layout has-scrolling-region="">
|
||||||
@ -99,7 +106,16 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
</a>
|
</a>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
|
|
||||||
<ha-config-navigation hass="[[hass]]"></ha-config-navigation>
|
<ha-config-navigation
|
||||||
|
hass="[[hass]]"
|
||||||
|
show-advanced="[[showAdvanced]]"
|
||||||
|
></ha-config-navigation>
|
||||||
|
|
||||||
|
<template is='dom-if' if='[[!showAdvanced]]'>
|
||||||
|
<div class='promo-advanced'>
|
||||||
|
Missing config options? Enable advanced mode on <a href="/profile">your profile page.</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
</div>
|
</div>
|
||||||
</app-header-layout>
|
</app-header-layout>
|
||||||
@ -111,6 +127,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
hass: Object,
|
hass: Object,
|
||||||
isWide: Boolean,
|
isWide: Boolean,
|
||||||
cloudStatus: Object,
|
cloudStatus: Object,
|
||||||
|
showAdvanced: Boolean,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
import "@polymer/iron-icon/iron-icon";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import NavigateMixin from "../../../mixins/navigate-mixin";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
import isComponentLoaded from "../../../common/config/is_component_loaded";
|
|
||||||
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
import "../../../components/ha-icon-next";
|
|
||||||
|
|
||||||
const CORE_PAGES = ["core", "customize", "entity_registry", "area_registry"];
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
* @appliesMixin NavigateMixin
|
|
||||||
*/
|
|
||||||
class HaConfigNavigation extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex">
|
|
||||||
ha-card {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-card>
|
|
||||||
<template is="dom-repeat" items="[[pages]]">
|
|
||||||
<template is="dom-if" if="[[_computeLoaded(hass, item)]]">
|
|
||||||
<paper-item on-click="_navigate">
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
[[_computeCaption(item, localize)]]
|
|
||||||
<div secondary="">[[_computeDescription(item, localize)]]</div>
|
|
||||||
</paper-item-body>
|
|
||||||
<ha-icon-next></ha-icon-next>
|
|
||||||
</paper-item>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
pages: {
|
|
||||||
type: Array,
|
|
||||||
value: [
|
|
||||||
"core",
|
|
||||||
"person",
|
|
||||||
"entity_registry",
|
|
||||||
"area_registry",
|
|
||||||
"automation",
|
|
||||||
"script",
|
|
||||||
"zha",
|
|
||||||
"zwave",
|
|
||||||
"customize",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeLoaded(hass, page) {
|
|
||||||
return CORE_PAGES.includes(page) || isComponentLoaded(hass, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeCaption(page, localize) {
|
|
||||||
return localize(`ui.panel.config.${page}.caption`);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeDescription(page, localize) {
|
|
||||||
return localize(`ui.panel.config.${page}.description`);
|
|
||||||
}
|
|
||||||
|
|
||||||
_navigate(ev) {
|
|
||||||
this.navigate("/config/" + ev.model.item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-config-navigation", HaConfigNavigation);
|
|
82
src/panels/config/dashboard/ha-config-navigation.ts
Normal file
82
src/panels/config/dashboard/ha-config-navigation.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import "@polymer/iron-icon/iron-icon";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
|
||||||
|
import isComponentLoaded from "../../../common/config/is_component_loaded";
|
||||||
|
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
import "../../../components/ha-icon-next";
|
||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
TemplateResult,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
const PAGES: Array<{
|
||||||
|
page: string;
|
||||||
|
core?: boolean;
|
||||||
|
advanced?: boolean;
|
||||||
|
}> = [
|
||||||
|
{ page: "core", core: true },
|
||||||
|
{ page: "person" },
|
||||||
|
{ page: "entity_registry", core: true },
|
||||||
|
{ page: "area_registry", core: true },
|
||||||
|
{ page: "automation" },
|
||||||
|
{ page: "script" },
|
||||||
|
{ page: "zha" },
|
||||||
|
{ page: "zwave" },
|
||||||
|
{ page: "customize", core: true, advanced: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("ha-config-navigation")
|
||||||
|
class HaConfigNavigation extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public showAdvanced!: boolean;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
${PAGES.map(({ page, core, advanced }) =>
|
||||||
|
(core || isComponentLoaded(this.hass, page)) &&
|
||||||
|
(!advanced || this.showAdvanced)
|
||||||
|
? html`
|
||||||
|
<a href=${`/config/${page}`}>
|
||||||
|
<paper-item>
|
||||||
|
<paper-item-body two-line="">
|
||||||
|
${this.hass.localize(`ui.panel.config.${page}.caption`)}
|
||||||
|
<div secondary>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.${page}.description`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</paper-item-body>
|
||||||
|
<ha-icon-next></ha-icon-next>
|
||||||
|
</paper-item>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-config-navigation": HaConfigNavigation;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,10 @@ import { HomeAssistant } from "../../types";
|
|||||||
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
|
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
|
||||||
import { listenMediaQuery } from "../../common/dom/media_query";
|
import { listenMediaQuery } from "../../common/dom/media_query";
|
||||||
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
|
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
|
||||||
|
import {
|
||||||
|
CoreFrontendUserData,
|
||||||
|
getOptimisticFrontendUserDataCollection,
|
||||||
|
} from "../../data/frontend";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -17,8 +21,6 @@ declare global {
|
|||||||
class HaPanelConfig extends HassRouterPage {
|
class HaPanelConfig extends HassRouterPage {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@property() public narrow!: boolean;
|
@property() public narrow!: boolean;
|
||||||
@property() public _wideSidebar: boolean = false;
|
|
||||||
@property() public _wide: boolean = false;
|
|
||||||
|
|
||||||
protected routerOptions: RouterOptions = {
|
protected routerOptions: RouterOptions = {
|
||||||
defaultPage: "dashboard",
|
defaultPage: "dashboard",
|
||||||
@ -93,6 +95,9 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@property() private _wideSidebar: boolean = false;
|
||||||
|
@property() private _wide: boolean = false;
|
||||||
|
@property() private _coreUserData?: CoreFrontendUserData;
|
||||||
@property() private _cloudStatus?: CloudStatus;
|
@property() private _cloudStatus?: CloudStatus;
|
||||||
|
|
||||||
private _listeners: Array<() => void> = [];
|
private _listeners: Array<() => void> = [];
|
||||||
@ -109,6 +114,14 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
this._wideSidebar = matches;
|
this._wideSidebar = matches;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
this._listeners.push(
|
||||||
|
getOptimisticFrontendUserDataCollection(
|
||||||
|
this.hass.connection,
|
||||||
|
"core"
|
||||||
|
).subscribe((coreUserData) => {
|
||||||
|
this._coreUserData = coreUserData || {};
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback() {
|
||||||
@ -131,6 +144,7 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
protected updatePageEl(el) {
|
protected updatePageEl(el) {
|
||||||
el.route = this.routeTail;
|
el.route = this.routeTail;
|
||||||
el.hass = this.hass;
|
el.hass = this.hass;
|
||||||
|
el.showAdvanced = !!(this._coreUserData && this._coreUserData.showAdvanced);
|
||||||
el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide;
|
el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide;
|
||||||
el.narrow = this.narrow;
|
el.narrow = this.narrow;
|
||||||
el.cloudStatus = this._cloudStatus;
|
el.cloudStatus = this._cloudStatus;
|
||||||
|
65
src/panels/profile/ha-advanced-mode-card.ts
Normal file
65
src/panels/profile/ha-advanced-mode-card.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
customElement,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import "../../components/ha-card";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import {
|
||||||
|
CoreFrontendUserData,
|
||||||
|
getOptimisticFrontendUserDataCollection,
|
||||||
|
} from "../../data/frontend";
|
||||||
|
|
||||||
|
@customElement("ha-advanced-mode-card")
|
||||||
|
class AdvancedModeCard extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public coreUserData?: CoreFrontendUserData;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="title">Advanced mode</div>
|
||||||
|
<paper-toggle-button
|
||||||
|
.checked=${this.coreUserData && this.coreUserData.showAdvanced}
|
||||||
|
.disabled=${this.coreUserData === undefined}
|
||||||
|
@change=${this._advancedToggled}
|
||||||
|
></paper-toggle-button>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
Home Assistant hides advanced features and options by default. You can
|
||||||
|
make these features accessible by checking this toggle. This is a
|
||||||
|
user-specific setting and does not impact other users using Home
|
||||||
|
Assistant.
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _advancedToggled(ev) {
|
||||||
|
getOptimisticFrontendUserDataCollection(this.hass.connection, "core").save({
|
||||||
|
showAdvanced: ev.currentTarget.checked,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-advanced-mode-card": AdvancedModeCard;
|
||||||
|
}
|
||||||
|
}
|
@ -11,11 +11,14 @@ import "../../components/ha-card";
|
|||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
import "../../resources/ha-style";
|
import "../../resources/ha-style";
|
||||||
|
|
||||||
|
import { getOptimisticFrontendUserDataCollection } from "../../data/frontend";
|
||||||
|
|
||||||
import { EventsMixin } from "../../mixins/events-mixin";
|
import { EventsMixin } from "../../mixins/events-mixin";
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||||
|
|
||||||
import "./ha-change-password-card";
|
import "./ha-change-password-card";
|
||||||
import "./ha-mfa-modules-card";
|
import "./ha-mfa-modules-card";
|
||||||
|
import "./ha-advanced-mode-card";
|
||||||
import "./ha-refresh-tokens-card";
|
import "./ha-refresh-tokens-card";
|
||||||
import "./ha-long-lived-access-tokens-card";
|
import "./ha-long-lived-access-tokens-card";
|
||||||
|
|
||||||
@ -98,6 +101,11 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
mfa-modules="[[hass.user.mfa_modules]]"
|
mfa-modules="[[hass.user.mfa_modules]]"
|
||||||
></ha-mfa-modules-card>
|
></ha-mfa-modules-card>
|
||||||
|
|
||||||
|
<ha-advanced-mode-card
|
||||||
|
hass="[[hass]]"
|
||||||
|
core-user-data="[[_coreUserData]]"
|
||||||
|
></ha-mfa-modules-card>
|
||||||
|
|
||||||
<ha-refresh-tokens-card
|
<ha-refresh-tokens-card
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
refresh-tokens="[[_refreshTokens]]"
|
refresh-tokens="[[_refreshTokens]]"
|
||||||
@ -119,12 +127,27 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
hass: Object,
|
hass: Object,
|
||||||
narrow: Boolean,
|
narrow: Boolean,
|
||||||
_refreshTokens: Array,
|
_refreshTokens: Array,
|
||||||
|
_coreUserData: Object,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._refreshRefreshTokens();
|
this._refreshRefreshTokens();
|
||||||
|
this._unsubCoreData = getOptimisticFrontendUserDataCollection(
|
||||||
|
this.hass.connection,
|
||||||
|
"core"
|
||||||
|
).subscribe((coreUserData) => {
|
||||||
|
this._coreUserData = coreUserData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._unsubCoreData) {
|
||||||
|
this._unsubCoreData();
|
||||||
|
this._unsubCoreData = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _refreshRefreshTokens() {
|
async _refreshRefreshTokens() {
|
||||||
|
@ -9,9 +9,11 @@ import { HassBaseEl } from "./hass-base-mixin";
|
|||||||
import { computeLocalize } from "../common/translations/localize";
|
import { computeLocalize } from "../common/translations/localize";
|
||||||
import { computeRTL } from "../common/util/compute_rtl";
|
import { computeRTL } from "../common/util/compute_rtl";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { saveFrontendUserData } from "../data/frontend";
|
|
||||||
import { storeState } from "../util/ha-pref-storage";
|
import { storeState } from "../util/ha-pref-storage";
|
||||||
import { getHassTranslations } from "../data/translation";
|
import {
|
||||||
|
getHassTranslations,
|
||||||
|
saveTranslationPreferences,
|
||||||
|
} from "../data/translation";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* superClass needs to contain `this.hass` and `this._updateHass`.
|
* superClass needs to contain `this.hass` and `this._updateHass`.
|
||||||
@ -65,7 +67,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||||||
this._updateHass({ language, selectedLanguage: language });
|
this._updateHass({ language, selectedLanguage: language });
|
||||||
storeState(this.hass);
|
storeState(this.hass);
|
||||||
if (saveToBackend) {
|
if (saveToBackend) {
|
||||||
saveFrontendUserData(this.hass, "language", { language });
|
saveTranslationPreferences(this.hass, { language });
|
||||||
}
|
}
|
||||||
|
|
||||||
this._applyTranslations(this.hass);
|
this._applyTranslations(this.hass);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
import { fetchFrontendUserData } from "../data/frontend";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import { fetchTranslationPreferences } from "../data/translation";
|
||||||
|
|
||||||
const STORAGE = window.localStorage || {};
|
const STORAGE = window.localStorage || {};
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ function findAvailableLanguage(language: string) {
|
|||||||
* Get user selected language from backend
|
* Get user selected language from backend
|
||||||
*/
|
*/
|
||||||
export async function getUserLanguage(hass: HomeAssistant) {
|
export async function getUserLanguage(hass: HomeAssistant) {
|
||||||
const result = await fetchFrontendUserData(hass, "language");
|
const result = await fetchTranslationPreferences(hass);
|
||||||
const language = result ? result.language : null;
|
const language = result ? result.language : null;
|
||||||
if (language) {
|
if (language) {
|
||||||
const availableLanguage = findAvailableLanguage(language);
|
const availableLanguage = findAvailableLanguage(language);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user