feat: multiple mod deletion

This commit is contained in:
Kentai Radiquum 2025-05-06 06:17:04 +05:00
parent 34df17d4dd
commit 01cef9a6ea
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
3 changed files with 96 additions and 3 deletions

View file

@ -15,6 +15,7 @@ type _PACKS_ENDPOINT = {
type _MOD_ENDPOINT = { type _MOD_ENDPOINT = {
addMod: string; addMod: string;
deleteMod: string; deleteMod: string;
deleteModBulk: string;
}; };
export const PACK_ENDPOINT = (endpoint: keyof _PACK_ENDPOINT, id: string) => { export const PACK_ENDPOINT = (endpoint: keyof _PACK_ENDPOINT, id: string) => {
@ -68,6 +69,7 @@ export const MOD_ENDPOINT = (
const _endpoints = { const _endpoints = {
addMod: `${API}/pack/${id}/mod/add`, addMod: `${API}/pack/${id}/mod/add`,
deleteMod: `${API}/pack/${id}/mod/${slug}/delete`, deleteMod: `${API}/pack/${id}/mod/${slug}/delete`,
deleteModBulk: `${API}/pack/${id}/mods/delete`,
}; };
return _endpoints[endpoint]; return _endpoints[endpoint];
}; };

View file

@ -10,6 +10,7 @@ import {
TableHeadCell, TableHeadCell,
TableRow, TableRow,
} from "flowbite-react"; } from "flowbite-react";
import { useState } from "react";
import { HiDownload, HiTrash } from "react-icons/hi"; import { HiDownload, HiTrash } from "react-icons/hi";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@ -18,6 +19,8 @@ export const ModTable = (props: {
updatePack: () => void; updatePack: () => void;
packID: string; packID: string;
}) => { }) => {
const [selectedMods, setSelectedMods] = useState<string[]>([]);
async function deleteMod(slug: string, title: string) { async function deleteMod(slug: string, title: string) {
if (!window) return; if (!window) return;
if (window.confirm(`Delete mod ${title}?`)) { if (window.confirm(`Delete mod ${title}?`)) {
@ -37,13 +40,54 @@ export const ModTable = (props: {
} }
} }
function selectAll() {
const deselect = selectedMods.length == props.mods.length;
console.log(selectedMods.length, props.mods.length, deselect);
if (deselect) {
setSelectedMods([]);
return;
}
props.mods.forEach((item) => {
if (!selectedMods.includes(item.slug)) {
setSelectedMods((state) => [item.slug, ...state]);
}
});
}
function handleCheckbox(slug: string) {
if (!selectedMods.includes(slug)) {
setSelectedMods((state) => [slug, ...state]);
} else {
const newArray = selectedMods.map((i) => i);
const idx = newArray.findIndex((item) => item == slug);
newArray.splice(idx, 1);
setSelectedMods(newArray);
}
}
async function deleteSelectedMods() {
await fetch(MOD_ENDPOINT("deleteModBulk", props.packID), {
method: "POST",
body: JSON.stringify(selectedMods),
headers: {
"content-type": "application/json",
accept: "application/json",
},
});
setSelectedMods([]);
props.updatePack();
}
return ( return (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table hoverable> <Table hoverable>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableHeadCell className="p-4"> <TableHeadCell className="p-4">
<Checkbox /> <Checkbox
checked={selectedMods.length == props.mods.length}
onChange={() => selectAll()}
/>
</TableHeadCell> </TableHeadCell>
<TableHeadCell>Icon</TableHeadCell> <TableHeadCell>Icon</TableHeadCell>
<TableHeadCell>Title</TableHeadCell> <TableHeadCell>Title</TableHeadCell>
@ -66,7 +110,12 @@ export const ModTable = (props: {
className="bg-white dark:border-gray-700 dark:bg-gray-800" className="bg-white dark:border-gray-700 dark:bg-gray-800"
> >
<TableCell className="p-4"> <TableCell className="p-4">
<Checkbox /> <Checkbox
checked={selectedMods.includes(mod.slug)}
onChange={() => {
handleCheckbox(mod.slug);
}}
/>
</TableCell> </TableCell>
<TableCell> <TableCell>
{/* eslint-disable-next-line @next/next/no-img-element */} {/* eslint-disable-next-line @next/next/no-img-element */}
@ -104,7 +153,16 @@ export const ModTable = (props: {
<TableCell></TableCell> <TableCell></TableCell>
<TableCell></TableCell> <TableCell></TableCell>
<TableCell></TableCell> <TableCell></TableCell>
<TableCell></TableCell> <TableCell>
<Button
color={"red"}
size="sm"
disabled={selectedMods.length == 0}
onClick={() => deleteSelectedMods()}
>
Delete Selected <HiTrash className="ml-2 h-4 w-4" />
</Button>
</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>

View file

@ -134,3 +134,36 @@ def deleteMod(id, slug):
"message": f"mod {slug} has been removed", "message": f"mod {slug} has been removed",
} }
) )
@apiPack.route("/<id>/mods/delete", methods=["POST"])
def deleteModBulk(id):
pack = {}
slugs = request.json
with open(f"{PACKS_FOLDER}/{id}/packfile.json") as fp:
pack = json.load(fp)
fp.close()
for slug in slugs:
for mod in pack.get("mods"):
if mod.get("slug") == slug:
pack["mods"].remove(mod)
pack["modpackVersion"] += 1
if os.path.exists(
f"{PACKS_FOLDER}/{id}/mods/{mod.get('file').get('filename')}"
):
os.remove(
f"{PACKS_FOLDER}/{id}/mods/{mod.get('file').get('filename')}"
)
with open(f"{PACKS_FOLDER}/{id}/packfile.json", mode="w", encoding="utf-8") as fp:
json.dump(pack, fp)
fp.close()
return jsonify(
{
"status": "ok",
"message": f"mods has been removed",
}
)