Compare commits

...
5 changed files with 103 additions and 35 deletions
@@ -0,0 +1,25 @@
import {
createQueryString,
decodeQueryParams,
type QueryParamConfig,
type QueryParamValues,
type SearchParamsSource,
} from "./query-params";
const marketplaceBrowseQueryParamConfig = {
list: ["status", "type", "source"],
string: ["search", "sort", "direction"],
} as const satisfies QueryParamConfig;
export type MarketplaceBrowseQueryParams = QueryParamValues<
typeof marketplaceBrowseQueryParamConfig
>;
export const decodeMarketplaceBrowseQueryParams = (
searchParams: SearchParamsSource
): MarketplaceBrowseQueryParams =>
decodeQueryParams(searchParams, marketplaceBrowseQueryParamConfig);
export const createMarketplaceBrowseQueryString = (
values: MarketplaceBrowseQueryParams
): string => createQueryString(values, marketplaceBrowseQueryParamConfig);
@@ -1,4 +1,8 @@
import type { LocalizeFunc } from "../../../common/translations/localize";
import {
createMarketplaceBrowseQueryString,
decodeMarketplaceBrowseQueryParams,
} from "../../../common/url/marketplace-query-params";
import type { DataTableRowData } from "../../../components/data-table/ha-data-table";
import type { RepositoryBase } from "../../../data/marketplace/repository";
import type { MarketplaceTab } from "./ha-marketplace-dashboard";
@@ -22,8 +26,6 @@ export const STATUS_FILTER = "status";
export const TYPE_FILTER = "type";
export const SOURCE_FILTER = "source";
const SORT_PARAM = "sort";
const DIRECTION_PARAM = "direction";
// Where a repository comes from, the community catalog or added from a link
export const SOURCE_ORDER = ["catalog", "custom"] as const;
@@ -150,55 +152,54 @@ export const filterRepositories = (
}));
// How to browse, the way a link says it, like
// /marketplace/browse?sort=stars&direction=desc&status=new
// /marketplace/browse?status=new&search=card&sort=stars&direction=desc
export interface BrowseSettings {
search?: string;
sorting?: { column: string; direction: "asc" | "desc" };
filters: RepositoryFilters;
}
export const browseUrl = ({ sorting, filters }: BrowseSettings): string => {
const params = new URLSearchParams();
if (sorting) {
params.set(SORT_PARAM, sorting.column);
params.set(DIRECTION_PARAM, sorting.direction);
}
for (const filter of [STATUS_FILTER, TYPE_FILTER, SOURCE_FILTER] as const) {
if (filters[filter]?.length) {
params.set(filter, filters[filter].join(","));
}
}
const query = params.toString();
export const browseUrl = ({
search,
sorting,
filters,
}: BrowseSettings): string => {
const query = createMarketplaceBrowseQueryString({
...filters,
search,
sort: sorting?.column,
direction: sorting?.direction,
});
return query ? `/marketplace/browse?${query}` : "/marketplace/browse";
};
// Nothing about browsing in the link keeps what was picked before
export const browseSettingsFromUrl = (
search: string
query: string
): BrowseSettings | undefined => {
const params = new URLSearchParams(search);
const { search, sort, direction, ...lists } =
decodeMarketplaceBrowseQueryParams(query);
const filters: RepositoryFilters = {
[STATUS_FILTER]: lists.status?.filter(Boolean),
[TYPE_FILTER]: lists.type?.filter(Boolean),
[SOURCE_FILTER]: lists.source?.filter(Boolean),
};
if (
![SORT_PARAM, STATUS_FILTER, TYPE_FILTER, SOURCE_FILTER].some((key) =>
params.has(key)
)
!search &&
!sort &&
!Object.values(filters).some((values) => values?.length)
) {
return undefined;
}
const column = params.get(SORT_PARAM);
const values = (key: string) => params.get(key)?.split(",").filter(Boolean);
return {
sorting: column
? {
column,
direction: params.get(DIRECTION_PARAM) === "asc" ? "asc" : "desc",
}
search,
sorting: sort
? // Ascending unless the link says otherwise, like picking a sort in the table
{ column: sort, direction: direction === "desc" ? "desc" : "asc" }
: undefined,
filters: {
[STATUS_FILTER]: values(STATUS_FILTER),
[TYPE_FILTER]: values(TYPE_FILTER),
[SOURCE_FILTER]: values(SOURCE_FILTER),
},
filters,
};
};
@@ -263,7 +263,7 @@ export class HaMarketplaceDashboard extends LitElement {
updateHistoryState({ [APPLIED_LINK_STATE]: search });
this._linksApplied++;
// A search left behind would hide part of what the link promised
this._activeSearch = "";
this._activeSearch = settings.search ?? "";
this._filters = settings.filters;
if (settings.sorting) {
this._activeSorting = settings.sorting;
+28
View File
@@ -21,6 +21,16 @@ import { documentationUrl } from "../../util/documentation-url";
// That's why redirects should be sorted with more specific ones first
// Or else pressing "M" will link to the higher level page.
// The Marketplace tabs with a table read these from the link
const marketplaceTableParams = {
search: "string?",
type: "string?",
status: "string?",
source: "string?",
sort: "string?",
direction: "string?",
} satisfies Redirect["params"];
export const getMyRedirects = (): Redirects => ({
application_credentials: {
redirect: "/config/application_credentials",
@@ -469,6 +479,24 @@ export const getMyRedirects = (): Redirects => ({
category: "string?",
},
},
marketplace_discover: {
component: "marketplace",
redirect: "/marketplace/discover",
},
marketplace_browse: {
component: "marketplace",
redirect: "/marketplace/browse",
params: marketplaceTableParams,
},
marketplace_installed: {
component: "marketplace",
redirect: "/marketplace/installed",
params: marketplaceTableParams,
},
marketplace_repositories: {
component: "marketplace",
redirect: "/marketplace/repositories",
},
});
const getRedirect = (path: string): Redirect | undefined =>
@@ -161,6 +161,7 @@ describe("filterRepositories", () => {
describe("browseUrl", () => {
it("links to what browseSettingsFromUrl reads back", () => {
const settings = {
search: "mushroom card",
sorting: { column: "stars", direction: "desc" as const },
filters: { status: ["new"], type: ["theme"], source: ["custom"] },
};
@@ -170,6 +171,19 @@ describe("browseUrl", () => {
expect(url.pathname).toBe("/marketplace/browse");
expect(browseSettingsFromUrl(url.search)).toEqual(settings);
});
it("sorts ascending when the link gives no direction", () => {
expect(browseSettingsFromUrl("?sort=name")?.sorting).toEqual({
column: "name",
direction: "asc",
});
});
it("keeps what was picked for a link that says nothing about browsing", () => {
expect(
browseSettingsFromUrl("?owner=hacs&repository=integration")
).toBeUndefined();
});
});
describe("filtersOfTab", () => {