mirror of
https://github.com/Radiquum/YAMPD.git
synced 2025-05-20 15:49:34 +05:00
feat: pack creating
This commit is contained in:
parent
5be021789b
commit
af7b8a6fea
27 changed files with 677 additions and 130 deletions
43
gui/app/App.tsx
Normal file
43
gui/app/App.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
"use client";
|
||||
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Menu } from "./components/Sidebar";
|
||||
import { Bounce, ToastContainer } from "react-toastify";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
type APPProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const App = ({ children }: APPProps) => {
|
||||
return (
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased flex h-screen overflow-hidden`}
|
||||
>
|
||||
<Menu></Menu>
|
||||
<div className="p-2 overflow-auto w-full">{children}</div>
|
||||
<ToastContainer
|
||||
position="bottom-right"
|
||||
autoClose={5000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick={false}
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
theme="colored"
|
||||
transition={Bounce}
|
||||
/>
|
||||
</body>
|
||||
);
|
||||
};
|
56
gui/app/components/Sidebar.tsx
Normal file
56
gui/app/components/Sidebar.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import {
|
||||
Sidebar,
|
||||
SidebarItem,
|
||||
SidebarItemGroup,
|
||||
SidebarItems,
|
||||
} from "flowbite-react";
|
||||
import {
|
||||
// HiArrowSmRight,
|
||||
HiChartPie,
|
||||
HiPlusCircle
|
||||
// HiInbox,
|
||||
// HiShoppingBag,
|
||||
// HiTable,
|
||||
// HiUser,
|
||||
// HiViewBoards,
|
||||
} from "react-icons/hi";
|
||||
|
||||
export const Menu = () => {
|
||||
return (
|
||||
<Sidebar aria-label="Default sidebar example">
|
||||
<SidebarItems>
|
||||
<SidebarItemGroup>
|
||||
<SidebarItem href="/" icon={HiChartPie}>
|
||||
Dashboard
|
||||
</SidebarItem>
|
||||
<SidebarItem href="/pack/new" icon={HiPlusCircle}>
|
||||
New mod pack
|
||||
</SidebarItem>
|
||||
{/* <SidebarItem
|
||||
href="#"
|
||||
icon={HiViewBoards}
|
||||
label="Pro"
|
||||
labelColor="dark"
|
||||
>
|
||||
Kanban
|
||||
</SidebarItem>
|
||||
<SidebarItem href="#" icon={HiInbox} label="3">
|
||||
Inbox
|
||||
</SidebarItem>
|
||||
<SidebarItem href="#" icon={HiUser}>
|
||||
Users
|
||||
</SidebarItem>
|
||||
<SidebarItem href="#" icon={HiShoppingBag}>
|
||||
Products
|
||||
</SidebarItem>
|
||||
<SidebarItem href="#" icon={HiArrowSmRight}>
|
||||
Sign In
|
||||
</SidebarItem>
|
||||
<SidebarItem href="#" icon={HiTable}>
|
||||
Sign Up
|
||||
</SidebarItem> */}
|
||||
</SidebarItemGroup>
|
||||
</SidebarItems>
|
||||
</Sidebar>
|
||||
);
|
||||
};
|
|
@ -1,4 +1,6 @@
|
|||
@import "tailwindcss";
|
||||
@plugin "flowbite-react/plugin/tailwindcss";
|
||||
@source "../.flowbite-react/class-list.json";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
|
@ -14,7 +16,7 @@
|
|||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--background: #111827;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
@ -22,5 +24,5 @@
|
|||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-sans), Helvetica, sans-serif;
|
||||
}
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
import { App } from "./App";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
|
@ -24,11 +14,7 @@ export default function RootLayout({
|
|||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
<App>{children}</App>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
221
gui/app/pack/new/page.tsx
Normal file
221
gui/app/pack/new/page.tsx
Normal file
|
@ -0,0 +1,221 @@
|
|||
"use client";
|
||||
|
||||
import { Card, FileInput } from "flowbite-react";
|
||||
import { Label, TextInput, Select } from "flowbite-react";
|
||||
import { useState } from "react";
|
||||
import { HiUser, HiAnnotation } from "react-icons/hi";
|
||||
import { Button } from "flowbite-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import mc from "../../../api/mc_version.json";
|
||||
import { ENDPOINTS, PACK_IMG_ENDPOINTS } from "@/api/ENDPOINTS";
|
||||
import { toast } from "react-toastify";
|
||||
const mcr = mc.reverse();
|
||||
|
||||
export default function PackNew() {
|
||||
const router = useRouter()
|
||||
const [image, setImage] = useState<null | string>(null);
|
||||
const [imageMime, setImageMime] = useState<null | string>(null);
|
||||
const [packInfo, setPackInfo] = useState({
|
||||
title: "",
|
||||
author: "",
|
||||
modloader: "Forge",
|
||||
version: "1.21.5",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleImagePreview = (e: any) => {
|
||||
const file = e.target.files[0];
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onloadend = () => {
|
||||
const content = fileReader.result;
|
||||
setImage(content as string);
|
||||
setImageMime(file.type);
|
||||
e.target.value = "";
|
||||
};
|
||||
fileReader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function handleInput(e: any) {
|
||||
const regex = /[^a-zA-Zа-яА-Я0-9_.()\- \[\]]/g;
|
||||
setPackInfo({
|
||||
...packInfo,
|
||||
[e.target.name]: e.target.value.replace(regex, ""),
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function submit(e: any) {
|
||||
e.preventDefault();
|
||||
|
||||
async function _submit() {
|
||||
const tid = toast.loading(`Creating Pack "${packInfo.title}"`)
|
||||
|
||||
const res = await fetch(`${ENDPOINTS.createPack}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(packInfo),
|
||||
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})
|
||||
return;
|
||||
}
|
||||
|
||||
if (image) {
|
||||
await fetch(`${PACK_IMG_ENDPOINTS("editPackImage", data.id)}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
image: image,
|
||||
mimetype: imageMime
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
accept: "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toast.update(tid, {render: data.message, type: "success", isLoading: false})
|
||||
router.push(`/pack/${data.id}`)
|
||||
}
|
||||
|
||||
_submit();
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<form
|
||||
className="flex flex-col gap-4"
|
||||
encType="multipart/form-data"
|
||||
onSubmit={(e) => submit(e)}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center">
|
||||
<Label
|
||||
htmlFor="dropzone-file"
|
||||
className="flex h-64 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-gray-500 dark:hover:bg-gray-600"
|
||||
>
|
||||
{image ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={image} alt="preview" className="overflow-hidden" />
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center pb-6 pt-5">
|
||||
<svg
|
||||
className="mb-4 h-8 w-8 text-gray-500 dark:text-gray-400"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 20 16"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
|
||||
/>
|
||||
</svg>
|
||||
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span className="font-semibold">Click to upload</span> or drag
|
||||
and drop
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
PNG or JPG
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<FileInput
|
||||
id="dropzone-file"
|
||||
className="hidden"
|
||||
name="image"
|
||||
onChange={(e) => handleImagePreview(e)}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="mb-2 block">
|
||||
<Label htmlFor="base" className="text-lg">
|
||||
Title
|
||||
</Label>
|
||||
</div>
|
||||
<TextInput
|
||||
id="base"
|
||||
type="text"
|
||||
sizing="md"
|
||||
name="title"
|
||||
onChange={(e) => handleInput(e)}
|
||||
value={packInfo.title}
|
||||
icon={HiAnnotation}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="mb-2 block">
|
||||
<Label htmlFor="base" className="text-lg">
|
||||
Author
|
||||
</Label>
|
||||
</div>
|
||||
<TextInput
|
||||
id="base"
|
||||
type="text"
|
||||
sizing="md"
|
||||
name="author"
|
||||
onChange={(e) => handleInput(e)}
|
||||
value={packInfo.author}
|
||||
icon={HiUser}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="mb-2 block">
|
||||
<Label htmlFor="base" className="text-lg">
|
||||
Mod Loader
|
||||
</Label>
|
||||
</div>
|
||||
<Select
|
||||
id="modloader"
|
||||
name="modloader"
|
||||
required
|
||||
onChange={(e) => handleInput(e)}
|
||||
>
|
||||
<option value="Forge">Forge</option>
|
||||
<option value="Fabric">Fabric</option>
|
||||
<option value="NeoForge">NeoForge</option>
|
||||
<option value="Quilt">Quilt</option>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="mb-2 block">
|
||||
<Label htmlFor="base" className="text-lg">
|
||||
Game Version
|
||||
</Label>
|
||||
</div>
|
||||
<Select
|
||||
id="version"
|
||||
name="version"
|
||||
required
|
||||
onChange={(e) => handleInput(e)}
|
||||
>
|
||||
{mcr.map((version) => (
|
||||
<option key={version} value={version}>
|
||||
{version}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">Create</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
);
|
||||
}
|
102
gui/app/page.tsx
102
gui/app/page.tsx
|
@ -1,103 +1,5 @@
|
|||
import Image from "next/image";
|
||||
"use client";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||
app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
return <></>
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue