Merge pull request #283 from community-scripts/feat/delete_downloaded_scripts
Add delete script functionality
This commit is contained in:
@@ -7,6 +7,7 @@ import type { Script } from "~/types/script";
|
||||
import { DiffViewer } from "./DiffViewer";
|
||||
import { TextViewer } from "./TextViewer";
|
||||
import { ExecutionModeModal } from "./ExecutionModeModal";
|
||||
import { ConfirmationModal } from "./ConfirmationModal";
|
||||
import { TypeBadge, UpdateableBadge, PrivilegedBadge, NoteBadge } from "./Badge";
|
||||
import { Button } from "./ui/button";
|
||||
import { useRegisterModal } from './modal/ModalStackProvider';
|
||||
@@ -37,6 +38,8 @@ export function ScriptDetailModal({
|
||||
const [selectedDiffFile, setSelectedDiffFile] = useState<string | null>(null);
|
||||
const [textViewerOpen, setTextViewerOpen] = useState(false);
|
||||
const [executionModeOpen, setExecutionModeOpen] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
|
||||
// Check if script files exist locally
|
||||
const {
|
||||
@@ -83,6 +86,31 @@ export function ScriptDetailModal({
|
||||
},
|
||||
});
|
||||
|
||||
// Delete script mutation
|
||||
const deleteScriptMutation = api.scripts.deleteScript.useMutation({
|
||||
onSuccess: (data) => {
|
||||
setIsDeleting(false);
|
||||
if (data.success) {
|
||||
const message =
|
||||
"message" in data ? data.message : "Script deleted successfully";
|
||||
setLoadMessage(`[SUCCESS] ${message}`);
|
||||
// Refetch script files status and comparison data to update the UI
|
||||
void refetchScriptFiles();
|
||||
void refetchComparison();
|
||||
} else {
|
||||
const error = "error" in data ? data.error : "Failed to delete script";
|
||||
setLoadMessage(`[ERROR] ${error}`);
|
||||
}
|
||||
// Clear message after 5 seconds
|
||||
setTimeout(() => setLoadMessage(null), 5000);
|
||||
},
|
||||
onError: (error) => {
|
||||
setIsDeleting(false);
|
||||
setLoadMessage(`[ERROR] ${error.message}`);
|
||||
setTimeout(() => setLoadMessage(null), 5000);
|
||||
},
|
||||
});
|
||||
|
||||
if (!isOpen || !script) return null;
|
||||
|
||||
const handleImageError = () => {
|
||||
@@ -130,6 +158,19 @@ export function ScriptDetailModal({
|
||||
setTextViewerOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteScript = () => {
|
||||
if (!script) return;
|
||||
setDeleteConfirmOpen(true);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
if (!script) return;
|
||||
setDeleteConfirmOpen(false);
|
||||
setIsDeleting(true);
|
||||
setLoadMessage(null);
|
||||
deleteScriptMutation.mutate({ slug: script.slug });
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-sm bg-black/50"
|
||||
@@ -373,6 +414,42 @@ export function ScriptDetailModal({
|
||||
);
|
||||
}
|
||||
})()}
|
||||
|
||||
{/* Delete Button - only show if script files exist */}
|
||||
{scriptFilesData?.success &&
|
||||
(scriptFilesData.ctExists || scriptFilesData.installExists) && (
|
||||
<Button
|
||||
onClick={handleDeleteScript}
|
||||
disabled={isDeleting}
|
||||
variant="destructive"
|
||||
size="default"
|
||||
className="w-full sm:w-auto flex items-center justify-center space-x-2"
|
||||
>
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-white"></div>
|
||||
<span>Deleting...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg
|
||||
className="h-4 w-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
<span>Delete Script</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
@@ -736,6 +813,20 @@ export function ScriptDetailModal({
|
||||
onExecute={handleExecuteScript}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{script && (
|
||||
<ConfirmationModal
|
||||
isOpen={deleteConfirmOpen}
|
||||
onClose={() => setDeleteConfirmOpen(false)}
|
||||
onConfirm={handleConfirmDelete}
|
||||
title="Delete Script"
|
||||
message={`Are you sure you want to delete all downloaded files for "${script.name}"? This action cannot be undone.`}
|
||||
variant="simple"
|
||||
confirmButtonText="Delete"
|
||||
cancelButtonText="Cancel"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -363,6 +363,34 @@ export const scriptsRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
|
||||
// Delete script files
|
||||
deleteScript: publicProcedure
|
||||
.input(z.object({ slug: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
// Get the script details
|
||||
const script = await localScriptsService.getScriptBySlug(input.slug);
|
||||
if (!script) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Script not found',
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
// Delete the script files
|
||||
const result = await scriptDownloaderService.deleteScript(script);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error in deleteScript:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete script',
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
}),
|
||||
|
||||
// Compare local and remote script content
|
||||
compareScriptContent: publicProcedure
|
||||
.input(z.object({ slug: z.string() }))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Real JavaScript implementation for script downloading
|
||||
import { join } from 'path';
|
||||
import { writeFile, mkdir, access, readFile } from 'fs/promises';
|
||||
import { writeFile, mkdir, access, readFile, unlink } from 'fs/promises';
|
||||
|
||||
export class ScriptDownloaderService {
|
||||
constructor() {
|
||||
@@ -293,6 +293,57 @@ export class ScriptDownloaderService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteScript(script) {
|
||||
this.initializeConfig();
|
||||
const deletedFiles = [];
|
||||
|
||||
try {
|
||||
// Get the list of files that exist for this script
|
||||
const fileCheck = await this.checkScriptExists(script);
|
||||
|
||||
if (fileCheck.files.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'No script files found to delete',
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
// Delete all files
|
||||
for (const filePath of fileCheck.files) {
|
||||
try {
|
||||
const fullPath = join(this.scriptsDirectory, filePath);
|
||||
await unlink(fullPath);
|
||||
deletedFiles.push(filePath);
|
||||
} catch (error) {
|
||||
// Log error but continue deleting other files
|
||||
console.error(`Error deleting file ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedFiles.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to delete any script files',
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully deleted ${deletedFiles.length} file(s) for ${script.name}`,
|
||||
deletedFiles
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error deleting script:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to delete script',
|
||||
deletedFiles
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async compareScriptContent(script) {
|
||||
this.initializeConfig();
|
||||
const differences = [];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFile, writeFile, mkdir } from 'fs/promises';
|
||||
import { readFile, writeFile, mkdir, unlink } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { env } from '~/env.js';
|
||||
import type { Script } from '~/types/script';
|
||||
@@ -461,6 +461,57 @@ export class ScriptDownloaderService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteScript(script: Script): Promise<{ success: boolean; message: string; deletedFiles: string[] }> {
|
||||
this.initializeConfig();
|
||||
const deletedFiles: string[] = [];
|
||||
|
||||
try {
|
||||
// Get the list of files that exist for this script
|
||||
const fileCheck = await this.checkScriptExists(script);
|
||||
|
||||
if (fileCheck.files.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'No script files found to delete',
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
// Delete all files
|
||||
for (const filePath of fileCheck.files) {
|
||||
try {
|
||||
const fullPath = join(this.scriptsDirectory!, filePath);
|
||||
await unlink(fullPath);
|
||||
deletedFiles.push(filePath);
|
||||
} catch (error) {
|
||||
// Log error but continue deleting other files
|
||||
console.error(`Error deleting file ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedFiles.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to delete any script files',
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully deleted ${deletedFiles.length} file(s) for ${script.name}`,
|
||||
deletedFiles
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error deleting script:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to delete script',
|
||||
deletedFiles
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async compareScriptContent(script: Script): Promise<{ hasDifferences: boolean; differences: string[] }> {
|
||||
this.initializeConfig();
|
||||
const differences: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user