feat: add Update all downloaded scripts button
- Add bulk update button on Downloaded Scripts tab - Use existing loadMultipleScripts API for all downloaded script slugs - Confirmation modal before running (may take several minutes) - Inline result: success/fail counts, hover for failed slugs - Invalidate getAllDownloadedScripts and getScriptCardsWithCategories on success
This commit is contained in:
@@ -8,7 +8,9 @@ import { ScriptDetailModal } from "./ScriptDetailModal";
|
||||
import { CategorySidebar } from "./CategorySidebar";
|
||||
import { FilterBar, type FilterState } from "./FilterBar";
|
||||
import { ViewToggle } from "./ViewToggle";
|
||||
import { ConfirmationModal } from "./ConfirmationModal";
|
||||
import { Button } from "./ui/button";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import type { ScriptCard as ScriptCardType } from "~/types/script";
|
||||
import type { Server } from "~/types/server";
|
||||
import { getDefaultFilters, mergeFiltersWithDefaults } from "./filterUtils";
|
||||
@@ -32,8 +34,15 @@ export function DownloadedScriptsTab({
|
||||
const [filters, setFilters] = useState<FilterState>(getDefaultFilters());
|
||||
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
|
||||
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
|
||||
const [updateAllConfirmOpen, setUpdateAllConfirmOpen] = useState(false);
|
||||
const [updateResult, setUpdateResult] = useState<{
|
||||
successCount: number;
|
||||
failCount: number;
|
||||
failed: { slug: string; error: string }[];
|
||||
} | null>(null);
|
||||
const gridRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const utils = api.useUtils();
|
||||
const {
|
||||
data: scriptCardsData,
|
||||
isLoading: githubLoading,
|
||||
@@ -50,6 +59,27 @@ export function DownloadedScriptsTab({
|
||||
{ enabled: !!selectedSlug },
|
||||
);
|
||||
|
||||
const loadMultipleScriptsMutation = api.scripts.loadMultipleScripts.useMutation({
|
||||
onSuccess: (data) => {
|
||||
void utils.scripts.getAllDownloadedScripts.invalidate();
|
||||
void utils.scripts.getScriptCardsWithCategories.invalidate();
|
||||
setUpdateResult({
|
||||
successCount: data.successful?.length ?? 0,
|
||||
failCount: data.failed?.length ?? 0,
|
||||
failed: data.failed ?? [],
|
||||
});
|
||||
setTimeout(() => setUpdateResult(null), 8000);
|
||||
},
|
||||
onError: (error) => {
|
||||
setUpdateResult({
|
||||
successCount: 0,
|
||||
failCount: 1,
|
||||
failed: [{ slug: "Request failed", error: error.message }],
|
||||
});
|
||||
setTimeout(() => setUpdateResult(null), 8000);
|
||||
},
|
||||
});
|
||||
|
||||
// Load SAVE_FILTER setting, saved filters, and view mode on component mount
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
@@ -416,6 +446,21 @@ export function DownloadedScriptsTab({
|
||||
setSelectedSlug(null);
|
||||
};
|
||||
|
||||
const handleUpdateAllClick = () => {
|
||||
setUpdateResult(null);
|
||||
setUpdateAllConfirmOpen(true);
|
||||
};
|
||||
|
||||
const handleUpdateAllConfirm = () => {
|
||||
setUpdateAllConfirmOpen(false);
|
||||
const slugs = downloadedScripts
|
||||
.map((s) => s.slug)
|
||||
.filter((slug): slug is string => Boolean(slug));
|
||||
if (slugs.length > 0) {
|
||||
loadMultipleScriptsMutation.mutate({ slugs });
|
||||
}
|
||||
};
|
||||
|
||||
if (githubLoading || localLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
@@ -508,6 +553,43 @@ export function DownloadedScriptsTab({
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="order-1 min-w-0 flex-1 lg:order-2" ref={gridRef}>
|
||||
{/* Update all downloaded scripts */}
|
||||
<div className="mb-4 flex flex-wrap items-center gap-3">
|
||||
<Button
|
||||
onClick={handleUpdateAllClick}
|
||||
disabled={loadMultipleScriptsMutation.isPending}
|
||||
variant="secondary"
|
||||
size="default"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{loadMultipleScriptsMutation.isPending ? (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||
<span>Updating...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
<span>Update all downloaded scripts</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{updateResult && (
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Updated {updateResult.successCount} successfully
|
||||
{updateResult.failCount > 0
|
||||
? `, ${updateResult.failCount} failed`
|
||||
: ""}
|
||||
.
|
||||
{updateResult.failCount > 0 && updateResult.failed.length > 0 && (
|
||||
<span className="ml-1" title={updateResult.failed.map((f) => `${f.slug}: ${f.error}`).join("\n")}>
|
||||
(hover for details)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Enhanced Filter Bar */}
|
||||
<FilterBar
|
||||
filters={filters}
|
||||
@@ -621,6 +703,17 @@ export function DownloadedScriptsTab({
|
||||
onClose={handleCloseModal}
|
||||
onInstallScript={onInstallScript}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
isOpen={updateAllConfirmOpen}
|
||||
onClose={() => setUpdateAllConfirmOpen(false)}
|
||||
onConfirm={handleUpdateAllConfirm}
|
||||
title="Update all downloaded scripts"
|
||||
message={`Update all ${downloadedScripts.length} downloaded scripts? This may take several minutes.`}
|
||||
variant="simple"
|
||||
confirmButtonText="Update all"
|
||||
cancelButtonText="Cancel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user