mirror of
https://github.com/Radiquum/YAMPD.git
synced 2025-05-20 07:39:35 +05:00
298 lines
8.6 KiB
TypeScript
298 lines
8.6 KiB
TypeScript
"use client";
|
|
|
|
import { MOD_ENDPOINT, PACK_ENDPOINT, PACKS_ENDPOINT } from "@/api/ENDPOINTS";
|
|
import { Pack } from "@/types/pack";
|
|
import { Button, Card, Label, Spinner, TextInput } from "flowbite-react";
|
|
import { useSearchParams } from "next/navigation";
|
|
import { useRouter } from "next/navigation";
|
|
import { useEffect, useState } from "react";
|
|
import { Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react";
|
|
|
|
import { HiDownload, HiPlusCircle, HiTrash } from "react-icons/hi";
|
|
import { ModTable } from "../components/ModTable";
|
|
import { toast } from "react-toastify";
|
|
|
|
export default function PackPage() {
|
|
const [packData, setPackData] = useState<Pack | null>(null);
|
|
const [packDataLoading, setPackDataLoading] = useState(true);
|
|
const router = useRouter();
|
|
const id = useSearchParams().get("id") || "";
|
|
|
|
const [addModModalOpen, setAddModModalOpen] = useState(true);
|
|
const [modUrl, setModUrl] = useState("");
|
|
|
|
async function _getPacksData() {
|
|
const res = await fetch(PACK_ENDPOINT("getPack", id));
|
|
if (!res.ok) router.push("/404");
|
|
const data: Pack = await res.json();
|
|
setPackData(data);
|
|
setPackDataLoading(false);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (id) {
|
|
_getPacksData();
|
|
} else {
|
|
router.push("/404");
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const handleImageUpdate = (e: any) => {
|
|
if (!packData || !window) return;
|
|
|
|
const file = e.target.files[0];
|
|
const fileReader = new FileReader();
|
|
fileReader.onloadend = () => {
|
|
const content = fileReader.result;
|
|
fetch(`${PACK_ENDPOINT("editPackImage", packData._id)}`, {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
image: content,
|
|
mimetype: file.type,
|
|
}),
|
|
headers: {
|
|
"content-type": "application/json",
|
|
accept: "application/json",
|
|
},
|
|
}).then(() => location.reload());
|
|
e.target.value = "";
|
|
};
|
|
fileReader.readAsDataURL(file);
|
|
};
|
|
|
|
function deletePack() {
|
|
if (!packData || !window) return;
|
|
|
|
if (window.confirm(`Delete pack ${packData.title}?`)) {
|
|
fetch(`${PACKS_ENDPOINT("deletePack", packData._id)}`);
|
|
const ur = new URL(window.location.href);
|
|
ur.searchParams.delete("id");
|
|
ur.pathname = "/";
|
|
window.location.href = ur.href;
|
|
}
|
|
}
|
|
|
|
async function addMod() {
|
|
let slug = null;
|
|
let version = null;
|
|
|
|
if (!modUrl) {
|
|
toast.error("Mod url is required", {
|
|
autoClose: 2500,
|
|
closeOnClick: true,
|
|
draggable: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
let source = null;
|
|
|
|
if (modUrl.includes("modrinth.com")) {
|
|
source = "Modrinth";
|
|
} else if (modUrl.includes("curseforge.com")) {
|
|
source = "CurseForge";
|
|
} else {
|
|
toast.error("unknown source", {
|
|
autoClose: 2500,
|
|
closeOnClick: true,
|
|
draggable: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
switch (source) {
|
|
case "Modrinth":
|
|
const _tmp = modUrl.split("/mod/");
|
|
if (_tmp.length == 1) {
|
|
toast.error("invalid Modrinth url", {
|
|
autoClose: 2500,
|
|
closeOnClick: true,
|
|
draggable: true,
|
|
});
|
|
return;
|
|
}
|
|
const _tmp2 = _tmp[1].split("/version/");
|
|
slug = _tmp2[0];
|
|
if (_tmp2.length > 1) {
|
|
version = _tmp2[1];
|
|
}
|
|
break;
|
|
case "CurseForge":
|
|
const _tmp3 = modUrl.split("/mc-mods/");
|
|
if (_tmp3.length == 1) {
|
|
toast.error("invalid CurseForge url", {
|
|
autoClose: 2500,
|
|
closeOnClick: true,
|
|
draggable: true,
|
|
});
|
|
return;
|
|
}
|
|
const _tmp4 = _tmp3[1].split("/files/");
|
|
slug = _tmp4[0];
|
|
if (_tmp4.length > 1) {
|
|
version = _tmp4[1];
|
|
}
|
|
break;
|
|
default:
|
|
toast.error("failed to parse url", {
|
|
autoClose: 2500,
|
|
closeOnClick: true,
|
|
draggable: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
slug = slug.replace("/", "");
|
|
version = version ? version.replace("/", "") : null;
|
|
|
|
if (!packData) return;
|
|
const tid = toast.loading(`Adding mod`);
|
|
const res = await fetch(MOD_ENDPOINT("addMod", packData._id), {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
slug,
|
|
version,
|
|
source,
|
|
}),
|
|
headers: {
|
|
"content-type": "application/json",
|
|
accept: "application/json",
|
|
},
|
|
});
|
|
const data = await res.json();
|
|
|
|
if (data.status != "ok") {
|
|
toast.update(tid, {
|
|
render: data.message,
|
|
type: "error",
|
|
isLoading: false,
|
|
autoClose: 2500,
|
|
closeOnClick: true,
|
|
draggable: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
toast.update(tid, {
|
|
render: data.message,
|
|
type: "success",
|
|
isLoading: false,
|
|
autoClose: 2500,
|
|
closeOnClick: true,
|
|
draggable: true,
|
|
});
|
|
// setModUrl({ ...modUrl, value: "" });
|
|
|
|
_getPacksData();
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
{packDataLoading && (
|
|
<div className="w-full flex justify-center items-center">
|
|
<Spinner></Spinner>
|
|
</div>
|
|
)}
|
|
{packData && (
|
|
<div>
|
|
<Card className="sticky top-0 left-0 right-0 z-10">
|
|
<div className="flex gap-4 items-center justify-between">
|
|
<div>
|
|
<p className="text-xl font-semibold">{packData.version}</p>
|
|
<p className="text-sm text-gray-400">{packData.modloader}</p>
|
|
</div>
|
|
<div className="flex gap-2 items-center">
|
|
<label htmlFor="pack-icon" className="cursor-pointer">
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
alt=""
|
|
src={PACK_ENDPOINT("getPackImage", packData._id)}
|
|
className="w-12 h-12 rounded-md"
|
|
/>
|
|
</label>
|
|
<input
|
|
onChange={(e) => handleImageUpdate(e)}
|
|
id="pack-icon"
|
|
className="hidden"
|
|
name="image"
|
|
type="file"
|
|
/>
|
|
<div>
|
|
<p className="text-xl font-semibold">{packData.title}</p>
|
|
<p className="text-sm text-gray-400">by {packData.author}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2 items-center">
|
|
<div>
|
|
<p className="text-lg font-semibold">
|
|
{packData.mods.length} mods
|
|
</p>
|
|
<p className="text-sm text-gray-400">
|
|
v{packData.modpackVersion}
|
|
</p>
|
|
</div>
|
|
<Button onClick={() => setAddModModalOpen(true)}>
|
|
Add mod <HiPlusCircle className="ml-2 h-5 w-5" />
|
|
</Button>
|
|
<Button>
|
|
Download <HiDownload className="ml-2 h-5 w-5" />
|
|
</Button>
|
|
<Button color={"red"} onClick={() => deletePack()}>
|
|
Delete <HiTrash className="ml-2 h-5 w-5" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
<div className="mt-4">
|
|
<ModTable
|
|
mods={packData.mods}
|
|
updatePack={_getPacksData}
|
|
packID={id}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<Modal
|
|
dismissible
|
|
show={addModModalOpen}
|
|
onClose={() => setAddModModalOpen(false)}
|
|
>
|
|
<ModalHeader>Add a mod</ModalHeader>
|
|
<ModalBody>
|
|
<div className="space-y-6">
|
|
<div className="flex-1">
|
|
<div className="mb-2 block">
|
|
<Label htmlFor="mod_url" className="text-lg">
|
|
Link
|
|
</Label>
|
|
</div>
|
|
<TextInput
|
|
id="mod_url"
|
|
type="text"
|
|
sizing="md"
|
|
name="mod_url"
|
|
onChange={(e) => setModUrl(e.target.value)}
|
|
value={modUrl}
|
|
required
|
|
/>
|
|
<p className="mt-2 text-gray-400">Can be one of:</p>
|
|
<ul className="text-gray-400 list-disc">
|
|
<li className="ml-4">
|
|
https://modrinth.com/mod/[slug][/version/file_id]
|
|
</li>
|
|
<li className="ml-4">
|
|
https://www.curseforge.com/minecraft/mc-mods/[slug][/files/file_id]
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</ModalBody>
|
|
<ModalFooter>
|
|
<Button onClick={() => addMod()}>Save</Button>
|
|
</ModalFooter>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
}
|