Added the backend to add mods
This commit is contained in:
parent
315a803d68
commit
7a8207d31d
@ -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;
|
||||
}
|
||||
@ -2117,4 +2048,46 @@ 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);
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod auth;
|
||||
pub mod modpack;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub mod surrealdb;
|
||||
|
@ -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?;
|
||||
@ -40,7 +40,7 @@ impl User {
|
||||
let session_token = session_token.value();
|
||||
|
||||
let mut res = DB
|
||||
.query("SELECT type::string(user.id) as id, user.email as email FROM session WHERE token = $session_token")
|
||||
.query("SELECT type::string(user.id) as id, user.email as email FROM session WHERE token = $session_token")
|
||||
.bind(("session_token", session_token))
|
||||
.await?;
|
||||
|
||||
|
63
src/server/modpack.rs
Normal file
63
src/server/modpack.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user