Added the backend to add mods

This commit is contained in:
xeovalyte 2024-06-07 13:02:01 +02:00
parent 315a803d68
commit 7a8207d31d
No known key found for this signature in database
6 changed files with 251 additions and 132 deletions

View File

@ -903,22 +903,6 @@ html {
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
}
.checkbox {
flex-shrink: 0;
--chkbg: var(--fallback-bc,oklch(var(--bc)/1));
--chkfg: var(--fallback-b1,oklch(var(--b1)/1));
height: 1.5rem;
width: 1.5rem;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: var(--rounded-btn, 0.5rem);
border-width: 1px;
border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
--tw-border-opacity: 0.2;
}
@media (hover: hover) {
.btm-nav > *.disabled:hover,
.btm-nav > *[disabled]:hover {
@ -1410,59 +1394,20 @@ input.tab:checked + .tab-content,
line-height: 1.25rem;
}
.card-title {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 600;
}
.card.image-full :where(figure) {
overflow: hidden;
border-radius: inherit;
}
.checkbox:focus {
box-shadow: none;
}
.checkbox:focus-visible {
outline-style: solid;
outline-width: 2px;
outline-offset: 2px;
outline-color: var(--fallback-bc,oklch(var(--bc)/1));
}
.checkbox:disabled {
border-width: 0px;
cursor: not-allowed;
border-color: transparent;
--tw-bg-opacity: 1;
background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
opacity: 0.2;
}
.checkbox:checked,
.checkbox[aria-checked="true"] {
background-repeat: no-repeat;
animation: checkmark var(--animation-input, 0.2s) ease-out;
background-color: var(--chkbg);
background-image: linear-gradient(-45deg, transparent 65%, var(--chkbg) 65.99%),
linear-gradient(45deg, transparent 75%, var(--chkbg) 75.99%),
linear-gradient(-45deg, var(--chkbg) 40%, transparent 40.99%),
linear-gradient(
45deg,
var(--chkbg) 30%,
var(--chkfg) 30.99%,
var(--chkfg) 40%,
transparent 40.99%
),
linear-gradient(-45deg, var(--chkfg) 50%, var(--chkbg) 50.99%);
}
.checkbox:indeterminate {
--tw-bg-opacity: 1;
background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
background-repeat: no-repeat;
animation: checkmark var(--animation-input, 0.2s) ease-out;
background-image: linear-gradient(90deg, transparent 80%, var(--chkbg) 80%),
linear-gradient(-90deg, transparent 80%, var(--chkbg) 80%),
linear-gradient(0deg, var(--chkbg) 43%, var(--chkfg) 43%, var(--chkfg) 57%, var(--chkbg) 57%);
}
@keyframes checkmark {
0% {
background-position-y: 5px;
@ -1895,32 +1840,6 @@ input.tab:checked + .tab-content,
}
}
.btn-sm {
height: 2rem;
min-height: 2rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 0.875rem;
}
.btn-square:where(.btn-sm) {
height: 2rem;
width: 2rem;
padding: 0px;
}
.btn-circle:where(.btn-sm) {
height: 2rem;
width: 2rem;
border-radius: 9999px;
padding: 0px;
}
[type="checkbox"].checkbox-sm {
height: 1.25rem;
width: 1.25rem;
}
.tabs-md :where(.tab) {
height: 2rem;
font-size: 0.875rem;
@ -1957,12 +1876,20 @@ input.tab:checked + .tab-content,
line-height: 1.25rem;
}
.card-compact .card-title {
margin-bottom: 0.25rem;
}
.card-normal .card-body {
padding: var(--padding-card, 2rem);
font-size: 1rem;
line-height: 1.5rem;
}
.card-normal .card-title {
margin-bottom: 0.75rem;
}
.static {
position: static;
}
@ -1972,6 +1899,10 @@ input.tab:checked + .tab-content,
margin-right: auto;
}
.mt-10 {
margin-top: 2.5rem;
}
.mt-20 {
margin-top: 5rem;
}
@ -1980,22 +1911,6 @@ input.tab:checked + .tab-content,
margin-top: 1.5rem;
}
.mt-10 {
margin-top: 2.5rem;
}
.mt-5 {
margin-top: 1.25rem;
}
.mt-16 {
margin-top: 4rem;
}
.mt-8 {
margin-top: 2rem;
}
.flex {
display: flex;
}
@ -2008,24 +1923,40 @@ input.tab:checked + .tab-content,
width: 100%;
}
.max-w-2xl {
max-width: 42rem;
.w-96 {
width: 24rem;
}
.max-w-lg {
max-width: 32rem;
.w-\[25rem\] {
width: 25rem;
}
.max-w-2xl {
max-width: 42rem;
}
.max-w-4xl {
max-width: 56rem;
}
.max-w-lg {
max-width: 32rem;
}
.max-w-md {
max-width: 28rem;
}
.max-w-xl {
max-width: 36rem;
.max-w-5xl {
max-width: 64rem;
}
.max-w-6xl {
max-width: 72rem;
}
.max-w-7xl {
max-width: 80rem;
}
.flex-1 {
@ -2036,20 +1967,20 @@ input.tab:checked + .tab-content,
flex: none;
}
.justify-start {
justify-content: flex-start;
.flex-wrap {
flex-wrap: wrap;
}
.gap-5 {
gap: 1.25rem;
}
.gap-2 {
gap: 0.5rem;
.gap-10 {
gap: 2.5rem;
}
.gap-1 {
gap: 0.25rem;
.gap-8 {
gap: 2rem;
}
.bg-base-100 {
@ -2067,6 +1998,11 @@ input.tab:checked + .tab-content,
padding-right: 0.25rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.py-1 {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
@ -2077,11 +2013,6 @@ input.tab:checked + .tab-content,
padding-bottom: 1.5rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.pt-40 {
padding-top: 10rem;
}
@ -2090,6 +2021,11 @@ input.tab:checked + .tab-content,
text-align: center;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.text-5xl {
font-size: 3rem;
line-height: 1;
@ -2100,11 +2036,6 @@ input.tab:checked + .tab-content,
line-height: 1.75rem;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.font-bold {
font-weight: 700;
}
@ -2118,3 +2049,45 @@ input.tab:checked + .tab-content,
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.hover\:cursor-pointer:hover {
cursor: pointer;
}
.hover\:bg-base-200:hover {
--tw-bg-opacity: 1;
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
}
.hover\:bg-base-200\/80:hover {
background-color: var(--fallback-b2,oklch(var(--b2)/0.8));
}
.hover\:bg-base-200\/60:hover {
background-color: var(--fallback-b2,oklch(var(--b2)/0.6));
}
.hover\:bg-base-200\/50:hover {
background-color: var(--fallback-b2,oklch(var(--b2)/0.5));
}
.hover\:bg-base-300\/100:hover {
background-color: var(--fallback-b3,oklch(var(--b3)/1));
}
.hover\:bg-base-300:hover {
--tw-bg-opacity: 1;
background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
}
.hover\:shadow-xl:hover {
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

View File

@ -1,11 +1,59 @@
use crate::server::modpack::Modpack;
use dioxus::prelude::*;
#[component]
pub fn Modpacks() -> Element {
let modpacks = use_resource(|| async move { get_modpacks().await });
rsx! {
div {
class: "max-w-4xl px-2 w-full mx-auto",
class: "max-w-7xl px-2 w-full mx-auto",
h2 { class: "text-2xl font-bold mt-10", "Modpacks" }
div {
class: "flex flex-wrap gap-8 mt-10",
match &*modpacks.read_unchecked() {
Some(Ok(response)) => rsx! {
for modpack in response {
ModpackCard { modpack: modpack.to_owned() }
}
},
Some(Err(_)) => rsx! { "Loading modpacks failed" },
None => rsx! { "Loading modpacks... " },
}
}
}
}
}
#[derive(PartialEq, Props, Clone)]
pub struct ModpackCardProps {
modpack: Modpack,
}
#[component]
pub fn ModpackCard(props: ModpackCardProps) -> Element {
rsx! {
div {
class: "card w-[25rem] bg-base-200 hover:bg-base-300 hover:cursor-pointer shadow-xl transition-all",
div {
class: "card-body",
h2 { class: "card-title", "{props.modpack.name}" },
div {
"For Minecraft ",
b { "{props.modpack.game_version}" },
" with ",
b { "{props.modpack.modloader}" },
}
}
}
}
}
#[server]
async fn get_modpacks() -> Result<Vec<Modpack>, ServerFnError> {
use crate::server::auth::User;
let user = User::from_cookie().await?;
Modpack::get_all(user.id).await
}

View File

@ -1,9 +1,12 @@
use dioxus::prelude::*;
use crate::modrinth::tags::game_versions::{get_game_versions, GameVersion};
use crate::Route;
#[component]
pub fn New() -> Element {
let nav = navigator();
let game_versions = use_resource(|| async move {
let response = get_game_versions().await;
@ -16,17 +19,31 @@ pub fn New() -> Element {
}
});
let submit = move |event: FormEvent| async move {
if let Ok(_modpack_id) = create_modpack(
event.values()["name"].as_value(),
event.values()["game_version"].as_value(),
event.values()["modloader"].as_value(),
)
.await
{
nav.push(Route::Modpacks {});
}
};
rsx! {
div {
class: "max-w-md w-full mx-auto",
h2 { class: "text-2xl font-bold mt-20", "Create a new modpack" }
form {
class: "mt-10",
input { r#type: "text", placeholder: "Name", class: "input input-bordered w-full" }
onsubmit: submit,
input { r#type: "text", name: "name", placeholder: "Name", class: "input input-bordered w-full" }
div {
class: "flex gap-5 mt-6",
select {
class: "select select-bordered w-full",
name: "game_version",
option { disabled: true, selected: true, "Minecraft Version" },
match &*game_versions.read_unchecked() {
Some(Ok(response)) => rsx! {
@ -39,6 +56,7 @@ pub fn New() -> Element {
}
select {
class: "select select-bordered",
name: "modloader",
option { disabled: true, selected: true, "Modloader" },
option { "Fabric" },
option { "Quilt" },
@ -51,3 +69,19 @@ pub fn New() -> Element {
}
}
}
#[server]
async fn create_modpack(
name: String,
game_version: String,
modloader: String,
) -> Result<String, ServerFnError> {
use crate::server::auth::User;
use crate::server::modpack::Modpack;
let user = User::from_cookie().await?;
let modpack = Modpack::new(name, game_version, modloader, user.id).await?;
Ok(modpack.id)
}

View File

@ -1,4 +1,5 @@
pub mod auth;
pub mod modpack;
#[cfg(feature = "server")]
pub mod surrealdb;

View File

@ -28,7 +28,7 @@ struct Session {
impl User {
#[cfg(feature = "server")]
async fn from_cookie() -> Result<Self, ServerFnError> {
pub async fn from_cookie() -> Result<Self, ServerFnError> {
use axum_extra::extract::CookieJar;
let jar: CookieJar = extract().await?;

63
src/server/modpack.rs Normal file
View File

@ -0,0 +1,63 @@
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use super::surrealdb::DB;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Modpack {
pub id: String,
pub owner_id: String,
pub name: String,
pub game_version: String,
pub modloader: String,
pub projects: Vec<Project>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Project {
pub project_id: String,
pub version_id: String,
}
impl Modpack {
#[cfg(feature = "server")]
pub async fn new(
name: String,
game_version: String,
modloader: String,
user_id: String,
) -> Result<Self, ServerFnError> {
let mut res = DB
.query("CREATE modpack SET name = $name, owner = type::thing($user_id), game_version = $game_version, modloader = $modloader, projects = [] RETURN type::string(id) as id, type::string(owner) as owner_id, game_version, modloader, name, projects")
.bind(("name", name))
.bind(("user_id", user_id))
.bind(("game_version", game_version))
.bind(("modloader", modloader))
.await?;
let modpack: Option<Modpack> = res.take(0)?;
match modpack {
Some(m) => {
tracing::info!("Created new modpack: {}", m.id);
Ok(m)
}
None => Err(ServerFnError::ServerError(
"Could not create modpack".to_string(),
)),
}
}
#[cfg(feature = "server")]
pub async fn get_all(user_id: String) -> Result<Vec<Self>, ServerFnError> {
let mut res = DB
.query("SELECT type::string(id) as id, type::string(owner) as owner_id, name, modloader, game_version, projects FROM modpack WHERE type::string(owner) = $owner_id;")
.bind(("owner_id", user_id))
.await?;
let modpacks: Vec<Self> = res.take(0)?;
Ok(modpacks)
}
}