perf(widgets): lazy load the application graph
This commit is contained in:
1 parent
4ca0b747bb
commit
6b74078766
38 files changed
+676
-202
No files matched your search
@@ -1,4 +1,3 @@
|
||||
import { objectEntries } from "@homarr/common";
|
||||
import type { IntegrationKind, WidgetKind } from "@homarr/definitions";
|
||||
import {
|
||||
featuredIntegrations,
|
||||
@@ -7,19 +6,18 @@ import {
|
||||
integrationDefs,
|
||||
integrationKinds,
|
||||
} from "@homarr/definitions";
|
||||
import { widgetImports } from "@homarr/widgets";
|
||||
import { loadAllWidgetDefinitions } from "@homarr/widgets/manifest";
|
||||
|
||||
const buildWidgetsByIntegration = () => {
|
||||
const buildWidgetsByIntegration = async () => {
|
||||
const map = Object.fromEntries(integrationKinds.map((kind) => [kind, [] as WidgetKind[]])) as Record<
|
||||
IntegrationKind,
|
||||
WidgetKind[]
|
||||
>;
|
||||
const definitions = await loadAllWidgetDefinitions();
|
||||
|
||||
for (const [widgetKind, widget] of objectEntries(widgetImports)) {
|
||||
for (const [widgetKind, definition] of definitions) {
|
||||
const supported =
|
||||
"supportedIntegrations" in widget.definition
|
||||
? (widget.definition.supportedIntegrations as IntegrationKind[])
|
||||
: [];
|
||||
"supportedIntegrations" in definition ? (definition.supportedIntegrations as IntegrationKind[]) : [];
|
||||
for (const kind of supported) {
|
||||
if (kind in map) {
|
||||
map[kind].push(widgetKind);
|
||||
@@ -29,8 +27,6 @@ const buildWidgetsByIntegration = () => {
|
||||
return map;
|
||||
};
|
||||
|
||||
export const widgetsByIntegration = buildWidgetsByIntegration();
|
||||
|
||||
export const categoryTranslationKeys: Record<string, string> = {
|
||||
dnsHole: "integration.category.dnsHole",
|
||||
mediaService: "integration.category.mediaService",
|
||||
@@ -68,10 +64,11 @@ export interface IntegrationGridItem {
|
||||
widgets: WidgetKind[];
|
||||
}
|
||||
|
||||
export const buildSortedIntegrations = (
|
||||
export const buildSortedIntegrations = async (
|
||||
options: { enableMockIntegration?: boolean; onboarding?: boolean } = {},
|
||||
): IntegrationGridItem[] =>
|
||||
integrationKinds
|
||||
): Promise<IntegrationGridItem[]> => {
|
||||
const widgetsByIntegration = await buildWidgetsByIntegration();
|
||||
return integrationKinds
|
||||
.filter((kind) => {
|
||||
if (options.onboarding && hiddenFromOnboarding.has(kind)) return false;
|
||||
if (options.onboarding && widgetsByIntegration[kind].length === 0) return false;
|
||||
@@ -84,7 +81,7 @@ export const buildSortedIntegrations = (
|
||||
categories: [...new Set(integrationDefs[kind].category.flat())] as string[],
|
||||
widgets: widgetsByIntegration[kind],
|
||||
}))
|
||||
.sort((left, right) => {
|
||||
.toSorted((left, right) => {
|
||||
const leftIdx = featuredIntegrations.indexOf(left.kind);
|
||||
const rightIdx = featuredIntegrations.indexOf(right.kind);
|
||||
if (leftIdx !== -1 && rightIdx !== -1) return leftIdx - rightIdx;
|
||||
@@ -92,6 +89,7 @@ export const buildSortedIntegrations = (
|
||||
if (rightIdx !== -1) return 1;
|
||||
return right.widgets.length - left.widgets.length || left.name.localeCompare(right.name);
|
||||
});
|
||||
};
|
||||
|
||||
export const filterIntegrations = (items: IntegrationGridItem[], search: string) =>
|
||||
items.filter((i) => i.name.toLowerCase().includes(search.toLowerCase().trim()));
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Badge, Card, Center, Group, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { Badge, Card, Center, Group, Loader, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { IconBrandDocker, IconCheck, IconPuzzle } from "@tabler/icons-react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
@@ -42,10 +43,10 @@ export const IntegrationMultiSelectGrid = ({
|
||||
[selectedKindsArray, onSelectionChange],
|
||||
);
|
||||
|
||||
const integrations = useMemo(
|
||||
() => buildSortedIntegrations({ enableMockIntegration, onboarding }),
|
||||
[enableMockIntegration, onboarding],
|
||||
);
|
||||
const { data: integrations = [], isPending } = useQuery({
|
||||
queryKey: ["widget-integrations", { enableMockIntegration, onboarding }],
|
||||
queryFn: () => buildSortedIntegrations({ enableMockIntegration, onboarding }),
|
||||
});
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
if (!detectedKinds || detectedKinds.size === 0) return integrations;
|
||||
@@ -64,6 +65,11 @@ export const IntegrationMultiSelectGrid = ({
|
||||
disableScroll={onboarding}
|
||||
disableAutoFocus={onboarding}
|
||||
>
|
||||
{isPending && (
|
||||
<Center p="xl">
|
||||
<Loader size="sm" />
|
||||
</Center>
|
||||
)}
|
||||
{filtered.map((integration) => {
|
||||
const isSelected = selectedKinds.has(integration.kind);
|
||||
const isDetected = detectedKinds?.has(integration.kind) ?? false;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { Badge, Card, Center, Group, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { Badge, Card, Center, Group, Loader, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { IconPuzzle } from "@tabler/icons-react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
@@ -19,7 +20,10 @@ export const IntegrationSelectGrid = ({ onSelect, enableMockIntegration = false
|
||||
const [search, setSearch] = useState("");
|
||||
const t = useI18n();
|
||||
|
||||
const integrations = useMemo(() => buildSortedIntegrations({ enableMockIntegration }), [enableMockIntegration]);
|
||||
const { data: integrations = [], isPending } = useQuery({
|
||||
queryKey: ["widget-integrations", { enableMockIntegration }],
|
||||
queryFn: () => buildSortedIntegrations({ enableMockIntegration }),
|
||||
});
|
||||
const filtered = useMemo(() => filterIntegrations(integrations, search), [integrations, search]);
|
||||
|
||||
return (
|
||||
@@ -28,6 +32,11 @@ export const IntegrationSelectGrid = ({ onSelect, enableMockIntegration = false
|
||||
onSearchChange={setSearch}
|
||||
placeholder={`${t("integration.page.list.search")}...`}
|
||||
>
|
||||
{isPending && (
|
||||
<Center p="xl">
|
||||
<Loader size="sm" />
|
||||
</Center>
|
||||
)}
|
||||
{filtered.map((integration) => (
|
||||
<Card
|
||||
key={integration.kind}
|
||||
|
||||
Reference in new issue
Block a user