packages/devtools-ui/src/components/packages.tsx
import React from "react";
import clsx from "clsx";
import { Fireworks } from "@fireworks-js/react";
import type { FireworksHandlers } from "@fireworks-js/react";
import { PackageType } from "@refinedev/devtools-shared";
import { getInstalledPackages, installPackages } from "src/utils/packages";
import { PackageItem } from "src/components/package-item";
import { Button } from "./button";
import { AddPackageDrawer } from "./add-package-drawer";
import { PlusCircleIcon } from "./icons/plus-circle";
import { UpdateIcon } from "./icons/update";
export const Packages = () => {
const ref = React.useRef<FireworksHandlers>(null);
const [packages, setPackages] = React.useState<PackageType[]>([]);
const [visible, setVisible] = React.useState(false);
const [outdatedPackages, setOutdatedPackages] = React.useState<string[]>([]);
React.useEffect(() => {
getInstalledPackages().then((data) => {
setPackages(data);
});
}, []);
const fireworks = React.useCallback(() => {
if (ref.current) {
ref.current.start();
setTimeout(() => {
ref.current?.waitStop();
}, 3000);
}
}, []);
const [installingAll, setInstallingAll] = React.useState(false);
const [installInProgress, setInstallInProgress] = React.useState(false);
const onInstall = React.useCallback(async (packagesToInstall: string[]) => {
setInstallInProgress(true);
const state = await installPackages(packagesToInstall);
if (state) {
fireworks();
getInstalledPackages({ force: true }).then((data) => {
setPackages(data);
});
setOutdatedPackages((p) =>
p.filter((item) => !packagesToInstall.includes(item)),
);
}
setInstallInProgress(false);
return state;
}, []);
const onOutdated = React.useCallback((packages: string[]) => {
setOutdatedPackages((p) => [...p, ...packages]);
}, []);
const onUpdateAll = React.useCallback(async () => {
if (installingAll) {
return;
}
setInstallingAll(true);
await onInstall(outdatedPackages);
setInstallingAll(false);
}, [outdatedPackages, installingAll, onInstall]);
return (
<>
<div
className={clsx(
"re-flex-1",
"re-flex",
"re-flex-col",
"re-h-full",
"re-w-full",
"re-justify-start",
"re-mx-auto",
)}
>
<div
className={clsx(
"re-flex",
"re-items-center",
"re-justify-between",
"re-flex-shrink-0",
)}
>
<div
className={clsx(
"re-text-sm",
"re-leading-6",
"re-text-gray-0",
"re-font-semibold",
)}
>
Package Overview
</div>
<div className={clsx("re-flex", "re-items-center", "re-gap-4")}>
<Button
onClick={() => setVisible(true)}
className={clsx(
"re-gap-2",
"re-text-alt-blue",
"re-bg-alt-blue",
"re-bg-opacity-[0.15]",
"re-flex-nowrap",
"re-flex",
"re-items-center",
"re-justify-between",
"!re-px-2",
)}
>
<PlusCircleIcon className="re-text-alt-blue" />
<span className="re-text-alt-blue">More packages</span>
</Button>
{outdatedPackages.length > 0 && (
<Button
onClick={() => onUpdateAll()}
className={clsx(
"re-gap-2",
"re-text-gray-0",
"re-bg-alt-blue",
"re-flex-nowrap",
"re-flex",
"re-items-center",
"re-justify-between",
"!re-px-2",
)}
>
<UpdateIcon
className={clsx(
"re-text-gray-0",
installingAll ? "re-animate-spin" : "",
)}
/>
<span className="re-text-gray-0">Update all</span>
</Button>
)}
</div>
</div>
<div className={clsx("re-flex-1", "re-overflow-auto")}>
<div
className={clsx("re-grid", "re-gap-8", "re-py-8")}
style={{
gridTemplateColumns: "repeat(auto-fill, minmax(380px, 1fr))",
}}
>
{packages.map((item) => (
<PackageItem
key={item.name}
item={item}
onUpdate={(v) => onInstall([v])}
onOutdated={(v) => onOutdated([v])}
blocked={installInProgress}
/>
))}
</div>
</div>
</div>
<AddPackageDrawer
installedPackages={packages.map((item) => item.name)}
onClose={() => {
setVisible(false);
getInstalledPackages().then((data) => {
setPackages(data);
});
}}
onInstall={(pkgs) => onInstall(pkgs)}
dismissOnOverlayClick
visible={visible}
/>
<Fireworks
ref={ref}
autostart={false}
options={{
intensity: 38,
explosion: 8,
}}
style={{
top: 0,
left: 0,
width: "100%",
height: "100%",
position: "fixed",
zIndex: 99999,
pointerEvents: "none",
}}
/>
</>
);
};