Skip to content

Commit b2cf0e0

Browse files
committed
feat: lazy load of the yaml library components
1 parent 14cf3eb commit b2cf0e0

File tree

3 files changed

+58
-48
lines changed

3 files changed

+58
-48
lines changed

src/components/shared/FavoriteComponentToggle.tsx

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
1+
import {
2+
useMutation,
3+
useQueryClient,
4+
useSuspenseQuery,
5+
} from "@tanstack/react-query";
16
import { PackagePlus, Star } from "lucide-react";
2-
import type { MouseEvent, PropsWithChildren } from "react";
7+
import type { ComponentProps, MouseEvent, PropsWithChildren } from "react";
38
import { useCallback, useMemo, useState } from "react";
49

510
import { ConfirmationDialog } from "@/components/shared/Dialogs";
611
import { Button } from "@/components/ui/button";
712
import { Icon } from "@/components/ui/icon";
13+
import { Spinner } from "@/components/ui/spinner";
14+
import { useGuaranteedHydrateComponentReference } from "@/hooks/useHydrateComponentReference";
815
import { cn } from "@/lib/utils";
916
import { useComponentLibrary } from "@/providers/ComponentLibraryProvider";
1017
import { hydrateComponentReference } from "@/services/componentService";
1118
import type { ComponentReference } from "@/utils/componentSpec";
1219
import { getComponentName } from "@/utils/getComponentName";
20+
import { getComponentById } from "@/utils/localforage";
21+
22+
import { withSuspenseWrapper } from "./SuspenseWrapper";
1323

1424
interface ComponentFavoriteToggleProps {
1525
component: ComponentReference;
1626
hideDelete?: boolean;
1727
}
1828

19-
interface StateButtonProps {
29+
interface StateButtonProps extends ComponentProps<typeof Button> {
2030
active?: boolean;
2131
isDanger?: boolean;
2232
onClick?: () => void;
@@ -27,6 +37,7 @@ const IconStateButton = ({
2737
isDanger = false,
2838
onClick,
2939
children,
40+
...props
3041
}: PropsWithChildren<StateButtonProps>) => {
3142
const handleFavorite = useCallback(
3243
(e: MouseEvent) => {
@@ -51,6 +62,7 @@ const IconStateButton = ({
5162
)}
5263
variant="ghost"
5364
size="icon"
65+
{...props}
5466
>
5567
{children}
5668
</Button>
@@ -84,28 +96,59 @@ const DeleteFromLibraryButton = ({ active, onClick }: StateButtonProps) => {
8496
);
8597
};
8698

99+
const favoriteComponentKey = (component: ComponentReference) => {
100+
return ["component", "is-favorite", component.digest];
101+
};
102+
103+
const FavoriteToggleButton = withSuspenseWrapper(
104+
({ component }: { component: ComponentReference }) => {
105+
const queryClient = useQueryClient();
106+
107+
const { setComponentFavorite } = useComponentLibrary();
108+
const hydratedComponent = useGuaranteedHydrateComponentReference(component);
109+
110+
const { data: isFavorited } = useSuspenseQuery({
111+
queryKey: favoriteComponentKey(component),
112+
queryFn: async () =>
113+
(await getComponentById(`component-${hydratedComponent.digest}`))
114+
?.favorited ?? false,
115+
});
116+
117+
const { mutate: setFavorite } = useMutation({
118+
mutationFn: async () =>
119+
setComponentFavorite(hydratedComponent, !isFavorited),
120+
onSuccess: () => {
121+
queryClient.invalidateQueries({
122+
queryKey: favoriteComponentKey(hydratedComponent),
123+
});
124+
},
125+
});
126+
127+
return <FavoriteStarButton active={isFavorited} onClick={setFavorite} />;
128+
},
129+
() => <Spinner size={10} />,
130+
() => (
131+
<IconStateButton disabled>
132+
<Icon name="Star" />
133+
</IconStateButton>
134+
),
135+
);
136+
87137
export const ComponentFavoriteToggle = ({
88138
component,
89139
hideDelete = false,
90140
}: ComponentFavoriteToggleProps) => {
91141
const {
92142
addToComponentLibrary,
93143
removeFromComponentLibrary,
94-
checkIfFavorited,
95144
checkIfUserComponent,
96145
checkLibraryContainsComponent,
97-
setComponentFavorite,
98146
} = useComponentLibrary();
99147

100148
const [isOpen, setIsOpen] = useState(false);
101149

102150
const { spec, url } = component;
103151

104-
const isFavorited = useMemo(
105-
() => checkIfFavorited(component),
106-
[component, checkIfFavorited],
107-
);
108-
109152
const isUserComponent = useMemo(
110153
() => checkIfUserComponent(component),
111154
[component, checkIfUserComponent],
@@ -121,10 +164,6 @@ export const ComponentFavoriteToggle = ({
121164
[spec, url],
122165
);
123166

124-
const onFavorite = useCallback(() => {
125-
setComponentFavorite(component, !isFavorited);
126-
}, [isFavorited, setComponentFavorite]);
127-
128167
// Delete User Components
129168
const handleDelete = useCallback(async () => {
130169
removeFromComponentLibrary(component);
@@ -166,7 +205,7 @@ export const ComponentFavoriteToggle = ({
166205
{!isInLibrary && <AddToLibraryButton onClick={openConfirmationDialog} />}
167206

168207
{isInLibrary && !isUserComponent && (
169-
<FavoriteStarButton active={isFavorited} onClick={onFavorite} />
208+
<FavoriteToggleButton component={component} />
170209
)}
171210

172211
{showDeleteButton && (

src/providers/ComponentLibraryProvider/ComponentLibraryProvider.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ type ComponentLibraryContextType = {
9393
component: ComponentReference,
9494
favorited: boolean,
9595
) => void;
96+
/**
97+
* @deprecated
98+
*/
9699
checkIfFavorited: (component: ComponentReference) => boolean;
97100
checkIfUserComponent: (component: ComponentReference) => boolean;
98101
checkLibraryContainsComponent: (component: ComponentReference) => boolean;
@@ -230,9 +233,7 @@ export const ComponentLibraryProvider = ({
230233
const { data: updatedLibrary } = await refetchLibrary();
231234

232235
if (updatedLibrary) {
233-
populateComponentRefs(updatedLibrary).then((result) => {
234-
setComponentLibrary(result);
235-
});
236+
setComponentLibrary(updatedLibrary);
236237
}
237238
}, [refetchLibrary]);
238239

@@ -571,9 +572,7 @@ export const ComponentLibraryProvider = ({
571572
setComponentLibrary(undefined);
572573
return;
573574
}
574-
populateComponentRefs(rawComponentLibrary).then((result) => {
575-
setComponentLibrary(result);
576-
});
575+
setComponentLibrary(rawComponentLibrary);
577576
}, [rawComponentLibrary]);
578577

579578
useEffect(() => {

src/services/componentService.ts

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { getAppSettings } from "@/appSettings";
22
import {
3-
type ComponentFolder,
43
type ComponentLibrary,
54
isValidComponentLibrary,
65
} from "@/types/componentLibrary";
@@ -112,36 +111,9 @@ export const fetchAndStoreComponentLibrary =
112111
updatedAt: Date.now(),
113112
});
114113

115-
// Also store individual components for future reference
116-
await storeComponentsFromLibrary(obj);
117-
118114
return obj;
119115
};
120116

121-
/**
122-
* Store all components from the library in local storage
123-
*/
124-
const storeComponentsFromLibrary = async (
125-
library: ComponentLibrary,
126-
): Promise<void> => {
127-
const processFolder = async (folder: ComponentFolder) => {
128-
// Store each component in the folder
129-
for (const component of folder.components || []) {
130-
await fetchAndStoreComponent(component);
131-
}
132-
133-
// Process subfolders recursively
134-
for (const subfolder of folder.folders || []) {
135-
await processFolder(subfolder);
136-
}
137-
};
138-
139-
// Process all top-level folders
140-
for (const folder of library.folders) {
141-
await processFolder(folder);
142-
}
143-
};
144-
145117
/**
146118
* Fetch and store a single component by URL
147119
*/

0 commit comments

Comments
 (0)