mirror of
https://github.com/Radiquum/YAMPD.git
synced 2025-05-20 15:49:34 +05:00
feat: add dependencies resolution for modrinth
This commit is contained in:
parent
50a1c8118e
commit
c517795725
6 changed files with 431 additions and 138 deletions
|
@ -1,18 +1,15 @@
|
||||||
import { MOD_ENDPOINT } from "@/api/ENDPOINTS";
|
import { MOD_ENDPOINT } from "@/api/ENDPOINTS";
|
||||||
import { Mod } from "@/types/mod";
|
import { Mod } from "@/types/mod";
|
||||||
import {
|
import { Button } from "flowbite-react";
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeadCell,
|
|
||||||
TableRow,
|
|
||||||
} from "flowbite-react";
|
|
||||||
import { useState } from "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";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionPanel,
|
||||||
|
AccordionTitle,
|
||||||
|
} from "flowbite-react";
|
||||||
|
|
||||||
export const ModTable = (props: {
|
export const ModTable = (props: {
|
||||||
mods: Mod[];
|
mods: Mod[];
|
||||||
|
@ -20,7 +17,13 @@ export const ModTable = (props: {
|
||||||
packID: string;
|
packID: string;
|
||||||
downloadMods: (mods: string[]) => void;
|
downloadMods: (mods: string[]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedMods, setSelectedMods] = useState<string[]>([]);
|
function bytesToSize(bytes) {
|
||||||
|
var sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
||||||
|
if (bytes == 0) return "n/a";
|
||||||
|
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||||
|
if (i == 0) return bytes + " " + sizes[i];
|
||||||
|
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteMod(slug: string, title: string) {
|
async function deleteMod(slug: string, title: string) {
|
||||||
if (!window) return;
|
if (!window) return;
|
||||||
|
@ -41,96 +44,133 @@ export const ModTable = (props: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAll() {
|
if (!props.mods || props.mods.length == 0) {
|
||||||
const deselect = selectedMods.length == props.mods.length;
|
return <></>;
|
||||||
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">
|
<Accordion>
|
||||||
<Table hoverable>
|
{props.mods.map((mod) => {
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableHeadCell className="p-4">
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedMods.length == props.mods.length}
|
|
||||||
onChange={() => selectAll()}
|
|
||||||
/>
|
|
||||||
</TableHeadCell>
|
|
||||||
<TableHeadCell>Icon</TableHeadCell>
|
|
||||||
<TableHeadCell>Title</TableHeadCell>
|
|
||||||
<TableHeadCell>Version</TableHeadCell>
|
|
||||||
<TableHeadCell>Developer</TableHeadCell>
|
|
||||||
<TableHeadCell>Source</TableHeadCell>
|
|
||||||
<TableHeadCell>Source URL</TableHeadCell>
|
|
||||||
<TableHeadCell>
|
|
||||||
<span className="sr-only">Actions</span>
|
|
||||||
</TableHeadCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody className="divide-y">
|
|
||||||
{props.mods &&
|
|
||||||
props.mods.length > 0 &&
|
|
||||||
props.mods.map((mod) => {
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<AccordionPanel key={`mod-${mod.slug}`}>
|
||||||
key={`mod-${mod.slug}`}
|
<AccordionTitle>
|
||||||
className="bg-white dark:border-gray-700 dark:bg-gray-800"
|
<div className="flex gap-2 items-center text-2xl">
|
||||||
>
|
|
||||||
<TableCell className="p-4">
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedMods.includes(mod.slug)}
|
|
||||||
onChange={() => {
|
|
||||||
handleCheckbox(mod.slug);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img alt="" src={mod.icon} className="w-8 h-8 rounded-lg" />
|
<img alt="" src={mod.icon} className="w-8 h-8 rounded-lg" />
|
||||||
</TableCell>
|
{mod.title} ({mod.slug})
|
||||||
<TableCell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
|
</div>
|
||||||
{mod.title}
|
</AccordionTitle>
|
||||||
</TableCell>
|
<AccordionContent>
|
||||||
<TableCell>{mod.file.version}</TableCell>
|
<div className="flex gap-8 flex-wrap">
|
||||||
<TableCell>{mod.developers.join(", ")}</TableCell>
|
<div className="flex gap-2 flex-col">
|
||||||
<TableCell>{mod.source}</TableCell>
|
<div>
|
||||||
<TableCell>{mod.url}</TableCell>
|
<p className="font-semibold text-xl">Developers</p>
|
||||||
<TableCell>
|
{mod.developers.join(", ")}
|
||||||
<div className="flex gap-2">
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-xl">Source</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">title:</span> {mod.source}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">id:</span>{" "}
|
||||||
|
{mod.project_id}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">link:</span> {mod.url}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-xl">Version info</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">filename:</span>{" "}
|
||||||
|
{mod.file.filename}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">version:</span>{" "}
|
||||||
|
{mod.file.version}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">file size:</span>{" "}
|
||||||
|
{bytesToSize(mod.file.size)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-xl">Environment</p>
|
||||||
|
{mod.environment.client && <p>client</p>}
|
||||||
|
{mod.environment.server && <p>server</p>}
|
||||||
|
{mod.environment.client && mod.environment.server && (
|
||||||
|
<p>client & server</p>
|
||||||
|
)}
|
||||||
|
{/* <p>
|
||||||
|
<span className="font-semibold">filename:</span>{" "}
|
||||||
|
{mod.file.filename}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">version:</span>{" "}
|
||||||
|
{mod.file.version}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">file size:</span>{" "}
|
||||||
|
{bytesToSize(mod.file.size)}
|
||||||
|
</p> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<p className="font-semibold text-xl">Hashes</p>
|
||||||
|
{Object.entries(mod.file.hashes).map((hash) => {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
key={`mod-${mod.slug}-hash-${hash[0]}`}
|
||||||
|
className="wrap-break-word"
|
||||||
|
>
|
||||||
|
<span className="font-semibold">{hash[0]}:</span>{" "}
|
||||||
|
{hash[1]}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{mod.dependencies.length > 0 ? (
|
||||||
|
<div className="mt-2">
|
||||||
|
<p className="font-semibold text-xl mb-1">Dependencies</p>
|
||||||
|
<div className="flex gap-2 overflow-x-auto overflow-y-hidden">
|
||||||
|
{mod.dependencies.map((dep) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`mod-${mod.slug}-dep-${dep.slug}`}
|
||||||
|
className="bg-[#f3f4f6] dark:bg-[#1f2937] p-4 rounded-lg"
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 items-center text-xl">
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
src={dep.icon}
|
||||||
|
className="w-6 h-6 rounded-lg"
|
||||||
|
/>
|
||||||
|
{dep.title} ({dep.slug})
|
||||||
|
</div>
|
||||||
|
<div className="mt-1">
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">filename:</span>{" "}
|
||||||
|
{dep.file.filename}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">version:</span>{" "}
|
||||||
|
{dep.file.version}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">file size:</span>{" "}
|
||||||
|
{bytesToSize(dep.file.size)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
<div className="flex justify-end w-full gap-2 mt-4">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => props.downloadMods([mod.slug])}
|
onClick={() => props.downloadMods([mod.slug])}
|
||||||
|
@ -145,40 +185,10 @@ export const ModTable = (props: {
|
||||||
Delete <HiTrash className="ml-2 h-4 w-4" />
|
Delete <HiTrash className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</AccordionContent>
|
||||||
</TableRow>
|
</AccordionPanel>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<TableRow className="bg-white dark:bg-[#374151] hover:bg-white! hover:dark:bg-[#374151]! dark:border-gray-700">
|
</Accordion>
|
||||||
<TableCell className="p-4"></TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
disabled={selectedMods.length == 0}
|
|
||||||
onClick={() => props.downloadMods(selectedMods)}
|
|
||||||
>
|
|
||||||
Download Selected <HiDownload className="ml-2 h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color={"red"}
|
|
||||||
size="sm"
|
|
||||||
disabled={selectedMods.length == 0}
|
|
||||||
onClick={() => deleteSelectedMods()}
|
|
||||||
>
|
|
||||||
Delete Selected <HiTrash className="ml-2 h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
234
gui/app/components/_ModTable.tsx
Normal file
234
gui/app/components/_ModTable.tsx
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
import { MOD_ENDPOINT } from "@/api/ENDPOINTS";
|
||||||
|
import { Mod } from "@/types/mod";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeadCell,
|
||||||
|
TableRow,
|
||||||
|
} from "flowbite-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { HiDownload, HiTrash } from "react-icons/hi";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
export const ModTable = (props: {
|
||||||
|
mods: Mod[];
|
||||||
|
updatePack: () => void;
|
||||||
|
packID: string;
|
||||||
|
downloadMods: (mods: string[]) => void;
|
||||||
|
}) => {
|
||||||
|
const [selectedMods, setSelectedMods] = useState<string[]>([]);
|
||||||
|
|
||||||
|
async function deleteMod(slug: string, title: string) {
|
||||||
|
if (!window) return;
|
||||||
|
if (window.confirm(`Delete mod ${title}?`)) {
|
||||||
|
const res = await fetch(MOD_ENDPOINT("deleteMod", props.packID, slug));
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.status != "ok") {
|
||||||
|
toast.error(data.message, {
|
||||||
|
autoClose: 2500,
|
||||||
|
closeOnClick: true,
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.updatePack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table hoverable>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableHeadCell className="p-4">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedMods.length == props.mods.length}
|
||||||
|
onChange={() => selectAll()}
|
||||||
|
/>
|
||||||
|
</TableHeadCell>
|
||||||
|
<TableHeadCell>Icon</TableHeadCell>
|
||||||
|
<TableHeadCell>Title</TableHeadCell>
|
||||||
|
<TableHeadCell>Version</TableHeadCell>
|
||||||
|
<TableHeadCell>Developer</TableHeadCell>
|
||||||
|
<TableHeadCell>Source</TableHeadCell>
|
||||||
|
<TableHeadCell>Source URL</TableHeadCell>
|
||||||
|
<TableHeadCell>
|
||||||
|
<span className="sr-only">Actions</span>
|
||||||
|
</TableHeadCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody className="divide-y">
|
||||||
|
{props.mods &&
|
||||||
|
props.mods.length > 0 &&
|
||||||
|
props.mods.map((mod) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableRow
|
||||||
|
key={`mod-${mod.slug}`}
|
||||||
|
className="bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||||
|
>
|
||||||
|
<TableCell className="p-4">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedMods.includes(mod.slug)}
|
||||||
|
onChange={() => {
|
||||||
|
handleCheckbox(mod.slug);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
src={mod.icon}
|
||||||
|
className="w-8 h-8 rounded-lg"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
|
||||||
|
{mod.title}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{mod.file.version}</TableCell>
|
||||||
|
<TableCell>{mod.developers.join(", ")}</TableCell>
|
||||||
|
<TableCell>{mod.source}</TableCell>
|
||||||
|
<TableCell>{mod.url}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => props.downloadMods([mod.slug])}
|
||||||
|
>
|
||||||
|
Download <HiDownload className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={"red"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => deleteMod(mod.slug, mod.title)}
|
||||||
|
>
|
||||||
|
Delete <HiTrash className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{mod.dependencies &&
|
||||||
|
mod.dependencies.length > 0 &&
|
||||||
|
mod.dependencies.map((dep) => {
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={`mod-${mod.slug}-dep-${dep.slug}`}
|
||||||
|
className="bg-white dark:border-gray-700 dark:bg-gray-800"
|
||||||
|
>
|
||||||
|
<TableCell className="p-4"></TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
src={dep.icon}
|
||||||
|
className="w-8 h-8 rounded-lg"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="whitespace-nowrap font-medium text-gray-900 dark:text-white">
|
||||||
|
{dep.title}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{dep.file.version}</TableCell>
|
||||||
|
<TableCell>{dep.developers.join(", ")}</TableCell>
|
||||||
|
<TableCell>{dep.source}</TableCell>
|
||||||
|
<TableCell>{dep.url}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{/* <div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => props.downloadMods([mod.slug])}
|
||||||
|
>
|
||||||
|
Download <HiDownload className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={"red"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => deleteMod(mod.slug, mod.title)}
|
||||||
|
>
|
||||||
|
Delete <HiTrash className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div> */}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<TableRow className="bg-white dark:bg-[#374151] hover:bg-white! hover:dark:bg-[#374151]! dark:border-gray-700">
|
||||||
|
<TableCell className="p-4"></TableCell>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
disabled={selectedMods.length == 0}
|
||||||
|
onClick={() => props.downloadMods(selectedMods)}
|
||||||
|
>
|
||||||
|
Download Selected <HiDownload className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={"red"}
|
||||||
|
size="sm"
|
||||||
|
disabled={selectedMods.length == 0}
|
||||||
|
onClick={() => deleteSelectedMods()}
|
||||||
|
>
|
||||||
|
Delete Selected <HiTrash className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
export type ModFile = {
|
export type ModFile = {
|
||||||
"version": string,
|
"version": string,
|
||||||
"hashes": unknown,
|
"hashes": Record<string, string>,
|
||||||
"url": string,
|
"url": string,
|
||||||
"filename": string,
|
"filename": string,
|
||||||
"size": number,
|
"size": number,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { ModFile } from "./file";
|
||||||
|
|
||||||
export type Mod = {
|
export type Mod = {
|
||||||
"slug": string,
|
"slug": string,
|
||||||
|
"project_id": string;
|
||||||
"icon": string,
|
"icon": string,
|
||||||
"title":string,
|
"title":string,
|
||||||
"developers": string[],
|
"developers": string[],
|
||||||
|
@ -11,5 +12,6 @@ export type Mod = {
|
||||||
"client": boolean,
|
"client": boolean,
|
||||||
"server": boolean,
|
"server": boolean,
|
||||||
},
|
},
|
||||||
|
"dependencies": Mod[],
|
||||||
"file": ModFile,
|
"file": ModFile,
|
||||||
}
|
}
|
|
@ -16,6 +16,10 @@ def getCurseForgeMod(slug, version, mod_loader, game_version):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"failed to fetch curseforge mod: {metaR.status_code}",
|
"message": f"failed to fetch curseforge mod: {metaR.status_code}",
|
||||||
|
"slug": slug,
|
||||||
|
"version": version,
|
||||||
|
"mod_loader": mod_loader,
|
||||||
|
"game_version": game_version
|
||||||
}
|
}
|
||||||
|
|
||||||
meta: dict = metaR.json()
|
meta: dict = metaR.json()
|
||||||
|
@ -42,6 +46,10 @@ def getCurseForgeMod(slug, version, mod_loader, game_version):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"failed to fetch curseforge mod versions: {versR.status_code}",
|
"message": f"failed to fetch curseforge mod versions: {versR.status_code}",
|
||||||
|
"slug": slug,
|
||||||
|
"version": version,
|
||||||
|
"mod_loader": mod_loader,
|
||||||
|
"game_version": game_version
|
||||||
}
|
}
|
||||||
|
|
||||||
vers: dict = versR.json()
|
vers: dict = versR.json()
|
||||||
|
@ -49,6 +57,10 @@ def getCurseForgeMod(slug, version, mod_loader, game_version):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"mod is not compatible with this game version or mod loader",
|
"message": f"mod is not compatible with this game version or mod loader",
|
||||||
|
"slug": slug,
|
||||||
|
"version": version,
|
||||||
|
"mod_loader": mod_loader,
|
||||||
|
"game_version": game_version
|
||||||
}
|
}
|
||||||
|
|
||||||
if version:
|
if version:
|
||||||
|
@ -64,10 +76,13 @@ def getCurseForgeMod(slug, version, mod_loader, game_version):
|
||||||
for hash in selected_version.get("hashes"):
|
for hash in selected_version.get("hashes"):
|
||||||
hashes[HASHALGO_ENUM[hash.get("algo")]] = hash.get("value")
|
hashes[HASHALGO_ENUM[hash.get("algo")]] = hash.get("value")
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"mod": {
|
"mod": {
|
||||||
"slug": slug,
|
"slug": slug,
|
||||||
|
"project_id": meta.get("id"),
|
||||||
"icon": meta.get("logo").get("url"),
|
"icon": meta.get("logo").get("url"),
|
||||||
"title": meta.get("name"),
|
"title": meta.get("name"),
|
||||||
"developers": developers,
|
"developers": developers,
|
||||||
|
@ -77,6 +92,7 @@ def getCurseForgeMod(slug, version, mod_loader, game_version):
|
||||||
"client": True,
|
"client": True,
|
||||||
"server": True,
|
"server": True,
|
||||||
},
|
},
|
||||||
|
"dependencies": dependencies,
|
||||||
"file": {
|
"file": {
|
||||||
"version": selected_version.get("id"),
|
"version": selected_version.get("id"),
|
||||||
"hashes": hashes,
|
"hashes": hashes,
|
||||||
|
|
|
@ -10,6 +10,10 @@ def getModrinthMod(slug, version, mod_loader, game_version):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"failed to fetch modrinth description: {descR.status_code}",
|
"message": f"failed to fetch modrinth description: {descR.status_code}",
|
||||||
|
"slug": slug,
|
||||||
|
"version": version,
|
||||||
|
"mod_loader": mod_loader,
|
||||||
|
"game_version": game_version
|
||||||
}
|
}
|
||||||
|
|
||||||
versR = requests.get(
|
versR = requests.get(
|
||||||
|
@ -21,6 +25,10 @@ def getModrinthMod(slug, version, mod_loader, game_version):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"failed to fetch modrinth mod versions: {versR.status_code}",
|
"message": f"failed to fetch modrinth mod versions: {versR.status_code}",
|
||||||
|
"slug": slug,
|
||||||
|
"version": version,
|
||||||
|
"mod_loader": mod_loader,
|
||||||
|
"game_version": game_version
|
||||||
}
|
}
|
||||||
|
|
||||||
devsR = requests.get(
|
devsR = requests.get(
|
||||||
|
@ -32,6 +40,10 @@ def getModrinthMod(slug, version, mod_loader, game_version):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"failed to fetch modrinth mod developers: {devsR.status_code}",
|
"message": f"failed to fetch modrinth mod developers: {devsR.status_code}",
|
||||||
|
"slug": slug,
|
||||||
|
"version": version,
|
||||||
|
"mod_loader": mod_loader,
|
||||||
|
"game_version": game_version
|
||||||
}
|
}
|
||||||
|
|
||||||
desc: dict = descR.json()
|
desc: dict = descR.json()
|
||||||
|
@ -42,6 +54,10 @@ def getModrinthMod(slug, version, mod_loader, game_version):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"mod is not compatible with this game version or mod loader",
|
"message": f"mod is not compatible with this game version or mod loader",
|
||||||
|
"slug": slug,
|
||||||
|
"version": version,
|
||||||
|
"mod_loader": mod_loader,
|
||||||
|
"game_version": game_version
|
||||||
}
|
}
|
||||||
|
|
||||||
selected_version = vers[0]
|
selected_version = vers[0]
|
||||||
|
@ -61,6 +77,10 @@ def getModrinthMod(slug, version, mod_loader, game_version):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": f"failed to get primary mod file",
|
"message": f"failed to get primary mod file",
|
||||||
|
"slug": slug,
|
||||||
|
"version": version,
|
||||||
|
"mod_loader": mod_loader,
|
||||||
|
"game_version": game_version
|
||||||
}
|
}
|
||||||
|
|
||||||
developers = []
|
developers = []
|
||||||
|
@ -74,10 +94,20 @@ def getModrinthMod(slug, version, mod_loader, game_version):
|
||||||
if desc.get("server_side") in ["optional", "required"]:
|
if desc.get("server_side") in ["optional", "required"]:
|
||||||
isServer = True
|
isServer = True
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
for dep in selected_version.get("dependencies"):
|
||||||
|
depDescR = requests.get(f"https://api.modrinth.com/v2/project/{dep.get('project_id')}", headers=headers)
|
||||||
|
if depDescR.status_code != 200:
|
||||||
|
continue
|
||||||
|
depDesc: dict = depDescR.json()
|
||||||
|
depMod = getModrinthMod(depDesc.get("slug"), None, mod_loader, game_version)
|
||||||
|
dependencies.append(depMod.get("mod"))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"mod": {
|
"mod": {
|
||||||
"slug": slug,
|
"slug": slug,
|
||||||
|
"project_id": desc.get("id"),
|
||||||
"icon": desc.get("icon_url"),
|
"icon": desc.get("icon_url"),
|
||||||
"title": desc.get("title"),
|
"title": desc.get("title"),
|
||||||
"developers": developers,
|
"developers": developers,
|
||||||
|
@ -87,6 +117,7 @@ def getModrinthMod(slug, version, mod_loader, game_version):
|
||||||
"client": isClient,
|
"client": isClient,
|
||||||
"server": isServer,
|
"server": isServer,
|
||||||
},
|
},
|
||||||
|
"dependencies": dependencies,
|
||||||
"file": {
|
"file": {
|
||||||
"version": selected_version.get("version_number"),
|
"version": selected_version.get("version_number"),
|
||||||
"hashes": primary_file.get("hashes"),
|
"hashes": primary_file.get("hashes"),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue