mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-23 14:14:36 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccd93972c9 | ||
|
|
6cc65bfe61 | ||
|
|
b32d1086e4 | ||
|
|
33f19d64f7 | ||
|
|
c080db0a58 | ||
|
|
aebbf7e732 | ||
|
|
a4acb9de92 |
No files matched your search
@@ -15,6 +15,10 @@ import {
|
||||
|
||||
const DEBOUNCE_WAIT = 300;
|
||||
|
||||
// A page can be opened with or without a trailing slash
|
||||
const trimSlash = (path: string) =>
|
||||
path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
||||
|
||||
export interface QueryParamsControllerOptions {
|
||||
/** Whether the host keeps its state in the URL. */
|
||||
enabled: () => boolean;
|
||||
@@ -24,6 +28,12 @@ export interface QueryParamsControllerOptions {
|
||||
* the URL with `searchParams()` and apply it, then `write()` the state back.
|
||||
*/
|
||||
navigated: () => void;
|
||||
/**
|
||||
* The path of the host's page, for a page that can first be connected on
|
||||
* a sub-path it sends on to itself, like an add link. Defaults to the path
|
||||
* the host is first connected on.
|
||||
*/
|
||||
path?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +67,7 @@ export class QueryParamsController<
|
||||
public hostConnected() {
|
||||
// A cached page is reconnected on the way to another page, before its
|
||||
// router has moved on, so it keeps the page it was first connected on
|
||||
this._path ??= currentPath();
|
||||
this._path ??= this._options.path ?? currentPath();
|
||||
this._connected = true;
|
||||
mainWindow.addEventListener("location-changed", this._navigated);
|
||||
mainWindow.addEventListener("popstate", this._navigated);
|
||||
@@ -127,7 +137,9 @@ export class QueryParamsController<
|
||||
|
||||
private _isActive(): boolean {
|
||||
return (
|
||||
this._connected && this._options.enabled() && currentPath() === this._path
|
||||
this._connected &&
|
||||
this._options.enabled() &&
|
||||
trimSlash(currentPath()) === trimSlash(this._path ?? "")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,7 +170,9 @@ export class QueryParamsController<
|
||||
const current = mainWindow.location.search;
|
||||
const queryString = updateQueryString(current, values, this._config);
|
||||
if (current !== (queryString ? `?${queryString}` : "")) {
|
||||
replaceCurrentUrl(constructUrlCurrentPath(queryString));
|
||||
// Keep a fragment like #config_entry=, the demo already routes with it
|
||||
const hash = __DEMO__ ? "" : mainWindow.location.hash;
|
||||
replaceCurrentUrl(`${constructUrlCurrentPath(queryString)}${hash}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
decodeQueryParams,
|
||||
hasQueryParams,
|
||||
type QueryParamConfig,
|
||||
type QueryParamValues,
|
||||
type SearchParamsSource,
|
||||
} from "./query-params";
|
||||
|
||||
// `status` adds the ignored and disabled integrations to what is shown.
|
||||
export const integrationsQueryParamConfig = {
|
||||
string: ["search"],
|
||||
list: ["status"],
|
||||
} as const satisfies QueryParamConfig;
|
||||
|
||||
export const integrationQueryParamConfig = {
|
||||
string: ["search"],
|
||||
} as const satisfies QueryParamConfig;
|
||||
|
||||
export type IntegrationsQueryParams = QueryParamValues<
|
||||
typeof integrationsQueryParamConfig
|
||||
>;
|
||||
|
||||
export interface IntegrationsState {
|
||||
search: string;
|
||||
showIgnored: boolean;
|
||||
showDisabled: boolean;
|
||||
}
|
||||
|
||||
export const integrationsQueryParams = (
|
||||
state: IntegrationsState
|
||||
): IntegrationsQueryParams => ({
|
||||
search: state.search,
|
||||
status: [
|
||||
...(state.showIgnored ? ["ignored"] : []),
|
||||
...(state.showDisabled ? ["disabled"] : []),
|
||||
],
|
||||
});
|
||||
|
||||
// What a link says to show, undefined when it has none of the page's params
|
||||
// so what was picked before stays. A link replaces the search and the toggles.
|
||||
export const integrationsLinkFromUrl = (
|
||||
searchParams: SearchParamsSource
|
||||
): IntegrationsState | undefined => {
|
||||
if (!hasQueryParams(searchParams, integrationsQueryParamConfig)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { search, status } = decodeQueryParams(
|
||||
searchParams,
|
||||
integrationsQueryParamConfig
|
||||
);
|
||||
|
||||
return {
|
||||
search: search ?? "",
|
||||
showIgnored: !!status?.includes("ignored"),
|
||||
showDisabled: !!status?.includes("disabled"),
|
||||
};
|
||||
};
|
||||
|
||||
export const integrationSearchFromUrl = (
|
||||
searchParams: SearchParamsSource
|
||||
): string | undefined => {
|
||||
if (!hasQueryParams(searchParams, integrationQueryParamConfig)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
decodeQueryParams(searchParams, integrationQueryParamConfig).search ?? ""
|
||||
);
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import { customElement, property, queryAll, state } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { QueryParamsController } from "../../../common/controllers/query-params-controller";
|
||||
import type { HASSDomTargetEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import {
|
||||
@@ -26,6 +27,10 @@ import {
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
integrationQueryParamConfig,
|
||||
integrationSearchFromUrl,
|
||||
} from "../../../common/url/integrations-query-params";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dropdown";
|
||||
@@ -176,6 +181,15 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
@queryAll("ha-config-entry-row")
|
||||
private _configEntryRows!: NodeListOf<HaConfigEntryRow>;
|
||||
|
||||
private _queryParams = new QueryParamsController(
|
||||
this,
|
||||
integrationQueryParamConfig,
|
||||
{
|
||||
enabled: () => true,
|
||||
navigated: () => this._applyUrl(),
|
||||
}
|
||||
);
|
||||
|
||||
private _handleHashChange = () => {
|
||||
this._searchParms = new URLSearchParams(
|
||||
__DEMO__ ? window.location.search : window.location.hash.substring(1)
|
||||
@@ -237,6 +251,10 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
this._fetchDiagnostics();
|
||||
this._fetchEntitySources();
|
||||
}
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._applyUrl();
|
||||
}
|
||||
if (
|
||||
changedProperties.has("configEntries") ||
|
||||
changedProperties.has("_extraConfigEntries")
|
||||
@@ -720,11 +738,12 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
|
||||
${
|
||||
normalData.length + attentionData.length > 0
|
||||
normalData.length + attentionData.length > 0 || this._filter
|
||||
? html`<ha-input-search
|
||||
appearance="outlined"
|
||||
.value=${this._filter}
|
||||
@input=${this._handleSearchChange}
|
||||
@blur=${this._flushUrl}
|
||||
></ha-input-search>`
|
||||
: nothing
|
||||
}
|
||||
@@ -1256,6 +1275,32 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _handleSearchChange(ev: HASSDomTargetEvent<HaInputSearch>) {
|
||||
this._filter = ev.target.value ?? "";
|
||||
|
||||
this._writeUrl(true);
|
||||
}
|
||||
|
||||
private _flushUrl() {
|
||||
this._queryParams.flush();
|
||||
}
|
||||
|
||||
private _writeUrl(debounced = false) {
|
||||
this._queryParams.write({ search: this._filter }, debounced);
|
||||
}
|
||||
|
||||
private _applyUrl() {
|
||||
const searchParams = this._queryParams.searchParams();
|
||||
|
||||
if (!searchParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
const search = integrationSearchFromUrl(searchParams);
|
||||
|
||||
if (search !== undefined) {
|
||||
this._filter = search;
|
||||
}
|
||||
|
||||
this._writeUrl();
|
||||
}
|
||||
|
||||
private async _handleEnableDebugLogging() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consume } from "../../../common/decorators/consume";
|
||||
import { QueryParamsController } from "../../../common/controllers/query-params-controller";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { deepActiveElement } from "../../../common/dom/deep-active-element";
|
||||
import type { HASSDomTargetEvent } from "../../../common/dom/fire_event";
|
||||
@@ -16,12 +17,13 @@ import {
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
protocolIntegrationPicked,
|
||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||
import {
|
||||
getHistoryState,
|
||||
navigate,
|
||||
updateHistoryState,
|
||||
} from "../../../common/navigate";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import {
|
||||
integrationsLinkFromUrl,
|
||||
integrationsQueryParamConfig,
|
||||
integrationsQueryParams,
|
||||
} from "../../../common/url/integrations-query-params";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import { nextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
@@ -169,7 +171,7 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
window.location.hash.substring(1)
|
||||
);
|
||||
|
||||
@state() private _filter: string = getHistoryState()?.filter || "";
|
||||
@state() private _filter = "";
|
||||
|
||||
@state() private _logInfos?: Record<string, IntegrationLogInfo>;
|
||||
|
||||
@@ -185,6 +187,17 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
|
||||
private _childReadyRegistered = false;
|
||||
|
||||
// The page can first be opened on /add, which sends itself on to the page
|
||||
private _queryParams = new QueryParamsController(
|
||||
this,
|
||||
integrationsQueryParamConfig,
|
||||
{
|
||||
enabled: () => true,
|
||||
navigated: () => this._applyUrl(),
|
||||
path: "/config/integrations/dashboard",
|
||||
}
|
||||
);
|
||||
|
||||
@consume({ context: childPanelReadyContext, subscribe: true })
|
||||
private _registerChildPanelReady?: RegisterChildPanelReady;
|
||||
|
||||
@@ -398,6 +411,14 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
}
|
||||
);
|
||||
|
||||
protected willUpdate(changed: PropertyValues<this>) {
|
||||
super.willUpdate(changed);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._applyUrl();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changed: PropertyValues<this>) {
|
||||
super.firstUpdated(changed);
|
||||
this._fetchManifests();
|
||||
@@ -556,6 +577,7 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
appearance="outlined"
|
||||
.value=${this._filter}
|
||||
@input=${this._handleSearchChange}
|
||||
@blur=${this._flushUrl}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.integrations.search"
|
||||
)}
|
||||
@@ -577,6 +599,7 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
appearance="outlined"
|
||||
.value=${this._filter}
|
||||
@input=${this._handleSearchChange}
|
||||
@blur=${this._flushUrl}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.integrations.search"
|
||||
)}
|
||||
@@ -886,6 +909,8 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
// Keep the search typed so far on the entry the dialog's Back returns to
|
||||
this._queryParams.flush();
|
||||
showAddIntegrationDialog(this, {
|
||||
initialFilter: this._filter,
|
||||
navigateToResult: true,
|
||||
@@ -897,6 +922,7 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
switch (action) {
|
||||
case "show-ignored":
|
||||
this._showIgnored = !this._showIgnored;
|
||||
this._writeUrl();
|
||||
break;
|
||||
case "show-disabled":
|
||||
this._toggleShowDisabled();
|
||||
@@ -906,11 +932,46 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
|
||||
private _toggleShowDisabled() {
|
||||
this._showDisabled = !this._showDisabled;
|
||||
this._writeUrl();
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: HASSDomTargetEvent<HaInputSearch>) {
|
||||
this._filter = ev.target.value ?? "";
|
||||
updateHistoryState({ filter: this._filter });
|
||||
|
||||
this._writeUrl(true);
|
||||
}
|
||||
|
||||
private _flushUrl() {
|
||||
this._queryParams.flush();
|
||||
}
|
||||
|
||||
private _writeUrl(debounced = false) {
|
||||
this._queryParams.write(
|
||||
integrationsQueryParams({
|
||||
search: this._filter,
|
||||
showIgnored: this._showIgnored,
|
||||
showDisabled: this._showDisabled,
|
||||
}),
|
||||
debounced
|
||||
);
|
||||
}
|
||||
|
||||
private _applyUrl() {
|
||||
const searchParams = this._queryParams.searchParams();
|
||||
|
||||
if (!searchParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
const link = integrationsLinkFromUrl(searchParams);
|
||||
|
||||
if (link) {
|
||||
this._filter = link.search;
|
||||
this._showIgnored = link.showIgnored;
|
||||
this._showDisabled = link.showDisabled;
|
||||
}
|
||||
|
||||
this._writeUrl();
|
||||
}
|
||||
|
||||
private async _highlightEntry() {
|
||||
@@ -945,7 +1006,7 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
}
|
||||
const brand = extractSearchParam("brand");
|
||||
const domain = extractSearchParam("domain");
|
||||
navigate("/config/integrations/dashboard/", { replace: true });
|
||||
navigate("/config/integrations/dashboard", { replace: true });
|
||||
|
||||
if (brand) {
|
||||
showAddIntegrationDialog(this, {
|
||||
|
||||
@@ -60,6 +60,7 @@ class HaConfigIntegrations extends SubscribeMixin(HassRouterPage) {
|
||||
},
|
||||
integration: {
|
||||
tag: "ha-config-integration-page",
|
||||
itemId: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -201,7 +202,12 @@ class HaConfigIntegrations extends SubscribeMixin(HassRouterPage) {
|
||||
if (urlParams.has("domain")) {
|
||||
const domain = urlParams.get("domain");
|
||||
pageEl.domain = domain;
|
||||
navigate(`/config/integrations/integration/${domain}`);
|
||||
urlParams.delete("domain");
|
||||
const search = urlParams.toString();
|
||||
navigate(
|
||||
`/config/integrations/integration/${domain}${search ? `?${search}` : ""}`,
|
||||
{ replace: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,10 +258,15 @@ export const getMyRedirects = (): Redirects => ({
|
||||
redirect: "/config/integrations/integration",
|
||||
params: {
|
||||
domain: "string",
|
||||
search: "string?",
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
redirect: "/config/integrations",
|
||||
redirect: "/config/integrations/dashboard",
|
||||
params: {
|
||||
search: "string?",
|
||||
status: "string?",
|
||||
},
|
||||
},
|
||||
config_mqtt: {
|
||||
component: "mqtt",
|
||||
@@ -847,7 +852,13 @@ class HaPanelMy extends LitElement {
|
||||
this._redirect!.redirect === "/config/integrations/integration" &&
|
||||
params.domain === "hacs"
|
||||
) {
|
||||
return `${this._redirect!.redirect}?${createSearchParam({ domain: "marketplace" })}`;
|
||||
const marketplaceParams = new URLSearchParams(
|
||||
this._createRedirectParams()
|
||||
);
|
||||
|
||||
marketplaceParams.set("domain", "marketplace");
|
||||
|
||||
return `${this._redirect!.redirect}?${marketplaceParams}`;
|
||||
}
|
||||
|
||||
// Special case for supervisor_app/supervisor_addon: use path-based URL
|
||||
|
||||
Reference in new issue
Block a user