small changes
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
ic3w0lf 2025-01-13 04:31:33 -07:00
parent e43f4bd0f0
commit 040488d85f
10 changed files with 248 additions and 119 deletions

View File

@ -10,7 +10,7 @@ $form-label-fontsize: 1.3rem;
:root { :root {
color-scheme: light dark; color-scheme: light dark;
--header-height: 60px; --header-height: 45px;
--page: white; --page: white;
--header-grad-left: #363b40; --header-grad-left: #363b40;

View File

@ -2,15 +2,6 @@
@use "../../globals.scss"; @use "../../globals.scss";
.container {
display: flex;
justify-content: center;
align-items: center;
height: calc(100vh - var(--header-height));
overflow: hidden;
position: relative;
}
a { a {
color:rgb(255, 255, 255); color:rgb(255, 255, 255);
@ -21,4 +12,64 @@ a {
&:active { &:active {
color: rgb(192, 192, 192) color: rgb(192, 192, 192)
} }
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
grid-template-rows: repeat(3, 1fr);
gap: 16px;
max-width: 100%;
margin: 0 auto;
overflow-x: hidden;
box-sizing: border-box;
}
@media (max-width: 768px) {
.grid {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
margin: 0.3rem;
}
.pagination button {
padding: 0.25rem 0.5rem;
font-size: 1.15rem;
border: none;
border-radius: 0.35rem;
background-color: #33333350;
color: #fff;
cursor: pointer;
}
.pagination button:disabled {
background-color: #5555559a;
cursor: not-allowed;
}
.pagination-dots {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
justify-content: center;
width: 100%;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #bbb;
cursor: pointer;
}
.dot.active {
background-color: #333;
} }

View File

@ -1,12 +1,87 @@
@use "../../../globals.scss"; @use "../../../globals.scss";
.submissionCard { .submissionCard {
@include globals.border-with-radius; display: flex;
background-color: #2020207c; background-color: #2020207c;
border: 1px solid #501717; border: 1px solid #97979783;
border-radius: 6px; border-radius: 10px;
padding: 6px; box-sizing: border-box;
text-align: center; flex-direction: column;
font-size: 14px; justify-content: space-between;
height: 100%;
min-width: 180px;
max-width: 340px;
}
.content {
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: space-between;
}
.details {
padding: 2px 4px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 3px 0px;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
margin: 3px 0px;
}
.map-image {
border-radius: 10px 10px 0 0;
overflow: hidden;
> img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.displayName {
font-size: 1rem;
font-weight: bold;
overflow: hidden;
max-width: 70%;
text-overflow: ellipsis;
white-space: nowrap;
}
.rating {
flex-shrink: 0;
}
.author {
display: flex;
align-items: center;
gap: 0.5rem;
flex-grow: 1;
min-width: 0;
}
.author span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rating {
margin-left: auto;
flex-shrink: 0;
}
.avatar {
border-radius: 50%;
object-fit: cover;
} }

View File

@ -9,7 +9,7 @@ interface CommentersProps {
} }
interface CreatorAndReviewStatus { interface CreatorAndReviewStatus {
asset_id: SubmissionInfo["AssetID"], asset_id: SubmissionInfo["AssetID"],
creator: SubmissionInfo["DisplayName"], creator: SubmissionInfo["DisplayName"],
review: SubmissionInfo["StatusID"], review: SubmissionInfo["StatusID"],
comments: Comment[], comments: Comment[],

View File

@ -1,5 +1,4 @@
import { Button, ButtonOwnProps } from "@mui/material"; import { Button, ButtonOwnProps } from "@mui/material";
import { SubmissionInfo } from "@/app/ts/Submission";
type Actions = "Completed" | "Submit" | "Reject" | "Revoke" type Actions = "Completed" | "Submit" | "Reject" | "Revoke"
type Review = Actions | "Accept" | "Validate" | "Upload" type Review = Actions | "Accept" | "Validate" | "Upload"
@ -12,6 +11,10 @@ interface ReviewButton {
color: ButtonOwnProps["color"] color: ButtonOwnProps["color"]
} }
interface ReviewId {
submissionId: string
}
function ReviewButtonClicked(action: Action, submissionId: string) { function ReviewButtonClicked(action: Action, submissionId: string) {
fetch(`/api/submissions/${submissionId}/status/${action}`, { fetch(`/api/submissions/${submissionId}/status/${action}`, {
method: "POST", method: "POST",
@ -28,8 +31,8 @@ function ReviewButton(props: ReviewButton) {
onClick={() => { ReviewButtonClicked(props.action, props.submissionId) }}>{props.name}</Button> onClick={() => { ReviewButtonClicked(props.action, props.submissionId) }}>{props.name}</Button>
} }
export default function ReviewButtons(props: SubmissionInfo) { export default function ReviewButtons(props: ReviewId) {
const submissionId = props.ID.toString() const submissionId = props.submissionId
// When is each button visible? // When is each button visible?
// Multiple buttons can be visible at once. // Multiple buttons can be visible at once.
// Action | Role | When Current Status is One of: // Action | Role | When Current Status is One of:

View File

@ -14,6 +14,10 @@ import { useState, useEffect } from "react";
import "./(styles)/page.scss"; import "./(styles)/page.scss";
interface ReviewId {
submissionId: string
}
function Ratings() { function Ratings() {
return ( return (
<Window className="rating-window" title="Rating"> <Window className="rating-window" title="Rating">
@ -35,15 +39,14 @@ function Ratings() {
) )
} }
function RatingArea(submission: SubmissionInfo) { function RatingArea(submission: ReviewId) {
return ( return (
<aside className="review-area"> <aside className="review-area">
<section className="map-image-area"> <section className="map-image-area">
<MapImage/> <MapImage/>
</section> </section>
<Ratings/> <Ratings/>
{/* TODO: NOT DO!!! */} {ReviewButtons(submission)} <ReviewButtons submissionId={submission.submissionId}/>
{/* <ReviewButtons submissionId={submission.ID}/> */}
</aside> </aside>
) )
} }
@ -91,7 +94,7 @@ export default function SubmissionInfoPage() {
<Webpage> <Webpage>
<main className="map-page-main"> <main className="map-page-main">
<section className="review-section"> <section className="review-section">
{RatingArea(submission)} <RatingArea submissionId={dynamicId.submissionId}/>
<TitleAndComments name={submission.DisplayName} creator={submission.Creator} review={submission.StatusID} asset_id={submission.AssetID} comments={[]}/> <TitleAndComments name={submission.DisplayName} creator={submission.Creator} review={submission.StatusID} asset_id={submission.AssetID} comments={[]}/>
</section> </section>
</main> </main>

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { Rating } from "@mui/material";
interface SubmissionCardProps { interface SubmissionCardProps {
displayName: string; displayName: string;
@ -14,11 +15,26 @@ export default function SubmissionCard(props: SubmissionCardProps) {
return ( return (
<Link href={`/submissions/${props.id}`}> <Link href={`/submissions/${props.id}`}>
<div className="submissionCard"> <div className="submissionCard">
{/* TODO: Grab image of model */} <div className="content">
<Image height={200} width={200} priority={true} src="https://api.ic3.space/strafe/map-images/11222350808" style={{ width: `100%` }} alt={props.displayName} /> <div className="map-image">
<h3>{props.displayName}</h3> {/* TODO: Grab image of model */}
<p>By {props.author}</p> <Image height={200} width={200} priority={true} src="https://api.ic3.space/strafe/map-images/11222350808" style={{ width: `100%` }} alt={props.displayName} />
<p> {props.rating}</p> {/* TODO: paste the star element from submission/1 page */} </div>
<div className="details">
<div className="header">
<span className="displayName">{props.displayName}</span>
<div className="rating">
<Rating value={props.rating} readOnly size="small" />
</div>
</div>
<div className="footer">
<div className="author">
<Image className="avatar" width={28} height={28} priority={true} src="https://api.ic3.space/strafe/map-images/11222350808" alt={props.author}/>
<span>{props.author}</span>
</div>
</div>
</div>
</div>
</div> </div>
</Link> </Link>
); );

View File

@ -1,43 +0,0 @@
//This can all be solved using 0 JavaScript,
//display: grid, ->1fr unit<-
import React, { useState, useEffect } from 'react';
import { Grid, Skeleton } from '@mui/material';
const elementWidth = 220;
function calculateSkeletonCount(setState: React.Dispatch<React.SetStateAction<number>>) {
const viewportWidth = window.innerWidth - 100 * 2;
setState(Math.floor(viewportWidth / elementWidth) * 2);
};
function SkeletonGrid() {
const [skeletonCount, setSkeletonCount] = useState(0);
useEffect(() => {
calculateSkeletonCount(setSkeletonCount);
window.addEventListener('resize', () => { calculateSkeletonCount(setSkeletonCount) });
return () => {
window.removeEventListener('resize', () => { calculateSkeletonCount(setSkeletonCount) });
};
}, []);
return (
<Grid
container
spacing={2}
alignItems="center"
justifyContent="center"
style={{ maxWidth: 'calc(100vw - 100px)', margin: '0 auto' }}
>
{Array.from({ length: skeletonCount }).map((_, index) => (
<Grid item key={index}>
<Skeleton variant="rectangular" width={215} height={340} />
</Grid>
))}
</Grid>
);
};
export default SkeletonGrid;

View File

@ -1,33 +1,53 @@
'use client' 'use client'
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from "react";
import { SubmissionInfo } from '../ts/Submission'; import { SubmissionInfo } from "../ts/Submission";
import { Grid2 as Grid } from '@mui/material';
import SubmissionCard from "./_card"; import SubmissionCard from "./_card";
import SkeletonGrid from './_loading';
import Webpage from "@/app/_components/webpage"; import Webpage from "@/app/_components/webpage";
import "./(styles)/page.scss"; import "./(styles)/page.scss";
export default function SubmissionInfoPage() { export default function SubmissionInfoPage() {
const [submissions, setSubmissions] = useState<SubmissionInfo[]>([]) const [submissions, setSubmissions] = useState<SubmissionInfo[]>([])
const [currentPage, setCurrentPage] = useState(0);
const cardsPerPage = 24; // built to fit on a 1920x1080 monitor
useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component) const totalPages = Math.ceil(submissions.length / cardsPerPage);
const currentCards = submissions.slice(
currentPage * cardsPerPage,
(currentPage + 1) * cardsPerPage
);
const nextPage = () => {
if (currentPage < totalPages - 1) {
setCurrentPage(currentPage + 1);
}
};
const prevPage = () => {
if (currentPage > 0) {
setCurrentPage(currentPage - 1);
}
};
useEffect(() => {
async function fetchSubmissions() { async function fetchSubmissions() {
const res = await fetch('/api/submissions?Page=1&Limit=100') const res = await fetch('/api/submissions?Page=1&Limit=100')
if (res.ok) { if (res.ok) {
setSubmissions(await res.json()) setSubmissions(await res.json())
} }
} }
setTimeout(() => { // testing loading screen made by chatGerbertPT
setTimeout(() => {
fetchSubmissions() fetchSubmissions()
}, 250); }, 50);
}, []) }, [])
if (!submissions) { if (!submissions || submissions.length == 0) {
return <Webpage> return <Webpage>
<main style={{ display: 'flex', justifyContent: 'center', padding: '1rem' }}> <main>
<SkeletonGrid /> Loading...
</main> </main>
</Webpage> </Webpage>
} }
@ -35,43 +55,48 @@ export default function SubmissionInfoPage() {
return ( return (
// TODO: Add filter settings & searchbar & page selector // TODO: Add filter settings & searchbar & page selector
<Webpage> <Webpage>
<main style={{ display: 'flex', justifyContent: 'center', padding: '1rem' }}> <main
<Grid style={{
container display: 'flex',
spacing={2} flexDirection: 'column',
alignItems="center" justifyContent: 'center',
justifyContent="center" alignItems: 'center',
style={{ maxWidth: 'calc(100vw - 100px)', margin: '0 auto' }} padding: '1rem',
> width: '100%',
{submissions.map((submission) => ( maxWidth: '100vw',
<Grid key={submission.ID}> boxSizing: 'border-box',
<SubmissionCard overflowX: 'hidden'
id={submission.ID} }}
assetId={submission.AssetID} >
displayName={submission.DisplayName} <div className="pagination-dots">
author={submission.Creator} {Array.from({ length: totalPages }).map((_, index) => (
rating={submission.StatusID} <span
/> key={index}
</Grid> className={`dot ${index === currentPage ? 'active' : ''}`}
onClick={() => setCurrentPage(index)}
></span>
))} ))}
</Grid> </div>
<div className="pagination">
<button onClick={prevPage} disabled={currentPage === 0}>&lt;</button>
<span>
Page {currentPage + 1} of {totalPages}
</span>
<button onClick={nextPage} disabled={currentPage === totalPages - 1}>&gt;</button>
</div>
<div className="grid">
{currentCards.map((submission) => (
<SubmissionCard
key={submission.ID}
id={submission.ID}
assetId={submission.AssetID}
displayName={submission.DisplayName}
author={submission.Creator}
rating={submission.StatusID}
/>
))}
</div>
</main> </main>
</Webpage> </Webpage>
) )
// {
// "ID": 1,
// "DisplayName": "81bfc7a",
// "Creator": "79fbe8d",
// "GameID": 1073741824,
// "CreatedAt": 1734490019,
// "UpdatedAt": 1734565641,
// "Submitter": 1,
// "AssetID": 6438937102481093,
// "AssetVersion": 1225679040570074,
// "Completed": false,
// "SubmissionType": 0,
// "TargetAssetID": 1057095197389979,
// "StatusID": 4
// }
} }

View File

@ -22,7 +22,6 @@ const BootstrapInput = styled(InputBase)(({ theme }) => ({
fontSize: 16, fontSize: 16,
padding: '10px 26px 10px 12px', padding: '10px 26px 10px 12px',
transition: theme.transitions.create(['border-color', 'box-shadow']), transition: theme.transitions.create(['border-color', 'box-shadow']),
// Use the system font instead of the default Roboto font.
fontFamily: [ fontFamily: [
'-apple-system', '-apple-system',
'BlinkMacSystemFont', 'BlinkMacSystemFont',