diff --git a/gui/api/ENDPOINTS.ts b/gui/api/ENDPOINTS.ts index 14015b8..57c45ae 100644 --- a/gui/api/ENDPOINTS.ts +++ b/gui/api/ENDPOINTS.ts @@ -15,6 +15,7 @@ type _PACKS_ENDPOINT = { type _MOD_ENDPOINT = { addMod: string; deleteMod: string; + deleteModBulk: string; }; export const PACK_ENDPOINT = (endpoint: keyof _PACK_ENDPOINT, id: string) => { @@ -68,6 +69,7 @@ export const MOD_ENDPOINT = ( const _endpoints = { addMod: `${API}/pack/${id}/mod/add`, deleteMod: `${API}/pack/${id}/mod/${slug}/delete`, + deleteModBulk: `${API}/pack/${id}/mods/delete`, }; return _endpoints[endpoint]; }; diff --git a/gui/app/components/ModTable.tsx b/gui/app/components/ModTable.tsx index 8c2aea1..afb3670 100644 --- a/gui/app/components/ModTable.tsx +++ b/gui/app/components/ModTable.tsx @@ -10,6 +10,7 @@ import { TableHeadCell, TableRow, } from "flowbite-react"; +import { useState } from "react"; import { HiDownload, HiTrash } from "react-icons/hi"; import { toast } from "react-toastify"; @@ -18,6 +19,8 @@ export const ModTable = (props: { updatePack: () => void; packID: string; }) => { + const [selectedMods, setSelectedMods] = useState([]); + async function deleteMod(slug: string, title: string) { if (!window) return; 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 (
- + selectAll()} + /> Icon Title @@ -66,7 +110,12 @@ export const ModTable = (props: { className="bg-white dark:border-gray-700 dark:bg-gray-800" > - + { + handleCheckbox(mod.slug); + }} + /> {/* eslint-disable-next-line @next/next/no-img-element */} @@ -104,7 +153,16 @@ export const ModTable = (props: { - + + +
diff --git a/src/api/pack.py b/src/api/pack.py index c6fdbde..c6a684d 100644 --- a/src/api/pack.py +++ b/src/api/pack.py @@ -134,3 +134,36 @@ def deleteMod(id, slug): "message": f"mod {slug} has been removed", } ) + + +@apiPack.route("//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", + } + )