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 { CategorySidebar } from "./CategorySidebar";
|
||||||
import { FilterBar, type FilterState } from "./FilterBar";
|
import { FilterBar, type FilterState } from "./FilterBar";
|
||||||
import { ViewToggle } from "./ViewToggle";
|
import { ViewToggle } from "./ViewToggle";
|
||||||
|
import { ConfirmationModal } from "./ConfirmationModal";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
|
import { RefreshCw } from "lucide-react";
|
||||||
import type { ScriptCard as ScriptCardType } from "~/types/script";
|
import type { ScriptCard as ScriptCardType } from "~/types/script";
|
||||||
import type { Server } from "~/types/server";
|
import type { Server } from "~/types/server";
|
||||||
import { getDefaultFilters, mergeFiltersWithDefaults } from "./filterUtils";
|
import { getDefaultFilters, mergeFiltersWithDefaults } from "./filterUtils";
|
||||||
@@ -32,8 +34,15 @@ export function DownloadedScriptsTab({
|
|||||||
const [filters, setFilters] = useState<FilterState>(getDefaultFilters());
|
const [filters, setFilters] = useState<FilterState>(getDefaultFilters());
|
||||||
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
|
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
|
||||||
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
|
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 gridRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const utils = api.useUtils();
|
||||||
const {
|
const {
|
||||||
data: scriptCardsData,
|
data: scriptCardsData,
|
||||||
isLoading: githubLoading,
|
isLoading: githubLoading,
|
||||||
@@ -50,6 +59,27 @@ export function DownloadedScriptsTab({
|
|||||||
{ enabled: !!selectedSlug },
|
{ 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
|
// Load SAVE_FILTER setting, saved filters, and view mode on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
@@ -416,6 +446,21 @@ export function DownloadedScriptsTab({
|
|||||||
setSelectedSlug(null);
|
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) {
|
if (githubLoading || localLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
@@ -508,6 +553,43 @@ export function DownloadedScriptsTab({
|
|||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="order-1 min-w-0 flex-1 lg:order-2" ref={gridRef}>
|
<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 */}
|
{/* Enhanced Filter Bar */}
|
||||||
<FilterBar
|
<FilterBar
|
||||||
filters={filters}
|
filters={filters}
|
||||||
@@ -621,6 +703,17 @@ export function DownloadedScriptsTab({
|
|||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
onInstallScript={onInstallScript}
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user